diff --git a/openfpga/CMakeLists.txt b/openfpga/CMakeLists.txt index 1e3ae7f13..1cdd56a32 100644 --- a/openfpga/CMakeLists.txt +++ b/openfpga/CMakeLists.txt @@ -22,6 +22,7 @@ target_link_libraries(libopenfpga libarchopenfpga libopenfpgashell libopenfpgautil + libini libvtrutil libvpr8) diff --git a/openfpga/src/base/io_location_map.cpp b/openfpga/src/base/io_location_map.cpp new file mode 100644 index 000000000..460ba080d --- /dev/null +++ b/openfpga/src/base/io_location_map.cpp @@ -0,0 +1,46 @@ +/****************************************************************************** + * Memember functions for data structure IoLocationMap + ******************************************************************************/ +#include "vtr_assert.h" + +#include "io_location_map.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Public Accessors + *************************************************/ +size_t IoLocationMap::io_index(const size_t& x, const size_t& y, const size_t& z) const { + if (x >= io_indices_.size()) { + return size_t(-1); + } + + if (y >= io_indices_[x].size()) { + return size_t(-1); + } + + if (z >= io_indices_[x][y].size()) { + return size_t(-1); + } + + return io_indices_[x][y][z]; +} + +void IoLocationMap::set_io_index(const size_t& x, const size_t& y, const size_t& z, const size_t& io_index) { + if (x >= io_indices_.size()) { + io_indices_.resize(x + 1); + } + + if (y >= io_indices_[x].size()) { + io_indices_[x].resize(y + 1); + } + + if (z >= io_indices_[x][y].size()) { + io_indices_[x][y].resize(z + 1); + } + + io_indices_[x][y][z] = io_index; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/io_location_map.h b/openfpga/src/base/io_location_map.h new file mode 100644 index 000000000..de6422d23 --- /dev/null +++ b/openfpga/src/base/io_location_map.h @@ -0,0 +1,39 @@ +#ifndef IO_LOCATION_MAP_H +#define IO_LOCATION_MAP_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * I/O location map is a data structure to bridge the index of I/Os + * in the FPGA fabric, i.e., the module graph, and logical location + * of the I/O in VPR coordinate system + * + * For example + * io[0] io[1] io[2] + * +-----------------+ +--------+ + * | | | | | + * | I/O | I/O | | I/O | + * | [0][y] | [0][y] | | [1][y] | + * | [0] | [1] | | [0] | + * +-----------------+ +--------+ + * + *******************************************************************/ +class IoLocationMap { + public: /* Public aggregators */ + size_t io_index(const size_t& x, const size_t& y, const size_t& z) const; + public: /* Public mutators */ + void set_io_index(const size_t& x, const size_t& y, const size_t& z, const size_t& io_index); + private: /* Internal Data */ + /* I/O index fast lookup by [x][y][z] location */ + std::vector>> io_indices_; +}; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/base/openfpga_bitstream.cpp b/openfpga/src/base/openfpga_bitstream.cpp index 08fa7bf16..e732dee92 100644 --- a/openfpga/src/base/openfpga_bitstream.cpp +++ b/openfpga/src/base/openfpga_bitstream.cpp @@ -5,6 +5,9 @@ #include "vtr_time.h" #include "vtr_log.h" +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + #include "build_device_bitstream.h" #include "bitstream_writer.h" #include "build_fabric_bitstream.h" @@ -30,6 +33,11 @@ void fpga_bitstream(OpenfpgaContext& openfpga_ctx, cmd_context.option_enable(cmd, opt_verbose)); if (true == cmd_context.option_enable(cmd, opt_file)) { + std::string src_dir_path = find_path_dir_name(cmd_context.option_value(cmd, opt_file)); + + /* Create directories */ + create_dir_path(src_dir_path.c_str()); + write_arch_independent_bitstream_to_xml_file(openfpga_ctx.bitstream_manager(), cmd_context.option_value(cmd, opt_file)); } diff --git a/openfpga/src/base/openfpga_build_fabric.cpp b/openfpga/src/base/openfpga_build_fabric.cpp index 5c3d8a69d..a1e90055b 100644 --- a/openfpga/src/base/openfpga_build_fabric.cpp +++ b/openfpga/src/base/openfpga_build_fabric.cpp @@ -21,43 +21,43 @@ namespace openfpga { * This function should only be called after the GSB builder is done *******************************************************************/ static -void compress_routing_hierarchy(OpenfpgaContext& openfpga_context, +void compress_routing_hierarchy(OpenfpgaContext& openfpga_ctx, const bool& verbose_output) { vtr::ScopedStartFinishTimer timer("Identify unique General Switch Blocks (GSBs)"); /* Build unique module lists */ - openfpga_context.mutable_device_rr_gsb().build_unique_module(g_vpr_ctx.device().rr_graph); + openfpga_ctx.mutable_device_rr_gsb().build_unique_module(g_vpr_ctx.device().rr_graph); /* Report the stats */ VTR_LOGV(verbose_output, "Detected %lu unique X-direction connection blocks from a total of %d (compression rate=%d%)\n", - openfpga_context.device_rr_gsb().get_num_cb_unique_module(CHANX), - find_device_rr_gsb_num_cb_modules(openfpga_context.device_rr_gsb(), CHANX), - 100 * (openfpga_context.device_rr_gsb().get_num_cb_unique_module(CHANX) / find_device_rr_gsb_num_cb_modules(openfpga_context.device_rr_gsb(), CHANX) - 1)); + openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANX), + find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANX), + 100 * (openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANX) / find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANX) - 1)); VTR_LOGV(verbose_output, "Detected %lu unique Y-direction connection blocks from a total of %d (compression rate=%d%)\n", - openfpga_context.device_rr_gsb().get_num_cb_unique_module(CHANY), - find_device_rr_gsb_num_cb_modules(openfpga_context.device_rr_gsb(), CHANY), - 100 * (openfpga_context.device_rr_gsb().get_num_cb_unique_module(CHANY) / find_device_rr_gsb_num_cb_modules(openfpga_context.device_rr_gsb(), CHANY) - 1)); + openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANY), + find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANY), + 100 * (openfpga_ctx.device_rr_gsb().get_num_cb_unique_module(CHANY) / find_device_rr_gsb_num_cb_modules(openfpga_ctx.device_rr_gsb(), CHANY) - 1)); VTR_LOGV(verbose_output, "Detected %lu unique switch blocks from a total of %d (compression rate=%d%)\n", - openfpga_context.device_rr_gsb().get_num_sb_unique_module(), - find_device_rr_gsb_num_sb_modules(openfpga_context.device_rr_gsb()), - 100 * (openfpga_context.device_rr_gsb().get_num_sb_unique_module() / find_device_rr_gsb_num_sb_modules(openfpga_context.device_rr_gsb()) - 1)); + openfpga_ctx.device_rr_gsb().get_num_sb_unique_module(), + find_device_rr_gsb_num_sb_modules(openfpga_ctx.device_rr_gsb()), + 100 * (openfpga_ctx.device_rr_gsb().get_num_sb_unique_module() / find_device_rr_gsb_num_sb_modules(openfpga_ctx.device_rr_gsb()) - 1)); VTR_LOGV(verbose_output, "Detected %lu unique general switch blocks from a total of %d (compression rate=%d%)\n", - openfpga_context.device_rr_gsb().get_num_gsb_unique_module(), - find_device_rr_gsb_num_gsb_modules(openfpga_context.device_rr_gsb()), - 100 * (openfpga_context.device_rr_gsb().get_num_gsb_unique_module() / find_device_rr_gsb_num_gsb_modules(openfpga_context.device_rr_gsb()) - 1)); + openfpga_ctx.device_rr_gsb().get_num_gsb_unique_module(), + find_device_rr_gsb_num_gsb_modules(openfpga_ctx.device_rr_gsb()), + 100 * (openfpga_ctx.device_rr_gsb().get_num_gsb_unique_module() / find_device_rr_gsb_num_gsb_modules(openfpga_ctx.device_rr_gsb()) - 1)); } /******************************************************************** * Build the module graph for FPGA device *******************************************************************/ -void build_fabric(OpenfpgaContext& openfpga_context, +void build_fabric(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context) { CommandOptionId opt_compress_routing = cmd.option("compress_routing"); @@ -65,16 +65,17 @@ void build_fabric(OpenfpgaContext& openfpga_context, CommandOptionId opt_verbose = cmd.option("verbose"); if (true == cmd_context.option_enable(cmd, opt_compress_routing)) { - compress_routing_hierarchy(openfpga_context, cmd_context.option_enable(cmd, opt_verbose)); + compress_routing_hierarchy(openfpga_ctx, cmd_context.option_enable(cmd, opt_verbose)); } VTR_LOG("\n"); - openfpga_context.mutable_module_graph() = build_device_module_graph(g_vpr_ctx.device(), - const_cast(openfpga_context), - cmd_context.option_enable(cmd, opt_compress_routing), - cmd_context.option_enable(cmd, opt_duplicate_grid_pin), - cmd_context.option_enable(cmd, opt_verbose)); + openfpga_ctx.mutable_module_graph() = build_device_module_graph(openfpga_ctx.mutable_io_location_map(), + g_vpr_ctx.device(), + const_cast(openfpga_ctx), + cmd_context.option_enable(cmd, opt_compress_routing), + cmd_context.option_enable(cmd, opt_duplicate_grid_pin), + cmd_context.option_enable(cmd, opt_verbose)); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_build_fabric.h b/openfpga/src/base/openfpga_build_fabric.h index 44aa3632c..824ed63a1 100644 --- a/openfpga/src/base/openfpga_build_fabric.h +++ b/openfpga/src/base/openfpga_build_fabric.h @@ -15,7 +15,7 @@ /* begin namespace openfpga */ namespace openfpga { -void build_fabric(OpenfpgaContext& openfpga_context, +void build_fabric(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 521a2cf6a..20e235a35 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -15,6 +15,7 @@ #include "openfpga_flow_manager.h" #include "bitstream_manager.h" #include "device_rr_gsb.h" +#include "io_location_map.h" /******************************************************************** * This file includes the declaration of the date structure @@ -58,6 +59,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_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } @@ -72,6 +75,8 @@ class OpenfpgaContext : public Context { openfpga::FlowManager& mutable_flow_manager() { return flow_manager_; } openfpga::BitstreamManager& mutable_bitstream_manager() { return bitstream_manager_; } std::vector& mutable_fabric_bitstream() { return fabric_bitstream_; } + openfpga::IoLocationMap& mutable_io_location_map() { return io_location_map_; } + std::unordered_map& mutable_net_activity() { return net_activity_; } private: /* Internal data */ /* Data structure to store information from read_openfpga_arch library */ openfpga::Arch arch_; @@ -102,10 +107,14 @@ class OpenfpgaContext : public Context { /* Fabric module graph */ openfpga::ModuleManager module_graph_; + openfpga::IoLocationMap io_location_map_; /* Bitstream database */ openfpga::BitstreamManager bitstream_manager_; std::vector fabric_bitstream_; + + /* Net activities of users' implementation */ + std::unordered_map net_activity_; /* Flow status */ openfpga::FlowManager flow_manager_; diff --git a/openfpga/src/base/openfpga_link_arch.cpp b/openfpga/src/base/openfpga_link_arch.cpp index 9dc1e38c7..fe89b7024 100644 --- a/openfpga/src/base/openfpga_link_arch.cpp +++ b/openfpga/src/base/openfpga_link_arch.cpp @@ -2,11 +2,20 @@ * This file includes functions to read an OpenFPGA architecture file * which are built on the libarchopenfpga library *******************************************************************/ +#include +#include + /* Headers from vtrutil library */ #include "vtr_time.h" #include "vtr_assert.h" #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" #include "pb_type_utils.h" #include "annotate_pb_types.h" @@ -46,6 +55,163 @@ 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 @@ -59,6 +225,7 @@ void link_arch(OpenfpgaContext& openfpga_ctx, vtr::ScopedStartFinishTimer timer("Link OpenFPGA architecture to VPR architecture"); + CommandOptionId opt_activity_file = cmd.option("activity_file"); CommandOptionId opt_verbose = cmd.option("verbose"); /* Annotate pb_type graphs @@ -118,6 +285,22 @@ void link_arch(OpenfpgaContext& openfpga_ctx, g_vpr_ctx.clustering(), g_vpr_ctx.placement(), openfpga_ctx.mutable_vpr_placement_annotation()); + + /* Read activity file is manadatory in the following flow-run settings + * - When users specify that number of clock cycles + * should be inferred from FPGA implmentation + * - When FPGA-SPICE is enabled + */ + openfpga_ctx.mutable_net_activity() = read_activity(g_vpr_ctx.atom().nlist, + cmd_context.option_value(cmd, opt_activity_file).c_str()); + + /* TODO: Annotate the number of clock cycles and clock frequency by following VPR results + * We SHOULD create a new simulation setting for OpenFPGA use only + * Avoid overwrite the raw data achieved when parsing!!! + */ + annotate_simulation_setting(g_vpr_ctx.atom(), + openfpga_ctx.net_activity(), + openfpga_ctx.mutable_arch().sim_setting); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp new file mode 100644 index 000000000..eaa2b9941 --- /dev/null +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -0,0 +1,80 @@ +/******************************************************************** + * This file includes functions to compress the hierachy of routing architecture + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_time.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "circuit_library_utils.h" +#include "pnr_sdc_writer.h" +#include "openfpga_sdc.h" + +/* Include global variables of VPR */ +#include "globals.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * A wrapper function to call the PnR SDC generator of FPGA-SDC + *******************************************************************/ +void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + + CommandOptionId opt_output_dir = cmd.option("file"); + CommandOptionId opt_constrain_global_port = cmd.option("constrain_global_port"); + CommandOptionId opt_constrain_grid = cmd.option("constrain_grid"); + CommandOptionId opt_constrain_sb = cmd.option("constrain_sb"); + CommandOptionId opt_constrain_cb = cmd.option("constrain_cb"); + CommandOptionId opt_constrain_configurable_memory_outputs = cmd.option("constrain_configurable_memory_outputs"); + CommandOptionId opt_constrain_routing_multiplexer_outputs = cmd.option("constrain_routing_multiplexer_outputs"); + CommandOptionId opt_constrain_switch_block_outputs = cmd.option("constrain_switch_block_outputs"); + + /* This is an intermediate data structure which is designed to modularize the FPGA-SDC + * Keep it independent from any other outside data structures + */ + std::string sdc_dir_path = format_dir_path(cmd_context.option_value(cmd, opt_output_dir)); + + /* Create directories */ + create_dir_path(sdc_dir_path.c_str()); + + PnrSdcOption options(sdc_dir_path); + + options.set_constrain_global_port(cmd_context.option_enable(cmd, opt_constrain_global_port)); + options.set_constrain_grid(cmd_context.option_enable(cmd, opt_constrain_grid)); + options.set_constrain_sb(cmd_context.option_enable(cmd, opt_constrain_sb)); + options.set_constrain_cb(cmd_context.option_enable(cmd, opt_constrain_cb)); + options.set_constrain_configurable_memory_outputs(cmd_context.option_enable(cmd, opt_constrain_configurable_memory_outputs)); + options.set_constrain_routing_multiplexer_outputs(cmd_context.option_enable(cmd, opt_constrain_routing_multiplexer_outputs)); + options.set_constrain_switch_block_outputs(cmd_context.option_enable(cmd, opt_constrain_switch_block_outputs)); + + /* We first turn on default sdc option and then disable part of them by following users' options */ + if (false == options.generate_sdc_pnr()) { + options.set_generate_sdc_pnr(true); + } + + /* Collect global ports from the circuit library: + * TODO: should we place this in the OpenFPGA context? + */ + std::vector global_ports = find_circuit_library_global_ports(openfpga_ctx.arch().circuit_lib); + + /* Execute only when sdc is enabled */ + if (true == options.generate_sdc_pnr()) { + print_pnr_sdc(options, + 1./openfpga_ctx.arch().sim_setting.programming_clock_frequency(), + 1./openfpga_ctx.arch().sim_setting.operating_clock_frequency(), + g_vpr_ctx.device(), + openfpga_ctx.vpr_device_annotation(), + openfpga_ctx.device_rr_gsb(), + openfpga_ctx.module_graph(), + openfpga_ctx.mux_lib(), + openfpga_ctx.arch().circuit_lib, + global_ports, + openfpga_ctx.flow_manager().compress_routing()); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_sdc.h b/openfpga/src/base/openfpga_sdc.h new file mode 100644 index 000000000..4e48964f3 --- /dev/null +++ b/openfpga/src/base/openfpga_sdc.h @@ -0,0 +1,23 @@ +#ifndef OPENFPGA_SDC_H +#define OPENFPGA_SDC_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "command.h" +#include "command_context.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp new file mode 100644 index 000000000..cf31d73b0 --- /dev/null +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -0,0 +1,79 @@ +/******************************************************************** + * Add commands to the OpenFPGA shell interface, + * in purpose of generate SDC files + * - write_pnr_sdc : generate SDC to constrain the back-end flow for FPGA fabric + * - write_analysis_sdc: TODO: generate SDC based on users' implementations + *******************************************************************/ +#include "openfpga_sdc.h" +#include "openfpga_sdc_command.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * - Add a command to Shell environment: generate PnR SDC + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +void add_openfpga_write_pnr_sdc_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const ShellCommandId& shell_cmd_build_fabric_id) { + Command shell_cmd("write_pnr_sdc"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId output_opt = shell_cmd.add_option("file", true, "Specify the output directory for SDC files"); + shell_cmd.set_option_short_name(output_opt, "f"); + shell_cmd.set_option_require_value(output_opt, openfpga::OPT_STRING); + + /* Add an option '--constrain_global_port' */ + shell_cmd.add_option("constrain_global_port", false, "Constrain all the global ports of FPGA fabric"); + + /* Add an option '--constrain_grid' */ + shell_cmd.add_option("constrain_grid", false, "Constrain all the grids of FPGA fabric"); + + /* Add an option '--constrain_sb' */ + shell_cmd.add_option("constrain_sb", false, "Constrain all the switch blocks of FPGA fabric"); + + /* Add an option '--constrain_cb' */ + shell_cmd.add_option("constrain_cb", false, "Constrain all the connection blocks of FPGA fabric"); + + /* Add an option '--constrain_configurable_memory_outputs' */ + shell_cmd.add_option("constrain_configurable_memory_outputs", false, "Constrain all the outputs of configurable memories of FPGA fabric"); + + /* Add an option '--constrain_routing_multiplexer_outputs' */ + shell_cmd.add_option("constrain_routing_multiplexer_outputs", false, "Constrain all the outputs of routing multiplexer of FPGA fabric"); + + /* Add an option '--constrain_switch_block_outputs' */ + shell_cmd.add_option("constrain_switch_block_outputs", false, "Constrain all the outputs of switch blocks of FPGA fabric"); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Enable verbose output"); + + /* Add command 'write_fabric_verilog' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "generate SDC files to constrain the backend flow for FPGA fabric"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, write_pnr_sdc); + + /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ + std::vector cmd_dependency; + cmd_dependency.push_back(shell_cmd_build_fabric_id); + shell.set_command_dependency(shell_cmd_id, cmd_dependency); +} + +void add_openfpga_sdc_commands(openfpga::Shell& shell) { + /* Get the unique id of 'build_fabric' command which is to be used in creating the dependency graph */ + const ShellCommandId& shell_cmd_build_fabric_id = shell.command(std::string("build_fabric")); + + /* Add a new class of commands */ + ShellCommandClassId openfpga_sdc_cmd_class = shell.add_command_class("FPGA-SDC"); + + /******************************** + * Command 'write_fabric_verilog' + */ + add_openfpga_write_pnr_sdc_command(shell, + openfpga_sdc_cmd_class, + shell_cmd_build_fabric_id); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_sdc_command.h b/openfpga/src/base/openfpga_sdc_command.h new file mode 100644 index 000000000..9fbeb42a5 --- /dev/null +++ b/openfpga/src/base/openfpga_sdc_command.h @@ -0,0 +1,21 @@ +#ifndef OPENFPGA_SDC_COMMAND_H +#define OPENFPGA_SDC_COMMAND_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "shell.h" +#include "openfpga_context.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void add_openfpga_sdc_commands(openfpga::Shell& shell); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/base/openfpga_setup_command.cpp b/openfpga/src/base/openfpga_setup_command.cpp index e84227187..e3354d046 100644 --- a/openfpga/src/base/openfpga_setup_command.cpp +++ b/openfpga/src/base/openfpga_setup_command.cpp @@ -14,9 +14,199 @@ /* begin namespace openfpga */ namespace openfpga { +/******************************************************************** + * - Add a command to Shell environment: read_openfpga_arch + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_read_arch_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id) { + Command shell_cmd("read_openfpga_arch"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId opt_arch_file = shell_cmd.add_option("file", true, "file path to the architecture XML"); + shell_cmd.set_option_short_name(opt_arch_file, "f"); + shell_cmd.set_option_require_value(opt_arch_file, openfpga::OPT_STRING); + + /* Add command 'read_openfpga_arch' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "read OpenFPGA architecture file"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, read_arch); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: write_openfpga_arch + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_write_arch_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + Command shell_cmd("write_openfpga_arch"); + /* Add an option '--file' in short '-f'*/ + CommandOptionId opt_file = shell_cmd.add_option("file", true, "file path to the architecture XML"); + shell_cmd.set_option_short_name(opt_file, "f"); + shell_cmd.set_option_require_value(opt_file, openfpga::OPT_STRING); + + /* Add command 'write_openfpga_arch' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "write OpenFPGA architecture file"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_const_execute_function(shell_cmd_id, write_arch); + + /* The 'write_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: link_openfpga_arch + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_link_arch_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + Command shell_cmd("link_openfpga_arch"); + + /* Add an option '--activity_file'*/ + CommandOptionId opt_act_file = shell_cmd.add_option("activity_file", true, "file path to the signal activity"); + shell_cmd.set_option_require_value(opt_act_file, openfpga::OPT_STRING); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Show verbose outputs"); + + /* Add command 'link_openfpga_arch' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Bind OpenFPGA architecture to VPR"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, link_arch); + + /* The 'link_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: check_netlist_naming_conflict + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_check_netlist_naming_conflict_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + + Command shell_cmd("check_netlist_naming_conflict"); + + /* Add an option '--fix' */ + shell_cmd.add_option("fix", false, "Apply correction to any conflicts found"); + + /* Add an option '--report' */ + CommandOptionId opt_rpt = shell_cmd.add_option("report", false, "Output a report file about what any correction applied"); + shell_cmd.set_option_require_value(opt_rpt, openfpga::OPT_STRING); + + /* Add command 'check_netlist_naming_conflict' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Check any block/net naming in users' BLIF netlist violates the syntax of fabric generator"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, check_netlist_naming_conflict); + + /* The 'link_openfpga_arch' command should NOT be executed before 'vpr' */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: pb_pin_fixup + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_pb_pin_fixup_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + + Command shell_cmd("pb_pin_fixup"); + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Show verbose outputs"); + + /* Add command 'pb_pin_fixup' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Fix up the packing results due to pin swapping during routing stage"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, pb_pin_fixup); + + /* The 'pb_pin_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: lut_truth_table_fixup + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_lut_truth_table_fixup_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + + Command shell_cmd("lut_truth_table_fixup"); + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Show verbose outputs"); + + /* Add command 'lut_truth_table_fixup' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Fix up the truth table of Look-Up Tables due to pin swapping during packing stage"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, lut_truth_table_fixup); + + /* The 'lut_truth_table_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + +/******************************************************************** + * - Add a command to Shell environment: build_fabric + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +ShellCommandId add_openfpga_build_fabric_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + + Command shell_cmd("build_fabric"); + + /* Add an option '--compress_routing' */ + shell_cmd.add_option("compress_routing", false, "Compress the number of unique routing modules by identifying the unique GSBs"); + + /* Add an option '--duplicate_grid_pin' */ + shell_cmd.add_option("duplicate_grid_pin", false, "Duplicate the pins on the same side of a grid"); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Show verbose outputs"); + + /* Add command 'compact_routing_hierarchy' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "Build the FPGA fabric in a graph of modules"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, build_fabric); + + /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + void add_openfpga_setup_commands(openfpga::Shell& shell) { /* Get the unique id of 'vpr' command which is to be used in creating the dependency graph */ - const ShellCommandId& shell_cmd_vpr_id = shell.command(std::string("vpr")); + const ShellCommandId& vpr_cmd_id = shell.command(std::string("vpr")); /* Add a new class of commands */ ShellCommandClassId openfpga_setup_cmd_class = shell.add_command_class("OpenFPGA setup"); @@ -24,119 +214,62 @@ void add_openfpga_setup_commands(openfpga::Shell& shell) { /******************************** * Command 'read_openfpga_arch' */ - Command shell_cmd_read_arch("read_openfpga_arch"); - /* Add an option '--file' in short '-f'*/ - CommandOptionId read_arch_opt_file = shell_cmd_read_arch.add_option("file", true, "file path to the architecture XML"); - shell_cmd_read_arch.set_option_short_name(read_arch_opt_file, "f"); - shell_cmd_read_arch.set_option_require_value(read_arch_opt_file, openfpga::OPT_STRING); - - /* Add command 'read_openfpga_arch' to the Shell */ - ShellCommandId shell_cmd_read_arch_id = shell.add_command(shell_cmd_read_arch, "read OpenFPGA architecture file"); - shell.set_command_class(shell_cmd_read_arch_id, openfpga_setup_cmd_class); - shell.set_command_execute_function(shell_cmd_read_arch_id, read_arch); + ShellCommandId read_arch_cmd_id = add_openfpga_read_arch_command(shell, + openfpga_setup_cmd_class); /******************************** * Command 'write_openfpga_arch' */ - Command shell_cmd_write_arch("write_openfpga_arch"); - /* Add an option '--file' in short '-f'*/ - CommandOptionId write_arch_opt_file = shell_cmd_write_arch.add_option("file", true, "file path to the architecture XML"); - shell_cmd_write_arch.set_option_short_name(write_arch_opt_file, "f"); - shell_cmd_write_arch.set_option_require_value(write_arch_opt_file, openfpga::OPT_STRING); - - /* Add command 'write_openfpga_arch' to the Shell */ - ShellCommandId shell_cmd_write_arch_id = shell.add_command(shell_cmd_write_arch, "write OpenFPGA architecture file"); - shell.set_command_class(shell_cmd_write_arch_id, openfpga_setup_cmd_class); - shell.set_command_const_execute_function(shell_cmd_write_arch_id, write_arch); - /* The 'write_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' */ - shell.set_command_dependency(shell_cmd_write_arch_id, std::vector(1, shell_cmd_read_arch_id)); + std::vector write_arch_dependent_cmds(1, read_arch_cmd_id); + add_openfpga_write_arch_command(shell, + openfpga_setup_cmd_class, + write_arch_dependent_cmds); /******************************** * Command 'link_openfpga_arch' */ - Command shell_cmd_link_openfpga_arch("link_openfpga_arch"); - /* Add an option '--verbose' */ - shell_cmd_link_openfpga_arch.add_option("verbose", false, "Show verbose outputs"); - - /* Add command 'link_openfpga_arch' to the Shell */ - ShellCommandId shell_cmd_link_openfpga_arch_id = shell.add_command(shell_cmd_link_openfpga_arch, "Bind OpenFPGA architecture to VPR"); - shell.set_command_class(shell_cmd_link_openfpga_arch_id, openfpga_setup_cmd_class); - shell.set_command_execute_function(shell_cmd_link_openfpga_arch_id, link_arch); - /* The 'link_openfpga_arch' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ - std::vector cmd_dependency_link_openfpga_arch; - cmd_dependency_link_openfpga_arch.push_back(shell_cmd_read_arch_id); - cmd_dependency_link_openfpga_arch.push_back(shell_cmd_vpr_id); - shell.set_command_dependency(shell_cmd_link_openfpga_arch_id, cmd_dependency_link_openfpga_arch); - + std::vector link_arch_dependent_cmds; + link_arch_dependent_cmds.push_back(read_arch_cmd_id); + link_arch_dependent_cmds.push_back(vpr_cmd_id); + ShellCommandId link_arch_cmd_id = add_openfpga_link_arch_command(shell, + openfpga_setup_cmd_class, + link_arch_dependent_cmds); /******************************************* * Command 'check_netlist_naming_conflict' */ - Command shell_cmd_check_netlist_naming_conflict("check_netlist_naming_conflict"); - /* Add an option '--fix' */ - shell_cmd_check_netlist_naming_conflict.add_option("fix", false, "Apply correction to any conflicts found"); - /* Add an option '--report' */ - CommandOptionId check_netlist_opt_rpt = shell_cmd_check_netlist_naming_conflict.add_option("report", false, "Output a report file about what any correction applied"); - shell_cmd_check_netlist_naming_conflict.set_option_require_value(check_netlist_opt_rpt, openfpga::OPT_STRING); - - /* Add command 'check_netlist_naming_conflict' to the Shell */ - ShellCommandId shell_cmd_check_netlist_naming_conflict_id = shell.add_command(shell_cmd_check_netlist_naming_conflict, "Check any block/net naming in users' BLIF netlist violates the syntax of fabric generator"); - shell.set_command_class(shell_cmd_check_netlist_naming_conflict_id, openfpga_setup_cmd_class); - shell.set_command_execute_function(shell_cmd_check_netlist_naming_conflict_id, check_netlist_naming_conflict); - /* The 'link_openfpga_arch' command should NOT be executed before 'vpr' */ - std::vector cmd_dependency_check_netlist_naming_conflict(1, shell_cmd_vpr_id); - shell.set_command_dependency(shell_cmd_link_openfpga_arch_id, cmd_dependency_check_netlist_naming_conflict); + std::vector nlist_naming_dependent_cmds; + nlist_naming_dependent_cmds.push_back(vpr_cmd_id); + add_openfpga_check_netlist_naming_conflict_command(shell, + openfpga_setup_cmd_class, + nlist_naming_dependent_cmds); /******************************** * Command 'pb_pin_fixup' */ - Command shell_cmd_pb_pin_fixup("pb_pin_fixup"); - /* Add an option '--verbose' */ - shell_cmd_pb_pin_fixup.add_option("verbose", false, "Show verbose outputs"); - - /* Add command 'pb_pin_fixup' to the Shell */ - ShellCommandId shell_cmd_pb_pin_fixup_id = shell.add_command(shell_cmd_pb_pin_fixup, "Fix up the packing results due to pin swapping during routing stage"); - shell.set_command_class(shell_cmd_pb_pin_fixup_id, openfpga_setup_cmd_class); - shell.set_command_execute_function(shell_cmd_pb_pin_fixup_id, pb_pin_fixup); - /* The 'pb_pin_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ - std::vector cmd_dependency_pb_pin_fixup; - cmd_dependency_pb_pin_fixup.push_back(shell_cmd_read_arch_id); - cmd_dependency_pb_pin_fixup.push_back(shell_cmd_vpr_id); - shell.set_command_dependency(shell_cmd_pb_pin_fixup_id, cmd_dependency_pb_pin_fixup); + std::vector pb_pin_fixup_dependent_cmds; + pb_pin_fixup_dependent_cmds.push_back(read_arch_cmd_id); + pb_pin_fixup_dependent_cmds.push_back(vpr_cmd_id); + add_openfpga_pb_pin_fixup_command(shell, + openfpga_setup_cmd_class, + pb_pin_fixup_dependent_cmds); /******************************** * Command 'lut_truth_table_fixup' */ - Command shell_cmd_lut_truth_table_fixup("lut_truth_table_fixup"); - /* Add an option '--verbose' */ - shell_cmd_lut_truth_table_fixup.add_option("verbose", false, "Show verbose outputs"); - - /* Add command 'lut_truth_table_fixup' to the Shell */ - ShellCommandId shell_cmd_lut_truth_table_fixup_id = shell.add_command(shell_cmd_lut_truth_table_fixup, "Fix up the truth table of Look-Up Tables due to pin swapping during packing stage"); - shell.set_command_class(shell_cmd_lut_truth_table_fixup_id, openfpga_setup_cmd_class); - shell.set_command_execute_function(shell_cmd_lut_truth_table_fixup_id, lut_truth_table_fixup); - /* The 'lut_truth_table_fixup' command should NOT be executed before 'read_openfpga_arch' and 'vpr' */ - std::vector cmd_dependency_lut_truth_table_fixup; - cmd_dependency_lut_truth_table_fixup.push_back(shell_cmd_read_arch_id); - cmd_dependency_lut_truth_table_fixup.push_back(shell_cmd_vpr_id); - shell.set_command_dependency(shell_cmd_lut_truth_table_fixup_id, cmd_dependency_lut_truth_table_fixup); - + std::vector lut_tt_fixup_dependent_cmds; + lut_tt_fixup_dependent_cmds.push_back(read_arch_cmd_id); + lut_tt_fixup_dependent_cmds.push_back(vpr_cmd_id); + add_openfpga_lut_truth_table_fixup_command(shell, + openfpga_setup_cmd_class, + lut_tt_fixup_dependent_cmds); /******************************** * Command 'build_fabric' */ - Command shell_cmd_build_fabric("build_fabric"); - /* Add an option '--verbose' */ - shell_cmd_build_fabric.add_option("compress_routing", false, "Compress the number of unique routing modules by identifying the unique GSBs"); - shell_cmd_build_fabric.add_option("duplicate_grid_pin", false, "Duplicate the pins on the same side of a grid"); - shell_cmd_build_fabric.add_option("verbose", false, "Show verbose outputs"); - - /* Add command 'compact_routing_hierarchy' to the Shell */ - ShellCommandId shell_cmd_build_fabric_id = shell.add_command(shell_cmd_build_fabric, "Build the FPGA fabric in a graph of modules"); - shell.set_command_class(shell_cmd_build_fabric_id, openfpga_setup_cmd_class); - shell.set_command_execute_function(shell_cmd_build_fabric_id, build_fabric); - /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ - std::vector cmd_dependency_build_fabric; - cmd_dependency_build_fabric.push_back(shell_cmd_link_openfpga_arch_id); - shell.set_command_dependency(shell_cmd_build_fabric_id, cmd_dependency_build_fabric); + std::vector build_fabric_dependent_cmds; + build_fabric_dependent_cmds.push_back(link_arch_cmd_id); + add_openfpga_build_fabric_command(shell, + openfpga_setup_cmd_class, + build_fabric_dependent_cmds); } } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_verilog.cpp b/openfpga/src/base/openfpga_verilog.cpp index 72b8fb100..1ce23a1d4 100644 --- a/openfpga/src/base/openfpga_verilog.cpp +++ b/openfpga/src/base/openfpga_verilog.cpp @@ -15,7 +15,7 @@ namespace openfpga { /******************************************************************** - * A wrapper function to call the fabric_verilog function of FPGA-Verilog + * A wrapper function to call the fabric Verilog generator of FPGA-Verilog *******************************************************************/ void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context) { @@ -26,9 +26,6 @@ void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, CommandOptionId opt_include_signal_init = cmd.option("include_signal_init"); CommandOptionId opt_support_icarus_simulator = cmd.option("support_icarus_simulator"); CommandOptionId opt_print_user_defined_template = cmd.option("print_user_defined_template"); - CommandOptionId opt_print_top_testbench = cmd.option("print_top_testbench"); - CommandOptionId opt_print_formal_verification_top_netlist = cmd.option("print_formal_verification_top_netlist"); - CommandOptionId opt_print_autocheck_top_testbench = cmd.option("print_autocheck_top_testbench"); CommandOptionId opt_verbose = cmd.option("verbose"); /* This is an intermediate data structure which is designed to modularize the FPGA-Verilog @@ -41,9 +38,6 @@ void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, options.set_include_signal_init(cmd_context.option_enable(cmd, opt_include_signal_init)); options.set_support_icarus_simulator(cmd_context.option_enable(cmd, opt_support_icarus_simulator)); options.set_print_user_defined_template(cmd_context.option_enable(cmd, opt_print_user_defined_template)); - options.set_print_top_testbench(cmd_context.option_enable(cmd, opt_print_top_testbench)); - options.set_print_formal_verification_top_netlist(cmd_context.option_enable(cmd, opt_print_formal_verification_top_netlist)); - options.set_print_autocheck_top_testbench(cmd_context.option_value(cmd, opt_print_autocheck_top_testbench)); options.set_verbose_output(cmd_context.option_enable(cmd, opt_verbose)); options.set_compress_routing(openfpga_ctx.flow_manager().compress_routing()); @@ -56,4 +50,43 @@ void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, options); } +/******************************************************************** + * A wrapper function to call the Verilog testbench generator of FPGA-Verilog + *******************************************************************/ +void write_verilog_testbench(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + + CommandOptionId opt_output_dir = cmd.option("file"); + CommandOptionId opt_reference_benchmark = cmd.option("reference_benchmark_file_path"); + CommandOptionId opt_print_top_testbench = cmd.option("print_top_testbench"); + CommandOptionId opt_print_formal_verification_top_netlist = cmd.option("print_formal_verification_top_netlist"); + CommandOptionId opt_print_preconfig_top_testbench = cmd.option("print_preconfig_top_testbench"); + CommandOptionId opt_print_simulation_ini = cmd.option("print_simulation_ini"); + CommandOptionId opt_verbose = cmd.option("verbose"); + + /* This is an intermediate data structure which is designed to modularize the FPGA-Verilog + * Keep it independent from any other outside data structures + */ + VerilogTestbenchOption options; + options.set_output_directory(cmd_context.option_value(cmd, opt_output_dir)); + options.set_reference_benchmark_file_path(cmd_context.option_value(cmd, opt_reference_benchmark)); + options.set_print_formal_verification_top_netlist(cmd_context.option_enable(cmd, opt_print_formal_verification_top_netlist)); + options.set_print_preconfig_top_testbench(cmd_context.option_enable(cmd, opt_print_preconfig_top_testbench)); + options.set_print_top_testbench(cmd_context.option_enable(cmd, opt_print_top_testbench)); + options.set_print_simulation_ini(cmd_context.option_value(cmd, opt_print_simulation_ini)); + options.set_verbose_output(cmd_context.option_enable(cmd, opt_verbose)); + + fpga_verilog_testbench(openfpga_ctx.module_graph(), + openfpga_ctx.bitstream_manager(), + openfpga_ctx.fabric_bitstream(), + g_vpr_ctx.atom(), + g_vpr_ctx.placement(), + openfpga_ctx.io_location_map(), + openfpga_ctx.vpr_netlist_annotation(), + openfpga_ctx.arch().circuit_lib, + openfpga_ctx.arch().sim_setting, + openfpga_ctx.arch().config_protocol.type(), + options); +} + } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_verilog.h b/openfpga/src/base/openfpga_verilog.h index a62d19411..faf8cf6b0 100644 --- a/openfpga/src/base/openfpga_verilog.h +++ b/openfpga/src/base/openfpga_verilog.h @@ -18,6 +18,9 @@ namespace openfpga { void write_fabric_verilog(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); +void write_verilog_testbench(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/openfpga_verilog_command.cpp b/openfpga/src/base/openfpga_verilog_command.cpp index 7eb7674b9..3d4f7784b 100644 --- a/openfpga/src/base/openfpga_verilog_command.cpp +++ b/openfpga/src/base/openfpga_verilog_command.cpp @@ -11,6 +11,98 @@ /* begin namespace openfpga */ namespace openfpga { +/******************************************************************** + * - Add a command to Shell environment: generate fabric Verilog + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +void add_openfpga_write_fabric_verilog_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const ShellCommandId& shell_cmd_build_fabric_id) { + Command shell_cmd("write_fabric_verilog"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId output_opt = shell_cmd.add_option("file", true, "Specify the output directory for Verilog netlists"); + shell_cmd.set_option_short_name(output_opt, "f"); + shell_cmd.set_option_require_value(output_opt, openfpga::OPT_STRING); + + /* Add an option '--explicit_port_mapping' */ + shell_cmd.add_option("explicit_port_mapping", false, "Use explicit port mapping in Verilog netlists"); + + /* Add an option '--include_timing' */ + shell_cmd.add_option("include_timing", false, "Enable timing annotation in Verilog netlists"); + + /* Add an option '--include_signal_init' */ + shell_cmd.add_option("include_signal_init", false, "Initialize all the signals in Verilog netlists"); + + /* Add an option '--support_icarus_simulator' */ + shell_cmd.add_option("support_icarus_simulator", false, "Fine-tune Verilog netlists to support icarus simulator"); + + /* Add an option '--print_user_defined_template' */ + shell_cmd.add_option("print_user_defined_template", false, "Generate a template Verilog files for user-defined circuit models"); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Enable verbose output"); + + /* Add command 'write_fabric_verilog' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "generate Verilog netlists modeling full FPGA fabric"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, write_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 a command to Shell environment: write Verilog testbench + * - Add associated options + * - Add command dependency + *******************************************************************/ +static +void add_openfpga_write_verilog_testbench_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const ShellCommandId& shell_cmd_build_fabric_id) { + Command shell_cmd("write_verilog_testbench"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId output_opt = shell_cmd.add_option("file", true, "Specify the output directory for Verilog netlists"); + shell_cmd.set_option_short_name(output_opt, "f"); + shell_cmd.set_option_require_value(output_opt, openfpga::OPT_STRING); + + /* Add an option '--reference_benchmark_file_path'*/ + CommandOptionId ref_bm_opt = shell_cmd.add_option("reference_benchmark_file_path", true, "Specify the file path to the reference Verilog netlist"); + shell_cmd.set_option_require_value(ref_bm_opt, openfpga::OPT_STRING); + + /* Add an option '--print_top_testbench' */ + shell_cmd.add_option("print_top_testbench", false, "Generate a full testbench for top-level fabric module with autocheck capability"); + + /* Add an option '--print_formal_verification_top_netlist' */ + shell_cmd.add_option("print_formal_verification_top_netlist", false, "Generate a top-level module which can be used in formal verification"); + + /* Add an option '--print_preconfig_top_testbench' */ + shell_cmd.add_option("print_preconfig_top_testbench", false, "Generate a pre-configured testbench for top-level fabric module with autocheck capability"); + + /* Add an option '--print_simulation_ini' */ + CommandOptionId sim_ini_opt = shell_cmd.add_option("print_simulation_ini", false, "Generate a .ini file as an exchangeable file to enable HDL simulations"); + shell_cmd.set_option_require_value(sim_ini_opt, openfpga::OPT_STRING); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Enable verbose output"); + + /* Add command to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "generate Verilog testbenches for full FPGA fabric"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, write_verilog_testbench); + + /* The command should NOT be executed before 'build_fabric' */ + std::vector cmd_dependency; + cmd_dependency.push_back(shell_cmd_build_fabric_id); + shell.set_command_dependency(shell_cmd_id, cmd_dependency); +} + void add_openfpga_verilog_commands(openfpga::Shell& shell) { /* Get the unique id of 'build_fabric' command which is to be used in creating the dependency graph */ const ShellCommandId& shell_cmd_build_fabric_id = shell.command(std::string("build_fabric")); @@ -19,42 +111,18 @@ void add_openfpga_verilog_commands(openfpga::Shell& shell) { ShellCommandClassId openfpga_verilog_cmd_class = shell.add_command_class("FPGA-Verilog"); /******************************** - * Command 'wirte_fabric_verilog' + * Command 'write_fabric_verilog' */ - Command shell_cmd_write_fabric_verilog("write_fabric_verilog"); - /* Add an option '--file' in short '-f'*/ - CommandOptionId fabric_verilog_output_opt = shell_cmd_write_fabric_verilog.add_option("file", true, "Specify the output directory for Verilog netlists"); - shell_cmd_write_fabric_verilog.set_option_short_name(fabric_verilog_output_opt, "f"); - shell_cmd_write_fabric_verilog.set_option_require_value(fabric_verilog_output_opt, openfpga::OPT_STRING); - /* Add an option '--explicit_port_mapping' */ - shell_cmd_write_fabric_verilog.add_option("explicit_port_mapping", false, "Use explicit port mapping in Verilog netlists"); - /* Add an option '--include_timing' */ - shell_cmd_write_fabric_verilog.add_option("include_timing", false, "Enable timing annotation in Verilog netlists"); - /* Add an option '--include_signal_init' */ - shell_cmd_write_fabric_verilog.add_option("include_signal_init", false, "Initialize all the signals in Verilog netlists"); - /* Add an option '--support_icarus_simulator' */ - shell_cmd_write_fabric_verilog.add_option("support_icarus_simulator", false, "Fine-tune Verilog netlists to support icarus simulator"); - /* Add an option '--print_user_defined_template' */ - shell_cmd_write_fabric_verilog.add_option("print_user_defined_template", false, "Generate a template Verilog files for user-defined circuit models"); - /* Add an option '--print_top_testbench' */ - shell_cmd_write_fabric_verilog.add_option("print_top_testbench", false, "Generate a testbench for top-level fabric module"); - /* Add an option '--print_formal_verification_top_netlist' */ - shell_cmd_write_fabric_verilog.add_option("print_formal_verification_top_netlist", false, "Generate a top-level module which can be used in formal verification"); - /* Add an option '--print_autocheck_top_testbench' */ - CommandOptionId fabric_verilog_autocheck_tb_opt = shell_cmd_write_fabric_verilog.add_option("print_autocheck_top_testbench", false, "Generate a testbench for top-level fabric module with autocheck capability"); - shell_cmd_write_fabric_verilog.set_option_require_value(fabric_verilog_autocheck_tb_opt, openfpga::OPT_STRING); - /* Add an option '--verbose' */ - shell_cmd_write_fabric_verilog.add_option("verbose", false, "Enable verbose output"); - - /* Add command 'write_fabric_verilog' to the Shell */ - ShellCommandId shell_cmd_write_fabric_verilog_id = shell.add_command(shell_cmd_write_fabric_verilog, "generate Verilog netlists modeling full FPGA fabric"); - shell.set_command_class(shell_cmd_write_fabric_verilog_id, openfpga_verilog_cmd_class); - shell.set_command_execute_function(shell_cmd_write_fabric_verilog_id, write_fabric_verilog); + add_openfpga_write_fabric_verilog_command(shell, + openfpga_verilog_cmd_class, + shell_cmd_build_fabric_id); - /* The 'build_fabric' command should NOT be executed before 'link_openfpga_arch' */ - std::vector cmd_dependency_write_fabric_verilog; - cmd_dependency_write_fabric_verilog.push_back(shell_cmd_build_fabric_id); - shell.set_command_dependency(shell_cmd_write_fabric_verilog_id, cmd_dependency_write_fabric_verilog); + /******************************** + * Command 'write_verilog_testbench' + */ + add_openfpga_write_verilog_testbench_command(shell, + openfpga_verilog_cmd_class, + shell_cmd_build_fabric_id); } } /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_device_module.cpp b/openfpga/src/fabric/build_device_module.cpp index a137407ed..9680285fa 100644 --- a/openfpga/src/fabric/build_device_module.cpp +++ b/openfpga/src/fabric/build_device_module.cpp @@ -26,7 +26,8 @@ namespace openfpga { * The main function to be called for building module graphs * for a FPGA fabric *******************************************************************/ -ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, +ModuleManager build_device_module_graph(IoLocationMap& io_location_map, + const DeviceContext& vpr_device_ctx, const OpenfpgaContext& openfpga_ctx, const bool& compress_routing, const bool& duplicate_grid_pin, @@ -97,7 +98,8 @@ ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, } /* Build FPGA fabric top-level module */ - build_top_module(module_manager, openfpga_ctx.arch().circuit_lib, + build_top_module(module_manager, io_location_map, + openfpga_ctx.arch().circuit_lib, vpr_device_ctx.grid, vpr_device_ctx.rr_graph, openfpga_ctx.device_rr_gsb(), diff --git a/openfpga/src/fabric/build_device_module.h b/openfpga/src/fabric/build_device_module.h index fd585d986..b8f8c0ac8 100644 --- a/openfpga/src/fabric/build_device_module.h +++ b/openfpga/src/fabric/build_device_module.h @@ -14,7 +14,8 @@ /* begin namespace openfpga */ namespace openfpga { -ModuleManager build_device_module_graph(const DeviceContext& vpr_device_ctx, +ModuleManager build_device_module_graph(IoLocationMap& io_location_map, + const DeviceContext& vpr_device_ctx, const OpenfpgaContext& openfpga_ctx, const bool& compress_routing, const bool& duplicate_grid_pin, diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index 17ecc67e2..a5c346025 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -1103,7 +1103,6 @@ void build_grid_modules(ModuleManager& module_manager, duplicate_grid_pin, verbose); } - continue; } else { /* For CLB and heterogenenous blocks */ build_physical_tile_module(module_manager, circuit_lib, diff --git a/openfpga/src/fabric/build_top_module.cpp b/openfpga/src/fabric/build_top_module.cpp index 3357b2b95..707ff8971 100644 --- a/openfpga/src/fabric/build_top_module.cpp +++ b/openfpga/src/fabric/build_top_module.cpp @@ -89,6 +89,7 @@ size_t add_top_module_grid_instance(ModuleManager& module_manager, static vtr::Matrix add_top_module_grid_instances(ModuleManager& module_manager, const ModuleId& top_module, + IoLocationMap& io_location_map, const DeviceGrid& grids) { /* Reserve an array for the instance ids */ vtr::Matrix grid_instance_ids({grids.width(), grids.height()}); @@ -142,6 +143,7 @@ vtr::Matrix add_top_module_grid_instances(ModuleManager& module_manager, } /* Add instances of I/O grids to top_module */ + size_t io_counter = 0; for (const e_side& io_side : io_sides) { for (const vtr::Point& io_coordinate : io_coordinates[io_side]) { /* Bypass EMPTY grid */ @@ -157,6 +159,21 @@ vtr::Matrix add_top_module_grid_instances(ModuleManager& module_manager, VTR_ASSERT(true == is_io_type(grids[io_coordinate.x()][io_coordinate.y()].type)); /* Add a grid module to top_module*/ grid_instance_ids[io_coordinate.x()][io_coordinate.y()] = add_top_module_grid_instance(module_manager, top_module, grids[io_coordinate.x()][io_coordinate.y()].type, io_side, io_coordinate); + + /* MUST DO: register in io location mapping! + * I/O location mapping is a critical look-up for testbench generators + * As we add the I/O grid instances to top module by following order: + * TOP -> RIGHT -> BOTTOM -> LEFT + * The I/O index will increase in this way as well. + * This organization I/O indices is also consistent to the way + * that GPIOs are wired in function connect_gpio_module() + * + * Note: if you change the GPIO function, you should update here as well! + */ + for (int z = 0; z < grids[io_coordinate.x()][io_coordinate.y()].type->capacity; ++z) { + io_location_map.set_io_index(io_coordinate.x(), io_coordinate.y(), z, io_counter); + io_counter++; + } } } @@ -280,6 +297,7 @@ vtr::Matrix add_top_module_connection_block_instances(ModuleManager& mod * 5. Add module nets/submodules to connect configuration ports *******************************************************************/ void build_top_module(ModuleManager& module_manager, + IoLocationMap& io_location_map, const CircuitLibrary& circuit_lib, const DeviceGrid& grids, const RRGraph& rr_graph, @@ -301,7 +319,7 @@ void build_top_module(ModuleManager& module_manager, /* Add sub modules, which are grid, SB and CBX/CBY modules as instances */ /* Add all the grids across the fabric */ - vtr::Matrix grid_instance_ids = add_top_module_grid_instances(module_manager, top_module, grids); + vtr::Matrix grid_instance_ids = add_top_module_grid_instances(module_manager, top_module, io_location_map, grids); /* Add all the SBs across the fabric */ vtr::Matrix sb_instance_ids = add_top_module_switch_block_instances(module_manager, top_module, device_rr_gsb, compact_routing_hierarchy); /* Add all the CBX and CBYs across the fabric */ diff --git a/openfpga/src/fabric/build_top_module.h b/openfpga/src/fabric/build_top_module.h index 3b6badfb2..4ced0735b 100644 --- a/openfpga/src/fabric/build_top_module.h +++ b/openfpga/src/fabric/build_top_module.h @@ -14,6 +14,7 @@ #include "tile_direct.h" #include "arch_direct.h" #include "module_manager.h" +#include "io_location_map.h" /******************************************************************** * Function declaration @@ -23,6 +24,7 @@ namespace openfpga { void build_top_module(ModuleManager& module_manager, + IoLocationMap& io_location_map, const CircuitLibrary& circuit_lib, const DeviceGrid& grids, const RRGraph& rr_graph, diff --git a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp index 253798c86..f69b57ae6 100644 --- a/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp +++ b/openfpga/src/fpga_bitstream/build_grid_bitstream.cpp @@ -168,7 +168,8 @@ void build_physical_block_pin_interc_bitstream(BitstreamManager& bitstream_manag for (t_pb_graph_pin* src_pb_graph_pin : pb_graph_pin_inputs(des_pb_graph_pin, cur_interc)) { const PhysicalPbId& src_pb_id = physical_pb.find_pb(src_pb_graph_pin->parent_node); /* If the src pb id is not valid, we bypass it */ - if ( (true != physical_pb.valid_pb_id(src_pb_id)) + if ( (true == physical_pb.valid_pb_id(src_pb_id)) + && (AtomNetId::INVALID() != physical_pb.pb_graph_pin_atom_net(des_pb_id, des_pb_graph_pin)) && (physical_pb.pb_graph_pin_atom_net(src_pb_id, src_pb_graph_pin) == physical_pb.pb_graph_pin_atom_net(des_pb_id, des_pb_graph_pin))) { break; } diff --git a/openfpga/src/fpga_sdc/analysis_sdc_option.cpp b/openfpga/src/fpga_sdc/analysis_sdc_option.cpp new file mode 100644 index 000000000..444e73fb4 --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_option.cpp @@ -0,0 +1,39 @@ +/******************************************************************** + * Member functions for a data structure which includes all the options for the SDC generator + ********************************************************************/ +#include "analysis_sdc_option.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Public Constructors + ********************************************************************/ +AnalysisSdcOption::AnalysisSdcOption(const std::string& sdc_dir) { + sdc_dir_ = sdc_dir; + generate_sdc_analysis_ = false; +} + +/******************************************************************** + * Public accessors + ********************************************************************/ +std::string AnalysisSdcOption::sdc_dir() const { + return sdc_dir_; +} + +bool AnalysisSdcOption::generate_sdc_analysis() const { + return generate_sdc_analysis_; +} + +/******************************************************************** + * Public mutators + ********************************************************************/ +void AnalysisSdcOption::set_sdc_dir(const std::string& sdc_dir) { + sdc_dir_ = sdc_dir; +} + +void AnalysisSdcOption::set_generate_sdc_analysis(const bool& generate_sdc_analysis) { + generate_sdc_analysis_ = generate_sdc_analysis; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/analysis_sdc_option.h b/openfpga/src/fpga_sdc/analysis_sdc_option.h new file mode 100644 index 000000000..514e5e81c --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_option.h @@ -0,0 +1,30 @@ +#ifndef ANALYSIS_SDC_OPTION_H +#define ANALYSIS_SDC_OPTION_H + +/******************************************************************** + * A data structure to include all the options for the SDC generator + * in purpose of analyzing users' implementations + ********************************************************************/ + +#include + +/* begin namespace openfpga */ +namespace openfpga { + +class AnalysisSdcOption { + public: /* Public Constructors */ + AnalysisSdcOption(const std::string& sdc_dir); + public: /* Public accessors */ + std::string sdc_dir() const; + bool generate_sdc_analysis() const; + public: /* Public mutators */ + void set_sdc_dir(const std::string& sdc_dir); + void set_generate_sdc_analysis(const bool& generate_sdc_analysis); + private: /* Internal data */ + std::string sdc_dir_; + bool generate_sdc_analysis_; +}; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp new file mode 100644 index 000000000..f8ce1c703 --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.cpp @@ -0,0 +1,346 @@ +/******************************************************************** + * This file includes functions that print SDC (Synopsys Design Constraint) + * files in physical design tools, i.e., Place & Route (PnR) tools + * The SDC files are used to constrain the physical design for each grid + * (CLBs, heterogeneous blocks etc.) + * + * Note that this is different from the SDC to constrain VPR Place&Route + * engine! These SDCs are designed for PnR to generate FPGA layouts!!! + *******************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" +#include "openfpga_side_manager.h" + + +#include "openfpga_interconnect_types.h" +#include "vpr_utils.h" +#include "mux_utils.h" + +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" +#include "pb_type_utils.h" +#include "pb_graph_utils.h" + +#include "sdc_writer_naming.h" +#include "sdc_writer_utils.h" +#include "pnr_sdc_grid_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print pin-to-pin timing constraints for a given interconnection + * at an output port of a pb_graph node + *******************************************************************/ +static +void print_pnr_sdc_constrain_pb_pin_interc_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + t_pb_graph_pin* des_pb_graph_pin, + t_mode* physical_mode) { + + /* Validate file stream */ + valid_file_stream(fp); + + /* 1. identify pin interconnection type, + * 2. Identify the number of fan-in (Consider interconnection edges of only selected mode) + * 3. Print SDC timing constraints + */ + t_interconnect* cur_interc = pb_graph_pin_interc(des_pb_graph_pin, physical_mode); + size_t fan_in = pb_graph_pin_inputs(des_pb_graph_pin, cur_interc).size(); + if ((nullptr == cur_interc) || (0 == fan_in)) { + /* No interconnection matched */ + return; + } + + /* Print pin-to-pin SDC contraint here */ + /* For more than one mode defined, the direct interc has more than one input_edge , + * We need to find which edge is connected the pin we want + */ + for (int iedge = 0; iedge < des_pb_graph_pin->num_input_edges; iedge++) { + if (cur_interc != des_pb_graph_pin->input_edges[iedge]->interconnect) { + continue; + } + + /* Source pin, node, pb_type*/ + t_pb_graph_pin* src_pb_graph_pin = des_pb_graph_pin->input_edges[iedge]->input_pins[0]; + t_pb_graph_node* src_pb_graph_node = src_pb_graph_pin->parent_node; + /* Des pin, node, pb_type */ + t_pb_graph_node* des_pb_graph_node = des_pb_graph_pin->parent_node; + + /* Find the src module in module manager */ + std::string src_module_name = generate_physical_block_module_name(src_pb_graph_pin->parent_node->pb_type); + ModuleId src_module = module_manager.find_module(src_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(src_module)); + + ModulePortId src_module_port_id = module_manager.find_module_port(src_module, generate_pb_type_port_name(src_pb_graph_pin->port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(src_module, src_module_port_id)); + + /* Generate the name of the des instance name + * If des module is not the parent module, it is a child module. + * We should find the instance id + */ + std::string src_instance_name = src_module_name; + if (parent_module != src_module) { + src_instance_name = module_manager.module_name(parent_module) + std::string("/"); + /* Instance id is actually the placement index */ + size_t instance_id = src_pb_graph_node->placement_index; + if (true == module_manager.instance_name(parent_module, src_module, instance_id).empty()) { + src_instance_name += src_module_name; + src_instance_name += "_"; + src_instance_name += std::to_string(instance_id); + src_instance_name += "_"; + } else { + src_instance_name += module_manager.instance_name(parent_module, src_module, instance_id); + } + } + + /* Generate src port information */ + BasicPort src_port = module_manager.module_port(src_module, src_module_port_id); + src_port.set_width(src_pb_graph_pin->pin_number, src_pb_graph_pin->pin_number); + + /* Find the des module in module manager */ + std::string des_module_name = generate_physical_block_module_name(des_pb_graph_pin->parent_node->pb_type); + ModuleId des_module = module_manager.find_module(des_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(des_module)); + ModulePortId des_module_port_id = module_manager.find_module_port(des_module, generate_pb_type_port_name(des_pb_graph_pin->port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(des_module, des_module_port_id)); + + /* Generate the name of the des instance name + * If des module is not the parent module, it is a child module. + * We should find the instance id + */ + std::string des_instance_name = des_module_name; + if (parent_module != des_module) { + des_instance_name = module_manager.module_name(parent_module) + std::string("/"); + /* Instance id is actually the placement index */ + size_t instance_id = des_pb_graph_node->placement_index; + if (true == module_manager.instance_name(parent_module, des_module, instance_id).empty()) { + des_instance_name += des_module_name; + des_instance_name += "_"; + des_instance_name += std::to_string(instance_id); + des_instance_name += "_"; + } else { + des_instance_name += module_manager.instance_name(parent_module, des_module, instance_id); + } + } + + /* Generate des port information */ + BasicPort des_port = module_manager.module_port(des_module, des_module_port_id); + des_port.set_width(des_pb_graph_pin->pin_number, des_pb_graph_pin->pin_number); + + /* Print a SDC timing constraint */ + print_pnr_sdc_constrain_max_delay(fp, + src_instance_name, + generate_sdc_port(src_port), + des_instance_name, + generate_sdc_port(des_port), + des_pb_graph_pin->input_edges[iedge]->delay_max); + } +} + +/******************************************************************** + * Print port-to-port timing constraints which source from + * an output port of a pb_graph node + *******************************************************************/ +static +void print_pnr_sdc_constrain_pb_interc_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + t_pb_graph_node* des_pb_graph_node, + const e_circuit_pb_port_type& pb_port_type, + t_mode* physical_mode) { + /* Validate file stream */ + valid_file_stream(fp); + + switch (pb_port_type) { + case CIRCUIT_PB_PORT_INPUT: { + for (int iport = 0; iport < des_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < des_pb_graph_node->num_input_pins[iport]; ++ipin) { + /* If this is a idle block, we set 0 to the selected edge*/ + /* Get the selected edge of current pin*/ + print_pnr_sdc_constrain_pb_pin_interc_timing(fp, + module_manager, parent_module, + &(des_pb_graph_node->input_pins[iport][ipin]), + physical_mode); + } + } + break; + } + case CIRCUIT_PB_PORT_OUTPUT: { + for (int iport = 0; iport < des_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < des_pb_graph_node->num_output_pins[iport]; ++ipin) { + print_pnr_sdc_constrain_pb_pin_interc_timing(fp, + module_manager, parent_module, + &(des_pb_graph_node->output_pins[iport][ipin]), + physical_mode); + } + } + break; + } + case CIRCUIT_PB_PORT_CLOCK: { + /* Do NOT constrain clock here, it should be handled by Clock Tree Synthesis */ + break; + } + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid pb port type!\n"); + exit(1); + } +} + +/******************************************************************** + * This function will generate a SDC file for each pb_type, + * constraining the pin-to-pin timing between + * 1. input port of parent_pb_graph_node and input port of child_pb_graph_nodes + * 2. output port of parent_pb_graph_node and output port of child_pb_graph_nodes + * 3. output port of child_pb_graph_node and input port of child_pb_graph_nodes + *******************************************************************/ +static +void print_pnr_sdc_constrain_pb_graph_node_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + t_pb_graph_node* parent_pb_graph_node, + t_mode* physical_mode) { + + /* Get the pb_type definition related to the node */ + t_pb_type* physical_pb_type = parent_pb_graph_node->pb_type; + std::string pb_module_name = generate_physical_block_module_name(physical_pb_type); + + /* Find the pb module in module manager */ + ModuleId pb_module = module_manager.find_module(pb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(pb_module)); + + /* Create the file name for SDC */ + std::string sdc_fname(sdc_dir + pb_module_name + std::string(SDC_FILE_NAME_POSTFIX)); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Timing constraints for Grid " + pb_module_name + " in PnR")); + + /* We check output_pins of cur_pb_graph_node and its the input_edges + * Built the interconnections between outputs of cur_pb_graph_node and outputs of child_pb_graph_node + * child_pb_graph_node.output_pins -----------------> cur_pb_graph_node.outpins + * /|\ + * | + * input_pins, edges, output_pins + */ + print_pnr_sdc_constrain_pb_interc_timing(fp, + module_manager, pb_module, + parent_pb_graph_node, + CIRCUIT_PB_PORT_OUTPUT, + physical_mode); + + /* We check input_pins of child_pb_graph_node and its the input_edges + * Built the interconnections between inputs of cur_pb_graph_node and inputs of child_pb_graph_node + * cur_pb_graph_node.input_pins -----------------> child_pb_graph_node.input_pins + * /|\ + * | + * input_pins, edges, output_pins + */ + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) { + t_pb_graph_node* child_pb_graph_node = &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]); + /* For each child_pb_graph_node input pins*/ + print_pnr_sdc_constrain_pb_interc_timing(fp, + module_manager, pb_module, + child_pb_graph_node, + CIRCUIT_PB_PORT_INPUT, + physical_mode); + /* Do NOT constrain clock here, it should be handled by Clock Tree Synthesis */ + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Recursively print SDC timing constraints for a pb_type + * This function will generate a SDC file for each pb_type, + * constraining the pin-to-pin timing + *******************************************************************/ +static +void rec_print_pnr_sdc_constrain_pb_graph_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const VprDeviceAnnotation& device_annotation, + t_pb_graph_node* parent_pb_graph_node) { + /* Validate pb_graph node */ + if (nullptr == parent_pb_graph_node) { + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid parent_pb_graph_node.\n"); + exit(1); + } + + /* Get the pb_type */ + t_pb_type* parent_pb_type = parent_pb_graph_node->pb_type; + + /* No need to constrain the primitive node */ + if (true == is_primitive_pb_type(parent_pb_type)) { + return; + } + + /* Note we only go through the graph through the physical modes. + * which we build the modules + */ + t_mode* physical_mode = device_annotation.physical_mode(parent_pb_type); + + /* Write a SDC file for this pb_type */ + print_pnr_sdc_constrain_pb_graph_node_timing(sdc_dir, + module_manager, + parent_pb_graph_node, + physical_mode); + + /* Go recursively to the lower level in the pb_graph + * Note that we assume a full hierarchical P&R, we will only visit pb_graph_node of unique pb_type + */ + for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) { + rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, module_manager, + device_annotation, + &(parent_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][0])); + } +} + +/******************************************************************** + * Top-level function to print timing constraints for pb_types + *******************************************************************/ +void print_pnr_sdc_constrain_grid_timing(const std::string& sdc_dir, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager) { + + /* Start time count */ + vtr::ScopedStartFinishTimer timer("Write SDC for constraining grid timing for P&R flow"); + + for (const t_physical_tile_type& physical_tile : device_ctx.physical_tile_types) { + /* Bypass empty type or nullptr */ + if (true == is_empty_type(&physical_tile)) { + continue; + } else { + VTR_ASSERT(1 == physical_tile.equivalent_sites.size()); + t_pb_graph_node* pb_graph_head = physical_tile.equivalent_sites[0]->pb_graph_head; + if (nullptr == pb_graph_head) { + continue; + } + /* Special for I/O block, generate one module for each border side */ + rec_print_pnr_sdc_constrain_pb_graph_timing(sdc_dir, module_manager, + device_annotation, + pb_graph_head); + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h new file mode 100644 index 000000000..91c1b7030 --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_grid_writer.h @@ -0,0 +1,27 @@ +#ifndef PNR_SDC_GRID_WRITER_H +#define PNR_SDC_GRID_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "vpr_context.h" +#include "vpr_device_annotation.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_pnr_sdc_constrain_grid_timing(const std::string& sdc_dir, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/pnr_sdc_option.cpp b/openfpga/src/fpga_sdc/pnr_sdc_option.cpp new file mode 100644 index 000000000..3a3906b50 --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_option.cpp @@ -0,0 +1,113 @@ +/******************************************************************** + * Member functions for a data structure which includes all the options for the SDC generator + ********************************************************************/ +#include "pnr_sdc_option.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Public Constructors + ********************************************************************/ +PnrSdcOption::PnrSdcOption(const std::string& sdc_dir) { + sdc_dir_ = sdc_dir; + constrain_global_port_ = false; + constrain_grid_ = false; + constrain_sb_ = false; + constrain_cb_ = false; + constrain_configurable_memory_outputs_ = false; + constrain_routing_multiplexer_outputs_ = false; + constrain_switch_block_outputs_ = false; +} + +/******************************************************************** + * Public accessors + ********************************************************************/ +std::string PnrSdcOption::sdc_dir() const { + return sdc_dir_; +} + +bool PnrSdcOption::generate_sdc_pnr() const { + return constrain_global_port_ + || constrain_grid_ + || constrain_sb_ + || constrain_cb_ + || constrain_configurable_memory_outputs_ + || constrain_routing_multiplexer_outputs_ + || constrain_switch_block_outputs_; +} + +bool PnrSdcOption::constrain_global_port() const { + return constrain_global_port_; +} + +bool PnrSdcOption::constrain_grid() const { + return constrain_grid_; +} + +bool PnrSdcOption::constrain_sb() const { + return constrain_sb_; +} + +bool PnrSdcOption::constrain_cb() const { + return constrain_cb_; +} + +bool PnrSdcOption::constrain_configurable_memory_outputs() const { + return constrain_configurable_memory_outputs_; +} + +bool PnrSdcOption::constrain_routing_multiplexer_outputs() const { + return constrain_routing_multiplexer_outputs_; +} + +bool PnrSdcOption::constrain_switch_block_outputs() const { + return constrain_switch_block_outputs_; +} + +/******************************************************************** + * Public mutators + ********************************************************************/ +void PnrSdcOption::set_sdc_dir(const std::string& sdc_dir) { + sdc_dir_ = sdc_dir; +} + +void PnrSdcOption::set_generate_sdc_pnr(const bool& generate_sdc_pnr) { + constrain_global_port_ = generate_sdc_pnr; + constrain_grid_ = generate_sdc_pnr; + constrain_sb_ = generate_sdc_pnr; + constrain_cb_ = generate_sdc_pnr; + constrain_configurable_memory_outputs_ = generate_sdc_pnr; + constrain_routing_multiplexer_outputs_ = generate_sdc_pnr; + constrain_switch_block_outputs_ = generate_sdc_pnr; +} + +void PnrSdcOption::set_constrain_global_port(const bool& constrain_global_port) { + constrain_global_port_ = constrain_global_port; +} + +void PnrSdcOption::set_constrain_grid(const bool& constrain_grid) { + constrain_grid_ = constrain_grid; +} + +void PnrSdcOption::set_constrain_sb(const bool& constrain_sb) { + constrain_sb_ = constrain_sb; +} + +void PnrSdcOption::set_constrain_cb(const bool& constrain_cb) { + constrain_cb_ = constrain_cb; +} + +void PnrSdcOption::set_constrain_configurable_memory_outputs(const bool& constrain_config_mem_outputs) { + constrain_configurable_memory_outputs_ = constrain_config_mem_outputs; +} + +void PnrSdcOption::set_constrain_routing_multiplexer_outputs(const bool& constrain_routing_mux_outputs) { + constrain_routing_multiplexer_outputs_ = constrain_routing_mux_outputs; +} + +void PnrSdcOption::set_constrain_switch_block_outputs(const bool& constrain_sb_outputs) { + constrain_switch_block_outputs_ = constrain_sb_outputs; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_option.h b/openfpga/src/fpga_sdc/pnr_sdc_option.h new file mode 100644 index 000000000..a485a1946 --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_option.h @@ -0,0 +1,50 @@ +#ifndef PNR_SDC_OPTION_H +#define PNR_SDC_OPTION_H + +/******************************************************************** + * A data structure to include all the options for the SDC generator + * in purpose of constraining physical design of FPGA fabric in back-end flow + ********************************************************************/ + +#include + +/* begin namespace openfpga */ +namespace openfpga { + +class PnrSdcOption { + public: /* Public Constructors */ + PnrSdcOption(const std::string& sdc_dir); + public: /* Public accessors */ + std::string sdc_dir() const; + bool generate_sdc_pnr() const; + bool constrain_global_port() const; + bool constrain_grid() const; + bool constrain_sb() const; + bool constrain_cb() const; + bool constrain_configurable_memory_outputs() const; + bool constrain_routing_multiplexer_outputs() const; + bool constrain_switch_block_outputs() const; + public: /* Public mutators */ + void set_sdc_dir(const std::string& sdc_dir); + void set_generate_sdc_pnr(const bool& generate_sdc_pnr); + void set_constrain_global_port(const bool& constrain_global_port); + void set_constrain_grid(const bool& constrain_grid); + void set_constrain_sb(const bool& constrain_sb); + void set_constrain_cb(const bool& constrain_cb); + void set_constrain_configurable_memory_outputs(const bool& constrain_config_mem_outputs); + void set_constrain_routing_multiplexer_outputs(const bool& constrain_routing_mux_outputs); + void set_constrain_switch_block_outputs(const bool& constrain_sb_outputs); + private: /* Internal data */ + std::string sdc_dir_; + bool constrain_global_port_; + bool constrain_grid_; + bool constrain_sb_; + bool constrain_cb_; + bool constrain_configurable_memory_outputs_; + bool constrain_routing_multiplexer_outputs_; + bool constrain_switch_block_outputs_; +}; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp new file mode 100644 index 000000000..dcb5b6849 --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.cpp @@ -0,0 +1,411 @@ +/******************************************************************** + * This file includes functions that print SDC (Synopsys Design Constraint) + * files in physical design tools, i.e., Place & Route (PnR) tools + * The SDC files are used to constrain the physical design for each routing modules + * in FPGA fabric, such as Switch Blocks (SBs) and Connection Blocks (CBs) + * + * Note that this is different from the SDC to constrain VPR Place&Route + * engine! These SDCs are designed for PnR to generate FPGA layouts!!! + *******************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_side_manager.h" +#include "openfpga_digest.h" + +#include "mux_utils.h" + +#include "openfpga_naming.h" + +#include "openfpga_rr_graph_utils.h" +#include "build_routing_module_utils.h" + +#include "sdc_writer_naming.h" +#include "sdc_writer_utils.h" +#include "pnr_sdc_routing_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Find the timing constraints between the inputs and outputs of a routing + * multiplexer in a Switch Block + *******************************************************************/ +static +float find_pnr_sdc_switch_tmax(const t_rr_switch_inf& switch_inf) { + return switch_inf.R * switch_inf.Cout + switch_inf.Tdel; +} + +/******************************************************************** + * Set timing constraints between the inputs and outputs of a routing + * multiplexer in a Switch Block + *******************************************************************/ +static +void print_pnr_sdc_constrain_sb_mux_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& sb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const e_side& output_node_side, + const RRNodeId& output_rr_node) { + /* Validate file stream */ + valid_file_stream(fp); + + VTR_ASSERT( ( CHANX == rr_graph.node_type(output_rr_node) ) + || ( CHANY == rr_graph.node_type(output_rr_node) )); + + /* Find the module port corresponding to the output rr_node */ + ModulePortId module_output_port = find_switch_block_module_chan_port(module_manager, + sb_module, + rr_graph, + rr_gsb, + output_node_side, + output_rr_node, + OUT_PORT); + + /* Find the module port corresponding to the fan-in rr_nodes of the output rr_node */ + std::vector module_input_ports = find_switch_block_module_input_ports(module_manager, + sb_module, + rr_graph, + rr_gsb, + get_rr_graph_configurable_driver_nodes(rr_graph, output_rr_node)); + + /* Find timing constraints for each path (edge) */ + std::map switch_delays; + size_t edge_counter = 0; + for (const RREdgeId& edge : rr_graph.node_configurable_in_edges(output_rr_node)) { + /* Get the switch delay */ + const RRSwitchId& driver_switch = rr_graph.edge_switch(edge); + switch_delays[module_input_ports[edge_counter]] = find_pnr_sdc_switch_tmax(rr_graph.get_switch(driver_switch)); + edge_counter++; + } + + /* Find the starting points */ + for (const ModulePortId& module_input_port : module_input_ports) { + /* Constrain a path */ + print_pnr_sdc_constrain_port2port_timing(fp, + module_manager, + sb_module, module_input_port, + sb_module, module_output_port, + switch_delays[module_input_port]); + } +} + +/******************************************************************** + * Set timing constraints between the inputs and outputs of SBs, + * which are connected by routing multiplexers with the given delays + * specified in architectural XML file + * + * To enable block by block timing constraining, we generate the SDC + * file for each unique SB module + *******************************************************************/ +static +void print_pnr_sdc_constrain_sb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const RRGSB& rr_gsb) { + + /* Create the file name for Verilog netlist */ + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + std::string sdc_fname(sdc_dir + generate_switch_block_module_name(gsb_coordinate) + std::string(SDC_FILE_NAME_POSTFIX)); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + /* Validate file stream */ + check_file_stream(sdc_fname.c_str(), fp); + + std::string sb_module_name = generate_switch_block_module_name(gsb_coordinate); + ModuleId sb_module = module_manager.find_module(sb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Constrain timing of Switch Block " + sb_module_name + " for PnR")); + + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + for (size_t itrack = 0; itrack < rr_gsb.get_chan_width(side_manager.get_side()); ++itrack) { + const RRNodeId& chan_rr_node = rr_gsb.get_chan_node(side_manager.get_side(), itrack); + /* We only care the output port and it should indicate a SB mux */ + if (OUT_PORT != rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)) { + continue; + } + /* Constrain thru wires */ + if (false != rr_gsb.is_sb_node_passing_wire(rr_graph, side_manager.get_side(), itrack)) { + continue; + } + /* This is a MUX, constrain all the paths from an input to an output */ + print_pnr_sdc_constrain_sb_mux_timing(fp, + module_manager, sb_module, + rr_graph, + rr_gsb, + side_manager.get_side(), + chan_rr_node); + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Print SDC timing constraints for Switch blocks + * This function is designed for flatten routing hierarchy + *******************************************************************/ +void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb) { + + /* Start time count */ + vtr::ScopedStartFinishTimer timer("Write SDC for constrain Switch Block timing for P&R flow"); + + /* Get the range of SB array */ + vtr::Point sb_range = device_rr_gsb.get_gsb_range(); + /* Go for each SB */ + for (size_t ix = 0; ix < sb_range.x(); ++ix) { + for (size_t iy = 0; iy < sb_range.y(); ++iy) { + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + if (false == rr_gsb.is_sb_exist()) { + continue; + } + print_pnr_sdc_constrain_sb_timing(sdc_dir, + module_manager, + rr_graph, + rr_gsb); + } + } +} + +/******************************************************************** + * Print SDC timing constraints for Switch blocks + * This function is designed for compact routing hierarchy + *******************************************************************/ +void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb) { + + /* Start time count */ + vtr::ScopedStartFinishTimer timer("Write SDC for constrain Switch Block timing for P&R flow"); + + for (size_t isb = 0; isb < device_rr_gsb.get_num_sb_unique_module(); ++isb) { + const RRGSB& rr_gsb = device_rr_gsb.get_sb_unique_module(isb); + if (false == rr_gsb.is_sb_exist()) { + continue; + } + print_pnr_sdc_constrain_sb_timing(sdc_dir, + module_manager, + rr_graph, + rr_gsb); + } +} + +/******************************************************************** + * Set timing constraints between the inputs and outputs of a routing + * multiplexer in a Connection Block + *******************************************************************/ +static +void print_pnr_sdc_constrain_cb_mux_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& cb_module, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const RRNodeId& output_rr_node) { + /* Validate file stream */ + valid_file_stream(fp); + + VTR_ASSERT(IPIN == rr_graph.node_type(output_rr_node)); + + /* We have OPINs since we may have direct connections: + * These connections should be handled by other functions in the compact_netlist.c + * So we just return here for OPINs + */ + if (0 == get_rr_graph_configurable_driver_nodes(rr_graph, output_rr_node).size()) { + return; + } + + /* Find the module port corresponding to the output rr_node */ + ModulePortId module_output_port = find_connection_block_module_ipin_port(module_manager, + cb_module, + rr_graph, + rr_gsb, + output_rr_node); + + /* Find the module port corresponding to the fan-in rr_nodes of the output rr_node */ + std::vector module_input_ports = find_connection_block_module_input_ports(module_manager, + cb_module, + rr_graph, + rr_gsb, + cb_type, + get_rr_graph_configurable_driver_nodes(rr_graph, output_rr_node)); + + /* Find timing constraints for each path (edge) */ + std::map switch_delays; + size_t edge_counter = 0; + for (const RREdgeId& edge : rr_graph.node_configurable_in_edges(output_rr_node)) { + /* Get the switch delay */ + const RRSwitchId& driver_switch = rr_graph.edge_switch(edge); + switch_delays[module_input_ports[edge_counter]] = find_pnr_sdc_switch_tmax(rr_graph.get_switch(driver_switch)); + edge_counter++; + } + + /* Find the starting points */ + for (const ModulePortId& module_input_port : module_input_ports) { + /* Constrain a path */ + print_pnr_sdc_constrain_port2port_timing(fp, + module_manager, + cb_module, module_input_port, + cb_module, module_output_port, + switch_delays[module_input_port]); + } +} + + +/******************************************************************** + * Print SDC timing constraints for a Connection block + * This function is designed for compact routing hierarchy + *******************************************************************/ +static +void print_pnr_sdc_constrain_cb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const RRGSB& rr_gsb, + const t_rr_type& cb_type) { + /* Create the netlist */ + vtr::Point gsb_coordinate(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + + /* Find the module name and create a SDC file for it */ + std::string sdc_fname(sdc_dir + generate_connection_block_module_name(cb_type, gsb_coordinate) + std::string(SDC_FILE_NAME_POSTFIX)); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + /* Validate file stream */ + check_file_stream(sdc_fname.c_str(), fp); + + std::string cb_module_name = generate_connection_block_module_name(cb_type, gsb_coordinate); + ModuleId cb_module = module_manager.find_module(cb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Constrain timing of Connection Block " + cb_module_name + " for PnR")); + + std::vector cb_sides = rr_gsb.get_cb_ipin_sides(cb_type); + + for (size_t side = 0; side < cb_sides.size(); ++side) { + enum e_side cb_ipin_side = cb_sides[side]; + SideManager side_manager(cb_ipin_side); + for (size_t inode = 0; inode < rr_gsb.get_num_ipin_nodes(cb_ipin_side); ++inode) { + const RRNodeId& ipin_rr_node = rr_gsb.get_ipin_node(cb_ipin_side, inode); + print_pnr_sdc_constrain_cb_mux_timing(fp, + module_manager, cb_module, + rr_graph, rr_gsb, cb_type, + ipin_rr_node); + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Iterate over all the connection blocks in a device + * and print SDC file for each of them + *******************************************************************/ +static +void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb, + const t_rr_type& cb_type) { + /* Build unique X-direction connection block modules */ + vtr::Point cb_range = device_rr_gsb.get_gsb_range(); + + for (size_t ix = 0; ix < cb_range.x(); ++ix) { + for (size_t iy = 0; iy < cb_range.y(); ++iy) { + /* Check if the connection block exists in the device! + * Some of them do NOT exist due to heterogeneous blocks (height > 1) + * We will skip those modules + */ + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + if (false == rr_gsb.is_cb_exist(cb_type)) { + continue; + } + print_pnr_sdc_constrain_cb_timing(sdc_dir, + module_manager, + rr_graph, + rr_gsb, + cb_type); + + } + } +} + +/******************************************************************** + * Iterate over all the connection blocks in a device + * and print SDC file for each of them + *******************************************************************/ +void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb) { + + /* Start time count */ + vtr::ScopedStartFinishTimer timer("Write SDC for constrain Connection Block timing for P&R flow"); + + print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, module_manager, + rr_graph, + device_rr_gsb, + CHANX); + + print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_dir, module_manager, + rr_graph, + device_rr_gsb, + CHANY); +} + +/******************************************************************** + * Print SDC timing constraints for Connection blocks + * This function is designed for compact routing hierarchy + *******************************************************************/ +void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb) { + + /* Start time count */ + vtr::ScopedStartFinishTimer timer("Write SDC for constrain Connection Block timing for P&R flow"); + + /* Print SDC for unique X-direction connection block modules */ + for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(CHANX); ++icb) { + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANX, icb); + print_pnr_sdc_constrain_cb_timing(sdc_dir, + module_manager, + rr_graph, + unique_mirror, + CHANX); + } + + /* Print SDC for unique Y-direction connection block modules */ + for (size_t icb = 0; icb < device_rr_gsb.get_num_cb_unique_module(CHANY); ++icb) { + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(CHANY, icb); + print_pnr_sdc_constrain_cb_timing(sdc_dir, + module_manager, + rr_graph, + unique_mirror, + CHANY); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h new file mode 100644 index 000000000..65ce60ca8 --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_routing_writer.h @@ -0,0 +1,42 @@ +#ifndef PNR_SDC_ROUTING_WRITER_H +#define PNR_SDC_ROUTING_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "module_manager.h" +#include "device_rr_gsb.h" +#include "rr_graph_obj.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_pnr_sdc_flatten_routing_constrain_sb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb); + +void print_pnr_sdc_compact_routing_constrain_sb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb); + +void print_pnr_sdc_flatten_routing_constrain_cb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb); + +void print_pnr_sdc_compact_routing_constrain_cb_timing(const std::string& sdc_dir, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const DeviceRRGSB& device_rr_gsb); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp new file mode 100644 index 000000000..9d1478037 --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.cpp @@ -0,0 +1,431 @@ +/******************************************************************** + * This file includes functions that print SDC (Synopsys Design Constraint) + * files in physical design tools, i.e., Place & Route (PnR) tools + * The SDC files are used to constrain the physical design for each module + * in FPGA fabric, such as Configurable Logic Blocks (CLBs), + * Heterogeneous blocks, Switch Blocks (SBs) and Connection Blocks (CBs) + * + * Note that this is different from the SDC to constrain VPR Place&Route + * engine! These SDCs are designed for PnR to generate FPGA layouts!!! + *******************************************************************/ +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_time.h" +#include "vtr_log.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "mux_utils.h" + +#include "openfpga_naming.h" + +#include "sdc_writer_naming.h" +#include "sdc_writer_utils.h" +#include "sdc_memory_utils.h" +#include "pnr_sdc_routing_writer.h" +#include "pnr_sdc_grid_writer.h" +#include "pnr_sdc_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * 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. + * To handle this, we disable the outputs of memory cells + *******************************************************************/ +static +void print_pnr_sdc_constrain_configurable_memory_outputs(const std::string& sdc_dir, + const ModuleManager& module_manager, + const ModuleId& top_module) { + + /* Create the file name for Verilog netlist */ + std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_CONFIG_MEM_OUTPUTS_FILE_NAME)); + + /* Start time count */ + std::string timer_message = std::string("Write SDC to disable configurable memory outputs for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Disable configurable memory outputs for PnR")); + + /* Go recursively in the module manager, starting from the top-level module: instance id of the top-level module is 0 by default */ + rec_print_pnr_sdc_disable_configurable_memory_module_output(fp, module_manager, top_module, + format_dir_path(module_manager.module_name(top_module))); + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Break combinational loops in FPGA fabric, which mainly come from + * loops of multiplexers. + * To handle this, we disable the timing at outputs of routing multiplexers + *******************************************************************/ +static +void print_sdc_disable_routing_multiplexer_outputs(const std::string& sdc_dir, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const ModuleManager& module_manager) { + /* Create the file name for Verilog netlist */ + std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_MUX_OUTPUTS_FILE_NAME)); + + /* Start time count */ + std::string timer_message = std::string("Write SDC to disable routing multiplexer outputs for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Disable routing multiplexer outputs for PnR")); + + /* Iterate over the MUX modules */ + for (const MuxId& mux_id : mux_lib.muxes()) { + const CircuitModelId& mux_model = mux_lib.mux_circuit_model(mux_id); + + /* Skip LUTs, we only care about multiplexers here */ + if (CIRCUIT_MODEL_MUX != circuit_lib.model_type(mux_model)) { + continue; + } + + const MuxGraph& mux_graph = mux_lib.mux_graph(mux_id); + std::string mux_module_name = generate_mux_subckt_name(circuit_lib, mux_model, + find_mux_num_datapath_inputs(circuit_lib, mux_model, mux_graph.num_inputs()), + std::string("")); + /* Find the module name in module manager */ + ModuleId mux_module = module_manager.find_module(mux_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(mux_module)); + + /* Disable the timing for the output ports */ + for (const BasicPort& output_port : module_manager.module_ports_by_type(mux_module, ModuleManager::MODULE_OUTPUT_PORT)) { + fp << "set_disable_timing [get_pins -filter \"name =~ " << output_port.get_name() << "*\" "; + fp << "-of [get_cells -hier -filter \"ref_lib_cell_name == " << mux_module_name << "\"]]" << std::endl; + fp << std::endl; + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Break combinational loops in FPGA fabric, which mainly come from + * loops of multiplexers. + * To handle this, we disable the timing at outputs of Switch blocks + * This function is designed for flatten routing hierarchy + *******************************************************************/ +static +void print_pnr_sdc_flatten_routing_disable_switch_block_outputs(const std::string& sdc_dir, + const ModuleManager& module_manager, + const DeviceRRGSB& device_rr_gsb) { + /* Create the file name for Verilog netlist */ + std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_SB_OUTPUTS_FILE_NAME)); + + /* Start time count */ + std::string timer_message = std::string("Write SDC to disable switch block outputs for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Disable Switch Block outputs for PnR")); + + /* Get the range of SB array */ + vtr::Point sb_range = device_rr_gsb.get_gsb_range(); + /* Go for each SB */ + for (size_t ix = 0; ix < sb_range.x(); ++ix) { + for (size_t iy = 0; iy < sb_range.y(); ++iy) { + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + + if (false == rr_gsb.is_sb_exist()) { + continue; + } + + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + std::string sb_instance_name = generate_switch_block_module_name(gsb_coordinate); + + ModuleId sb_module = module_manager.find_module(sb_instance_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + /* Disable the outputs of the module */ + for (const BasicPort& output_port : module_manager.module_ports_by_type(sb_module, ModuleManager::MODULE_OUTPUT_PORT)) { + fp << "set_disable_timing " << sb_instance_name << "/" << output_port.get_name() << std::endl; + fp << std::endl; + } + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Break combinational loops in FPGA fabric, which mainly come from + * loops of multiplexers. + * To handle this, we disable the timing at outputs of Switch blocks + * This function is designed for compact routing hierarchy + *******************************************************************/ +static +void print_pnr_sdc_compact_routing_disable_switch_block_outputs(const std::string& sdc_dir, + const ModuleManager& module_manager, + const ModuleId& top_module, + const DeviceRRGSB& device_rr_gsb) { + /* Create the file name for Verilog netlist */ + std::string sdc_fname(sdc_dir + std::string(SDC_DISABLE_SB_OUTPUTS_FILE_NAME)); + + /* Start time count */ + std::string timer_message = std::string("Write SDC to disable switch block outputs for P&R flow '") + sdc_fname + std::string("'"); + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Disable Switch Block outputs for PnR")); + + /* Build unique switch block modules */ + for (size_t isb = 0; isb < device_rr_gsb.get_num_sb_unique_module(); ++isb) { + const RRGSB& rr_gsb = device_rr_gsb.get_sb_unique_module(isb); + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + std::string sb_module_name = generate_switch_block_module_name(gsb_coordinate); + + ModuleId sb_module = module_manager.find_module(sb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + /* Find all the instances in the top-level module */ + for (const size_t& instance_id : module_manager.child_module_instances(top_module, sb_module)) { + std::string sb_instance_name = module_manager.instance_name(top_module, sb_module, instance_id); + /* Disable the outputs of the module */ + for (const BasicPort& output_port : module_manager.module_ports_by_type(sb_module, ModuleManager::MODULE_OUTPUT_PORT)) { + fp << "set_disable_timing " << sb_instance_name << "/" << output_port.get_name() << std::endl; + fp << std::endl; + } + } + } + + /* Close file handler */ + fp.close(); +} + +/******************************************************************** + * Top-level function to print a number of SDC files in different purpose + * This function will generate files upon the options provided by users + * 1. Design constraints for CLBs + * 2. Design constraints for Switch Blocks + * 3. Design constraints for Connection Blocks + * 4. Design constraints for breaking the combinational loops in FPGA fabric + *******************************************************************/ +void print_pnr_sdc(const PnrSdcOption& sdc_options, + const float& programming_critical_path_delay, + const float& operating_critical_path_delay, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const DeviceRRGSB& device_rr_gsb, + const ModuleManager& module_manager, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const bool& compact_routing_hierarchy) { + + /* Constrain global ports */ + if (true == sdc_options.constrain_global_port()) { + print_pnr_sdc_global_ports(sdc_options.sdc_dir(), + programming_critical_path_delay, + operating_critical_path_delay, + circuit_lib, global_ports); + } + + std::string top_module_name = generate_fpga_top_module_name(); + ModuleId top_module = module_manager.find_module(top_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(top_module)); + + /* Output Design Constraints to disable outputs of memory cells */ + if (true == sdc_options.constrain_configurable_memory_outputs()) { + print_pnr_sdc_constrain_configurable_memory_outputs(sdc_options.sdc_dir(), module_manager, top_module); + } + + /* Break loops from Multiplexer Output */ + if (true == sdc_options.constrain_routing_multiplexer_outputs()) { + print_sdc_disable_routing_multiplexer_outputs(sdc_options.sdc_dir(), + mux_lib, circuit_lib, + module_manager); + } + + /* Break loops from any SB output */ + if (true == sdc_options.constrain_switch_block_outputs()) { + if (true == compact_routing_hierarchy) { + print_pnr_sdc_compact_routing_disable_switch_block_outputs(sdc_options.sdc_dir(), + module_manager, top_module, + device_rr_gsb); + } else { + VTR_ASSERT_SAFE (false == compact_routing_hierarchy); + print_pnr_sdc_flatten_routing_disable_switch_block_outputs(sdc_options.sdc_dir(), + module_manager, + device_rr_gsb); + } + } + + /* Output routing constraints for Switch Blocks */ + if (true == sdc_options.constrain_sb()) { + if (true == compact_routing_hierarchy) { + print_pnr_sdc_compact_routing_constrain_sb_timing(sdc_options.sdc_dir(), + module_manager, + device_ctx.rr_graph, + device_rr_gsb); + } else { + VTR_ASSERT_SAFE (false == compact_routing_hierarchy); + print_pnr_sdc_flatten_routing_constrain_sb_timing(sdc_options.sdc_dir(), + module_manager, + device_ctx.rr_graph, + device_rr_gsb); + } + } + + /* Output routing constraints for Connection Blocks */ + if (true == sdc_options.constrain_cb()) { + if (true == compact_routing_hierarchy) { + print_pnr_sdc_compact_routing_constrain_cb_timing(sdc_options.sdc_dir(), + module_manager, + device_ctx.rr_graph, + device_rr_gsb); + } else { + VTR_ASSERT_SAFE (false == compact_routing_hierarchy); + print_pnr_sdc_flatten_routing_constrain_cb_timing(sdc_options.sdc_dir(), + module_manager, + device_ctx.rr_graph, + device_rr_gsb); + } + } + + /* Output Timing constraints for Programmable blocks */ + if (true == sdc_options.constrain_grid()) { + print_pnr_sdc_constrain_grid_timing(sdc_options.sdc_dir(), + device_ctx, + device_annotation, + module_manager); + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/pnr_sdc_writer.h b/openfpga/src/fpga_sdc/pnr_sdc_writer.h new file mode 100644 index 000000000..1016d57da --- /dev/null +++ b/openfpga/src/fpga_sdc/pnr_sdc_writer.h @@ -0,0 +1,38 @@ +#ifndef PNR_SDC_WRITER_H +#define PNR_SDC_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "vpr_context.h" +#include "vpr_device_annotation.h" +#include "device_rr_gsb.h" +#include "module_manager.h" +#include "mux_library.h" +#include "circuit_library.h" +#include "pnr_sdc_option.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_pnr_sdc(const PnrSdcOption& sdc_options, + const float& programming_critical_path_delay, + const float& operating_critical_path_delay, + const DeviceContext& device_ctx, + const VprDeviceAnnotation& device_annotation, + const DeviceRRGSB& device_rr_gsb, + const ModuleManager& module_manager, + const MuxLibrary& mux_lib, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const bool& compact_routing_hierarchy); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/sdc_memory_utils.cpp b/openfpga/src/fpga_sdc/sdc_memory_utils.cpp new file mode 100644 index 000000000..1910b4197 --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_memory_utils.cpp @@ -0,0 +1,69 @@ +/******************************************************************** + * Most utilized function used to constrain memory cells in FPGA + * fabric using SDC commands + *******************************************************************/ + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "sdc_writer_utils.h" + +#include "sdc_memory_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print SDC commands to disable outputs of all the configurable memory modules + * in a given module + * This function will be executed in a recursive way, + * using a Depth-First Search (DFS) strategy + * It will iterate over all the configurable children under each module + * and print a SDC command to disable its outputs + *******************************************************************/ +void rec_print_pnr_sdc_disable_configurable_memory_module_output(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_module_path) { + + /* For each configurable child, we will go one level down in priority */ + for (size_t child_index = 0; child_index < module_manager.configurable_children(parent_module).size(); ++child_index) { + std::string child_module_path = parent_module_path; + ModuleId child_module_id = module_manager.configurable_children(parent_module)[child_index]; + size_t child_instance_id = module_manager.configurable_child_instances(parent_module)[child_index]; + if (true == module_manager.instance_name(parent_module, child_module_id, child_instance_id).empty()) { + /* Give a default name __ */ + child_module_path += module_manager.module_name(child_module_id); + child_module_path += "_"; + child_module_path += std::to_string(child_instance_id); + child_module_path += "_"; + } else { + child_module_path += module_manager.instance_name(parent_module, child_module_id, child_instance_id); + } + child_module_path = format_dir_path(child_module_path); + + rec_print_pnr_sdc_disable_configurable_memory_module_output(fp, module_manager, + child_module_id, + child_module_path); + } + + /* If there is no configurable children any more, this is a leaf module, print a SDC command for disable timing */ + if (0 < module_manager.configurable_children(parent_module).size()) { + return; + } + + /* Validate file stream */ + valid_file_stream(fp); + + /* Disable timing for each output port of this module */ + for (const BasicPort& output_port : module_manager.module_ports_by_type(parent_module, ModuleManager::MODULE_OUTPUT_PORT)) { + for (const size_t& pin : output_port.pins()) { + BasicPort output_pin(output_port.get_name(), pin, pin); + fp << "set_disable_timing "; + fp << parent_module_path << generate_sdc_port(output_pin); + fp << std::endl; + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/sdc_memory_utils.h b/openfpga/src/fpga_sdc/sdc_memory_utils.h new file mode 100644 index 000000000..7fbf92294 --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_memory_utils.h @@ -0,0 +1,25 @@ +#ifndef SDC_MEMORY_UTILS_H +#define SDC_MEMORY_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void rec_print_pnr_sdc_disable_configurable_memory_module_output(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_module_path); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/sdc_writer_naming.h b/openfpga/src/fpga_sdc/sdc_writer_naming.h new file mode 100644 index 000000000..9783e29f2 --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_writer_naming.h @@ -0,0 +1,20 @@ +#ifndef SDC_WRITER_NAMING_H +#define SDC_WRITER_NAMING_H + +/* begin namespace openfpga */ +namespace openfpga { + +constexpr char* SDC_FILE_NAME_POSTFIX = ".sdc"; + +constexpr char* SDC_GLOBAL_PORTS_FILE_NAME = "global_ports.sdc"; +constexpr char* SDC_BENCHMARK_ANALYSIS_FILE_NAME= "fpga_top_analysis.sdc"; +constexpr char* SDC_DISABLE_CONFIG_MEM_OUTPUTS_FILE_NAME = "disable_configurable_memory_outputs.sdc"; +constexpr char* SDC_DISABLE_MUX_OUTPUTS_FILE_NAME = "disable_routing_multiplexer_outputs.sdc"; +constexpr char* SDC_DISABLE_SB_OUTPUTS_FILE_NAME = "disable_sb_outputs.sdc"; +constexpr char* SDC_CB_FILE_NAME = "cb.sdc"; + +constexpr char* SDC_ANALYSIS_FILE_NAME = "fpga_top_analysis.sdc"; + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp new file mode 100644 index 000000000..328f87ce6 --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_writer_utils.cpp @@ -0,0 +1,202 @@ +/******************************************************************** + * This file include most utilized functions to be used in SDC writers + *******************************************************************/ +#include +#include +#include + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "sdc_writer_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Write a head (description) in SDC file + *******************************************************************/ +void print_sdc_file_header(std::fstream& fp, + const std::string& usage) { + + valid_file_stream(fp); + + auto end = std::chrono::system_clock::now(); + std::time_t end_time = std::chrono::system_clock::to_time_t(end); + + fp << "#############################################" << std::endl; + fp << "#\tSynopsys Design Constraints (SDC)" << std::endl; + fp << "#\tFor FPGA fabric " << std::endl; + fp << "#\tDescription: " << usage << std::endl; + fp << "#\tAuthor: Xifan TANG " << std::endl; + fp << "#\tOrganization: University of Utah " << std::endl; + fp << "#\tDate: " << std::ctime(&end_time); + fp << "#############################################" << std::endl; + fp << std::endl; +} + +/******************************************************************** + * Write a port in SDC format + *******************************************************************/ +std::string generate_sdc_port(const BasicPort& port) { + std::string sdc_line; + + std::string size_str = "[" + std::to_string(port.get_lsb()) + ":" + std::to_string(port.get_msb()) + "]"; + + /* Only connection require a format of [:] + * others require a format of [:] + */ + /* When LSB == MSB, we can use a simplified format []*/ + if ( 1 == port.get_width()) { + size_str = "[" + std::to_string(port.get_lsb()) + "]"; + } + sdc_line = port.get_name() + size_str; + + return sdc_line; +} + +/******************************************************************** + * Constrain a path between two ports of a module with a given maximum timing value + *******************************************************************/ +void print_pnr_sdc_constrain_max_delay(std::fstream& fp, + const std::string& src_instance_name, + const std::string& src_port_name, + const std::string& des_instance_name, + const std::string& des_port_name, + const float& delay) { + /* Validate file stream */ + valid_file_stream(fp); + + fp << "set_max_delay"; + + fp << " -from "; + if (!src_instance_name.empty()) { + fp << src_instance_name << "/"; + } + fp << src_port_name; + + fp << " -to "; + + if (!des_instance_name.empty()) { + fp << des_instance_name << "/"; + } + fp << des_port_name; + + fp << " " << std::setprecision(10) << delay; + + fp << std::endl; +} + +/******************************************************************** + * Constrain a path between two ports of a module with a given timing value + * Note: this function uses set_max_delay !!! + *******************************************************************/ +void print_pnr_sdc_constrain_module_port2port_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& input_parent_module_id, + const ModulePortId& module_input_port_id, + const ModuleId& output_parent_module_id, + const ModulePortId& module_output_port_id, + const float& tmax) { + print_pnr_sdc_constrain_max_delay(fp, + module_manager.module_name(input_parent_module_id), + generate_sdc_port(module_manager.module_port(input_parent_module_id, module_input_port_id)), + module_manager.module_name(output_parent_module_id), + generate_sdc_port(module_manager.module_port(output_parent_module_id, module_output_port_id)), + tmax); + +} + +/******************************************************************** + * Constrain a path between two ports of a module with a given timing value + * This function will NOT output the module name + * Note: this function uses set_max_delay !!! + *******************************************************************/ +void print_pnr_sdc_constrain_port2port_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& input_parent_module_id, + const ModulePortId& module_input_port_id, + const ModuleId& output_parent_module_id, + const ModulePortId& module_output_port_id, + const float& tmax) { + print_pnr_sdc_constrain_max_delay(fp, + std::string(), + generate_sdc_port(module_manager.module_port(input_parent_module_id, module_input_port_id)), + std::string(), + generate_sdc_port(module_manager.module_port(output_parent_module_id, module_output_port_id)), + tmax); + +} + +/******************************************************************** + * Disable timing for a port + *******************************************************************/ +void print_sdc_disable_port_timing(std::fstream& fp, + const BasicPort& port) { + /* Validate file stream */ + valid_file_stream(fp); + + fp << "set_disable_timing "; + + fp << generate_sdc_port(port); + + fp << std::endl; +} + +/******************************************************************** + * Set the input delay for a port in SDC format + * Note that the input delay will be bounded by a clock port + *******************************************************************/ +void print_sdc_set_port_input_delay(std::fstream& fp, + const BasicPort& port, + const BasicPort& clock_port, + const float& delay) { + /* Validate file stream */ + valid_file_stream(fp); + + fp << "set_input_delay "; + + fp << "-clock "; + + fp << generate_sdc_port(clock_port); + + fp << " -max "; + + fp << std::setprecision(10) << delay; + + fp << " "; + + fp << generate_sdc_port(port); + + fp << std::endl; +} + +/******************************************************************** + * Set the output delay for a port in SDC format + * Note that the output delay will be bounded by a clock port + *******************************************************************/ +void print_sdc_set_port_output_delay(std::fstream& fp, + const BasicPort& port, + const BasicPort& clock_port, + const float& delay) { + /* Validate file stream */ + valid_file_stream(fp); + + fp << "set_output_delay "; + + fp << "-clock "; + + fp << generate_sdc_port(clock_port); + + fp << " -max "; + + fp << std::setprecision(10) << delay; + + fp << " "; + + fp << generate_sdc_port(port); + + fp << std::endl; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/sdc_writer_utils.h b/openfpga/src/fpga_sdc/sdc_writer_utils.h new file mode 100644 index 000000000..4e0d86839 --- /dev/null +++ b/openfpga/src/fpga_sdc/sdc_writer_utils.h @@ -0,0 +1,62 @@ +#ifndef SDC_WRITER_UTILS_H +#define SDC_WRITER_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "openfpga_port.h" +#include "module_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_sdc_file_header(std::fstream& fp, + const std::string& usage); + +std::string generate_sdc_port(const BasicPort& port); + +void print_pnr_sdc_constrain_max_delay(std::fstream& fp, + const std::string& src_instance_name, + const std::string& src_port_name, + const std::string& des_instance_name, + const std::string& des_port_name, + const float& delay); + +void print_pnr_sdc_constrain_module_port2port_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& input_parent_module_id, + const ModulePortId& module_input_port_id, + const ModuleId& output_parent_module_id, + const ModulePortId& module_output_port_id, + const float& tmax); + +void print_pnr_sdc_constrain_port2port_timing(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& input_parent_module_id, + const ModulePortId& module_input_port_id, + const ModuleId& output_parent_module_id, + const ModulePortId& module_output_port_id, + const float& tmax); + +void print_sdc_disable_port_timing(std::fstream& fp, + const BasicPort& port); + +void print_sdc_set_port_input_delay(std::fstream& fp, + const BasicPort& port, + const BasicPort& clock_port, + const float& delay); + +void print_sdc_set_port_output_delay(std::fstream& fp, + const BasicPort& port, + const BasicPort& clock_port, + const float& delay); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_options.cpp b/openfpga/src/fpga_verilog/fabric_verilog_options.cpp similarity index 75% rename from openfpga/src/fpga_verilog/verilog_options.cpp rename to openfpga/src/fpga_verilog/fabric_verilog_options.cpp index b3b398dd7..ba843ef20 100644 --- a/openfpga/src/fpga_verilog/verilog_options.cpp +++ b/openfpga/src/fpga_verilog/fabric_verilog_options.cpp @@ -3,7 +3,7 @@ ******************************************************************************/ #include "vtr_assert.h" -#include "verilog_options.h" +#include "fabric_verilog_options.h" /* begin namespace openfpga */ namespace openfpga { @@ -52,22 +52,6 @@ bool FabricVerilogOption::compress_routing() const { return compress_routing_; } -bool FabricVerilogOption::print_top_testbench() const { - return print_top_testbench_; -} - -bool FabricVerilogOption::print_formal_verification_top_netlist() const { - return print_formal_verification_top_netlist_; -} - -bool FabricVerilogOption::print_autocheck_top_testbench() const { - return false == reference_verilog_file_path_.empty(); -} - -std::string FabricVerilogOption::reference_verilog_file_path() const { - return reference_verilog_file_path_; -} - bool FabricVerilogOption::print_user_defined_template() const { return print_user_defined_template_; } @@ -103,18 +87,6 @@ void FabricVerilogOption::set_compress_routing(const bool& enabled) { compress_routing_ = enabled; } -void FabricVerilogOption::set_print_top_testbench(const bool& enabled) { - print_top_testbench_ = enabled; -} - -void FabricVerilogOption::set_print_formal_verification_top_netlist(const bool& enabled) { - print_formal_verification_top_netlist_ = enabled; -} - -void FabricVerilogOption::set_print_autocheck_top_testbench(const std::string& reference_verilog_file_path) { - reference_verilog_file_path_ = reference_verilog_file_path; -} - void FabricVerilogOption::set_print_user_defined_template(const bool& enabled) { print_user_defined_template_ = enabled; } diff --git a/openfpga/src/fpga_verilog/verilog_options.h b/openfpga/src/fpga_verilog/fabric_verilog_options.h similarity index 89% rename from openfpga/src/fpga_verilog/verilog_options.h rename to openfpga/src/fpga_verilog/fabric_verilog_options.h index 48e32086b..38ef05f9a 100644 --- a/openfpga/src/fpga_verilog/verilog_options.h +++ b/openfpga/src/fpga_verilog/fabric_verilog_options.h @@ -1,5 +1,5 @@ -#ifndef VERILOG_OPTIONS_H -#define VERILOG_OPTIONS_H +#ifndef FABRIC_VERILOG_OPTIONS_H +#define FABRIC_VERILOG_OPTIONS_H /******************************************************************** * Include header files required by the data structure definition @@ -10,11 +10,7 @@ namespace openfpga { /******************************************************************** - * FlowManager aims to resolve the dependency between OpenFPGA functional - * code blocks - * It can provide flags for downstream modules about if the data structures - * they require have already been constructed - * + * Options for Fabric Verilog generator *******************************************************************/ class FabricVerilogOption { public: /* Public constructor */ diff --git a/openfpga/src/fpga_verilog/simulation_info_writer.cpp b/openfpga/src/fpga_verilog/simulation_info_writer.cpp new file mode 100644 index 000000000..fbbb7e83c --- /dev/null +++ b/openfpga/src/fpga_verilog/simulation_info_writer.cpp @@ -0,0 +1,67 @@ +/********************************************************************* + * This function includes the writer for generating exchangeable + * information, in order to interface different simulators + ********************************************************************/ +#include +#include +#include +#define MINI_CASE_SENSITIVE +#include "ini.h" + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_time.h" + +#include "simulation_utils.h" + +#include "verilog_constants.h" +#include "simulation_info_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/********************************************************************* + * Top-level function to write an ini file which contains exchangeable + * information, in order to interface different Verilog simulators + ********************************************************************/ +void print_verilog_simulation_info(const std::string& ini_fname, + const std::string& circuit_name, + const std::string& src_dir, + const size_t& num_program_clock_cycles, + const int& num_operating_clock_cycles, + const float& prog_clock_freq, + const float& op_clock_freq) { + + std::string timer_message = std::string("Write exchangeable file containing simulation information '") + ini_fname + std::string("'"); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Use default name if user does not provide one */ + VTR_ASSERT(true != ini_fname.empty()); + + mINI::INIStructure ini; + // std::map units_map; + // units_map['s']=1; // units_map['ms']=1E-3; // units_map['us']=1E-6; + // units_map['ns']=1E-9; // units_map['ps']=1E-12; // units_map['fs']=1E-15; + + /* Compute simulation time period */ + float simulation_time_period = find_simulation_time_period(1E-3, + num_program_clock_cycles, + 1. / prog_clock_freq, + num_operating_clock_cycles, + 1. / op_clock_freq); + ini["SIMULATION_DECK"]["PROJECTNAME "] = "ModelSimProject"; + ini["SIMULATION_DECK"]["BENCHMARK "] = circuit_name; + ini["SIMULATION_DECK"]["TOP_TB"] = circuit_name + std::string(FORMAL_RANDOM_TOP_TESTBENCH_POSTFIX); + ini["SIMULATION_DECK"]["SIMTIME "] = std::to_string(simulation_time_period); + ini["SIMULATION_DECK"]["UNIT "] = "ms"; + ini["SIMULATION_DECK"]["VERILOG_PATH "] = std::string(src_dir); + ini["SIMULATION_DECK"]["VERILOG_FILE1"] = std::string(DEFINES_VERILOG_FILE_NAME); + ini["SIMULATION_DECK"]["VERILOG_FILE2"] = std::string(circuit_name + std::string(TOP_INCLUDE_NETLIST_FILE_NAME_POSTFIX)); + + mINI::INIFile file(ini_fname); + file.generate(ini, true); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/simulation_info_writer.h b/openfpga/src/fpga_verilog/simulation_info_writer.h new file mode 100644 index 000000000..a9a8bdcb1 --- /dev/null +++ b/openfpga/src/fpga_verilog/simulation_info_writer.h @@ -0,0 +1,26 @@ +#ifndef SIMULATION_INFO_WRITER_H +#define SIMULATION_INFO_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_simulation_info(const std::string& ini_fname, + const std::string& circuit_name, + const std::string& src_dir, + const size_t& num_program_clock_cycles, + const int& num_operating_clock_cycles, + const float& prog_clock_freq, + const float& op_clock_freq); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_api.cpp b/openfpga/src/fpga_verilog/verilog_api.cpp index 645033d65..dc332db58 100644 --- a/openfpga/src/fpga_verilog/verilog_api.cpp +++ b/openfpga/src/fpga_verilog/verilog_api.cpp @@ -20,6 +20,11 @@ #include "verilog_grid.h" #include "verilog_top_module.h" +#include "verilog_preconfig_top_module.h" +#include "verilog_formal_random_top_testbench.h" +#include "verilog_top_testbench.h" +#include "simulation_info_writer.h" + /* Header file for this source file */ #include "verilog_api.h" @@ -27,17 +32,18 @@ namespace openfpga { /******************************************************************** - * Top-level function of FPGA-Verilog + * A top-level function of FPGA-Verilog which focuses on fabric Verilog generation * This function will generate - * 1. primitive modules required by the full fabric - * which are LUTs, routing multiplexer, logic gates, transmission-gates etc. - * 2. Routing modules, which are Switch Blocks (SBs) and Connection Blocks (CBs) - * 3. Logic block modules, which are Configuration Logic Blocks (CLBs) - * 4. FPGA module, which are the full FPGA fabric with configuration protocol - * 5. A wrapper module, which encapsulate the FPGA module in a Verilog module which have the same port as the input benchmark - * 6. Testbench, where a FPGA module is configured with a bitstream and then driven by input vectors - * 7. Pre-configured testbench, which can skip the configuration phase and pre-configure the FPGA module. This testbench is created for quick verification and formal verification purpose. - * 8. Verilog netlist including preprocessing flags and all the Verilog netlists that have been generated + * - primitive modules required by the full fabric + * - which are LUTs, routing multiplexer, logic gates, transmission-gates etc. + * - Routing modules, which are Switch Blocks (SBs) and Connection Blocks (CBs) + * - Logic block modules, which are Configuration Logic Blocks (CLBs) + * - FPGA module, which are the full FPGA fabric with configuration protocol + * + * Note: + * - Please do NOT include ANY testbench generation in this function!!! + * It is about the fabric itself, independent from any implementation + * All the testbench generation should be in the function fpga_testbench_verilog() * * TODO: We should use module manager as a constant here. * All the modification should be done before this writer! @@ -75,9 +81,6 @@ void fpga_fabric_verilog(ModuleManager& module_manager, print_verilog_preprocessing_flags_netlist(std::string(src_dir_path), options); - print_verilog_simulation_preprocessing_flags(std::string(src_dir_path), - options); - /* Generate primitive Verilog modules, which are corner stones of FPGA fabric * Note that this function MUST be called before Verilog generation of * core logic (i.e., logic blocks and routing resources) !!! @@ -121,4 +124,106 @@ void fpga_fabric_verilog(ModuleManager& module_manager, module_manager.num_modules()); } + +/******************************************************************** + * A top-level function of FPGA-Verilog which focuses on fabric Verilog generation + * This function will generate + * - A wrapper module, which encapsulate the FPGA module in a Verilog module which have the same port as the input benchmark + * - Testbench, where a FPGA module is configured with a bitstream and then driven by input vectors + * - Pre-configured testbench, which can skip the configuration phase and pre-configure the FPGA module. + * This testbench is created for quick verification and formal verification purpose. + * - Verilog netlist including preprocessing flags and all the Verilog netlists that have been generated + ********************************************************************/ +void fpga_verilog_testbench(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const std::vector& fabric_bitstream, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, + const CircuitLibrary& circuit_lib, + const SimulationSetting& simulation_setting, + const e_config_protocol_type& config_protocol_type, + const VerilogTestbenchOption& options) { + + vtr::ScopedStartFinishTimer timer("Write Verilog testbenches for FPGA fabric\n"); + + std::string src_dir_path = format_dir_path(options.output_directory()); + + std::string netlist_name = atom_ctx.nlist.netlist_name(); + + /* Create directories */ + create_dir_path(src_dir_path.c_str()); + + /* TODO: check if this works here. This function was in fabric generator */ + print_verilog_simulation_preprocessing_flags(std::string(src_dir_path), + options); + + /* Collect global ports from the circuit library: + * TODO: should we place this in the OpenFPGA context? + */ + std::vector global_ports = find_circuit_library_global_ports(circuit_lib); + + /* Generate wrapper module for FPGA fabric (mapped by the input benchmark and pre-configured testbench for verification */ + if (true == options.print_formal_verification_top_netlist()) { + std::string formal_verification_top_netlist_file_path = src_dir_path + netlist_name + + std::string(FORMAL_VERIFICATION_VERILOG_FILE_POSTFIX); + print_verilog_preconfig_top_module(module_manager, bitstream_manager, + circuit_lib, global_ports, + atom_ctx, place_ctx, io_location_map, + netlist_annotation, + netlist_name, + formal_verification_top_netlist_file_path, + src_dir_path); + } + + if (true == options.print_preconfig_top_testbench()) { + /* Generate top-level testbench using random vectors */ + std::string random_top_testbench_file_path = src_dir_path + netlist_name + + std::string(RANDOM_TOP_TESTBENCH_VERILOG_FILE_POSTFIX); + print_verilog_random_top_testbench(netlist_name, + random_top_testbench_file_path, + src_dir_path, + atom_ctx, + netlist_annotation, + simulation_setting); + } + + /* Generate full testbench for verification, including configuration phase and operating phase */ + if (true == options.print_top_testbench()) { + std::string top_testbench_file_path = src_dir_path + netlist_name + + std::string(AUTOCHECK_TOP_TESTBENCH_VERILOG_FILE_POSTFIX); + print_verilog_top_testbench(module_manager, + bitstream_manager, fabric_bitstream, + config_protocol_type, + circuit_lib, global_ports, + atom_ctx, place_ctx, io_location_map, + netlist_annotation, + netlist_name, + top_testbench_file_path, + src_dir_path, + simulation_setting); + } + + /* Generate exchangeable files which contains simulation settings */ + if (true == options.print_simulation_ini()) { + std::string simulation_ini_file_name = options.simulation_ini_path(); + VTR_ASSERT(true != options.simulation_ini_path().empty()); + print_verilog_simulation_info(simulation_ini_file_name, + netlist_name, + src_dir_path, + bitstream_manager.bits().size(), + simulation_setting.num_clock_cycles(), + simulation_setting.programming_clock_frequency(), + simulation_setting.operating_clock_frequency()); + } + + /* Generate a Verilog file including all the netlists that have been generated */ + print_include_netlists(src_dir_path, + netlist_name, + options.reference_benchmark_file_path(), + circuit_lib); + +} + } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_api.h b/openfpga/src/fpga_verilog/verilog_api.h index 7494a5a8f..66eb069ba 100644 --- a/openfpga/src/fpga_verilog/verilog_api.h +++ b/openfpga/src/fpga_verilog/verilog_api.h @@ -7,14 +7,18 @@ #include #include -#include "vpr_types.h" #include "mux_library.h" #include "circuit_library.h" #include "vpr_context.h" #include "vpr_device_annotation.h" #include "device_rr_gsb.h" #include "module_manager.h" -#include "verilog_options.h" +#include "bitstream_manager.h" +#include "simulation_setting.h" +#include "io_location_map.h" +#include "vpr_netlist_annotation.h" +#include "fabric_verilog_options.h" +#include "verilog_testbench_options.h" /******************************************************************** * Function declaration @@ -31,6 +35,19 @@ void fpga_fabric_verilog(ModuleManager& module_manager, const DeviceRRGSB& device_rr_gsb, const FabricVerilogOption& options); +void fpga_verilog_testbench(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const std::vector& fabric_bitstream, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, + const CircuitLibrary& circuit_lib, + const SimulationSetting& simulation_parameters, + const e_config_protocol_type& config_protocol_type, + const VerilogTestbenchOption& options); + + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp index f1a12798d..fd01a462f 100644 --- a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.cpp @@ -23,7 +23,6 @@ namespace openfpga { /******************************************************************** * Local constant variables *******************************************************************/ -constexpr char* TOP_INCLUDE_NETLIST_FILE_NAME_POSTFIX = "_include_netlists.v"; /******************************************************************** * Print a file that includes all the netlists that have been generated @@ -115,7 +114,7 @@ void print_include_netlists(const std::string& src_dir, * which are used enable/disable some features in FPGA Verilog modules *******************************************************************/ void print_verilog_preprocessing_flags_netlist(const std::string& src_dir, - const FabricVerilogOption& fpga_verilog_opts) { + const FabricVerilogOption& fabric_verilog_opts) { std::string verilog_fname = src_dir + std::string(DEFINES_VERILOG_FILE_NAME); @@ -130,25 +129,19 @@ void print_verilog_preprocessing_flags_netlist(const std::string& src_dir, print_verilog_file_header(fp, std::string("Preprocessing flags to enable/disable features in FPGA Verilog modules")); /* To enable timing */ - if (true == fpga_verilog_opts.include_timing()) { + if (true == fabric_verilog_opts.include_timing()) { print_verilog_define_flag(fp, std::string(VERILOG_TIMING_PREPROC_FLAG), 1); fp << std::endl; } /* To enable timing */ - if (true == fpga_verilog_opts.include_signal_init()) { + if (true == fabric_verilog_opts.include_signal_init()) { print_verilog_define_flag(fp, std::string(VERILOG_SIGNAL_INIT_PREPROC_FLAG), 1); fp << std::endl; } - /* To enable formal verfication flag */ - if (true == fpga_verilog_opts.print_formal_verification_top_netlist()) { - print_verilog_define_flag(fp, std::string(VERILOG_FORMAL_VERIFICATION_PREPROC_FLAG), 1); - fp << std::endl; - } - /* To enable functional verfication with Icarus */ - if (true == fpga_verilog_opts.support_icarus_simulator()) { + if (true == fabric_verilog_opts.support_icarus_simulator()) { print_verilog_define_flag(fp, std::string(ICARUS_SIMULATOR_FLAG), 1); fp << std::endl; } @@ -161,7 +154,7 @@ void print_verilog_preprocessing_flags_netlist(const std::string& src_dir, * Print a Verilog file containing simulation-related preprocessing flags *******************************************************************/ void print_verilog_simulation_preprocessing_flags(const std::string& src_dir, - const FabricVerilogOption& fpga_verilog_opts) { + const VerilogTestbenchOption& verilog_testbench_opts) { std::string verilog_fname = src_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME); @@ -176,19 +169,26 @@ void print_verilog_simulation_preprocessing_flags(const std::string& src_dir, print_verilog_file_header(fp, std::string("Preprocessing flags to enable/disable simulation features")); /* To enable manualy checked simulation */ - if (true == fpga_verilog_opts.print_top_testbench()) { + if (true == verilog_testbench_opts.print_top_testbench()) { print_verilog_define_flag(fp, std::string(INITIAL_SIMULATION_FLAG), 1); fp << std::endl; } /* To enable auto-checked simulation */ - if (true == fpga_verilog_opts.print_autocheck_top_testbench()) { + if ( (true == verilog_testbench_opts.print_preconfig_top_testbench()) + || (true == verilog_testbench_opts.print_top_testbench()) ) { print_verilog_define_flag(fp, std::string(AUTOCHECKED_SIMULATION_FLAG), 1); fp << std::endl; } /* To enable pre-configured FPGA simulation */ - if (true == fpga_verilog_opts.print_formal_verification_top_netlist()) { + if (true == verilog_testbench_opts.print_formal_verification_top_netlist()) { + print_verilog_define_flag(fp, std::string(VERILOG_FORMAL_VERIFICATION_PREPROC_FLAG), 1); + fp << std::endl; + } + + /* To enable pre-configured FPGA simulation */ + if (true == verilog_testbench_opts.print_preconfig_top_testbench()) { print_verilog_define_flag(fp, std::string(FORMAL_SIMULATION_FLAG), 1); fp << std::endl; } diff --git a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h index 42f3b0042..291ca8915 100644 --- a/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h +++ b/openfpga/src/fpga_verilog/verilog_auxiliary_netlists.h @@ -6,7 +6,8 @@ *******************************************************************/ #include #include "circuit_library.h" -#include "verilog_options.h" +#include "fabric_verilog_options.h" +#include "verilog_testbench_options.h" /******************************************************************** * Function declaration @@ -21,10 +22,10 @@ void print_include_netlists(const std::string& src_dir, const CircuitLibrary& circuit_lib); void print_verilog_preprocessing_flags_netlist(const std::string& src_dir, - const FabricVerilogOption& fpga_verilog_opts); + const FabricVerilogOption& fabric_verilog_opts); void print_verilog_simulation_preprocessing_flags(const std::string& src_dir, - const FabricVerilogOption& fpga_verilog_opts); + const VerilogTestbenchOption& verilog_testbench_opts); } /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_constants.h b/openfpga/src/fpga_verilog/verilog_constants.h index e355b7a5f..797a00574 100644 --- a/openfpga/src/fpga_verilog/verilog_constants.h +++ b/openfpga/src/fpga_verilog/verilog_constants.h @@ -22,6 +22,7 @@ constexpr char* MODELSIM_SIMULATION_TIME_UNIT = "ms"; constexpr char* ICARUS_SIMULATOR_FLAG = "ICARUS_SIMULATOR"; // the flag to enable specific Verilog code in testbenches // End of Icarus variables and flag +constexpr char* TOP_INCLUDE_NETLIST_FILE_NAME_POSTFIX = "_include_netlists.v"; constexpr char* VERILOG_TOP_POSTFIX = "_top.v"; constexpr char* FORMAL_VERIFICATION_VERILOG_FILE_POSTFIX = "_top_formal_verification.v"; constexpr char* TOP_TESTBENCH_VERILOG_FILE_POSTFIX = "_top_tb.v"; /* !!! must be consist with the modelsim_testbench_module_postfix */ @@ -48,4 +49,12 @@ constexpr char* SB_VERILOG_FILE_NAME_PREFIX = "sb_"; constexpr char* LOGICAL_MODULE_VERILOG_FILE_NAME_PREFIX = "logical_tile_"; constexpr char* GRID_VERILOG_FILE_NAME_PREFIX = "grid_"; +constexpr char* FORMAL_VERIFICATION_TOP_MODULE_POSTFIX = "_top_formal_verification"; +constexpr char* FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX = "_fm"; +constexpr char* FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME = "U0_formal_verification"; + +constexpr char* FORMAL_RANDOM_TOP_TESTBENCH_POSTFIX = "_top_formal_verification_random_tb"; + +#define VERILOG_DEFAULT_SIGNAL_INIT_VALUE 0 + #endif diff --git a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp new file mode 100644 index 000000000..fa4f68938 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.cpp @@ -0,0 +1,268 @@ +/******************************************************************** + * This file includes functions that are used to generate a Verilog + * testbench for the top-level module (FPGA fabric), in purpose of + * running formal verification with random input vectors + *******************************************************************/ +#include +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "openfpga_atom_netlist_utils.h" +#include "simulation_utils.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_testbench_utils.h" +#include "verilog_formal_random_top_testbench.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Local variables used only in this file + *******************************************************************/ +constexpr char* FPGA_PORT_POSTFIX = "_gfpga"; +constexpr char* BENCHMARK_PORT_POSTFIX = "_bench"; +constexpr char* CHECKFLAG_PORT_POSTFIX = "_flag"; +constexpr char* DEFAULT_CLOCK_NAME = "clk"; +constexpr char* BENCHMARK_INSTANCE_NAME = "REF_DUT"; +constexpr char* FPGA_INSTANCE_NAME = "FPGA_DUT"; +constexpr char* ERROR_COUNTER = "nb_error"; +constexpr char* FORMAL_TB_SIM_START_PORT_NAME = "sim_start"; +constexpr int MAGIC_NUMBER_FOR_SIMULATION_TIME = 200; + +/******************************************************************** + * Print the module ports for the Verilog testbench + * using random vectors + * This function generates + * 1. the input ports to drive both input benchmark module and FPGA fabric module + * 2. the output ports for input benchmark module + * 3. the output ports for FPGA fabric module + * 4. the error checking ports + *******************************************************************/ +static +void print_verilog_top_random_testbench_ports(std::fstream& fp, + const std::string& circuit_name, + const std::vector& clock_port_names, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Print the declaration for the module */ + fp << "module " << circuit_name << FORMAL_RANDOM_TOP_TESTBENCH_POSTFIX << ";" << std::endl; + + /* Create a clock port if the benchmark does not have one! + * The clock is used for counting and synchronizing input stimulus + */ + BasicPort clock_port = generate_verilog_testbench_clock_port(clock_port_names, std::string(DEFAULT_CLOCK_NAME)); + print_verilog_comment(fp, std::string("----- Default clock port is added here since benchmark does not contain one -------")); + fp << "\t" << generate_verilog_port(VERILOG_PORT_REG, clock_port) << ";" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; + + print_verilog_testbench_shared_ports(fp, atom_ctx, netlist_annotation, + clock_port_names, + std::string(BENCHMARK_PORT_POSTFIX), + std::string(FPGA_PORT_POSTFIX), + std::string(CHECKFLAG_PORT_POSTFIX), + std::string(AUTOCHECKED_SIMULATION_FLAG)); + + /* Instantiate an integer to count the number of error + * and determine if the simulation succeed or failed + */ + print_verilog_comment(fp, std::string("----- Error counter -------")); + fp << "\tinteger " << ERROR_COUNTER << "= 0;" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Instanciate the input benchmark module + *******************************************************************/ +static +void print_verilog_top_random_testbench_benchmark_instance(std::fstream& fp, + const std::string& reference_verilog_top_name, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Benchmark is instanciated conditionally: only when a preprocessing flag is enable */ + print_verilog_preprocessing_flag(fp, std::string(AUTOCHECKED_SIMULATION_FLAG)); + + print_verilog_comment(fp, std::string("----- Reference Benchmark Instanication -------")); + + /* Do NOT use explicit port mapping here: + * VPR added a prefix of "out_" to the output ports of input benchmark + */ + print_verilog_testbench_benchmark_instance(fp, reference_verilog_top_name, + std::string(BENCHMARK_INSTANCE_NAME), + std::string(), + std::string(), + std::string(BENCHMARK_PORT_POSTFIX), + atom_ctx, netlist_annotation, + false); + + print_verilog_comment(fp, std::string("----- End reference Benchmark Instanication -------")); + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Condition ends for the benchmark instanciation */ + print_verilog_endif(fp); + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Instanciate the FPGA fabric module + *******************************************************************/ +static +void print_verilog_random_testbench_fpga_instance(std::fstream& fp, + const std::string& circuit_name, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- FPGA fabric instanciation -------")); + + /* Always use explicit port mapping */ + print_verilog_testbench_benchmark_instance(fp, std::string(circuit_name + std::string(FORMAL_VERIFICATION_TOP_MODULE_POSTFIX)), + std::string(FPGA_INSTANCE_NAME), + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), + std::string(FPGA_PORT_POSTFIX), + atom_ctx, netlist_annotation, + true); + + print_verilog_comment(fp, std::string("----- End FPGA Fabric Instanication -------")); + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/********************************************************************* + * Top-level function in this file: + * Create a Verilog testbench using random input vectors + * The testbench consists of two modules, i.e., Design Under Test (DUT) + * 1. top-level module of FPGA fabric + * 2. top-level module of users' benchmark, + * i.e., the input benchmark of VPR flow + * +----------+ + * | FPGA | +------------+ + * +----->| Fabric |------>| | + * | | | | | + * | +----------+ | | + * | | Output | + * random_input_vectors -----+ | Vector |---->Functional correct? + * | | Comparator | + * | +-----------+ | | + * | | Input | | | + * +----->| Benchmark |----->| | + * +-----------+ +------------+ + * + * Same input vectors are given to drive both DUTs. + * The output vectors of the DUTs are compared to verify if they + * have the same functionality. + * A flag will be raised to indicate the result + ********************************************************************/ +void print_verilog_random_top_testbench(const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, + const SimulationSetting& simulation_parameters) { + std::string timer_message = std::string("Write configuration-skip testbench for FPGA top-level Verilog netlist implemented by '") + circuit_name.c_str() + std::string("'"); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(verilog_fname.c_str(), fp); + + /* Generate a brief description on the Verilog file*/ + std::string title = std::string("FPGA Verilog Testbench for Formal Top-level netlist of Design: ") + circuit_name; + print_verilog_file_header(fp, title); + + /* Print preprocessing flags and external netlists */ + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + print_verilog_include_netlist(fp, std::string(verilog_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME))); + + /* Preparation: find all the clock ports */ + std::vector clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist, netlist_annotation); + + /* Start of testbench */ + print_verilog_top_random_testbench_ports(fp, circuit_name, clock_port_names, atom_ctx, netlist_annotation); + + /* Call defined top-level module */ + print_verilog_random_testbench_fpga_instance(fp, circuit_name, atom_ctx, netlist_annotation); + + /* Call defined benchmark */ + print_verilog_top_random_testbench_benchmark_instance(fp, circuit_name, atom_ctx, netlist_annotation); + + /* Find clock port to be used */ + BasicPort clock_port = generate_verilog_testbench_clock_port(clock_port_names, std::string(DEFAULT_CLOCK_NAME)); + + /* Add stimuli for reset, set, clock and iopad signals */ + print_verilog_testbench_clock_stimuli(fp, simulation_parameters, + clock_port); + print_verilog_testbench_random_stimuli(fp, atom_ctx, + netlist_annotation, + clock_port_names, + std::string(CHECKFLAG_PORT_POSTFIX), + clock_port); + + print_verilog_testbench_check(fp, + std::string(AUTOCHECKED_SIMULATION_FLAG), + std::string(FORMAL_TB_SIM_START_PORT_NAME), + std::string(BENCHMARK_PORT_POSTFIX), + std::string(FPGA_PORT_POSTFIX), + std::string(CHECKFLAG_PORT_POSTFIX), + std::string(ERROR_COUNTER), + atom_ctx, + netlist_annotation, + clock_port_names, + std::string(DEFAULT_CLOCK_NAME)); + + int simulation_time = find_operating_phase_simulation_time(MAGIC_NUMBER_FOR_SIMULATION_TIME, + simulation_parameters.num_clock_cycles(), + 1./simulation_parameters.operating_clock_frequency(), + VERILOG_SIM_TIMESCALE); + + /* Add Icarus requirement */ + print_verilog_timeout_and_vcd(fp, + std::string(ICARUS_SIMULATOR_FLAG), + std::string(circuit_name + std::string(FORMAL_RANDOM_TOP_TESTBENCH_POSTFIX)), + std::string(circuit_name + std::string("_formal.vcd")), + std::string(FORMAL_TB_SIM_START_PORT_NAME), + std::string(ERROR_COUNTER), + simulation_time); + + /* Testbench ends*/ + print_verilog_module_end(fp, std::string(circuit_name) + std::string(FORMAL_RANDOM_TOP_TESTBENCH_POSTFIX)); + + /* Close the file stream */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h new file mode 100644 index 000000000..88692ae4f --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_formal_random_top_testbench.h @@ -0,0 +1,27 @@ +#ifndef VERILOG_FORMAL_RANDOM_TOP_TESTBENCH +#define VERILOG_FORMAL_RANDOM_TOP_TESTBENCH + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include "vpr_context.h" +#include "simulation_setting.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_random_top_testbench(const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, + const SimulationSetting& simulation_parameters); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp new file mode 100644 index 000000000..695d9275f --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp @@ -0,0 +1,452 @@ +/******************************************************************** + * This file includes functions that are used to generate + * a Verilog module of a pre-configured FPGA fabric + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "bitstream_manager_utils.h" +#include "openfpga_atom_netlist_utils.h" + +#include "openfpga_naming.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_testbench_utils.h" +#include "verilog_preconfig_top_module.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print module declaration and ports for the pre-configured + * FPGA top module + * The module ports do exactly match the input benchmark + *******************************************************************/ +static +void print_verilog_preconfig_top_module_ports(std::fstream& fp, + const std::string& circuit_name, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation) { + + /* Validate the file stream */ + valid_file_stream(fp); + + /* Module declaration */ + fp << "module " << circuit_name << std::string(FORMAL_VERIFICATION_TOP_MODULE_POSTFIX); + fp << " (" << std::endl; + + /* Add module ports */ + size_t port_counter = 0; + + /* Port type-to-type mapping */ + std::map port_type2type_map; + port_type2type_map[AtomBlockType::INPAD] = VERILOG_PORT_INPUT; + port_type2type_map[AtomBlockType::OUTPAD] = VERILOG_PORT_OUTPUT; + + /* Print all the I/Os of the circuit implementation to be tested*/ + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* We only care I/O logical blocks !*/ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + + if (0 < port_counter) { + fp << "," << std::endl; + } + /* Both input and output ports have only size of 1 */ + BasicPort module_port(std::string(block_name + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX)), 1); + fp << generate_verilog_port(port_type2type_map[atom_ctx.nlist.block_type(atom_blk)], module_port); + + /* Update port counter */ + port_counter++; + } + + fp << ");" << std::endl; + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print internal wires for the pre-configured FPGA top module + * The internal wires are tailored for the ports of FPGA top module + * which will be different in various configuration protocols + *******************************************************************/ +static +void print_verilog_preconfig_top_module_internal_wires(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Global ports of top-level module */ + print_verilog_comment(fp, std::string("----- Local wires for FPGA fabric -----")); + for (const ModulePortId& module_port_id : module_manager.module_ports(top_module)) { + BasicPort module_port = module_manager.module_port(top_module, module_port_id); + fp << generate_verilog_port(VERILOG_PORT_WIRE, module_port) << ";" << std::endl; + } + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Connect global ports of FPGA top module to constants except: + * 1. operating clock, which should be wired to the clock port of + * this pre-configured FPGA top module + *******************************************************************/ +static +void print_verilog_preconfig_top_module_connect_global_ports(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const std::vector& benchmark_clock_port_names) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Begin Connect Global ports of FPGA top module -----")); + + /* Global ports of the top module in module manager do not carry any attributes, + * such as is_clock, is_set, etc. + * Therefore, for each global port in the top module, we find the circuit port in the circuit library + * which share the same name. We can access to the attributes. + * To gurantee the correct link between global ports in module manager and those in circuit library + * We have performed some critical check in check_circuit_library() for global ports, + * where we guarantee all the global ports share the same name must have the same attributes. + * So that each global port with the same name is unique! + */ + for (const BasicPort& module_global_port : module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GLOBAL_PORT)) { + CircuitPortId linked_circuit_port_id = CircuitPortId::INVALID(); + /* Find the circuit port with the same name */ + for (const CircuitPortId& circuit_port_id : global_ports) { + if (0 != module_global_port.get_name().compare(circuit_lib.port_prefix(circuit_port_id))) { + continue; + } + linked_circuit_port_id = circuit_port_id; + break; + } + /* Must find one valid circuit port */ + VTR_ASSERT(CircuitPortId::INVALID() != linked_circuit_port_id); + /* Port size should match! */ + VTR_ASSERT(module_global_port.get_width() == circuit_lib.port_size(linked_circuit_port_id)); + /* Now, for operating clock port, we should wire it to the clock of benchmark! */ + if ( (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(linked_circuit_port_id)) + && (false == circuit_lib.port_is_prog(linked_circuit_port_id)) ) { + /* Wiring to each pin of the global port: benchmark clock is always 1-bit */ + for (const size_t& pin : module_global_port.pins()) { + for (const std::string& clock_port_name : benchmark_clock_port_names) { + BasicPort module_clock_pin(module_global_port.get_name(), pin, pin); + BasicPort benchmark_clock_pin(clock_port_name + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), 1); + print_verilog_wire_connection(fp, module_clock_pin, benchmark_clock_pin, false); + } + } + /* Finish, go to the next */ + continue; + } + + /* For other ports, give an default value */ + std::vector default_values(module_global_port.get_width(), circuit_lib.port_default_value(linked_circuit_port_id)); + print_verilog_wire_constant_values(fp, module_global_port, default_values); + } + + print_verilog_comment(fp, std::string("----- End Connect Global ports of FPGA top module -----")); + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Impose the bitstream on the configuration memories + * This function uses 'assign' syntax to impost the bitstream at mem port + * while uses 'force' syntax to impost the bitstream at mem_inv port + *******************************************************************/ +static +void print_verilog_preconfig_top_module_assign_bitstream(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const BitstreamManager& bitstream_manager) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Begin assign bitstream to configuration memories -----")); + + for (const ConfigBlockId& config_block_id : bitstream_manager.blocks()) { + /* We only cares blocks with configuration bits */ + if (0 == bitstream_manager.block_bits(config_block_id).size()) { + continue; + } + /* Build the hierarchical path of the configuration bit in modules */ + std::vector block_hierarchy = find_bitstream_manager_block_hierarchy(bitstream_manager, config_block_id); + /* Drop the first block, which is the top module, it should be replaced by the instance name here */ + /* Ensure that this is the module we want to drop! */ + VTR_ASSERT(0 == module_manager.module_name(top_module).compare(bitstream_manager.block_name(block_hierarchy[0]))); + block_hierarchy.erase(block_hierarchy.begin()); + /* Build the full hierarchy path */ + std::string bit_hierarchy_path(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME); + for (const ConfigBlockId& temp_block : block_hierarchy) { + bit_hierarchy_path += std::string("."); + bit_hierarchy_path += bitstream_manager.block_name(temp_block); + } + bit_hierarchy_path += std::string("."); + + /* Find the bit index in the parent block */ + BasicPort config_data_port(bit_hierarchy_path + generate_configuration_chain_data_out_name(), + bitstream_manager.block_bits(config_block_id).size()); + + /* Wire it to the configuration bit: access both data out and data outb ports */ + std::vector config_data_values; + for (const ConfigBitId config_bit : bitstream_manager.block_bits(config_block_id)) { + config_data_values.push_back(bitstream_manager.bit_value(config_bit)); + } + print_verilog_wire_constant_values(fp, config_data_port, config_data_values); + } + + fp << "initial begin" << std::endl; + + for (const ConfigBlockId& config_block_id : bitstream_manager.blocks()) { + /* We only cares blocks with configuration bits */ + if (0 == bitstream_manager.block_bits(config_block_id).size()) { + continue; + } + /* Build the hierarchical path of the configuration bit in modules */ + std::vector block_hierarchy = find_bitstream_manager_block_hierarchy(bitstream_manager, config_block_id); + /* Drop the first block, which is the top module, it should be replaced by the instance name here */ + /* Ensure that this is the module we want to drop! */ + VTR_ASSERT(0 == module_manager.module_name(top_module).compare(bitstream_manager.block_name(block_hierarchy[0]))); + block_hierarchy.erase(block_hierarchy.begin()); + /* Build the full hierarchy path */ + std::string bit_hierarchy_path(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME); + for (const ConfigBlockId& temp_block : block_hierarchy) { + bit_hierarchy_path += std::string("."); + bit_hierarchy_path += bitstream_manager.block_name(temp_block); + } + bit_hierarchy_path += std::string("."); + + /* Find the bit index in the parent block */ + BasicPort config_datab_port(bit_hierarchy_path + generate_configuration_chain_inverted_data_out_name(), + bitstream_manager.block_bits(config_block_id).size()); + + std::vector config_datab_values; + for (const ConfigBitId config_bit : bitstream_manager.block_bits(config_block_id)) { + config_datab_values.push_back(!bitstream_manager.bit_value(config_bit)); + } + print_verilog_force_wire_constant_values(fp, config_datab_port, config_datab_values); + } + + fp << "end" << std::endl; + + print_verilog_comment(fp, std::string("----- End assign bitstream to configuration memories -----")); +} + +/******************************************************************** + * Impose the bitstream on the configuration memories + * This function uses '$deposit' syntax to do so + *******************************************************************/ +static +void print_verilog_preconfig_top_module_deposit_bitstream(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const BitstreamManager& bitstream_manager) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Begin deposit bitstream to configuration memories -----")); + + fp << "initial begin" << std::endl; + + for (const ConfigBlockId& config_block_id : bitstream_manager.blocks()) { + /* We only cares blocks with configuration bits */ + if (0 == bitstream_manager.block_bits(config_block_id).size()) { + continue; + } + /* Build the hierarchical path of the configuration bit in modules */ + std::vector block_hierarchy = find_bitstream_manager_block_hierarchy(bitstream_manager, config_block_id); + /* Drop the first block, which is the top module, it should be replaced by the instance name here */ + /* Ensure that this is the module we want to drop! */ + VTR_ASSERT(0 == module_manager.module_name(top_module).compare(bitstream_manager.block_name(block_hierarchy[0]))); + block_hierarchy.erase(block_hierarchy.begin()); + /* Build the full hierarchy path */ + std::string bit_hierarchy_path(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME); + for (const ConfigBlockId& temp_block : block_hierarchy) { + bit_hierarchy_path += std::string("."); + bit_hierarchy_path += bitstream_manager.block_name(temp_block); + } + bit_hierarchy_path += std::string("."); + + /* Find the bit index in the parent block */ + BasicPort config_data_port(bit_hierarchy_path + generate_configuration_chain_data_out_name(), + bitstream_manager.block_bits(config_block_id).size()); + + BasicPort config_datab_port(bit_hierarchy_path + generate_configuration_chain_inverted_data_out_name(), + bitstream_manager.block_bits(config_block_id).size()); + + /* Wire it to the configuration bit: access both data out and data outb ports */ + std::vector config_data_values; + for (const ConfigBitId config_bit : bitstream_manager.block_bits(config_block_id)) { + config_data_values.push_back(bitstream_manager.bit_value(config_bit)); + } + print_verilog_deposit_wire_constant_values(fp, config_data_port, config_data_values); + + std::vector config_datab_values; + for (const ConfigBitId config_bit : bitstream_manager.block_bits(config_block_id)) { + config_datab_values.push_back(!bitstream_manager.bit_value(config_bit)); + } + print_verilog_deposit_wire_constant_values(fp, config_datab_port, config_datab_values); + } + + fp << "end" << std::endl; + + print_verilog_comment(fp, std::string("----- End deposit bitstream to configuration memories -----")); +} + +/******************************************************************** + * Impose the bitstream on the configuration memories + * We branch here for different simulators: + * 1. iVerilog Icarus prefers using 'assign' syntax to force the values + * 2. Mentor Modelsim prefers using '$deposit' syntax to do so + *******************************************************************/ +static +void print_verilog_preconfig_top_module_load_bitstream(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const BitstreamManager& bitstream_manager) { + print_verilog_comment(fp, std::string("----- Begin load bitstream to configuration memories -----")); + + print_verilog_preprocessing_flag(fp, std::string(ICARUS_SIMULATOR_FLAG)); + + /* Use assign syntax for Icarus simulator */ + print_verilog_preconfig_top_module_assign_bitstream(fp, module_manager, top_module, bitstream_manager); + + fp << "`else" << std::endl; + + /* Use assign syntax for Icarus simulator */ + print_verilog_preconfig_top_module_deposit_bitstream(fp, module_manager, top_module, bitstream_manager); + + print_verilog_endif(fp); + + print_verilog_comment(fp, std::string("----- End load bitstream to configuration memories -----")); +} + + +/******************************************************************** + * Top-level function to generate a Verilog module of + * a pre-configured FPGA fabric. + * + * Pre-configured FPGA fabric + * +-------------------------------------------- + * | + * | FPGA fabric + * | +-------------------------------+ + * | | | + * | 0/1---->|FPGA global ports | + * | | | + * benchmark_clock----->|--------->|FPGA_clock | + * | | | + * benchmark_inputs---->|--------->|FPGA mapped I/Os | + * | | | + * benchmark_outputs<---|<---------|FPGA mapped I/Os | + * | | | + * | 0/1---->|FPGA unmapped I/Os | + * | | | + * fabric_bitstream---->|--------->|Internal_configuration_ports | + * | +-------------------------------+ + * | + * +------------------------------------------- + * + * Note: we do NOT put this module in the module manager. + * Because, it is not a standard module, where we force configuration signals + * This module is a wrapper for the FPGA fabric to be compatible in + * the port map of input benchmark. + * It includes wires to force constant values to part of FPGA datapath I/Os + * All these are hard to implement as a module in module manager + *******************************************************************/ +void print_verilog_preconfig_top_module(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, + const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir) { + std::string timer_message = std::string("Write pre-configured FPGA top-level Verilog netlist for design '") + circuit_name + std::string("'"); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(verilog_fname.c_str(), fp); + + /* Generate a brief description on the Verilog file*/ + std::string title = std::string("Verilog netlist for pre-configured FPGA fabric by design: ") + circuit_name; + print_verilog_file_header(fp, title); + + /* Print preprocessing flags and external netlists */ + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + print_verilog_include_netlist(fp, std::string(verilog_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME))); + + /* Print module declaration and ports */ + print_verilog_preconfig_top_module_ports(fp, circuit_name, atom_ctx, netlist_annotation); + + /* Find the top_module */ + ModuleId top_module = module_manager.find_module(generate_fpga_top_module_name()); + VTR_ASSERT(true == module_manager.valid_module_id(top_module)); + + /* Print internal wires */ + print_verilog_preconfig_top_module_internal_wires(fp, module_manager, top_module); + + /* Instanciate FPGA top-level module */ + print_verilog_testbench_fpga_instance(fp, module_manager, top_module, + std::string(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME)); + + /* Find clock ports in benchmark */ + std::vector benchmark_clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist, netlist_annotation); + + /* Connect FPGA top module global ports to constant or benchmark global signals! */ + print_verilog_preconfig_top_module_connect_global_ports(fp, module_manager, top_module, + circuit_lib, global_ports, + benchmark_clock_port_names); + + /* Connect I/Os to benchmark I/Os or constant driver */ + print_verilog_testbench_connect_fpga_ios(fp, module_manager, top_module, + atom_ctx, place_ctx, io_location_map, + netlist_annotation, + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), + (size_t)VERILOG_DEFAULT_SIGNAL_INIT_VALUE); + + /* Assign FPGA internal SRAM/Memory ports to bitstream values */ + print_verilog_preconfig_top_module_load_bitstream(fp, module_manager, top_module, + bitstream_manager); + + /* Testbench ends*/ + print_verilog_module_end(fp, std::string(circuit_name) + std::string(FORMAL_VERIFICATION_TOP_MODULE_POSTFIX)); + + /* Close the file stream */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h new file mode 100644 index 000000000..499cafea9 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h @@ -0,0 +1,37 @@ +#ifndef VERILOG_PRECONFIG_TOP_MODULE_H +#define VERILOG_PRECONFIG_TOP_MODULE_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "circuit_library.h" +#include "vpr_context.h" +#include "module_manager.h" +#include "bitstream_manager.h" +#include "io_location_map.h" +#include "vpr_netlist_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_preconfig_top_module(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, + const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_submodule.h b/openfpga/src/fpga_verilog/verilog_submodule.h index 4f7cb511c..88e1ce965 100644 --- a/openfpga/src/fpga_verilog/verilog_submodule.h +++ b/openfpga/src/fpga_verilog/verilog_submodule.h @@ -6,7 +6,7 @@ *******************************************************************/ #include "module_manager.h" #include "mux_library.h" -#include "verilog_options.h" +#include "fabric_verilog_options.h" /******************************************************************** * Function declaration diff --git a/openfpga/src/fpga_verilog/verilog_testbench_options.cpp b/openfpga/src/fpga_verilog/verilog_testbench_options.cpp new file mode 100644 index 000000000..8cd2969f2 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_testbench_options.cpp @@ -0,0 +1,104 @@ +/****************************************************************************** + * Memember functions for data structure VerilogTestbenchOption + ******************************************************************************/ +#include "vtr_assert.h" +#include "vtr_log.h" + +#include "verilog_testbench_options.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/************************************************** + * Public Constructors + *************************************************/ +VerilogTestbenchOption::VerilogTestbenchOption() { + output_directory_.clear(); + reference_benchmark_file_path_.clear(); + print_preconfig_top_testbench_ = false; + print_formal_verification_top_netlist_ = false; + print_top_testbench_ = false; + simulation_ini_path_.clear(); + verbose_output_ = false; +} + +/************************************************** + * Public Accessors + *************************************************/ +std::string VerilogTestbenchOption::output_directory() const { + return output_directory_; +} + +std::string VerilogTestbenchOption::reference_benchmark_file_path() const { + return reference_benchmark_file_path_; +} + +bool VerilogTestbenchOption::print_formal_verification_top_netlist() const { + return print_formal_verification_top_netlist_; +} + +bool VerilogTestbenchOption::print_preconfig_top_testbench() const { + return print_preconfig_top_testbench_; +} + +bool VerilogTestbenchOption::print_top_testbench() const { + return print_top_testbench_; +} + +bool VerilogTestbenchOption::print_simulation_ini() const { + return !simulation_ini_path_.empty(); +} + +std::string VerilogTestbenchOption::simulation_ini_path() const { + return simulation_ini_path_; +} + +bool VerilogTestbenchOption::verbose_output() const { + return verbose_output_; +} + +/****************************************************************************** + * Private Mutators + ******************************************************************************/ +void VerilogTestbenchOption::set_output_directory(const std::string& output_dir) { + output_directory_ = output_dir; +} + +void VerilogTestbenchOption::set_reference_benchmark_file_path(const std::string& reference_benchmark_file_path) { + reference_benchmark_file_path_ = reference_benchmark_file_path; + /* Chain effect on other options: + * Enable/disable the print_preconfig_top_testbench and print_top_testbench + */ + set_print_preconfig_top_testbench(print_preconfig_top_testbench_); + set_print_top_testbench(print_top_testbench_); +} + +void VerilogTestbenchOption::set_print_formal_verification_top_netlist(const bool& enabled) { + print_formal_verification_top_netlist_ = enabled; +} + +void VerilogTestbenchOption::set_print_preconfig_top_testbench(const bool& enabled) { + print_preconfig_top_testbench_ = enabled + && (!reference_benchmark_file_path_.empty()); + /* Enable print formal verification top_netlist if this is enabled */ + if (true == print_preconfig_top_testbench_) { + if (false == print_formal_verification_top_netlist_) { + VTR_LOG_WARN("Forcely enable to print top-level Verilog netlist in formal verification purpose as print pre-configured top-level Verilog testbench is enabled\n"); + print_formal_verification_top_netlist_ = true; + } + } +} + +void VerilogTestbenchOption::set_print_top_testbench(const bool& enabled) { + print_top_testbench_ = enabled && (!reference_benchmark_file_path_.empty()); +} + +void VerilogTestbenchOption::set_print_simulation_ini(const std::string& simulation_ini_path) { + simulation_ini_path_ = simulation_ini_path; +} + +void VerilogTestbenchOption::set_verbose_output(const bool& enabled) { + verbose_output_ = enabled; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_testbench_options.h b/openfpga/src/fpga_verilog/verilog_testbench_options.h new file mode 100644 index 000000000..d18bfa224 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_testbench_options.h @@ -0,0 +1,62 @@ +#ifndef VERILOG_TESTBENCH_OPTIONS_H +#define VERILOG_TESTBENCH_OPTIONS_H + +/******************************************************************** + * Include header files required by the data structure definition + *******************************************************************/ +#include + +/* Begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Options for Verilog Testbench generator + * Typicall usage: + * VerilogTestbench verilog_tb_opt(); + * // Set options + * ... + * + *******************************************************************/ +class VerilogTestbenchOption { + public: /* Public constructor */ + /* Set default options */ + VerilogTestbenchOption(); + public: /* Public accessors */ + std::string output_directory() const; + std::string reference_benchmark_file_path() const; + bool print_formal_verification_top_netlist() const; + bool print_preconfig_top_testbench() const; + bool print_top_testbench() const; + bool print_simulation_ini() const; + std::string simulation_ini_path() const; + bool verbose_output() const; + public: /* Public validator */ + bool validate() const; + public: /* Public mutators */ + void set_output_directory(const std::string& output_dir); + /* The reference verilog file path is the key parameters that will have an impact on other options: + * - print_preconfig_top_testbench + * - print_top_testbench + * If the file path is empty, the above testbench generation will not be enabled + */ + void set_reference_benchmark_file_path(const std::string& reference_benchmark_file_path); + void set_print_formal_verification_top_netlist(const bool& enabled); + /* The preconfig top testbench generation can be enabled only when formal verification top netlist is enabled */ + void set_print_preconfig_top_testbench(const bool& enabled); + void set_print_top_testbench(const bool& enabled); + void set_print_simulation_ini(const std::string& simulation_ini_path); + void set_verbose_output(const bool& enabled); + private: /* Internal Data */ + std::string output_directory_; + std::string reference_benchmark_file_path_; + bool print_formal_verification_top_netlist_; + bool print_preconfig_top_testbench_; + bool print_top_testbench_; + /* Print simulation ini is enabled only when the path is not empty */ + std::string simulation_ini_path_; + bool verbose_output_; +}; + +} /* End namespace openfpga*/ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp new file mode 100644 index 000000000..e952d13c4 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.cpp @@ -0,0 +1,664 @@ +/******************************************************************** + * This file includes most utilized functions that are used to create + * Verilog testbenches + * + * Note: please try to avoid using global variables in this file + * so that we can make it free to use anywhere + *******************************************************************/ +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "verilog_port_types.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_testbench_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print an instance of the FPGA top-level module + *******************************************************************/ +void print_verilog_testbench_fpga_instance(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const std::string& top_instance_name) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Include defined top-level module */ + print_verilog_comment(fp, std::string("----- FPGA top-level module to be capsulated -----")); + + /* Create an empty port-to-port name mapping, because we use default names */ + std::map port2port_name_map; + + /* Use explicit port mapping for a clean instanciation */ + print_verilog_module_instance(fp, module_manager, top_module, + top_instance_name, + port2port_name_map, true); + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Instanciate the input benchmark module + *******************************************************************/ +void print_verilog_testbench_benchmark_instance(std::fstream& fp, + const std::string& module_name, + const std::string& instance_name, + const std::string& module_input_port_postfix, + const std::string& module_output_port_postfix, + const std::string& output_port_postfix, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, + const bool& use_explicit_port_map) { + /* Validate the file stream */ + valid_file_stream(fp); + + fp << "\t" << module_name << " " << instance_name << "(" << std::endl; + + size_t port_counter = 0; + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + + /* The first port does not need a comma */ + if(0 < port_counter){ + fp << "," << std::endl; + } + /* Input port follows the logical block name while output port requires a special postfix */ + if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { + fp << "\t\t"; + if (true == use_explicit_port_map) { + fp << "." << block_name << module_input_port_postfix << "("; + } + fp << block_name; + if (true == use_explicit_port_map) { + fp << ")"; + } + } else { + VTR_ASSERT_SAFE(AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)); + fp << "\t\t"; + if (true == use_explicit_port_map) { + fp << "." << block_name << module_output_port_postfix << "("; + } + fp << block_name << output_port_postfix; + if (true == use_explicit_port_map) { + fp << ")"; + } + } + /* Update the counter */ + port_counter++; + } + fp << "\t);" << std::endl; +} + +/******************************************************************** + * This function adds stimuli to I/Os of FPGA fabric + * 1. For mapped I/Os, this function will wire them to the input ports + * of the pre-configured FPGA top module + * 2. For unmapped I/Os, this function will assign a constant value + * by default + *******************************************************************/ +void print_verilog_testbench_connect_fpga_ios(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, + const std::string& io_input_port_name_postfix, + const std::string& io_output_port_name_postfix, + const size_t& unused_io_value) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* In this function, we support only 1 type of I/Os */ + VTR_ASSERT(1 == module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT).size()); + BasicPort module_io_port = module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT)[0]; + + /* Keep tracking which I/Os have been used */ + std::vector io_used(module_io_port.get_width(), false); + + /* See if this I/O should be wired to a benchmark input/output */ + /* Add signals from blif benchmark and short-wire them to FPGA I/O PADs + * This brings convenience to checking functionality + */ + print_verilog_comment(fp, std::string("----- Link BLIF Benchmark I/Os to FPGA I/Os -----")); + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + /* Find the index of the mapped GPIO in top-level FPGA fabric */ + size_t io_index = io_location_map.io_index(place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.x, + place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.y, + place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.z); + + /* Ensure that IO index is in range */ + BasicPort module_mapped_io_port = module_io_port; + /* Set the port pin index */ + VTR_ASSERT(io_index < module_mapped_io_port.get_width()); + module_mapped_io_port.set_width(io_index, io_index); + + /* 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; + } + + /* Add an empty line as a splitter */ + fp << std::endl; + + /* Wire the unused iopads to a constant */ + print_verilog_comment(fp, std::string("----- Wire unused FPGA I/Os to constants -----")); + for (size_t io_index = 0; io_index < io_used.size(); ++io_index) { + /* Bypass used iopads */ + if (true == io_used[io_index]) { + continue; + } + + /* Wire to a contant */ + BasicPort module_unused_io_port = module_io_port; + /* Set the port pin index */ + module_unused_io_port.set_width(io_index, io_index); + + std::vector default_values(module_unused_io_port.get_width(), unused_io_value); + print_verilog_wire_constant_values(fp, module_unused_io_port, default_values); + } + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print Verilog codes to set up a timeout for the simulation + * and dump the waveform to VCD files + * + * Note that: these codes are tuned for Icarus simulator!!! + *******************************************************************/ +void print_verilog_timeout_and_vcd(std::fstream& fp, + const std::string& icarus_preprocessing_flag, + const std::string& module_name, + const std::string& vcd_fname, + const std::string& simulation_start_counter_name, + const std::string& error_counter_name, + const int& simulation_time) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* The following verilog codes are tuned for Icarus */ + print_verilog_preprocessing_flag(fp, icarus_preprocessing_flag); + + print_verilog_comment(fp, std::string("----- Begin Icarus requirement -------")); + + fp << "\tinitial begin" << std::endl; + fp << "\t\t$dumpfile(\"" << vcd_fname << "\");" << std::endl; + fp << "\t\t$dumpvars(1, " << module_name << ");" << std::endl; + fp << "\tend" << std::endl; + + /* Condition ends for the Icarus requirement */ + print_verilog_endif(fp); + + print_verilog_comment(fp, std::string("----- END Icarus requirement -------")); + + /* Add an empty line as splitter */ + fp << std::endl; + + BasicPort sim_start_port(simulation_start_counter_name, 1); + + fp << "initial begin" << std::endl; + fp << "\t" << generate_verilog_port(VERILOG_PORT_CONKT, sim_start_port) << " <= 1'b1;" << std::endl; + fp << "\t$timeformat(-9, 2, \"ns\", 20);" << std::endl; + fp << "\t$display(\"Simulation start\");" << std::endl; + print_verilog_comment(fp, std::string("----- Can be changed by the user for his/her need -------")); + fp << "\t#" << simulation_time << std::endl; + fp << "\tif(" << error_counter_name << " == 0) begin" << std::endl; + fp << "\t\t$display(\"Simulation Succeed\");" << std::endl; + fp << "\tend else begin" << std::endl; + fp << "\t\t$display(\"Simulation Failed with " << std::string("%d") << " error(s)\", " << error_counter_name << ");" << std::endl; + fp << "\tend" << std::endl; + fp << "\t$finish;" << std::endl; + fp << "end" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Generate the clock port name to be used in this testbench + * + * Restrictions: + * Assume this is a single clock benchmark + *******************************************************************/ +BasicPort generate_verilog_testbench_clock_port(const std::vector& clock_port_names, + const std::string& default_clock_name) { + if (0 == clock_port_names.size()) { + return BasicPort(default_clock_name, 1); + } + + VTR_ASSERT(1 == clock_port_names.size()); + return BasicPort(clock_port_names[0], 1); +} + +/******************************************************************** + * Print Verilog codes to check the equivalence of output vectors + * + * Restriction: this function only supports single clock benchmarks! + *******************************************************************/ +void print_verilog_testbench_check(std::fstream& fp, + const std::string& autochecked_preprocessing_flag, + const std::string& simulation_start_counter_name, + const std::string& benchmark_port_postfix, + const std::string& fpga_port_postfix, + const std::string& check_flag_port_postfix, + const std::string& error_counter_name, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, + const std::vector& clock_port_names, + const std::string& default_clock_name) { + + /* Validate the file stream */ + valid_file_stream(fp); + + /* Add output autocheck conditionally: only when a preprocessing flag is enable */ + print_verilog_preprocessing_flag(fp, autochecked_preprocessing_flag); + + print_verilog_comment(fp, std::string("----- Begin checking output vectors -------")); + + BasicPort clock_port = generate_verilog_testbench_clock_port(clock_port_names, default_clock_name); + + print_verilog_comment(fp, std::string("----- Skip the first falling edge of clock, it is for initialization -------")); + + BasicPort sim_start_port(simulation_start_counter_name, 1); + + fp << "\t" << generate_verilog_port(VERILOG_PORT_REG, sim_start_port) << ";" << std::endl; + fp << std::endl; + + fp << "\talways@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, clock_port) << ") begin" << std::endl; + fp << "\t\tif (1'b1 == " << generate_verilog_port(VERILOG_PORT_CONKT, sim_start_port) << ") begin" << std::endl; + fp << "\t\t"; + print_verilog_register_connection(fp, sim_start_port, sim_start_port, true); + fp << "\t\tend else begin" << std::endl; + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + + if (AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)) { + fp << "\t\t\tif(!(" << block_name << fpga_port_postfix; + fp << " === " << block_name << benchmark_port_postfix; + fp << ") && !(" << block_name << benchmark_port_postfix; + fp << " === 1'bx)) begin" << std::endl; + fp << "\t\t\t\t" << block_name << check_flag_port_postfix << " <= 1'b1;" << std::endl; + fp << "\t\t\tend else begin" << std::endl; + fp << "\t\t\t\t" << block_name << check_flag_port_postfix << "<= 1'b0;" << std::endl; + fp << "\t\t\tend" << std::endl; + } + } + fp << "\t\tend" << std::endl; + fp << "\tend" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Only care about output atom blocks ! */ + if (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) { + continue; + } + + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + + fp << "\talways@(posedge " << block_name << check_flag_port_postfix << ") begin" << std::endl; + fp << "\t\tif(" << block_name << check_flag_port_postfix << ") begin" << std::endl; + fp << "\t\t\t" << error_counter_name << " = " << error_counter_name << " + 1;" << std::endl; + fp << "\t\t\t$display(\"Mismatch on " << block_name << fpga_port_postfix << " at time = " << std::string("%t") << "\", $realtime);" << std::endl; + fp << "\t\tend" << std::endl; + fp << "\tend" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; + } + + /* Condition ends */ + print_verilog_endif(fp); + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Generate random stimulus for the clock port + * This function is designed to drive the clock port of a benchmark module + * If there is no clock port found, we will give a default clock name + * In such case, this clock will not be wired to the benchmark module + * but be only used as a synchronizer in verification + *******************************************************************/ +void print_verilog_testbench_clock_stimuli(std::fstream& fp, + const SimulationSetting& simulation_parameters, + const BasicPort& clock_port) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Clock Initialization -------")); + + fp << "\tinitial begin" << std::endl; + /* Create clock stimuli */ + fp << "\t\t" << generate_verilog_port(VERILOG_PORT_CONKT, clock_port) << " <= 1'b0;" << std::endl; + fp << "\t\twhile(1) begin" << std::endl; + fp << "\t\t\t#" << std::setprecision(10) << ((0.5/simulation_parameters.operating_clock_frequency())/VERILOG_SIM_TIMESCALE) << std::endl; + fp << "\t\t\t" << generate_verilog_port(VERILOG_PORT_CONKT, clock_port); + fp << " <= !"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, clock_port); + fp << ";" << std::endl; + fp << "\t\tend" << std::endl; + + fp << "\tend" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Generate random stimulus for the input ports (non-clock signals) + * For clock signals, please use print_verilog_testbench_clock_stimuli + *******************************************************************/ +void print_verilog_testbench_random_stimuli(std::fstream& fp, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, + const std::vector& clock_port_names, + const std::string& check_flag_port_postfix, + const BasicPort& clock_port) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Input Initialization -------")); + + fp << "\tinitial begin" << std::endl; + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + /* 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); + } + + /* Bypass clock ports */ + if (clock_port_names.end() != std::find(clock_port_names.begin(), clock_port_names.end(), block_name)) { + continue; + } + + /* TODO: find the clock inputs will be initialized later */ + if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { + fp << "\t\t" << block_name << " <= 1'b0;" << std::endl; + } + } + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Set 0 to registers for checking flags */ + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) { + continue; + } + + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + + /* Each logical block assumes a single-width port */ + BasicPort output_port(std::string(block_name + check_flag_port_postfix), 1); + fp << "\t\t" << generate_verilog_port(VERILOG_PORT_CONKT, output_port) << " <= 1'b0;" << std::endl; + } + + fp << "\tend" << std::endl; + /* Finish initialization */ + + /* Add an empty line as splitter */ + fp << std::endl; + + // Not ready yet to determine if input is reset +/* + fprintf(fp, "//----- Reset Stimulis\n"); + fprintf(fp, " initial begin\n"); + fprintf(fp, " #%.3f\n",(rand() % 10) + 0.001); + fprintf(fp, " %s <= !%s;\n", reset_input_name, reset_input_name); + fprintf(fp, " #%.3f\n",(rand() % 10) + 0.001); + fprintf(fp, " %s <= !%s;\n", reset_input_name, reset_input_name); + fprintf(fp, " while(1) begin\n"); + fprintf(fp, " #%.3f\n", (rand() % 15) + 0.5); + fprintf(fp, " %s <= !%s;\n", reset_input_name, reset_input_name); + fprintf(fp, " #%.3f\n", (rand() % 10000) + 200); + fprintf(fp, " %s <= !%s;\n", reset_input_name, reset_input_name); + fprintf(fp, " end\n"); + fprintf(fp, " end\n\n"); +*/ + + print_verilog_comment(fp, std::string("----- Input Stimulus -------")); + fp << "\talways@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, clock_port) << ") begin" << std::endl; + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + /* 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); + } + + /* Bypass clock ports */ + if (clock_port_names.end() != std::find(clock_port_names.begin(), clock_port_names.end(), block_name)) { + continue; + } + + /* TODO: find the clock inputs will be initialized later */ + if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { + fp << "\t\t" << block_name << " <= $random;" << std::endl; + } + } + + fp << "\tend" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print Verilog declaration of shared ports appear in testbenches + * which are + * 1. the shared input ports (registers) to drive both + * FPGA fabric and benchmark instance + * 2. the output ports (wires) for both FPGA fabric and benchmark instance + * 3. the checking flag ports to evaluate if outputs matches under the + * same input vectors + *******************************************************************/ +void print_verilog_testbench_shared_ports(std::fstream& fp, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, + const std::vector& clock_port_names, + const std::string& benchmark_output_port_postfix, + const std::string& fpga_output_port_postfix, + const std::string& check_flag_port_postfix, + const std::string& autocheck_preprocessing_flag) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Instantiate register for inputs stimulis */ + print_verilog_comment(fp, std::string("----- Shared inputs -------")); + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* We care only those logic blocks which are input I/Os */ + if (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) { + continue; + } + + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + + /* TODO: Skip clocks because they are handled in another function */ + if (clock_port_names.end() != std::find(clock_port_names.begin(), clock_port_names.end(), block_name)) { + continue; + } + + /* Each logical block assumes a single-width port */ + BasicPort input_port(block_name, 1); + fp << "\t" << generate_verilog_port(VERILOG_PORT_REG, input_port) << ";" << std::endl; + } + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Instantiate wires for FPGA fabric outputs */ + print_verilog_comment(fp, std::string("----- FPGA fabric outputs -------")); + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* We care only those logic blocks which are output I/Os */ + if (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) { + continue; + } + + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + + /* Each logical block assumes a single-width port */ + BasicPort output_port(std::string(block_name + fpga_output_port_postfix), 1); + fp << "\t" << generate_verilog_port(VERILOG_PORT_WIRE, output_port) << ";" << std::endl; + } + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Benchmark is instanciated conditionally: only when a preprocessing flag is enable */ + print_verilog_preprocessing_flag(fp, std::string(autocheck_preprocessing_flag)); + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Instantiate wire for benchmark output */ + print_verilog_comment(fp, std::string("----- Benchmark outputs -------")); + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* We care only those logic blocks which are output I/Os */ + if (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) { + continue; + } + + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + + /* Each logical block assumes a single-width port */ + BasicPort output_port(std::string(block_name + benchmark_output_port_postfix), 1); + fp << "\t" << generate_verilog_port(VERILOG_PORT_WIRE, output_port) << ";" << std::endl; + } + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Instantiate register for output comparison */ + print_verilog_comment(fp, std::string("----- Output vectors checking flags -------")); + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* We care only those logic blocks which are output I/Os */ + if (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) { + continue; + } + + /* The block may be renamed as it contains special characters which violate Verilog syntax */ + std::string block_name = atom_ctx.nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + + /* Each logical block assumes a single-width port */ + BasicPort output_port(std::string(block_name + check_flag_port_postfix), 1); + fp << "\t" << generate_verilog_port(VERILOG_PORT_REG, output_port) << ";" << std::endl; + } + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Condition ends for the benchmark instanciation */ + print_verilog_endif(fp); + + /* Add an empty line as splitter */ + fp << std::endl; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_testbench_utils.h b/openfpga/src/fpga_verilog/verilog_testbench_utils.h new file mode 100644 index 000000000..5e0bd69f9 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_testbench_utils.h @@ -0,0 +1,94 @@ +#ifndef VERILOG_TESTBENCH_UTILS_H +#define VERILOG_TESTBENCH_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include +#include "module_manager.h" +#include "vpr_context.h" +#include "io_location_map.h" +#include "vpr_netlist_annotation.h" +#include "simulation_setting.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_testbench_fpga_instance(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const std::string& top_instance_name); + +void print_verilog_testbench_benchmark_instance(std::fstream& fp, + const std::string& module_name, + const std::string& instance_name, + const std::string& module_input_port_postfix, + const std::string& module_output_port_postfix, + const std::string& output_port_postfix, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, + const bool& use_explicit_port_map); + +void print_verilog_testbench_connect_fpga_ios(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, + const std::string& io_input_port_name_postfix, + const std::string& io_output_port_name_postfix, + const size_t& unused_io_value); + +void print_verilog_timeout_and_vcd(std::fstream& fp, + const std::string& icarus_preprocessing_flag, + const std::string& module_name, + const std::string& vcd_fname, + const std::string& simulation_start_counter_name, + const std::string& error_counter_name, + const int& simulation_time); + +BasicPort generate_verilog_testbench_clock_port(const std::vector& clock_port_names, + const std::string& default_clock_name); + +void print_verilog_testbench_check(std::fstream& fp, + const std::string& autochecked_preprocessing_flag, + const std::string& simulation_start_counter_name, + const std::string& benchmark_port_postfix, + const std::string& fpga_port_postfix, + const std::string& check_flag_port_postfix, + const std::string& error_counter_name, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, + const std::vector& clock_port_names, + const std::string& default_clock_name); + +void print_verilog_testbench_clock_stimuli(std::fstream& fp, + const SimulationSetting& simulation_parameters, + const BasicPort& clock_port); + +void print_verilog_testbench_random_stimuli(std::fstream& fp, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, + const std::vector& clock_port_names, + const std::string& check_flag_port_postfix, + const BasicPort& clock_port); + +void print_verilog_testbench_shared_ports(std::fstream& fp, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, + const std::vector& clock_port_names, + const std::string& benchmark_output_port_postfix, + const std::string& fpga_output_port_postfix, + const std::string& check_flag_port_postfix, + const std::string& autocheck_preprocessing_flag); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.cpp b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp new file mode 100644 index 000000000..911c6defb --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.cpp @@ -0,0 +1,900 @@ +/******************************************************************** + * This file includes functions that are used to create + * an auto-check top-level testbench for a FPGA fabric + *******************************************************************/ +#include +#include +#include + + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "bitstream_manager_utils.h" + +#include "openfpga_naming.h" +#include "simulation_utils.h" +#include "openfpga_atom_netlist_utils.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_testbench_utils.h" +#include "verilog_top_testbench.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Local variables used only in this file + *******************************************************************/ +constexpr char* TOP_TESTBENCH_REFERENCE_INSTANCE_NAME = "REF_DUT"; +constexpr char* TOP_TESTBENCH_FPGA_INSTANCE_NAME = "FPGA_DUT"; +constexpr char* TOP_TESTBENCH_REFERENCE_OUTPUT_POSTFIX = "_benchmark"; +constexpr char* TOP_TESTBENCH_FPGA_OUTPUT_POSTFIX = "_fpga"; + +constexpr char* TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX = "_flag"; + +constexpr char* TOP_TESTBENCH_CC_PROG_TASK_NAME = "prog_cycle_task"; + +constexpr char* TOP_TESTBENCH_SIM_START_PORT_NAME = "sim_start"; + +constexpr int TOP_TESTBENCH_MAGIC_NUMBER_FOR_SIMULATION_TIME = 200; +constexpr char* TOP_TESTBENCH_ERROR_COUNTER = "nb_error"; + +constexpr char* TOP_TB_RESET_PORT_NAME = "greset"; +constexpr char* TOP_TB_SET_PORT_NAME = "gset"; +constexpr char* TOP_TB_PROG_RESET_PORT_NAME = "prog_reset"; +constexpr char* TOP_TB_PROG_SET_PORT_NAME = "prog_set"; +constexpr char* TOP_TB_CONFIG_DONE_PORT_NAME = "config_done"; +constexpr char* TOP_TB_OP_CLOCK_PORT_NAME = "op_clock"; +constexpr char* TOP_TB_PROG_CLOCK_PORT_NAME = "prog_clock"; +constexpr char* TOP_TB_INOUT_REG_POSTFIX = "_reg"; +constexpr char* TOP_TB_CLOCK_REG_POSTFIX = "_reg"; + +constexpr char* AUTOCHECK_TOP_TESTBENCH_VERILOG_MODULE_POSTFIX = "_autocheck_top_tb"; + +/******************************************************************** + * Print local wires for configuration chain protocols + *******************************************************************/ +static +void print_verilog_top_testbench_config_chain_port(std::fstream& fp) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Print the head of configuraion-chains here */ + print_verilog_comment(fp, std::string("---- Configuration-chain head -----")); + BasicPort config_chain_head_port(generate_configuration_chain_head_name(), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, config_chain_head_port) << ";" << std::endl; + + /* Print the tail of configuration-chains here */ + print_verilog_comment(fp, std::string("---- Configuration-chain tail -----")); + BasicPort config_chain_tail_port(generate_configuration_chain_tail_name(), 1); + fp << generate_verilog_port(VERILOG_PORT_WIRE, config_chain_tail_port) << ";" << std::endl; +} + +/******************************************************************** + * Print local wires for different types of configuration protocols + *******************************************************************/ +static +void print_verilog_top_testbench_config_protocol_port(std::fstream& fp, + const e_config_protocol_type& sram_orgz_type) { + switch(sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* TODO */ + break; + case CONFIG_MEM_SCAN_CHAIN: + print_verilog_top_testbench_config_chain_port(fp); + break; + case CONFIG_MEM_MEMORY_BANK: + /* TODO */ + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of SRAM organization!\n"); + exit(1); + } +} + +/******************************************************************** + * Wire the global ports of FPGA fabric to local wires + *******************************************************************/ +static +void print_verilog_top_testbench_global_ports_stimuli(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Begin connecting global ports of FPGA fabric to stimuli -----")); + + /* Connect global clock ports to operating or programming clock signal */ + for (const CircuitPortId& model_global_port : global_ports) { + if (CIRCUIT_MODEL_PORT_CLOCK != circuit_lib.port_type(model_global_port)) { + continue; + } + /* Reach here, it means we have a global clock to deal with: + * 1. if the port is identified as a programming clock, + * connect it to the local wire of programming clock + * 2. if the port is identified as an operating clock + * connect it to the local wire of operating clock + */ + /* Find the module port */ + ModulePortId module_global_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(model_global_port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(top_module, module_global_port)); + + BasicPort stimuli_clock_port; + if (true == circuit_lib.port_is_prog(model_global_port)) { + stimuli_clock_port.set_name(std::string(TOP_TB_PROG_CLOCK_PORT_NAME)); + stimuli_clock_port.set_width(1); + } else { + VTR_ASSERT_SAFE(false == circuit_lib.port_is_prog(model_global_port)); + stimuli_clock_port.set_name(std::string(TOP_TB_OP_CLOCK_PORT_NAME)); + stimuli_clock_port.set_width(1); + } + /* Wire the port to the input stimuli: + * The wiring will be inverted if the default value of the global port is 1 + * Otherwise, the wiring will not be inverted! + */ + print_verilog_wire_connection(fp, module_manager.module_port(top_module, module_global_port), + stimuli_clock_port, + 1 == circuit_lib.port_default_value(model_global_port)); + } + + /* Connect global configuration done ports to configuration done signal */ + for (const CircuitPortId& model_global_port : global_ports) { + /* Bypass clock signals, they have been processed */ + if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(model_global_port)) { + continue; + } + if (false == circuit_lib.port_is_config_enable(model_global_port)) { + continue; + } + /* Reach here, it means we have a configuration done port to deal with */ + /* Find the module port */ + ModulePortId module_global_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(model_global_port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(top_module, module_global_port)); + + BasicPort stimuli_config_done_port(std::string(TOP_TB_CONFIG_DONE_PORT_NAME), 1); + /* Wire the port to the input stimuli: + * The wiring will be inverted if the default value of the global port is 1 + * Otherwise, the wiring will not be inverted! + */ + print_verilog_wire_connection(fp, module_manager.module_port(top_module, module_global_port), + stimuli_config_done_port, + 1 == circuit_lib.port_default_value(model_global_port)); + } + + /* Connect global reset ports to operating or programming reset signal */ + for (const CircuitPortId& model_global_port : global_ports) { + /* Bypass clock signals, they have been processed */ + if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(model_global_port)) { + continue; + } + /* Bypass config_done signals, they have been processed */ + if (true == circuit_lib.port_is_config_enable(model_global_port)) { + continue; + } + + if (false == circuit_lib.port_is_reset(model_global_port)) { + continue; + } + /* Reach here, it means we have a reset port to deal with */ + /* Find the module port */ + ModulePortId module_global_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(model_global_port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(top_module, module_global_port)); + + BasicPort stimuli_reset_port; + if (true == circuit_lib.port_is_prog(model_global_port)) { + stimuli_reset_port.set_name(std::string(TOP_TB_PROG_RESET_PORT_NAME)); + stimuli_reset_port.set_width(1); + } else { + VTR_ASSERT_SAFE(false == circuit_lib.port_is_prog(model_global_port)); + stimuli_reset_port.set_name(std::string(TOP_TB_RESET_PORT_NAME)); + stimuli_reset_port.set_width(1); + } + /* Wire the port to the input stimuli: + * The wiring will be inverted if the default value of the global port is 1 + * Otherwise, the wiring will not be inverted! + */ + print_verilog_wire_connection(fp, module_manager.module_port(top_module, module_global_port), + stimuli_reset_port, + 1 == circuit_lib.port_default_value(model_global_port)); + } + + /* Connect global set ports to operating or programming set signal */ + for (const CircuitPortId& model_global_port : global_ports) { + /* Bypass clock signals, they have been processed */ + if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(model_global_port)) { + continue; + } + /* Bypass config_done signals, they have been processed */ + if (true == circuit_lib.port_is_config_enable(model_global_port)) { + continue; + } + + /* Bypass reset signals, they have been processed */ + if (true == circuit_lib.port_is_reset(model_global_port)) { + continue; + } + + if (false == circuit_lib.port_is_set(model_global_port)) { + continue; + } + /* Reach here, it means we have a set port to deal with */ + /* Find the module port */ + ModulePortId module_global_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(model_global_port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(top_module, module_global_port)); + + BasicPort stimuli_set_port; + if (true == circuit_lib.port_is_prog(model_global_port)) { + stimuli_set_port.set_name(std::string(TOP_TB_PROG_SET_PORT_NAME)); + stimuli_set_port.set_width(1); + } else { + VTR_ASSERT_SAFE(false == circuit_lib.port_is_prog(model_global_port)); + stimuli_set_port.set_name(std::string(TOP_TB_SET_PORT_NAME)); + stimuli_set_port.set_width(1); + } + /* Wire the port to the input stimuli: + * The wiring will be inverted if the default value of the global port is 1 + * Otherwise, the wiring will not be inverted! + */ + print_verilog_wire_connection(fp, module_manager.module_port(top_module, module_global_port), + stimuli_set_port, + 1 == circuit_lib.port_default_value(model_global_port)); + } + + /* For the rest of global ports, wire them to constant signals */ + for (const CircuitPortId& model_global_port : global_ports) { + /* Bypass clock signals, they have been processed */ + if (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(model_global_port)) { + continue; + } + /* Bypass config_done signals, they have been processed */ + if (true == circuit_lib.port_is_config_enable(model_global_port)) { + continue; + } + + /* Bypass reset signals, they have been processed */ + if (true == circuit_lib.port_is_reset(model_global_port)) { + continue; + } + + /* Bypass set signals, they have been processed */ + if (true == circuit_lib.port_is_set(model_global_port)) { + continue; + } + + /* Reach here, it means we have a port to deal with */ + /* Find the module port and wire it to constant values */ + ModulePortId module_global_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(model_global_port)); + VTR_ASSERT(true == module_manager.valid_module_port_id(top_module, module_global_port)); + + BasicPort module_port = module_manager.module_port(top_module, module_global_port); + std::vector default_values(module_port.get_width(), circuit_lib.port_default_value(model_global_port)); + print_verilog_wire_constant_values(fp, module_port, default_values); + } + + print_verilog_comment(fp, std::string("----- End connecting global ports of FPGA fabric to stimuli -----")); +} + +/******************************************************************** + * This function prints the top testbench module declaration + * and internal wires/port declaration + * Ports can be classified in two categories: + * 1. General-purpose ports, which are datapath I/Os, clock signals + * for the FPGA fabric and input benchmark + * 2. Fabric-featured ports, which are required by configuration + * protocols. + * Due the difference in configuration protocols, the internal + * wires and ports will be different: + * (a) configuration-chain: we will have two ports, + * a head and a tail for the configuration chain, + * in addition to the regular ports. + * (b) memory-decoders: we will have a few ports to drive + * address lines for decoders and a bit input port to feed + * configuration bits + *******************************************************************/ +static +void print_verilog_top_testbench_ports(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation, + const std::vector& clock_port_names, + const e_config_protocol_type& sram_orgz_type, + const std::string& circuit_name){ + /* Validate the file stream */ + valid_file_stream(fp); + + /* Print module definition */ + fp << "module " << circuit_name << std::string(AUTOCHECK_TOP_TESTBENCH_VERILOG_MODULE_POSTFIX); + fp << ";" << std::endl; + + /* Print regular local wires: + * 1. global ports, i.e., reset, set and clock signals + * 2. datapath I/O signals + */ + /* Global ports of top-level module */ + print_verilog_comment(fp, std::string("----- Local wires for global ports of FPGA fabric -----")); + for (const BasicPort& module_port : module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GLOBAL_PORT)) { + fp << generate_verilog_port(VERILOG_PORT_WIRE, module_port) << ";" << std::endl; + } + /* Add an empty line as a splitter */ + fp << std::endl; + + /* Datapath I/Os of top-level module */ + print_verilog_comment(fp, std::string("----- Local wires for I/Os of FPGA fabric -----")); + for (const BasicPort& module_port : module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT)) { + fp << generate_verilog_port(VERILOG_PORT_WIRE, module_port) << ";" << std::endl; + } + /* Add an empty line as a splitter */ + fp << std::endl; + + /* Add local wires/registers that drive stimulus + * We create these general purpose ports here, + * and then wire them to the ports of FPGA fabric depending on their usage + */ + /* Configuration done port */ + BasicPort config_done_port(std::string(TOP_TB_CONFIG_DONE_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, config_done_port) << ";" << std::endl; + + /* Programming clock */ + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_WIRE, prog_clock_port) << ";" << std::endl; + BasicPort prog_clock_register_port(std::string(std::string(TOP_TB_PROG_CLOCK_PORT_NAME) + std::string(TOP_TB_CLOCK_REG_POSTFIX)), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, prog_clock_register_port) << ";" << std::endl; + + /* Operating clock */ + BasicPort op_clock_port(std::string(TOP_TB_OP_CLOCK_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_WIRE, op_clock_port) << ";" << std::endl; + BasicPort op_clock_register_port(std::string(std::string(TOP_TB_OP_CLOCK_PORT_NAME) + std::string(TOP_TB_CLOCK_REG_POSTFIX)), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, op_clock_register_port) << ";" << std::endl; + + /* Programming set and reset */ + BasicPort prog_reset_port(std::string(TOP_TB_PROG_RESET_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, prog_reset_port) << ";" << std::endl; + BasicPort prog_set_port(std::string(TOP_TB_PROG_SET_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, prog_set_port) << ";" << std::endl; + + /* Global set and reset */ + BasicPort reset_port(std::string(TOP_TB_RESET_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, reset_port) << ";" << std::endl; + BasicPort set_port(std::string(TOP_TB_SET_PORT_NAME), 1); + fp << generate_verilog_port(VERILOG_PORT_REG, set_port) << ";" << std::endl; + + /* Configuration ports depend on the organization of SRAMs */ + print_verilog_top_testbench_config_protocol_port(fp, sram_orgz_type); + + /* Create a clock port if the benchmark have one but not in the default name! + * We will wire the clock directly to the operating clock directly + */ + for (const std::string clock_port_name : clock_port_names) { + if (0 == clock_port_name.compare(op_clock_port.get_name())) { + continue; + } + /* Ensure the clock port name is not a duplication of global ports of the FPGA module */ + bool print_clock_port = true; + for (const BasicPort& module_port : module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GLOBAL_PORT)) { + if (0 == clock_port_name.compare(module_port.get_name())) { + print_clock_port = false; + } + } + if (false == print_clock_port) { + continue; + } + + /* Print the clock and wire it to op_clock */ + print_verilog_comment(fp, std::string("----- Create a clock for benchmark and wire it to op_clock -------")); + BasicPort clock_port(clock_port_name, 1); + fp << "\t" << generate_verilog_port(VERILOG_PORT_WIRE, clock_port) << ";" << std::endl; + print_verilog_wire_connection(fp, clock_port, op_clock_port, false); + } + + print_verilog_testbench_shared_ports(fp, atom_ctx, netlist_annotation, + clock_port_names, + std::string(TOP_TESTBENCH_REFERENCE_OUTPUT_POSTFIX), + std::string(TOP_TESTBENCH_FPGA_OUTPUT_POSTFIX), + std::string(TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX), + std::string(AUTOCHECKED_SIMULATION_FLAG)); + + /* Instantiate an integer to count the number of error and + * determine if the simulation succeed or failed + */ + print_verilog_comment(fp, std::string("----- Error counter -----")); + fp << "\tinteger " << TOP_TESTBENCH_ERROR_COUNTER << "= 0;" << std::endl; +} + +/******************************************************************** + * Instanciate the input benchmark module + *******************************************************************/ +static +void print_verilog_top_testbench_benchmark_instance(std::fstream& fp, + const std::string& reference_verilog_top_name, + const AtomContext& atom_ctx, + const VprNetlistAnnotation& netlist_annotation) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Benchmark is instanciated conditionally: only when a preprocessing flag is enable */ + print_verilog_preprocessing_flag(fp, std::string(AUTOCHECKED_SIMULATION_FLAG)); + + print_verilog_comment(fp, std::string("----- Reference Benchmark Instanication -------")); + + /* Do NOT use explicit port mapping here: + * VPR added a prefix of "out_" to the output ports of input benchmark + */ + print_verilog_testbench_benchmark_instance(fp, reference_verilog_top_name, + std::string(TOP_TESTBENCH_REFERENCE_INSTANCE_NAME), + std::string(), + std::string(), + std::string(TOP_TESTBENCH_REFERENCE_OUTPUT_POSTFIX), + atom_ctx, netlist_annotation, + false); + + print_verilog_comment(fp, std::string("----- End reference Benchmark Instanication -------")); + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Condition ends for the benchmark instanciation */ + print_verilog_endif(fp); + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print tasks (processes) in Verilog format, + * which is very useful in generating stimuli for each clock cycle + * This function is tuned for configuration-chain manipulation: + * During each programming cycle, we feed the input of scan chain with a memory bit + *******************************************************************/ +static +void print_verilog_top_testbench_load_bitstream_task_configuration_chain(std::fstream& fp) { + + /* Validate the file stream */ + valid_file_stream(fp); + + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + BasicPort cc_head_port(generate_configuration_chain_head_name(), 1); + BasicPort cc_head_value(generate_configuration_chain_head_name() + std::string("_val"), 1); + + /* Add an empty line as splitter */ + fp << std::endl; + + /* Feed the scan-chain input at each falling edge of programming clock + * It aims at avoid racing the programming clock (scan-chain data changes at the rising edge). + */ + print_verilog_comment(fp, std::string("----- Task: input values during a programming clock cycle -----")); + fp << "task " << std::string(TOP_TESTBENCH_CC_PROG_TASK_NAME) << ";" << std::endl; + fp << generate_verilog_port(VERILOG_PORT_INPUT, cc_head_value) << ";" << std::endl; + fp << "\tbegin" << std::endl; + fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; + fp << "\t\t\t"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, cc_head_port); + fp << " = "; + fp << generate_verilog_port(VERILOG_PORT_CONKT, cc_head_value); + fp << ";" << std::endl; + + fp << "\tend" << std::endl; + fp << "endtask" << std::endl; + + /* Add an empty line as splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print tasks, which is very useful in generating stimuli for each clock cycle + *******************************************************************/ +static +void print_verilog_top_testbench_load_bitstream_task(std::fstream& fp, + const e_config_protocol_type& sram_orgz_type) { + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + break; + case CONFIG_MEM_SCAN_CHAIN: + print_verilog_top_testbench_load_bitstream_task_configuration_chain(fp); + break; + case CONFIG_MEM_MEMORY_BANK: + /* TODO: + dump_verilog_top_testbench_stimuli_serial_version_tasks_memory_bank(cur_sram_orgz_info, fp); + */ + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid type of SRAM organization!\n"); + exit(1); + } +} + +/******************************************************************** + * Print generatic input stimuli for the top testbench + * include: + * 1. configuration done signal + * 2. programming clock + * 3. operating clock + * 4. programming reset signal + * 5. programming set signal + * 6. reset signal + * 7. set signal + *******************************************************************/ +static +void print_verilog_top_testbench_generic_stimulus(std::fstream& fp, + const size_t& num_config_clock_cycles, + const float& prog_clock_period, + const float& op_clock_period, + const float& timescale) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Number of clock cycles in configuration phase: " + std::to_string(num_config_clock_cycles) + " -----")); + + BasicPort config_done_port(std::string(TOP_TB_CONFIG_DONE_PORT_NAME), 1); + + BasicPort op_clock_port(std::string(TOP_TB_OP_CLOCK_PORT_NAME), 1); + BasicPort op_clock_register_port(std::string(std::string(TOP_TB_OP_CLOCK_PORT_NAME) + std::string(TOP_TB_CLOCK_REG_POSTFIX)), 1); + + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + BasicPort prog_clock_register_port(std::string(std::string(TOP_TB_PROG_CLOCK_PORT_NAME) + std::string(TOP_TB_CLOCK_REG_POSTFIX)), 1); + + BasicPort prog_reset_port(std::string(TOP_TB_PROG_RESET_PORT_NAME), 1); + BasicPort prog_set_port(std::string(TOP_TB_PROG_SET_PORT_NAME), 1); + + BasicPort reset_port(std::string(TOP_TB_RESET_PORT_NAME), 1); + BasicPort set_port(std::string(TOP_TB_SET_PORT_NAME), 1); + + /* Generate stimuli waveform for configuration done signals */ + print_verilog_comment(fp, "----- Begin configuration done signal generation -----"); + print_verilog_pulse_stimuli(fp, config_done_port, + 0, /* Initial value */ + num_config_clock_cycles * prog_clock_period / timescale, 0); + print_verilog_comment(fp, "----- End configuration done signal generation -----"); + fp << std::endl; + + /* Generate stimuli waveform for programming clock signals */ + print_verilog_comment(fp, "----- Begin raw programming clock signal generation -----"); + print_verilog_clock_stimuli(fp, prog_clock_register_port, + 0, /* Initial value */ + 0.5 * prog_clock_period / timescale, + std::string()); + print_verilog_comment(fp, "----- End raw programming clock signal generation -----"); + fp << std::endl; + + /* Programming clock should be only enabled during programming phase. + * When configuration is done (config_done is enabled), programming clock should be always zero. + */ + print_verilog_comment(fp, std::string("----- Actual programming clock is triggered only when " + config_done_port.get_name() + " and " + prog_reset_port.get_name() + " are disabled -----")); + fp << "\tassign " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port); + fp << " = " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_register_port); + fp << " & (~" << generate_verilog_port(VERILOG_PORT_CONKT, config_done_port) << ")"; + fp << " & (~" << generate_verilog_port(VERILOG_PORT_CONKT, prog_reset_port) << ")"; + fp << ";" << std::endl; + + fp << std::endl; + + /* Generate stimuli waveform for operating clock signals */ + print_verilog_comment(fp, "----- Begin raw operating clock signal generation -----"); + print_verilog_clock_stimuli(fp, op_clock_register_port, + 0, /* Initial value */ + 0.5 * op_clock_period / timescale, + std::string("~" + reset_port.get_name())); + print_verilog_comment(fp, "----- End raw operating clock signal generation -----"); + + /* Operation clock should be enabled after programming phase finishes. + * Before configuration is done (config_done is enabled), operation clock should be always zero. + */ + print_verilog_comment(fp, std::string("----- Actual operating clock is triggered only when " + config_done_port.get_name() + " is enabled -----")); + fp << "\tassign " << generate_verilog_port(VERILOG_PORT_CONKT, op_clock_port); + fp << " = " << generate_verilog_port(VERILOG_PORT_CONKT, op_clock_register_port); + fp << " & " << generate_verilog_port(VERILOG_PORT_CONKT, config_done_port); + fp << ";" << std::endl; + + fp << std::endl; + + /* Reset signal for configuration circuit: + * only enable during the first clock cycle in programming phase + */ + print_verilog_comment(fp, "----- Begin programming reset signal generation -----"); + print_verilog_pulse_stimuli(fp, prog_reset_port, + 1, /* Initial value */ + prog_clock_period / timescale, 0); + print_verilog_comment(fp, "----- End programming reset signal generation -----"); + + fp << std::endl; + + /* Programming set signal for configuration circuit : always disabled */ + print_verilog_comment(fp, "----- Begin programming set signal generation: always disabled -----"); + print_verilog_pulse_stimuli(fp, prog_set_port, + 0, /* Initial value */ + prog_clock_period / timescale, 0); + print_verilog_comment(fp, "----- End programming set signal generation: always disabled -----"); + + fp << std::endl; + + /* Operating reset signals: only enabled during the first clock cycle in operation phase */ + std::vector reset_pulse_widths; + reset_pulse_widths.push_back(op_clock_period / timescale); + reset_pulse_widths.push_back(2 * op_clock_period / timescale); + + std::vector reset_flip_values; + reset_flip_values.push_back(1); + reset_flip_values.push_back(0); + + print_verilog_comment(fp, "----- Begin operating reset signal generation -----"); + print_verilog_comment(fp, "----- Reset signal is enabled until the first clock cycle in operation phase -----"); + print_verilog_pulse_stimuli(fp, reset_port, + 1, + reset_pulse_widths, + reset_flip_values, + config_done_port.get_name()); + print_verilog_comment(fp, "----- End operating reset signal generation -----"); + + /* Operating set signal for configuration circuit : always disabled */ + print_verilog_comment(fp, "----- Begin operating set signal generation: always disabled -----"); + print_verilog_pulse_stimuli(fp, set_port, + 0, /* Initial value */ + op_clock_period / timescale, 0); + print_verilog_comment(fp, "----- End operating set signal generation: always disabled -----"); + + fp << std::endl; +} + +/******************************************************************** + * Print stimulus for a FPGA fabric with a configuration chain protocol + * where configuration bits are programming in serial (one by one) + * Task list: + * 1. For clock signal, we should create voltage waveforms for two types of clock signals: + * a. operation clock + * b. programming clock + * 2. For Set/Reset, we reset the chip after programming phase ends + * and before operation phase starts + * 3. For input/output clb nets (mapped to I/O grids), + * we should create voltage waveforms only after programming phase + *******************************************************************/ +static +void print_verilog_top_testbench_configuration_chain_bitstream(std::fstream& fp, + const BitstreamManager& bitstream_manager, + const std::vector& fabric_bitstream) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Initial value should be the first configuration bits + * In the rest of programming cycles, + * configuration bits are fed at the falling edge of programming clock. + * We do not care the value of scan_chain head during the first programming cycle + * It is reset anyway + */ + BasicPort config_chain_head_port(generate_configuration_chain_head_name(), 1); + std::vector initial_values(config_chain_head_port.get_width(), 0); + + print_verilog_comment(fp, "----- Begin bitstream loading during configuration phase -----"); + fp << "initial" << std::endl; + fp << "\tbegin" << std::endl; + print_verilog_comment(fp, "----- Configuration chain default input -----"); + fp << "\t\t"; + fp << generate_verilog_port_constant_values(config_chain_head_port, initial_values); + fp << ";"; + + fp << std::endl; + + /* Attention: the configuration chain protcol requires the last configuration bit is fed first + * We will visit the fabric bitstream in a reverse way + */ + std::vector cc_bitstream = fabric_bitstream; + std::reverse(cc_bitstream.begin(), cc_bitstream.end()); + for (const ConfigBitId& bit_id : cc_bitstream) { + fp << "\t\t" << std::string(TOP_TESTBENCH_CC_PROG_TASK_NAME); + fp << "(1'b" << (size_t)bitstream_manager.bit_value(bit_id) << ");" << std::endl; + } + + /* Raise the flag of configuration done when bitstream loading is complete */ + BasicPort prog_clock_port(std::string(TOP_TB_PROG_CLOCK_PORT_NAME), 1); + fp << "\t\t@(negedge " << generate_verilog_port(VERILOG_PORT_CONKT, prog_clock_port) << ");" << std::endl; + + BasicPort config_done_port(std::string(TOP_TB_CONFIG_DONE_PORT_NAME), 1); + fp << "\t\t\t"; + fp << generate_verilog_port(VERILOG_PORT_CONKT, config_done_port); + fp << " <= "; + std::vector config_done_enable_values(config_done_port.get_width(), 1); + fp << generate_verilog_constant_values(config_done_enable_values); + fp << ";" << std::endl; + + fp << "\tend" << std::endl; + print_verilog_comment(fp, "----- End bitstream loading during configuration phase -----"); +} + +/******************************************************************** + * Generate the stimuli for the top-level testbench + * The simulation consists of two phases: configuration phase and operation phase + * Configuration bits are loaded serially. + * This is actually what we do for a physical FPGA + *******************************************************************/ +static +void print_verilog_top_testbench_bitstream(std::fstream& fp, + const e_config_protocol_type& sram_orgz_type, + const BitstreamManager& bitstream_manager, + const std::vector& fabric_bitstream) { + /* Branch on the type of configuration protocol */ + switch (sram_orgz_type) { + case CONFIG_MEM_STANDALONE: + /* TODO */ + break; + case CONFIG_MEM_SCAN_CHAIN: + print_verilog_top_testbench_configuration_chain_bitstream(fp, bitstream_manager, fabric_bitstream); + break; + case CONFIG_MEM_MEMORY_BANK: + /* TODO */ + break; + default: + VTR_LOGF_ERROR(__FILE__, __LINE__, + "Invalid SRAM organization type!\n"); + exit(1); + } +} + +/******************************************************************** + * The top-level function to generate a testbench, in order to verify: + * 1. Configuration phase of the FPGA fabric, where the bitstream is + * loaded to the configuration protocol of the FPGA fabric + * 2. Operating phase of the FPGA fabric, where input stimuli are + * fed to the I/Os of the FPGA fabric + * +----------+ + * | FPGA | +------------+ + * +----->| Fabric |------>| | + * | | | | | + * | +----------+ | | + * | | Output | + * random_input_vectors -----+ | Vector |---->Functional correct? + * | | Comparator | + * | +-----------+ | | + * | | Input | | | + * +----->| Benchmark |----->| | + * +-----------+ +------------+ + * + *******************************************************************/ +void print_verilog_top_testbench(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const std::vector& fabric_bitstream, + const e_config_protocol_type& sram_orgz_type, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, + const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir, + const SimulationSetting& simulation_parameters) { + + std::string timer_message = std::string("Write autocheck testbench for FPGA top-level Verilog netlist for '") + circuit_name + std::string("'"); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(verilog_fname.c_str(), fp); + + /* Generate a brief description on the Verilog file*/ + std::string title = std::string("FPGA Verilog Testbench for Top-level netlist of Design: ") + circuit_name; + print_verilog_file_header(fp, title); + + /* Print preprocessing flags and external netlists */ + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + /* Find the top_module */ + ModuleId top_module = module_manager.find_module(generate_fpga_top_module_name()); + VTR_ASSERT(true == module_manager.valid_module_id(top_module)); + + /* Preparation: find all the clock ports */ + std::vector clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist, netlist_annotation); + + /* Start of testbench */ + print_verilog_top_testbench_ports(fp, module_manager, top_module, + atom_ctx, netlist_annotation, clock_port_names, + sram_orgz_type, circuit_name); + + /* Find the clock period */ + float prog_clock_period = (1./simulation_parameters.programming_clock_frequency()); + float op_clock_period = (1./simulation_parameters.operating_clock_frequency()); + /* Estimate the number of configuration clock cycles + * by traversing the linked-list and count the number of SRAM=1 or BL=1&WL=1 in it. + * We plus 1 additional config clock cycle here because we need to reset everything during the first clock cycle + */ + size_t num_config_clock_cycles = 1 + fabric_bitstream.size(); + + /* Generate stimuli for general control signals */ + print_verilog_top_testbench_generic_stimulus(fp, + num_config_clock_cycles, + prog_clock_period, + op_clock_period, + VERILOG_SIM_TIMESCALE); + + /* Generate stimuli for global ports or connect them to existed signals */ + print_verilog_top_testbench_global_ports_stimuli(fp, + module_manager, top_module, + circuit_lib, global_ports); + + /* Instanciate FPGA top-level module */ + print_verilog_testbench_fpga_instance(fp, module_manager, top_module, + std::string(TOP_TESTBENCH_FPGA_INSTANCE_NAME)); + + /* Connect I/Os to benchmark I/Os or constant driver */ + print_verilog_testbench_connect_fpga_ios(fp, module_manager, top_module, + atom_ctx, place_ctx, io_location_map, + netlist_annotation, + std::string(), + std::string(TOP_TESTBENCH_FPGA_OUTPUT_POSTFIX), + (size_t)VERILOG_DEFAULT_SIGNAL_INIT_VALUE); + + /* Instanciate input benchmark */ + print_verilog_top_testbench_benchmark_instance(fp, + circuit_name, + atom_ctx, + netlist_annotation); + + /* Print tasks used for loading bitstreams */ + print_verilog_top_testbench_load_bitstream_task(fp, sram_orgz_type); + + /* load bitstream to FPGA fabric in a configuration phase */ + print_verilog_top_testbench_bitstream(fp, sram_orgz_type, + bitstream_manager, fabric_bitstream); + + /* Add stimuli for reset, set, clock and iopad signals */ + print_verilog_testbench_random_stimuli(fp, atom_ctx, + netlist_annotation, + clock_port_names, + std::string(TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX), + BasicPort(std::string(TOP_TB_OP_CLOCK_PORT_NAME), 1)); + + /* Add output autocheck */ + print_verilog_testbench_check(fp, + std::string(AUTOCHECKED_SIMULATION_FLAG), + std::string(TOP_TESTBENCH_SIM_START_PORT_NAME), + std::string(TOP_TESTBENCH_REFERENCE_OUTPUT_POSTFIX), + std::string(TOP_TESTBENCH_FPGA_OUTPUT_POSTFIX), + std::string(TOP_TESTBENCH_CHECKFLAG_PORT_POSTFIX), + std::string(TOP_TESTBENCH_ERROR_COUNTER), + atom_ctx, + netlist_annotation, + clock_port_names, + std::string(TOP_TB_OP_CLOCK_PORT_NAME)); + + /* Find simulation time */ + float simulation_time = find_simulation_time_period(VERILOG_SIM_TIMESCALE, + num_config_clock_cycles, + 1./simulation_parameters.programming_clock_frequency(), + simulation_parameters.num_clock_cycles(), + 1./simulation_parameters.operating_clock_frequency()); + + + /* Add Icarus requirement */ + print_verilog_timeout_and_vcd(fp, + std::string(ICARUS_SIMULATOR_FLAG), + std::string(circuit_name + std::string(AUTOCHECK_TOP_TESTBENCH_VERILOG_MODULE_POSTFIX)), + std::string(circuit_name + std::string("_formal.vcd")), + std::string(TOP_TESTBENCH_SIM_START_PORT_NAME), + std::string(TOP_TESTBENCH_ERROR_COUNTER), + (int)simulation_time); + + + /* Testbench ends*/ + print_verilog_module_end(fp, std::string(circuit_name) + std::string(AUTOCHECK_TOP_TESTBENCH_VERILOG_MODULE_POSTFIX)); + + /* Close the file stream */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_top_testbench.h b/openfpga/src/fpga_verilog/verilog_top_testbench.h new file mode 100644 index 000000000..5684b37e2 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_top_testbench.h @@ -0,0 +1,41 @@ +#ifndef VERILOG_TOP_TESTBENCH +#define VERILOG_TOP_TESTBENCH + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "module_manager.h" +#include "bitstream_manager.h" +#include "circuit_library.h" +#include "vpr_context.h" +#include "io_location_map.h" +#include "vpr_netlist_annotation.h" +#include "simulation_setting.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_top_testbench(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const std::vector& fabric_bitstream, + const e_config_protocol_type& sram_orgz_type, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const VprNetlistAnnotation& netlist_annotation, + const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir, + const SimulationSetting& simulation_parameters); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_writer_utils.h b/openfpga/src/fpga_verilog/verilog_writer_utils.h index b145d8be6..8e6cddaaa 100644 --- a/openfpga/src/fpga_verilog/verilog_writer_utils.h +++ b/openfpga/src/fpga_verilog/verilog_writer_utils.h @@ -14,6 +14,7 @@ #include #include "openfpga_port.h" #include "verilog_port_types.h" +#include "circuit_library.h" #include "module_manager.h" /******************************************************************** diff --git a/openfpga/src/main.cpp b/openfpga/src/main.cpp index c018db4bf..b2c9d2e6c 100644 --- a/openfpga/src/main.cpp +++ b/openfpga/src/main.cpp @@ -14,6 +14,7 @@ #include "openfpga_setup_command.h" #include "openfpga_verilog_command.h" #include "openfpga_bitstream_command.h" +#include "openfpga_sdc_command.h" #include "basic_command.h" #include "openfpga_title.h" @@ -60,6 +61,9 @@ int main(int argc, char** argv) { /* Add openfpga bitstream commands */ openfpga::add_openfpga_bitstream_commands(shell); + /* Add openfpga sdc commands */ + openfpga::add_openfpga_sdc_commands(shell); + /* Add basic commands: exit, help, etc. * Note: * This MUST be the last command group to be added! diff --git a/openfpga/src/utils/openfpga_atom_netlist_utils.cpp b/openfpga/src/utils/openfpga_atom_netlist_utils.cpp new file mode 100644 index 000000000..8d1876f90 --- /dev/null +++ b/openfpga/src/utils/openfpga_atom_netlist_utils.cpp @@ -0,0 +1,41 @@ +/*************************************************************************************** + * This file includes most utilized functions that are used to acquire data from + * VPR atom netlist (users' netlist to implement) + ***************************************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from vtrutil library */ +#include "atom_netlist_utils.h" + +#include "openfpga_atom_netlist_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Find the names of all the atom blocks that drive clock nets + * This function will find if the block has been renamed due to contain sensitive characters + * that violates the Verilog syntax + ***************************************************************************************/ +std::vector find_atom_netlist_clock_port_names(const AtomNetlist& atom_nlist, + const VprNetlistAnnotation& netlist_annotation) { + std::vector clock_names; + + std::set clock_pins = find_netlist_logical_clock_drivers(atom_nlist); + for (const AtomPinId& clock_pin : clock_pins) { + const AtomBlockId& atom_blk = atom_nlist.port_block(atom_nlist.pin_port(clock_pin)); + std::string block_name = atom_nlist.block_name(atom_blk); + if (true == netlist_annotation.is_block_renamed(atom_blk)) { + block_name = netlist_annotation.block_name(atom_blk); + } + clock_names.push_back(block_name); + } + + return clock_names; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/openfpga_atom_netlist_utils.h b/openfpga/src/utils/openfpga_atom_netlist_utils.h new file mode 100644 index 000000000..dfa5743a2 --- /dev/null +++ b/openfpga/src/utils/openfpga_atom_netlist_utils.h @@ -0,0 +1,24 @@ +#ifndef OPENFPGA_ATOM_NETLIST_UTILS_H +#define OPENFPGA_ATOM_NETLIST_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "atom_netlist.h" +#include "vpr_netlist_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +std::vector find_atom_netlist_clock_port_names(const AtomNetlist& atom_nlist, + const VprNetlistAnnotation& netlist_annotation); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/utils/simulation_utils.cpp b/openfpga/src/utils/simulation_utils.cpp new file mode 100644 index 000000000..e7d8d33bf --- /dev/null +++ b/openfpga/src/utils/simulation_utils.cpp @@ -0,0 +1,47 @@ +/******************************************************************** + * This file include most utilized functions in generating simulations + * Note: function placed here MUST be generic enough for both SPICE + * and Verilog simulations! + *******************************************************************/ +#include + +#include "simulation_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + + +/******************************************************************** + * Compute the time period for the simulation + *******************************************************************/ +int find_operating_phase_simulation_time(const int& factor, + const int& num_op_clock_cycles, + const float& op_clock_period, + const float& timescale) { + /* Take into account the prog_reset and reset cycles + * 1e9 is to change the unit to ns rather than second + */ + return (factor * num_op_clock_cycles * op_clock_period) / timescale; +} + +/******************************************************************** + * Find the the full time period of a simulation, including + * both the programming time and operating time + * This is a generic function that can be used to generate simulation + * time period for SPICE/Verilog simulators + *******************************************************************/ +float find_simulation_time_period(const float &time_unit, + const int &num_prog_clock_cycles, + const float &prog_clock_period, + const int &num_op_clock_cycles, + const float &op_clock_period) { + float total_time_period = 0.; + + /* Take into account the prog_reset and reset cycles */ + total_time_period = (num_prog_clock_cycles + 2) * prog_clock_period + num_op_clock_cycles * op_clock_period; + total_time_period = total_time_period / time_unit; + + return total_time_period; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/simulation_utils.h b/openfpga/src/utils/simulation_utils.h new file mode 100644 index 000000000..d99a6226f --- /dev/null +++ b/openfpga/src/utils/simulation_utils.h @@ -0,0 +1,28 @@ +#ifndef SIMULATION_UTILS_H +#define SIMULATION_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +int find_operating_phase_simulation_time(const int& factor, + const int& num_op_clock_cycles, + const float& op_clock_period, + const float& timescale); + +float find_simulation_time_period(const float& time_unit, + const int& num_prog_clock_cycles, + const float& prog_clock_period, + const int& num_op_clock_cycles, + const float& op_clock_period); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/test_blif/and.blif b/openfpga/test_blif/and.blif new file mode 100644 index 000000000..67d978741 --- /dev/null +++ b/openfpga/test_blif/and.blif @@ -0,0 +1,8 @@ +.model top +.inputs a b +.outputs c + +.names a b c +11 1 + +.end diff --git a/openfpga/test_blif/and.v b/openfpga/test_blif/and.v new file mode 100644 index 000000000..876f1c6fe --- /dev/null +++ b/openfpga/test_blif/and.v @@ -0,0 +1,14 @@ +`timescale 1ns / 1ps + +module top( + a, + b, + c); + +input wire a; +input wire b; +output wire c; + +assign c = a & b; + +endmodule diff --git a/openfpga/test_blif/s298.blif b/openfpga/test_blif/s298.blif deleted file mode 100644 index c588a6e0a..000000000 --- a/openfpga/test_blif/s298.blif +++ /dev/null @@ -1,90 +0,0 @@ -# Benchmark "s298" written by ABC on Tue Mar 12 09:40:31 2019 -.model s298 -.inputs clock G0 G1 G2 -.outputs G117 G132 G66 G118 G133 G67 - -.latch n21 G10 re clock 0 -.latch n26 G11 re clock 0 -.latch n31 G12 re clock 0 -.latch n36 G13 re clock 0 -.latch n41 G14 re clock 0 -.latch n46 G15 re clock 0 -.latch n51 G66 re clock 0 -.latch n55 G67 re clock 0 -.latch n59 G117 re clock 0 -.latch n63 G118 re clock 0 -.latch n67 G132 re clock 0 -.latch n71 G133 re clock 0 -.latch n75 G22 re clock 0 -.latch n80 G23 re clock 0 - -.names n56 n57 G10 n63 -0-0 1 -11- 1 -.names G15 G11 G13 G22 G14 G12 n56 -01---- 1 -0-0--- 1 -0--0-- 1 -0---1- 1 -0----1 1 --11000 1 -.names G14 G13 G12 G118 G11 n57 -01--- 1 -100-0 1 -1-11- 1 --1-1- 1 -.names n56 n59_1 G10 n67 -0-0 1 -11- 1 -.names G14 G13 G12 G132 G11 n59_1 -100-0 1 -11-1- 1 -1-11- 1 -.names G0 G10 n21 -00 1 -.names G10 G11 G0 G12 G13 n26 -010-- 1 -1001- 1 -100-0 1 -.names G12 G0 G11 G10 n31 -0011 1 -100- 1 -10-0 1 -.names G13 G0 G11 G12 G10 n36 -00111 1 -1001- 1 -1010- 1 -10--0 1 -.names n65 G14 G0 n41 -000 1 -110 1 -.names G23 G10 G13 G11 G12 n65 -1---- 0 --1100 0 -.names G0 n56 n46 -00 1 -.names n56 G66 G14 G13 G12 n51 -111-1 1 -11-1- 1 -1-01- 1 -.names n56 G13 G14 G11 G67 G12 n55 -1000-- 1 -10-1-0 1 -111-1- 1 -1-1-11 1 -.names n56 G13 G117 G14 G12 G11 n59 -10-0-- 1 -10--01 1 -1111-- 1 -1-111- 1 -.names n56 G14 G12 G13 G133 G11 n71 -1010-1 1 -111-1- 1 -11-11- 1 -.names G2 G22 G0 n75 -010 1 -100 1 -.names G1 G23 G0 n80 -010 1 -100 1 -.end diff --git a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml index 2f318cbaa..9644530da 100644 --- a/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml +++ b/openfpga/test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml @@ -168,11 +168,11 @@ - + - + @@ -232,6 +232,7 @@ + diff --git a/openfpga/test_script/and_k6_frac.openfpga b/openfpga/test_script/and_k6_frac.openfpga new file mode 100644 index 000000000..8a16f8cb5 --- /dev/null +++ b/openfpga/test_script/and_k6_frac.openfpga @@ -0,0 +1,53 @@ +# 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 #--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 +fpga_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml + +# 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 + +# Finish and exit OpenFPGA +exit diff --git a/openfpga/test_script/s298.openfpga b/openfpga/test_script/s298.openfpga deleted file mode 100644 index 216f6d8c4..000000000 --- a/openfpga/test_script/s298.openfpga +++ /dev/null @@ -1,14 +0,0 @@ -# Run VPR for the s298 design -vpr ./test_vpr_arch/k6_N10_40nm.xml ./test_blif/s298.blif - -# Read OpenFPGA architecture definition -read_openfpga_arch -f ./test_openfpga_arch/k6_N10_40nm_openfpga.xml - -# Annotate the OpenFPGA architecture to VPR data base -link_openfpga_arch - -# Check and correct any naming conflicts in the BLIF netlist -check_netlist_naming_conflict --fix --report ./netlist_renaming.xml - -# Finish and exit OpenFPGA -exit diff --git a/openfpga/test_script/s298_k6_frac.openfpga b/openfpga/test_script/s298_k6_frac.openfpga deleted file mode 100644 index 5caf1d24c..000000000 --- a/openfpga/test_script/s298_k6_frac.openfpga +++ /dev/null @@ -1,41 +0,0 @@ -# Run VPR for the s298 design -vpr ./test_vpr_arch/k6_frac_N10_40nm.xml ./test_blif/s298.blif --write_rr_graph example_rr_graph.xml - -# Read OpenFPGA architecture definition -read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml - -# Write out the architecture XML as a proof -#write_openfpga_arch -f ./arch_echo.xml - -# Annotate the OpenFPGA architecture to VPR data base -link_openfpga_arch #--verbose - -# 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 -fpga_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml - -# Write the Verilog netlit for FPGA fabric -# - Enable the use of explicit port mapping in Verilog netlist -write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose - -# Finish and exit OpenFPGA -exit diff --git a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml index 97001c497..44e2ef289 100644 --- a/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml +++ b/openfpga/test_vpr_arch/k6_frac_N10_40nm.xml @@ -45,19 +45,22 @@ + - - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock - io.outpad io.inpad io.clock + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad + io.outpad io.inpad @@ -145,7 +148,10 @@ - + diff --git a/vpr7_x2p/vpr/SRC/fpga_x2p/verilog/verilog_testbench_utils.cpp b/vpr7_x2p/vpr/SRC/fpga_x2p/verilog/verilog_testbench_utils.cpp index ee2a80ea6..9293c7cf6 100644 --- a/vpr7_x2p/vpr/SRC/fpga_x2p/verilog/verilog_testbench_utils.cpp +++ b/vpr7_x2p/vpr/SRC/fpga_x2p/verilog/verilog_testbench_utils.cpp @@ -479,7 +479,6 @@ void print_verilog_testbench_shared_ports(std::fstream& fp, /* Validate the file stream */ check_file_handler(fp); - /* Instantiate register for inputs stimulis */ print_verilog_comment(fp, std::string("----- Shared inputs -------")); for (const t_logical_block& lb : L_logical_blocks) {