diff --git a/openfpga/src/base/openfpga_context.h b/openfpga/src/base/openfpga_context.h index 20e235a35..d4320915f 100644 --- a/openfpga/src/base/openfpga_context.h +++ b/openfpga/src/base/openfpga_context.h @@ -59,8 +59,8 @@ class OpenfpgaContext : public Context { const openfpga::FlowManager& flow_manager() const { return flow_manager_; } const openfpga::BitstreamManager& bitstream_manager() const { return bitstream_manager_; } const std::vector& fabric_bitstream() const { return fabric_bitstream_; } - const openfpga::IoLocationMap& io_location_map() { return io_location_map_; } - const std::unordered_map& net_activity() { return net_activity_; } + const openfpga::IoLocationMap& io_location_map() const { return io_location_map_; } + const std::unordered_map& net_activity() const { return net_activity_; } public: /* Public mutators */ openfpga::Arch& mutable_arch() { return arch_; } openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; } diff --git a/openfpga/src/base/openfpga_naming.cpp b/openfpga/src/base/openfpga_naming.cpp index fab27a12b..9bfad01a2 100644 --- a/openfpga/src/base/openfpga_naming.cpp +++ b/openfpga/src/base/openfpga_naming.cpp @@ -17,6 +17,19 @@ /* begin namespace openfpga */ namespace openfpga { +/************************************************ + * A generic function to generate the instance name + * in the following format: + * __ + * This is mainly used by module manager to give a default + * name for each instance when outputting the module + * in Verilog/SPICE format + ***********************************************/ +std::string generate_instance_name(const std::string& instance_name, + const size_t& instance_id) { + return instance_name + std::string("_") + std::to_string(instance_id) + std::string("_"); +} + /************************************************ * Generate the node name for a multiplexing structure * Case 1 : If there is an intermediate buffer followed by, diff --git a/openfpga/src/base/openfpga_naming.h b/openfpga/src/base/openfpga_naming.h index 13d4c56c0..e4f1ee8f8 100644 --- a/openfpga/src/base/openfpga_naming.h +++ b/openfpga/src/base/openfpga_naming.h @@ -23,6 +23,9 @@ /* begin namespace openfpga */ namespace openfpga { +std::string generate_instance_name(const std::string& instance_name, + const size_t& instance_id); + std::string generate_mux_node_name(const size_t& node_level, const bool& add_buffer_postfix); diff --git a/openfpga/src/base/openfpga_sdc.cpp b/openfpga/src/base/openfpga_sdc.cpp index eaa2b9941..8b708f133 100644 --- a/openfpga/src/base/openfpga_sdc.cpp +++ b/openfpga/src/base/openfpga_sdc.cpp @@ -10,6 +10,7 @@ #include "circuit_library_utils.h" #include "pnr_sdc_writer.h" +#include "analysis_sdc_writer.h" #include "openfpga_sdc.h" /* Include global variables of VPR */ @@ -77,4 +78,38 @@ void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, } } +/******************************************************************** + * A wrapper function to call the analysis SDC generator of FPGA-SDC + *******************************************************************/ +void write_analysis_sdc(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context) { + + CommandOptionId opt_output_dir = cmd.option("file"); + + /* This is an intermediate data structure which is designed to modularize the FPGA-SDC + * Keep it independent from any other outside data structures + */ + std::string sdc_dir_path = format_dir_path(cmd_context.option_value(cmd, opt_output_dir)); + + /* Create directories */ + create_dir_path(sdc_dir_path.c_str()); + + AnalysisSdcOption options(sdc_dir_path); + options.set_generate_sdc_analysis(true); + + /* Collect global ports from the circuit library: + * TODO: should we place this in the OpenFPGA context? + */ + std::vector global_ports = find_circuit_library_global_ports(openfpga_ctx.arch().circuit_lib); + + if (true == options.generate_sdc_analysis()) { + print_analysis_sdc(options, + 1./openfpga_ctx.arch().sim_setting.operating_clock_frequency(), + g_vpr_ctx, + openfpga_ctx, + global_ports, + openfpga_ctx.flow_manager().compress_routing()); + } +} + } /* end namespace openfpga */ diff --git a/openfpga/src/base/openfpga_sdc.h b/openfpga/src/base/openfpga_sdc.h index 4e48964f3..110a8cd72 100644 --- a/openfpga/src/base/openfpga_sdc.h +++ b/openfpga/src/base/openfpga_sdc.h @@ -18,6 +18,9 @@ namespace openfpga { void write_pnr_sdc(OpenfpgaContext& openfpga_ctx, const Command& cmd, const CommandContext& cmd_context); +void write_analysis_sdc(OpenfpgaContext& openfpga_ctx, + const Command& cmd, const CommandContext& cmd_context); + } /* end namespace openfpga */ #endif diff --git a/openfpga/src/base/openfpga_sdc_command.cpp b/openfpga/src/base/openfpga_sdc_command.cpp index 799fad4e6..91d65aa0a 100644 --- a/openfpga/src/base/openfpga_sdc_command.cpp +++ b/openfpga/src/base/openfpga_sdc_command.cpp @@ -61,6 +61,36 @@ ShellCommandId add_openfpga_write_pnr_sdc_command(openfpga::Shell& shell, + const ShellCommandClassId& cmd_class_id, + const std::vector& dependent_cmds) { + Command shell_cmd("write_analysis_sdc"); + + /* Add an option '--file' in short '-f'*/ + CommandOptionId output_opt = shell_cmd.add_option("file", true, "Specify the output directory for SDC files"); + shell_cmd.set_option_short_name(output_opt, "f"); + shell_cmd.set_option_require_value(output_opt, openfpga::OPT_STRING); + + /* Add an option '--verbose' */ + shell_cmd.add_option("verbose", false, "Enable verbose output"); + + /* Add command 'write_fabric_verilog' to the Shell */ + ShellCommandId shell_cmd_id = shell.add_command(shell_cmd, "generate SDC files for timing analysis a PnRed FPGA fabric mapped by a benchmark"); + shell.set_command_class(shell_cmd_id, cmd_class_id); + shell.set_command_execute_function(shell_cmd_id, write_analysis_sdc); + + /* Add command dependency to the Shell */ + shell.set_command_dependency(shell_cmd_id, dependent_cmds); + + return shell_cmd_id; +} + void add_openfpga_sdc_commands(openfpga::Shell& shell) { /* Get the unique id of 'build_fabric' command which is to be used in creating the dependency graph */ const ShellCommandId& build_fabric_id = shell.command(std::string("build_fabric")); @@ -77,6 +107,17 @@ void add_openfpga_sdc_commands(openfpga::Shell& shell) { add_openfpga_write_pnr_sdc_command(shell, openfpga_sdc_cmd_class, pnr_sdc_cmd_dependency); + + /******************************** + * Command 'write_analysis_sdc' + */ + /* The 'write_analysis_sdc' command should NOT be executed before 'build_fabric' */ + std::vector analysis_sdc_cmd_dependency; + analysis_sdc_cmd_dependency.push_back(build_fabric_id); + add_openfpga_write_analysis_sdc_command(shell, + openfpga_sdc_cmd_class, + analysis_sdc_cmd_dependency); + } } /* end namespace openfpga */ diff --git a/openfpga/src/fabric/build_grid_modules.cpp b/openfpga/src/fabric/build_grid_modules.cpp index a5c346025..2ee5527e7 100644 --- a/openfpga/src/fabric/build_grid_modules.cpp +++ b/openfpga/src/fabric/build_grid_modules.cpp @@ -428,6 +428,12 @@ void add_module_pb_graph_pin_interc(ModuleManager& module_manager, size_t wire_instance = module_manager.num_instance(pb_module, wire_module); module_manager.add_child_module(pb_module, wire_module); + /* Give an instance name: this name should be consistent with the block name given in SDC generator, + * If you want to bind the SDC generation to modules + */ + std::string wire_instance_name = generate_instance_name(module_manager.module_name(wire_module), wire_instance); + module_manager.set_child_instance_name(pb_module, wire_module, wire_instance, wire_instance_name); + /* Ensure input and output ports of the wire model has only 1 pin respectively */ VTR_ASSERT(1 == circuit_lib.port_size(interc_model_inputs[0])); VTR_ASSERT(1 == circuit_lib.port_size(interc_model_outputs[0])); diff --git a/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp new file mode 100644 index 000000000..d9a85280b --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.cpp @@ -0,0 +1,651 @@ +/******************************************************************** + * This file includes functions that are used to write SDC commands + * to disable unused ports of grids, such as Configurable Logic Block + * (CLBs), heterogeneous blocks, etc. + *******************************************************************/ +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +/* Headers from vprutil library */ +#include "vpr_utils.h" + +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" + +#include "pb_type_utils.h" + +#include "sdc_writer_utils.h" +#include "analysis_sdc_writer_utils.h" +#include "analysis_sdc_grid_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Recursively visit all the pb_types in the hierarchy + * and disable all the ports + * + * Note: it is a must to disable all the ports in all the child pb_types! + * This can prohibit timing analyzer to consider any FF-to-FF path or + * combinatinal path inside an unused grid, when finding critical paths!!! + *******************************************************************/ +static +void rec_print_analysis_sdc_disable_unused_pb_graph_nodes(std::fstream& fp, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& hierarchy_name, + t_pb_graph_node* physical_pb_graph_node) { + t_pb_type* physical_pb_type = physical_pb_graph_node->pb_type; + + /* Validate file stream */ + valid_file_stream(fp); + + /* Disable all the ports of current module (parent_module)! + * Hierarchy name already includes the instance name of parent_module + */ + fp << "#######################################" << std::endl; + fp << "# Disable all the ports for pb_graph_node " << physical_pb_graph_node->pb_type->name << "[" << physical_pb_graph_node->placement_index << "]" << std::endl; + fp << "#######################################" << std::endl; + + fp << "set_disable_timing "; + fp << hierarchy_name; + fp << "*"; + fp << std::endl; + + /* Return if this is the primitive pb_type */ + if (true == is_primitive_pb_type(physical_pb_type)) { + return; + } + + /* Go recursively */ + t_mode* physical_mode = device_annotation.physical_mode(physical_pb_type); + + /* Disable all the ports by iterating over its instance in the parent module */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + /* Generate the name of the Verilog module for this child */ + std::string child_module_name = generate_physical_block_module_name(&(physical_mode->pb_type_children[ichild])); + + ModuleId child_module = module_manager.find_module(child_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(child_module)); + + /* Each child may exist multiple times in the hierarchy*/ + for (int inst = 0; inst < physical_mode->pb_type_children[ichild].num_pb; ++inst) { + std::string child_instance_name = module_manager.instance_name(parent_module, child_module, module_manager.child_module_instances(parent_module, child_module)[inst]); + /* Must have a valid instance name!!! */ + VTR_ASSERT(false == child_instance_name.empty()); + + std::string updated_hierarchy_name = hierarchy_name + child_instance_name + std::string("/"); + + rec_print_analysis_sdc_disable_unused_pb_graph_nodes(fp, device_annotation, module_manager, child_module, updated_hierarchy_name, + &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ichild][inst])); + } + } +} + +/******************************************************************** + * Disable an unused pin of a pb_graph_node (parent_module) + *******************************************************************/ +static +void disable_pb_graph_node_unused_pin(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& hierarchy_name, + const t_pb_graph_pin* pb_graph_pin, + const PhysicalPb& physical_pb, + const PhysicalPbId& pb_id) { + /* Validate file stream */ + valid_file_stream(fp); + + /* Identify if the pb_graph_pin has been used or not + * TODO: identify if this is a parasitic net + */ + if (AtomNetId::INVALID() != physical_pb.pb_graph_pin_atom_net(pb_id, pb_graph_pin)) { + /* Used pin; Nothing to do */ + return; + } + + /* Reach here, it means that this pin is not used. Disable timing analysis for the pin */ + /* Find the module port by name */ + std::string module_port_name = generate_pb_type_port_name(pb_graph_pin->port); + ModulePortId module_port = module_manager.find_module_port(parent_module, module_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(parent_module, module_port)); + BasicPort port_to_disable = module_manager.module_port(parent_module, module_port); + port_to_disable.set_width(pb_graph_pin->pin_number, pb_graph_pin->pin_number); + + fp << "set_disable_timing "; + fp << hierarchy_name; + fp << generate_sdc_port(port_to_disable); + fp << std::endl; +} + +/******************************************************************** + * Disable unused input ports and output ports of this pb_graph_node (parent_module) + * This function will iterate over all the input pins, output pins + * of the physical_pb_graph_node, and check if they are mapped + * For unused pins, we will find the port in parent_module + * and then print SDC commands to disable them + *******************************************************************/ +static +void disable_pb_graph_node_unused_pins(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& hierarchy_name, + t_pb_graph_node* physical_pb_graph_node, + const PhysicalPb& physical_pb) { + const PhysicalPbId& pb_id = physical_pb.find_pb(physical_pb_graph_node); + VTR_ASSERT(true == physical_pb.valid_pb_id(pb_id)); + + fp << "#######################################" << std::endl; + fp << "# Disable unused pins for pb_graph_node " << physical_pb_graph_node->pb_type->name << "[" << physical_pb_graph_node->placement_index << "]" << std::endl; + fp << "#######################################" << std::endl; + + /* Disable unused input pins */ + for (int iport = 0; iport < physical_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_input_pins[iport]; ++ipin) { + disable_pb_graph_node_unused_pin(fp, module_manager, parent_module, + hierarchy_name, + &(physical_pb_graph_node->input_pins[iport][ipin]), + physical_pb, pb_id); + } + } + + /* Disable unused output pins */ + for (int iport = 0; iport < physical_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_output_pins[iport]; ++ipin) { + disable_pb_graph_node_unused_pin(fp, module_manager, parent_module, + hierarchy_name, + &(physical_pb_graph_node->output_pins[iport][ipin]), + physical_pb, pb_id); + } + } + + /* Disable unused clock pins */ + for (int iport = 0; iport < physical_pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_clock_pins[iport]; ++ipin) { + disable_pb_graph_node_unused_pin(fp, module_manager, parent_module, + hierarchy_name, + &(physical_pb_graph_node->clock_pins[iport][ipin]), + physical_pb, pb_id); + } + } +} + +/******************************************************************** + * Disable unused inputs of routing multiplexers of this pb_graph_node + * This function will first cache the nets for each input and output pins + * and store the results in a mux_name-to-net mapping + *******************************************************************/ +static +void disable_pb_graph_node_unused_mux_inputs(std::fstream& fp, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& hierarchy_name, + t_pb_graph_node* physical_pb_graph_node, + const PhysicalPb& physical_pb) { + + fp << "#######################################" << std::endl; + fp << "# Disable unused mux_inputs for pb_graph_node " << physical_pb_graph_node->pb_type->name << "[" << physical_pb_graph_node->placement_index << "]" << std::endl; + fp << "#######################################" << std::endl; + + t_pb_type* physical_pb_type = physical_pb_graph_node->pb_type; + + t_mode* physical_mode = device_annotation.physical_mode(physical_pb_type); + + std::map mux_instance_to_net_map; + + /* Cache the nets for each input pins of each child pb_graph_node */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + for (int inst = 0; inst < physical_mode->pb_type_children[ichild].num_pb; ++inst) { + + t_pb_graph_node* child_pb_graph_node = &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ichild][inst]); + + /* Cache the nets for input pins of the child pb_graph_node */ + for (int iport = 0; iport < child_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < child_pb_graph_node->num_input_pins[iport]; ++ipin) { + const PhysicalPbId& pb_id = physical_pb.find_pb(child_pb_graph_node); + VTR_ASSERT(true == physical_pb.valid_pb_id(pb_id)); + /* Generate the mux name */ + std::string mux_instance_name = generate_pb_mux_instance_name(GRID_MUX_INSTANCE_PREFIX, &(child_pb_graph_node->input_pins[iport][ipin]), std::string("")); + /* Cache the net */ + mux_instance_to_net_map[mux_instance_name] = physical_pb.pb_graph_pin_atom_net(pb_id, &(child_pb_graph_node->input_pins[iport][ipin])); + } + } + + /* Cache the nets for clock pins of the child pb_graph_node */ + for (int iport = 0; iport < child_pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < child_pb_graph_node->num_clock_pins[iport]; ++ipin) { + const PhysicalPbId& pb_id = physical_pb.find_pb(child_pb_graph_node); + /* Generate the mux name */ + std::string mux_instance_name = generate_pb_mux_instance_name(GRID_MUX_INSTANCE_PREFIX, &(child_pb_graph_node->clock_pins[iport][ipin]), std::string("")); + /* Cache the net */ + mux_instance_to_net_map[mux_instance_name] = physical_pb.pb_graph_pin_atom_net(pb_id, &(child_pb_graph_node->clock_pins[iport][ipin])); + } + } + + } + } + + /* Cache the nets for each output pins of this pb_graph_node */ + for (int iport = 0; iport < physical_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_output_pins[iport]; ++ipin) { + const PhysicalPbId& pb_id = physical_pb.find_pb(physical_pb_graph_node); + /* Generate the mux name */ + std::string mux_instance_name = generate_pb_mux_instance_name(GRID_MUX_INSTANCE_PREFIX, &(physical_pb_graph_node->output_pins[iport][ipin]), std::string("")); + /* Cache the net */ + mux_instance_to_net_map[mux_instance_name] = physical_pb.pb_graph_pin_atom_net(pb_id, &(physical_pb_graph_node->output_pins[iport][ipin])); + } + } + + /* Now disable unused inputs of routing multiplexers, by tracing from input pins of the parent_module */ + for (int iport = 0; iport < physical_pb_graph_node->num_input_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_input_pins[iport]; ++ipin) { + /* Find the module port by name */ + std::string module_port_name = generate_pb_type_port_name(physical_pb_graph_node->input_pins[iport][ipin].port); + ModulePortId module_port = module_manager.find_module_port(parent_module, module_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(parent_module, module_port)); + + const PhysicalPbId& pb_id = physical_pb.find_pb(physical_pb_graph_node); + const AtomNetId& mapped_net = physical_pb.pb_graph_pin_atom_net(pb_id, &(physical_pb_graph_node->input_pins[iport][ipin])); + + disable_analysis_module_input_pin_net_sinks(fp, module_manager, parent_module, + hierarchy_name, + module_port, ipin, + mapped_net, + mux_instance_to_net_map); + } + } + + for (int iport = 0; iport < physical_pb_graph_node->num_clock_ports; ++iport) { + for (int ipin = 0; ipin < physical_pb_graph_node->num_clock_pins[iport]; ++ipin) { + /* Find the module port by name */ + std::string module_port_name = generate_pb_type_port_name(physical_pb_graph_node->clock_pins[iport][ipin].port); + ModulePortId module_port = module_manager.find_module_port(parent_module, module_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(parent_module, module_port)); + + const PhysicalPbId& pb_id = physical_pb.find_pb(physical_pb_graph_node); + const AtomNetId& mapped_net = physical_pb.pb_graph_pin_atom_net(pb_id, &(physical_pb_graph_node->clock_pins[iport][ipin])); + + disable_analysis_module_input_pin_net_sinks(fp, module_manager, parent_module, + hierarchy_name, + module_port, ipin, + mapped_net, + mux_instance_to_net_map); + } + } + + /* Now disable unused inputs of routing multiplexers, by tracing from output pins of the child_module */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + /* Generate the name of the Verilog module for this child */ + std::string child_module_name = generate_physical_block_module_name(&(physical_mode->pb_type_children[ichild])); + + ModuleId child_module = module_manager.find_module(child_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(child_module)); + + for (int inst = 0; inst < physical_mode->pb_type_children[ichild].num_pb; ++inst) { + + t_pb_graph_node* child_pb_graph_node = &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ichild][inst]); + + for (int iport = 0; iport < child_pb_graph_node->num_output_ports; ++iport) { + for (int ipin = 0; ipin < child_pb_graph_node->num_output_pins[iport]; ++ipin) { + /* Find the module port by name */ + std::string module_port_name = generate_pb_type_port_name(child_pb_graph_node->output_pins[iport][ipin].port); + ModulePortId module_port = module_manager.find_module_port(child_module, module_port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(child_module, module_port)); + + const PhysicalPbId& pb_id = physical_pb.find_pb(child_pb_graph_node); + const AtomNetId& mapped_net = physical_pb.pb_graph_pin_atom_net(pb_id, &(child_pb_graph_node->output_pins[iport][ipin])); + + /* Corner case: if the pb_graph_pin has no fan-out we will skip this pin */ + if (0 == child_pb_graph_node->output_pins[iport][ipin].num_output_edges) { + continue; + } + + disable_analysis_module_output_pin_net_sinks(fp, module_manager, parent_module, + hierarchy_name, + child_module, inst, + module_port, ipin, + mapped_net, + mux_instance_to_net_map); + } + } + } + } +} + +/******************************************************************** + * Recursively visit all the pb_types in the hierarchy + * and disable all the unused resources, including: + * 1. input ports + * 2. output ports + * 3. unused inputs of routing multiplexers + * + * As this function is executed in a recursive way. + * To avoid repeated disable timing for ports, during each run of this function, + * only the unused input ports, output ports of the parent module will be disabled. + * In addition, we will cache all the net ids mapped to the input ports of + * child modules, and the net ids mapped to the output ports of parent module. + * As such, we can trace from + * 1. the input ports of parent module to disable unused inputs of routing multiplexer + * which drives the inputs of child modules + * + * Parent_module + * +--------------------------------------------- + * | MUX child_module + * | +-------------+ +-------- + * input_pin0(netA) --->|-------->| Routing |------>| + * input_pin1(netB) --->|----x--->| Multiplexer | netA | + * | +-------------+ | + * | | + * + * 2. the output ports of child module to disable unused inputs of routing multiplexer + * which drives the outputs of parent modules + * + * Case 1: + * parent_module + * --------------------------------------+ + * child_module | + * -------------+ | + * | +-------------+ | + * output_pin0 (netA) |--->| Routing |----->|----> + * output_pin1 (netB) |-x->| Multiplexer | netA | + * | +-------------+ | + * + * Case 2: + * + * Parent_module + * +--------------------------------------------- + * | + * | +--------------------------------------------+ + * | | MUX child_module | + * | | +-------------+ +-----------+ | + * | +--->| Routing |------>| | | + * input_pin0(netA) --->|----x--->| Multiplexer | netA | output_pin|-----+ + * | +-------------+ | | netA + * | | | + * + * + * Note: it is a must to disable all the ports in all the child pb_types! + * This can prohibit timing analyzer to consider any FF-to-FF path or + * combinatinal path inside an unused grid, when finding critical paths!!! + *******************************************************************/ +static +void rec_print_analysis_sdc_disable_pb_graph_node_unused_resources(std::fstream& fp, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& hierarchy_name, + t_pb_graph_node* physical_pb_graph_node, + const PhysicalPb& physical_pb) { + t_pb_type* physical_pb_type = physical_pb_graph_node->pb_type; + + /* Disable unused input ports and output ports of this pb_graph_node (parent_module) */ + disable_pb_graph_node_unused_pins(fp, module_manager, parent_module, + hierarchy_name, physical_pb_graph_node, physical_pb); + + /* Return if this is the primitive pb_type + * Note: this must return before we disable any unused inputs of routing multiplexer! + * This is due to that primitive pb_type does NOT contain any routing multiplexers inside!!! + */ + if (true == is_primitive_pb_type(physical_pb_type)) { + return; + } + + /* Disable unused inputs of routing multiplexers of this pb_graph_node */ + disable_pb_graph_node_unused_mux_inputs(fp, device_annotation, + module_manager, parent_module, + hierarchy_name, physical_pb_graph_node, + physical_pb); + + + t_mode* physical_mode = device_annotation.physical_mode(physical_pb_type); + + /* Disable all the ports by iterating over its instance in the parent module */ + for (int ichild = 0; ichild < physical_mode->num_pb_type_children; ++ichild) { + /* Generate the name of the Verilog module for this child */ + std::string child_module_name = generate_physical_block_module_name(&(physical_mode->pb_type_children[ichild])); + + ModuleId child_module = module_manager.find_module(child_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(child_module)); + + /* Each child may exist multiple times in the hierarchy*/ + for (int inst = 0; inst < physical_pb_type->modes[physical_mode->index].pb_type_children[ichild].num_pb; ++inst) { + std::string child_instance_name = module_manager.instance_name(parent_module, child_module, module_manager.child_module_instances(parent_module, child_module)[inst]); + /* Must have a valid instance name!!! */ + VTR_ASSERT(false == child_instance_name.empty()); + + std::string updated_hierarchy_name = hierarchy_name + child_instance_name + std::string("/"); + + rec_print_analysis_sdc_disable_pb_graph_node_unused_resources(fp, device_annotation, + module_manager, child_module, updated_hierarchy_name, + &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ichild][inst]), + physical_pb); + } + } +} + +/******************************************************************** + * This function can work in two differnt modes: + * 1. For partially unused pb blocks + * --------------------------------- + * Disable the timing for only unused resources in a physical block + * We have to walk through pb_graph node, port by port and pin by pin. + * Identify which pins have not been used, and then disable the timing + * for these ports. + * Plus, for input ports, we will trace the routing multiplexers + * and disable the timing for unused inputs. + * + * 2. For fully unused pb_blocks + * ----------------------------- + * Disable the timing for a fully unused grid! + * This is very straightforward! + * Just walk through each pb_type and disable all the ports using wildcards + *******************************************************************/ +static +void print_analysis_sdc_disable_pb_block_unused_resources(std::fstream& fp, + t_physical_tile_type_ptr grid_type, + const vtr::Point& grid_coordinate, + const VprDeviceAnnotation& device_annotation, + const ModuleManager& module_manager, + const std::string& grid_instance_name, + const size_t& grid_z, + const PhysicalPb& physical_pb, + const bool& unused_block) { + /* If the block is partially unused, we should have a physical pb */ + if (false == unused_block) { + VTR_ASSERT(false == physical_pb.empty()); + } + + VTR_ASSERT(1 == grid_type->equivalent_sites.size()); + t_pb_graph_node* pb_graph_head = grid_type->equivalent_sites[0]->pb_graph_head; + VTR_ASSERT(nullptr != pb_graph_head); + + /* Find an unique name to the pb instance in this grid + * Note: this must be consistent with the instance name we used in build_grid_module()!!! + */ + /* TODO: validate that the instance name is used in module manager!!! */ + std::string pb_module_name = generate_physical_block_module_name(pb_graph_head->pb_type); + std::string pb_instance_name = generate_physical_block_instance_name(pb_graph_head->pb_type, grid_z); + + ModuleId pb_module = module_manager.find_module(pb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(pb_module)); + + /* Print comments */ + fp << "#######################################" << std::endl; + + if (true == unused_block) { + fp << "# Disable Timing for unused grid[" << grid_coordinate.x() << "][" << grid_coordinate.y() << "][" << grid_z << "]" << std::endl; + } else { + VTR_ASSERT_SAFE(false == unused_block); + fp << "# Disable Timing for unused resources in grid[" << grid_coordinate.x() << "][" << grid_coordinate.y() << "][" << grid_z << "]" << std::endl; + } + + fp << "#######################################" << std::endl; + + std::string hierarchy_name = grid_instance_name + std::string("/") + pb_instance_name + std::string("/"); + + /* Go recursively through the pb_graph hierarchy, and disable all the ports level by level */ + if (true == unused_block) { + rec_print_analysis_sdc_disable_unused_pb_graph_nodes(fp, device_annotation, + module_manager, pb_module, hierarchy_name, + pb_graph_head); + } else { + VTR_ASSERT_SAFE(false == unused_block); + rec_print_analysis_sdc_disable_pb_graph_node_unused_resources(fp, device_annotation, + module_manager, pb_module, hierarchy_name, + pb_graph_head, physical_pb); + } +} + +/******************************************************************** + * Disable the timing for a fully unused grid! + * This is very straightforward! + * Just walk through each pb_type and disable all the ports using wildcards + *******************************************************************/ +static +void print_analysis_sdc_disable_unused_grid(std::fstream& fp, + const vtr::Point& grid_coordinate, + const DeviceGrid& grids, + const VprDeviceAnnotation& device_annotation, + const VprClusteringAnnotation& cluster_annotation, + const VprPlacementAnnotation& place_annotation, + const ModuleManager& module_manager, + const e_side& border_side) { + /* Validate file stream */ + valid_file_stream(fp); + + t_physical_tile_type_ptr grid_type = grids[grid_coordinate.x()][grid_coordinate.y()].type; + /* Bypass conditions for grids : + * 1. EMPTY type, which is by nature unused + * 2. Offset > 0, which has already been processed when offset = 0 + */ + if ( (true == is_empty_type(grid_type)) + || (0 < grids[grid_coordinate.x()][grid_coordinate.y()].width_offset) + || (0 < grids[grid_coordinate.x()][grid_coordinate.y()].height_offset) ) { + return; + } + + /* Find an unique name to the grid instane + * Note: this must be consistent with the instance name we used in build_top_module()!!! + */ + /* TODO: validate that the instance name is used in module manager!!! */ + std::string grid_module_name_prefix(GRID_MODULE_NAME_PREFIX); + std::string grid_module_name = generate_grid_block_module_name(grid_module_name_prefix, std::string(grid_type->name), is_io_type(grid_type), border_side); + std::string grid_instance_name = generate_grid_block_instance_name(grid_module_name_prefix, std::string(grid_type->name), is_io_type(grid_type), border_side, grid_coordinate); + + ModuleId grid_module = module_manager.find_module(grid_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(grid_module)); + + /* Print comments */ + fp << "#######################################" << std::endl; + fp << "# Disable Timing for grid[" << grid_coordinate.x() << "][" << grid_coordinate.y() << "]" << std::endl; + fp << "#######################################" << std::endl; + + /* For used grid, find the unused rr_node in the local rr_graph + * and then disable each port which is not used + * as well as the unused inputs of routing multiplexers! + */ + size_t grid_z = 0; + for (const ClusterBlockId& blk_id : place_annotation.grid_blocks(grid_coordinate)) { + if (ClusterBlockId::INVALID() != blk_id) { + const PhysicalPb& physical_pb = cluster_annotation.physical_pb(blk_id); + print_analysis_sdc_disable_pb_block_unused_resources(fp, grid_type, grid_coordinate, + device_annotation, + module_manager, grid_instance_name, grid_z, + physical_pb, false); + } else { + VTR_ASSERT(ClusterBlockId::INVALID() == blk_id); + /* For unused grid, disable all the pins in the physical_pb_type */ + print_analysis_sdc_disable_pb_block_unused_resources(fp, grid_type, grid_coordinate, + device_annotation, + module_manager, grid_instance_name, grid_z, + PhysicalPb(), true); + } + grid_z++; + } +} + +/******************************************************************** + * Top-level function writes SDC commands to disable unused ports + * of grids, such as Configurable Logic Block (CLBs), heterogeneous blocks, etc. + * + * This function will iterate over all the grids available in the FPGA fabric + * It will disable the timing analysis for + * 1. Grids, which are totally not used (no logic has been mapped to) + * 2. Unused part of grids, including the ports, inputs of routing multiplexers + * + * Note that it is a must to disable the unused inputs of routing multiplexers + * because it will cause unexpected paths in timing analysis + * For example: + * +---------------------+ + * inputA (net0) ------->| | + * | Routing multiplexer |----> output (net0) + * inputB (net1) ------->| | + * +---------------------+ + * + * During timing analysis, the path from inputA to output should be considered + * while the path from inputB to output should NOT be considered!!! + * + *******************************************************************/ +void print_analysis_sdc_disable_unused_grids(std::fstream& fp, + const DeviceGrid& grids, + const VprDeviceAnnotation& device_annotation, + const VprClusteringAnnotation& cluster_annotation, + const VprPlacementAnnotation& place_annotation, + const ModuleManager& module_manager) { + + /* Process unused core grids */ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + /* We should not meet any I/O grid */ + VTR_ASSERT(false == is_io_type(grids[ix][iy].type)); + + print_analysis_sdc_disable_unused_grid(fp, vtr::Point(ix, iy), + grids, device_annotation, cluster_annotation, place_annotation, + module_manager, NUM_SIDES); + } + } + + /* Instanciate I/O grids */ + /* Create the coordinate range for each side of FPGA fabric */ + std::vector io_sides{TOP, RIGHT, BOTTOM, LEFT}; + std::map>> io_coordinates; + + /* TOP side*/ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + io_coordinates[TOP].push_back(vtr::Point(ix, grids.height() - 1)); + } + + /* RIGHT side */ + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + io_coordinates[RIGHT].push_back(vtr::Point(grids.width() - 1, iy)); + } + + /* BOTTOM side*/ + for (size_t ix = 1; ix < grids.width() - 1; ++ix) { + io_coordinates[BOTTOM].push_back(vtr::Point(ix, 0)); + } + + /* LEFT side */ + for (size_t iy = 1; iy < grids.height() - 1; ++iy) { + io_coordinates[LEFT].push_back(vtr::Point(0, iy)); + } + + /* Add instances of I/O grids to top_module */ + for (const e_side& io_side : io_sides) { + for (const vtr::Point& io_coordinate : io_coordinates[io_side]) { + /* We should not meet any I/O grid */ + VTR_ASSERT(true == is_io_type(grids[io_coordinate.x()][io_coordinate.y()].type)); + + print_analysis_sdc_disable_unused_grid(fp, io_coordinate, + grids, device_annotation, cluster_annotation, place_annotation, + module_manager, io_side); + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.h b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.h new file mode 100644 index 000000000..be01bbf28 --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_grid_writer.h @@ -0,0 +1,31 @@ +#ifndef ANALYSIS_SDC_GRID_WRITER_H +#define ANALYSIS_SDC_GRID_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "device_grid.h" +#include "module_manager.h" +#include "vpr_device_annotation.h" +#include "vpr_clustering_annotation.h" +#include "vpr_placement_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_analysis_sdc_disable_unused_grids(std::fstream& fp, + const DeviceGrid& grids, + const VprDeviceAnnotation& device_annotation, + const VprClusteringAnnotation& cluster_annotation, + const VprPlacementAnnotation& place_annotation, + const ModuleManager& module_manager); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.cpp new file mode 100644 index 000000000..d210ecacd --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.cpp @@ -0,0 +1,540 @@ +/******************************************************************** + * This file includes functions that are used to output a SDC file + * that constrain routing modules of a FPGA fabric (P&Red netlist) + * using a benchmark + *******************************************************************/ +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" +#include "openfpga_side_manager.h" +#include "openfpga_port.h" + +#include "openfpga_reserved_words.h" +#include "openfpga_naming.h" + +#include "sdc_writer_utils.h" +#include "analysis_sdc_writer_utils.h" +#include "analysis_sdc_routing_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * This function will disable + * 1. all the unused port (unmapped by a benchmark) of a connection block + * 2. all the unused inputs (unmapped by a benchmark) of routing multiplexers + * in a connection block + *******************************************************************/ +static +void print_analysis_sdc_disable_cb_unused_resources(std::fstream& fp, + const AtomContext& atom_ctx, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const RRGSB& rr_gsb, + const t_rr_type& cb_type, + const bool& compact_routing_hierarchy) { + /* Validate file stream */ + valid_file_stream(fp); + + vtr::Point gsb_coordinate(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + + std::string cb_instance_name = generate_connection_block_module_name(cb_type, gsb_coordinate); + + /* If we use the compact routing hierarchy, we need to find the module name !*/ + vtr::Point cb_coordinate(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type)); + if (true == compact_routing_hierarchy) { + vtr::Point cb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + /* Note: use GSB coordinate when inquire for unique modules!!! */ + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(cb_type, cb_coord); + cb_coordinate.set_x(unique_mirror.get_cb_x(cb_type)); + cb_coordinate.set_y(unique_mirror.get_cb_y(cb_type)); + } + + std::string cb_module_name = generate_connection_block_module_name(cb_type, cb_coordinate); + + ModuleId cb_module = module_manager.find_module(cb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(cb_module)); + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Disable timing for Connection block " << cb_module_name << std::endl; + fp << "##################################################" << std::endl; + + /* Disable all the input port (routing tracks), which are not used by benchmark */ + for (size_t itrack = 0; itrack < rr_gsb.get_cb_chan_width(cb_type); ++itrack) { + const RRNodeId& chan_node = rr_gsb.get_chan_node(rr_gsb.get_cb_chan_side(cb_type), itrack); + /* Check if this node is used by benchmark */ + if (false == is_rr_node_to_be_disable_for_analysis(routing_annotation, chan_node)) { + continue; + } + + /* Disable both input of the routing track if it is not used! */ + std::string port_name = generate_cb_module_track_port_name(cb_type, + itrack, + IN_PORT); + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(cb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(cb_module, module_port)); + + fp << "set_disable_timing "; + fp << cb_instance_name << "/"; + fp << generate_sdc_port(module_manager.module_port(cb_module, module_port)); + fp << std::endl; + } + + /* Disable all the output port (routing tracks), which are not used by benchmark */ + for (size_t itrack = 0; itrack < rr_gsb.get_cb_chan_width(cb_type); ++itrack) { + const RRNodeId& chan_node = rr_gsb.get_chan_node(rr_gsb.get_cb_chan_side(cb_type), itrack); + /* Check if this node is used by benchmark */ + if (false == is_rr_node_to_be_disable_for_analysis(routing_annotation, chan_node)) { + continue; + } + + /* Disable both input of the routing track if it is not used! */ + std::string port_name = generate_cb_module_track_port_name(cb_type, + itrack, + OUT_PORT); + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(cb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(cb_module, module_port)); + + fp << "set_disable_timing "; + fp << cb_instance_name << "/"; + fp << generate_sdc_port(module_manager.module_port(cb_module, module_port)); + fp << std::endl; + } + + /* Build a map between mux_instance name and net_num */ + std::map mux_instance_to_net_map; + + /* Disable all the output port (grid input pins), which are not used by benchmark */ + std::vector cb_sides = rr_gsb.get_cb_ipin_sides(cb_type); + + for (size_t side = 0; side < cb_sides.size(); ++side) { + enum e_side cb_ipin_side = cb_sides[side]; + for (size_t inode = 0; inode < rr_gsb.get_num_ipin_nodes(cb_ipin_side); ++inode) { + RRNodeId ipin_node = rr_gsb.get_ipin_node(cb_ipin_side, inode); + + /* Find the MUX instance that drives the IPIN! */ + std::string mux_instance_name = generate_cb_mux_instance_name(CONNECTION_BLOCK_MUX_INSTANCE_PREFIX, rr_graph.node_side(ipin_node), inode, std::string("")); + mux_instance_to_net_map[mux_instance_name] = atom_ctx.lookup.atom_net(routing_annotation.rr_node_net(ipin_node)); + + if (false == is_rr_node_to_be_disable_for_analysis(routing_annotation, ipin_node)) { + continue; + } + + if (0 == std::distance(rr_graph.node_configurable_in_edges(ipin_node).begin(), rr_graph.node_configurable_in_edges(ipin_node).end())) { + continue; + } + + std::string port_name = generate_cb_module_grid_port_name(cb_ipin_side, + rr_graph.node_pin_num(ipin_node)); + + /* Find the port in unique mirror! */ + if (true == compact_routing_hierarchy) { + /* Note: use GSB coordinate when inquire for unique modules!!! */ + vtr::Point cb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_cb_unique_module(cb_type, cb_coord); + const RRNodeId& unique_mirror_ipin_node = unique_mirror.get_ipin_node(cb_ipin_side, inode); + port_name = generate_cb_module_grid_port_name(cb_ipin_side, + rr_graph.node_pin_num(unique_mirror_ipin_node)); + } + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(cb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(cb_module, module_port)); + + fp << "set_disable_timing "; + fp << cb_instance_name << "/"; + fp << generate_sdc_port(module_manager.module_port(cb_module, module_port)); + fp << std::endl; + } + } + + /* Disable all the unused inputs of routing multiplexers, which are not used by benchmark + * Here, we start from each input of the Connection Blocks, and traverse forward to the sink + * port of the module net whose source is the input + * We will find the instance name which is the parent of the sink port, and search the + * net id through the instance_name_to_net_map + * The the net id does not match the net id of this input, we will disable the sink port! + * + * cb_module + * +----------------------- + * | MUX instance A + * | +----------- + * input_port--->|--+---x-->| sink port (disable!) + * | | +---------- + * | | MUX instance B + * | | +---------- + * | +------>| sink port (do not disable!) + */ + for (size_t itrack = 0; itrack < rr_gsb.get_cb_chan_width(cb_type); ++itrack) { + const RRNodeId& chan_node = rr_gsb.get_chan_node(rr_gsb.get_cb_chan_side(cb_type), itrack); + + /* Disable both input of the routing track if it is not used! */ + std::string port_name = generate_cb_module_track_port_name(cb_type, + itrack, + OUT_PORT); + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(cb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(cb_module, module_port)); + + AtomNetId mapped_atom_net = atom_ctx.lookup.atom_net(routing_annotation.rr_node_net(chan_node)); + + disable_analysis_module_input_port_net_sinks(fp, + module_manager, cb_module, + cb_instance_name, + module_port, + mapped_atom_net, + mux_instance_to_net_map); + } +} + +/******************************************************************** + * Iterate over all the connection blocks in a device + * and disable unused ports for each of them + *******************************************************************/ +static +void print_analysis_sdc_disable_unused_cb_ports(std::fstream& fp, + const AtomContext& atom_ctx, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const t_rr_type& cb_type, + const bool& compact_routing_hierarchy) { + /* Build unique X-direction connection block modules */ + vtr::Point cb_range = device_rr_gsb.get_gsb_range(); + + for (size_t ix = 0; ix < cb_range.x(); ++ix) { + for (size_t iy = 0; iy < cb_range.y(); ++iy) { + /* Check if the connection block exists in the device! + * Some of them do NOT exist due to heterogeneous blocks (height > 1) + * We will skip those modules + */ + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + if (false == rr_gsb.is_cb_exist(cb_type)) { + continue; + } + + print_analysis_sdc_disable_cb_unused_resources(fp, + atom_ctx, + module_manager, + rr_graph, + routing_annotation, + device_rr_gsb, + rr_gsb, + cb_type, + compact_routing_hierarchy); + } + } +} + +/******************************************************************** + * Iterate over all the connection blocks in a device + * and disable unused ports for each of them + *******************************************************************/ +void print_analysis_sdc_disable_unused_cbs(std::fstream& fp, + const AtomContext& atom_ctx, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const bool& compact_routing_hierarchy) { + + print_analysis_sdc_disable_unused_cb_ports(fp, atom_ctx, + module_manager, + rr_graph, + routing_annotation, + device_rr_gsb, + CHANX, compact_routing_hierarchy); + + print_analysis_sdc_disable_unused_cb_ports(fp, atom_ctx, + module_manager, + rr_graph, + routing_annotation, + device_rr_gsb, + CHANY, compact_routing_hierarchy); +} + +/******************************************************************** + * This function will disable + * 1. all the unused port (unmapped by a benchmark) of a switch block + * 2. all the unused inputs (unmapped by a benchmark) of routing multiplexers + * in a switch block + *******************************************************************/ +static +void print_analysis_sdc_disable_sb_unused_resources(std::fstream& fp, + const AtomContext& atom_ctx, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const RRGSB& rr_gsb, + const bool& compact_routing_hierarchy) { + /* Validate file stream */ + valid_file_stream(fp); + + vtr::Point gsb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + + std::string sb_instance_name = generate_switch_block_module_name(gsb_coordinate); + + /* If we use the compact routing hierarchy, we need to find the module name !*/ + vtr::Point sb_coordinate(rr_gsb.get_sb_x(), rr_gsb.get_sb_y()); + if (true == compact_routing_hierarchy) { + vtr::Point sb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + /* Note: use GSB coordinate when inquire for unique modules!!! */ + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(sb_coord); + sb_coordinate.set_x(unique_mirror.get_sb_x()); + sb_coordinate.set_y(unique_mirror.get_sb_y()); + } + + std::string sb_module_name = generate_switch_block_module_name(sb_coordinate); + + ModuleId sb_module = module_manager.find_module(sb_module_name); + VTR_ASSERT(true == module_manager.valid_module_id(sb_module)); + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Disable timing for Switch block " << sb_module_name << std::endl; + fp << "##################################################" << std::endl; + + /* Build a map between mux_instance name and net_num */ + std::map mux_instance_to_net_map; + + /* Disable all the input/output port (routing tracks), which are not used by benchmark */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + + for (size_t itrack = 0; itrack < rr_gsb.get_chan_width(side_manager.get_side()); ++itrack) { + const RRNodeId& chan_node = rr_gsb.get_chan_node(side_manager.get_side(), itrack); + + std::string port_name = generate_sb_module_track_port_name(rr_graph.node_type(rr_gsb.get_chan_node(side_manager.get_side(), itrack)), + side_manager.get_side(), itrack, + rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)); + + if (true == compact_routing_hierarchy) { + /* Note: use GSB coordinate when inquire for unique modules!!! */ + vtr::Point sb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(sb_coord); + port_name = generate_sb_module_track_port_name(rr_graph.node_type(unique_mirror.get_chan_node(side_manager.get_side(), itrack)), + side_manager.get_side(), itrack, + unique_mirror.get_chan_node_direction(side_manager.get_side(), itrack)); + } + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(sb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sb_module, module_port)); + + /* Cache the net name for routing tracks which are outputs of the switch block */ + if (OUT_PORT == rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)) { + /* Generate the name of mux instance related to this output node */ + std::string mux_instance_name = generate_sb_memory_instance_name(SWITCH_BLOCK_MUX_INSTANCE_PREFIX, side_manager.get_side(), itrack, std::string("")); + mux_instance_to_net_map[mux_instance_name] = atom_ctx.lookup.atom_net(routing_annotation.rr_node_net(chan_node)); + } + + /* Check if this node is used by benchmark */ + if (false == is_rr_node_to_be_disable_for_analysis(routing_annotation, chan_node)) { + continue; + } + + fp << "set_disable_timing "; + fp << sb_instance_name << "/"; + fp << generate_sdc_port(module_manager.module_port(sb_module, module_port)); + fp << std::endl; + } + } + + /* Disable all the input port (grid output pins), which are not used by benchmark */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + + for (size_t inode = 0; inode < rr_gsb.get_num_opin_nodes(side_manager.get_side()); ++inode) { + const RRNodeId& opin_node = rr_gsb.get_opin_node(side_manager.get_side(), inode); + + std::string port_name = generate_sb_module_grid_port_name(side_manager.get_side(), + rr_graph.node_side(opin_node), + rr_graph.node_pin_num(opin_node)); + + if (true == compact_routing_hierarchy) { + /* Note: use GSB coordinate when inquire for unique modules!!! */ + vtr::Point sb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(sb_coord); + const RRNodeId& unique_mirror_opin_node = unique_mirror.get_opin_node(side_manager.get_side(), inode); + + port_name = generate_sb_module_grid_port_name(side_manager.get_side(), + rr_graph.node_side(unique_mirror_opin_node), + rr_graph.node_pin_num(unique_mirror_opin_node)); + } + + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(sb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sb_module, module_port)); + + /* Check if this node is used by benchmark */ + if (false == is_rr_node_to_be_disable_for_analysis(routing_annotation, opin_node)) { + continue; + } + + fp << "set_disable_timing "; + fp << sb_instance_name << "/"; + fp << generate_sdc_port(module_manager.module_port(sb_module, module_port)); + fp << std::endl; + } + } + + /* Disable all the unused inputs of routing multiplexers, which are not used by benchmark + * Here, we start from each input of the Switch Blocks, and traverse forward to the sink + * port of the module net whose source is the input + * We will find the instance name which is the parent of the sink port, and search the + * net id through the instance_name_to_net_map + * The the net id does not match the net id of this input, we will disable the sink port! + * + * sb_module + * +----------------------- + * | MUX instance A + * | +----------- + * input_port--->|--+---x-->| sink port (disable! net_id = Y) + * (net_id = X) | | +---------- + * | | MUX instance B + * | | +---------- + * | +------>| sink port (do not disable! net_id = X) + * + * Because the input ports of a SB module come from + * 1. Grid output pins + * 2. routing tracks + * We will walk through these ports and do conditionally disable_timing + */ + + /* Iterate over input ports coming from grid output pins */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + + for (size_t inode = 0; inode < rr_gsb.get_num_opin_nodes(side_manager.get_side()); ++inode) { + const RRNodeId& opin_node = rr_gsb.get_opin_node(side_manager.get_side(), inode); + + std::string port_name = generate_sb_module_grid_port_name(side_manager.get_side(), + rr_graph.node_side(opin_node), + rr_graph.node_pin_num(opin_node)); + + if (true == compact_routing_hierarchy) { + /* Note: use GSB coordinate when inquire for unique modules!!! */ + vtr::Point sb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(sb_coord); + const RRNodeId& unique_mirror_opin_node = unique_mirror.get_opin_node(side_manager.get_side(), inode); + + port_name = generate_sb_module_grid_port_name(side_manager.get_side(), + rr_graph.node_side(unique_mirror_opin_node), + rr_graph.node_pin_num(unique_mirror_opin_node)); + } + + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(sb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sb_module, module_port)); + + AtomNetId mapped_atom_net = atom_ctx.lookup.atom_net(routing_annotation.rr_node_net(opin_node)); + + disable_analysis_module_input_port_net_sinks(fp, module_manager, + sb_module, + sb_instance_name, + module_port, + mapped_atom_net, + mux_instance_to_net_map); + } + } + + /* Iterate over input ports coming from routing tracks */ + for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) { + SideManager side_manager(side); + + for (size_t itrack = 0; itrack < rr_gsb.get_chan_width(side_manager.get_side()); ++itrack) { + /* Skip output ports, they have already been disabled or not */ + if (OUT_PORT == rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)) { + continue; + } + + const RRNodeId& chan_node = rr_gsb.get_chan_node(side_manager.get_side(), itrack); + + std::string port_name = generate_sb_module_track_port_name(rr_graph.node_type(chan_node), + side_manager.get_side(), itrack, + rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)); + + if (true == compact_routing_hierarchy) { + /* Note: use GSB coordinate when inquire for unique modules!!! */ + vtr::Point sb_coord(rr_gsb.get_x(), rr_gsb.get_y()); + const RRGSB& unique_mirror = device_rr_gsb.get_sb_unique_module(sb_coord); + const RRNodeId& unique_mirror_chan_node = unique_mirror.get_chan_node(side_manager.get_side(), itrack); + + port_name = generate_sb_module_track_port_name(rr_graph.node_type(unique_mirror_chan_node), + side_manager.get_side(), itrack, + unique_mirror.get_chan_node_direction(side_manager.get_side(), itrack)); + } + + + /* Ensure we have this port in the module! */ + ModulePortId module_port = module_manager.find_module_port(sb_module, port_name); + VTR_ASSERT(true == module_manager.valid_module_port_id(sb_module, module_port)); + + AtomNetId mapped_atom_net = atom_ctx.lookup.atom_net(routing_annotation.rr_node_net(chan_node)); + + disable_analysis_module_input_port_net_sinks(fp, module_manager, + sb_module, + sb_instance_name, + module_port, + mapped_atom_net, + mux_instance_to_net_map); + } + } +} + + +/******************************************************************** + * Iterate over all the connection blocks in a device + * and disable unused ports for each of them + *******************************************************************/ +void print_analysis_sdc_disable_unused_sbs(std::fstream& fp, + const AtomContext& atom_ctx, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const bool& compact_routing_hierarchy) { + + /* Build unique X-direction connection block modules */ + vtr::Point sb_range = device_rr_gsb.get_gsb_range(); + + for (size_t ix = 0; ix < sb_range.x(); ++ix) { + for (size_t iy = 0; iy < sb_range.y(); ++iy) { + /* Check if the connection block exists in the device! + * Some of them do NOT exist due to heterogeneous blocks (height > 1) + * We will skip those modules + */ + const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy); + if (false == rr_gsb.is_sb_exist()) { + continue; + } + + print_analysis_sdc_disable_sb_unused_resources(fp, + atom_ctx, + module_manager, + rr_graph, + routing_annotation, + device_rr_gsb, + rr_gsb, + compact_routing_hierarchy); + } + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h new file mode 100644 index 000000000..e50bc73b6 --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_routing_writer.h @@ -0,0 +1,39 @@ +#ifndef ANALYSIS_SDC_ROUTING_WRITER_H +#define ANALYSIS_SDC_ROUTING_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "vpr_context.h" +#include "module_manager.h" +#include "device_rr_gsb.h" +#include "vpr_routing_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_analysis_sdc_disable_unused_cbs(std::fstream& fp, + const AtomContext& atom_ctx, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const bool& compact_routing_hierarchy); + +void print_analysis_sdc_disable_unused_sbs(std::fstream& fp, + const AtomContext& atom_ctx, + const ModuleManager& module_manager, + const RRGraph& rr_graph, + const VprRoutingAnnotation& routing_annotation, + const DeviceRRGSB& device_rr_gsb, + const bool& compact_routing_hierarchy); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp b/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp new file mode 100644 index 000000000..de9dfb55d --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer.cpp @@ -0,0 +1,284 @@ +/******************************************************************** + * This file includes functions that are used to output a SDC file + * that constrain a FPGA fabric (P&Red netlist) using a benchmark + *******************************************************************/ +#include +#include +#include + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" +#include "openfpga_port.h" + +#include "mux_utils.h" + +#include "openfpga_naming.h" +#include "openfpga_atom_netlist_utils.h" + +#include "sdc_writer_naming.h" +#include "sdc_writer_utils.h" +#include "sdc_memory_utils.h" + +#include "analysis_sdc_grid_writer.h" +#include "analysis_sdc_routing_writer.h" +#include "analysis_sdc_writer.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Generate SDC constaints for inputs and outputs + * We consider the top module in formal verification purpose here + * which is easier + *******************************************************************/ +static +void print_analysis_sdc_io_delays(std::fstream& fp, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const VprNetlistAnnotation& netlist_annotation, + const IoLocationMap& io_location_map, + const ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports, + const float& critical_path_delay) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Create clock " << std::endl; + fp << "##################################################" << std::endl; + + /* Get clock port from the global port */ + std::vector operating_clock_ports; + for (const CircuitPortId& clock_port : global_ports) { + if (CIRCUIT_MODEL_PORT_CLOCK != circuit_lib.port_type(clock_port)) { + continue; + } + /* We only constrain operating clock here! */ + if (true == circuit_lib.port_is_prog(clock_port)) { + continue; + } + + /* Find the module port and Update the operating port list */ + ModulePortId module_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(clock_port)); + operating_clock_ports.push_back(module_manager.module_port(top_module, module_port)); + } + + for (const BasicPort& operating_clock_port : operating_clock_ports) { + /* Reach here, it means a clock port and we need print constraints */ + fp << "create_clock "; + fp << generate_sdc_port(operating_clock_port); + fp << " -period " << std::setprecision(10) << critical_path_delay; + fp << " -waveform {0 " << std::setprecision(10) << critical_path_delay / 2 << "}"; + fp << std::endl; + + /* Add an empty line as a splitter */ + fp << std::endl; + } + + /* There should be only one operating clock! + * TODO: this should be changed when developing multi-clock support!!! + */ + VTR_ASSERT(1 == operating_clock_ports.size()); + + /* In this function, we support only 1 type of I/Os */ + VTR_ASSERT(1 == module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT).size()); + BasicPort module_io_port = module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GPIO_PORT)[0]; + + /* Keep tracking which I/Os have been used */ + std::vector io_used(module_io_port.get_width(), false); + + /* Find clock ports in benchmark */ + std::vector benchmark_clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist, netlist_annotation); + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Create input and output delays for used I/Os " << std::endl; + fp << "##################################################" << std::endl; + + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* Bypass non-I/O atom blocks ! */ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + + /* clock net or constant generator should be disabled in timing analysis */ + if (benchmark_clock_port_names.end() != std::find(benchmark_clock_port_names.begin(), benchmark_clock_port_names.end(), atom_ctx.nlist.block_name(atom_blk))) { + continue; + } + + /* Find the index of the mapped GPIO in top-level FPGA fabric */ + size_t io_index = io_location_map.io_index(place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.x, + place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.y, + place_ctx.block_locs[atom_ctx.lookup.atom_clb(atom_blk)].loc.z); + + /* Ensure that IO index is in range */ + BasicPort module_mapped_io_port = module_io_port; + /* Set the port pin index */ + VTR_ASSERT(io_index < module_mapped_io_port.get_width()); + module_mapped_io_port.set_width(io_index, io_index); + + /* For input I/O, we set an input delay constraint correlated to the operating clock + * For output I/O, we set an output delay constraint correlated to the operating clock + */ + if (AtomBlockType::INPAD == atom_ctx.nlist.block_type(atom_blk)) { + print_sdc_set_port_input_delay(fp, module_mapped_io_port, + operating_clock_ports[0], critical_path_delay); + } else { + VTR_ASSERT(AtomBlockType::OUTPAD == atom_ctx.nlist.block_type(atom_blk)); + print_sdc_set_port_output_delay(fp, module_mapped_io_port, + operating_clock_ports[0], critical_path_delay); + } + + /* Mark this I/O has been used/wired */ + io_used[io_index] = true; + } + + /* Add an empty line as a splitter */ + fp << std::endl; + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Disable timing for unused I/Os " << std::endl; + fp << "##################################################" << std::endl; + + /* Wire the unused iopads to a constant */ + for (size_t io_index = 0; io_index < io_used.size(); ++io_index) { + /* Bypass used iopads */ + if (true == io_used[io_index]) { + continue; + } + + /* Wire to a contant */ + BasicPort module_unused_io_port = module_io_port; + /* Set the port pin index */ + module_unused_io_port.set_width(io_index, io_index); + print_sdc_disable_port_timing(fp, module_unused_io_port); + } + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Disable the timing for all the global port except the operating clock ports + *******************************************************************/ +static +void print_analysis_sdc_disable_global_ports(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const std::vector& global_ports) { + /* Validate file stream */ + valid_file_stream(fp); + + /* Print comments */ + fp << "##################################################" << std::endl; + fp << "# Disable timing for global ports " << std::endl; + fp << "##################################################" << std::endl; + + for (const CircuitPortId& global_port : global_ports) { + /* Skip operating clock here! */ + if ( (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(global_port)) + && (false == circuit_lib.port_is_prog(global_port)) ) { + continue; + } + + ModulePortId module_port = module_manager.find_module_port(top_module, circuit_lib.port_prefix(global_port)); + BasicPort port_to_disable = module_manager.module_port(top_module, module_port); + + print_sdc_disable_port_timing(fp, port_to_disable); + } +} + +/******************************************************************** + * Top-level function outputs a SDC file + * that constrain a FPGA fabric (P&Red netlist) using a benchmark + *******************************************************************/ +void print_analysis_sdc(const AnalysisSdcOption& option, + const float& critical_path_delay, + const VprContext& vpr_ctx, + const OpenfpgaContext& openfpga_ctx, + const std::vector& global_ports, + const bool& compact_routing_hierarchy) { + /* Create the file name for Verilog netlist */ + std::string sdc_fname(option.sdc_dir() + std::string(SDC_ANALYSIS_FILE_NAME)); + + std::string timer_message = std::string("Generating SDC for Timing/Power analysis on the mapped FPGA '") + + sdc_fname + + std::string("'"); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(sdc_fname, std::fstream::out | std::fstream::trunc); + + /* Validate file stream */ + check_file_stream(sdc_fname.c_str(), fp); + + /* Generate the descriptions*/ + print_sdc_file_header(fp, std::string("Constrain for Timing/Power analysis on the mapped FPGA")); + + /* Find the top_module */ + ModuleId top_module = openfpga_ctx.module_graph().find_module(generate_fpga_top_module_name()); + VTR_ASSERT(true == openfpga_ctx.module_graph().valid_module_id(top_module)); + + /* Create clock and set I/O ports with input/output delays */ + print_analysis_sdc_io_delays(fp, + vpr_ctx.atom(), vpr_ctx.placement(), + openfpga_ctx.vpr_netlist_annotation(), openfpga_ctx.io_location_map(), + openfpga_ctx.module_graph(), top_module, + openfpga_ctx.arch().circuit_lib, global_ports, + critical_path_delay); + + /* Disable the timing for global ports */ + print_analysis_sdc_disable_global_ports(fp, + openfpga_ctx.module_graph(), top_module, + openfpga_ctx.arch().circuit_lib, global_ports); + + /* Disable the timing for configuration cells */ + rec_print_pnr_sdc_disable_configurable_memory_module_output(fp, + openfpga_ctx.module_graph(), top_module, + format_dir_path(openfpga_ctx.module_graph().module_name(top_module))); + + + /* Disable timing for unused routing resources in connection blocks */ + print_analysis_sdc_disable_unused_cbs(fp, + vpr_ctx.atom(), + openfpga_ctx.module_graph(), + vpr_ctx.device().rr_graph, + openfpga_ctx.vpr_routing_annotation(), + openfpga_ctx.device_rr_gsb(), + compact_routing_hierarchy); + + /* Disable timing for unused routing resources in switch blocks */ + print_analysis_sdc_disable_unused_sbs(fp, + vpr_ctx.atom(), + openfpga_ctx.module_graph(), + vpr_ctx.device().rr_graph, + openfpga_ctx.vpr_routing_annotation(), + openfpga_ctx.device_rr_gsb(), + compact_routing_hierarchy); + + /* Disable timing for unused routing resources in grids (programmable blocks) */ + print_analysis_sdc_disable_unused_grids(fp, + vpr_ctx.device().grid, + openfpga_ctx.vpr_device_annotation(), + openfpga_ctx.vpr_clustering_annotation(), + openfpga_ctx.vpr_placement_annotation(), + openfpga_ctx.module_graph()); + + /* Close file handler */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer.h b/openfpga/src/fpga_sdc/analysis_sdc_writer.h new file mode 100644 index 000000000..c3d08794a --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer.h @@ -0,0 +1,29 @@ +#ifndef ANALYSIS_SDC_WRITER_H +#define ANALYSIS_SDC_WRITER_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include "vpr_context.h" +#include "openfpga_context.h" +#include "analysis_sdc_option.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_analysis_sdc(const AnalysisSdcOption& option, + const float& critical_path_delay, + const VprContext& vpr_ctx, + const OpenfpgaContext& openfpga_ctx, + const std::vector& global_ports, + const bool& compact_routing_hierarchy); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp new file mode 100644 index 000000000..f761cafea --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.cpp @@ -0,0 +1,237 @@ +/******************************************************************** + * This file includes most utilized functions + * that are used to output a SDC file + * in order to constrain a FPGA fabric (P&Red netlist) mapped to a benchmark + *******************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_assert.h" + +/* Headers from openfpgautil library */ +#include "openfpga_digest.h" + +#include "sdc_writer_utils.h" +#include "analysis_sdc_writer_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Identify if a node should be disabled during analysis SDC generation + *******************************************************************/ +bool is_rr_node_to_be_disable_for_analysis(const VprRoutingAnnotation& routing_annotation, + const RRNodeId& cur_rr_node) { + /* Conditions to enable timing analysis for a node + * 1st condition: it have a valid net_number + * TODO: 2nd condition: it is not an parasitic net + */ + return ClusterNetId::INVALID() == routing_annotation.rr_node_net(cur_rr_node); +} + +/******************************************************************** + * Disable all the unused inputs of routing multiplexers, which are not used by benchmark + * Here, we start from each input of a routing module, and traverse forward to the sink + * port of the module net whose source is the input + * We will find the instance name which is the parent of the sink port, and search the + * net id through the instance_name_to_net_map + * The the net id does not match the net id of this input, we will disable the sink port! + * + * parent_module + * +----------------------- + * | MUX instance A + * | +----------- + * input_port--->|--+---x-->| sink port (disable! net_id = Y) + * (net_id = X) | | +---------- + * | | MUX instance B + * | | +---------- + * | +------>| sink port (do not disable! net_id = X) + * + *******************************************************************/ +void disable_analysis_module_input_pin_net_sinks(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_instance_name, + const ModulePortId& module_input_port, + const size_t& module_input_pin, + const AtomNetId& mapped_net, + const std::map mux_instance_to_net_map) { + /* Validate file stream */ + valid_file_stream(fp); + + /* Find the module net which sources from this port! */ + ModuleNetId module_net = module_manager.module_instance_port_net(parent_module, parent_module, 0, module_input_port, module_input_pin); + VTR_ASSERT(true == module_manager.valid_module_net_id(parent_module, module_net)); + + /* Touch each sink of the net! */ + for (const ModuleNetSinkId& sink_id : module_manager.module_net_sinks(parent_module, module_net)) { + ModuleId sink_module = module_manager.net_sink_modules(parent_module, module_net)[sink_id]; + size_t sink_instance = module_manager.net_sink_instances(parent_module, module_net)[sink_id]; + + /* Skip when sink module is the parent module, + * the output ports of parent modules have been disabled/enabled already! + */ + if (sink_module == parent_module) { + continue; + } + + std::string sink_instance_name = module_manager.instance_name(parent_module, sink_module, sink_instance); + bool disable_timing = false; + /* Check if this node is used by benchmark */ + if (AtomNetId::INVALID() == mapped_net) { + /* Disable all the sinks! */ + disable_timing = true; + } else { + std::map::const_iterator it = mux_instance_to_net_map.find(sink_instance_name); + if (it != mux_instance_to_net_map.end()) { + /* See if the net id matches. If does not match, we should disable! */ + if (mapped_net != mux_instance_to_net_map.at(sink_instance_name)) { + disable_timing = true; + } + } + } + + /* Time to write SDC command to disable timing or not */ + if (false == disable_timing) { + continue; + } + + BasicPort sink_port = module_manager.module_port(sink_module, module_manager.net_sink_ports(parent_module, module_net)[sink_id]); + sink_port.set_width(module_manager.net_sink_pins(parent_module, module_net)[sink_id], + module_manager.net_sink_pins(parent_module, module_net)[sink_id]); + + VTR_ASSERT(!sink_instance_name.empty()); + /* Get the input id that is used! Disable the unused inputs! */ + fp << "set_disable_timing "; + fp << parent_instance_name; + fp << sink_instance_name << "/"; + fp << generate_sdc_port(sink_port); + fp << std::endl; + } +} + +/******************************************************************** + * Disable all the unused inputs of routing multiplexers, which are not used by benchmark + * Here, we start from each input of a routing module, and traverse forward to the sink + * port of the module net whose source is the input + * We will find the instance name which is the parent of the sink port, and search the + * net id through the instance_name_to_net_map + * The the net id does not match the net id of this input, we will disable the sink port! + * + * parent_module + * +----------------------- + * | MUX instance A + * | +----------- + * input_port--->|--+---x-->| sink port (disable! net_id = Y) + * (net_id = X) | | +---------- + * | | MUX instance B + * | | +---------- + * | +------>| sink port (do not disable! net_id = X) + * + *******************************************************************/ +void disable_analysis_module_input_port_net_sinks(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_instance_name, + const ModulePortId& module_input_port, + const AtomNetId& mapped_net, + const std::map mux_instance_to_net_map) { + /* Validate file stream */ + valid_file_stream(fp); + + /* Find the module net which sources from this port! */ + for (const size_t& pin : module_manager.module_port(parent_module, module_input_port).pins()) { + disable_analysis_module_input_pin_net_sinks(fp, module_manager, parent_module, + parent_instance_name, + module_input_port, pin, + mapped_net, + mux_instance_to_net_map); + } +} + +/******************************************************************** + * Disable all the unused inputs of routing multiplexers, which are not used by benchmark + * Here, we start from each output of a child module, and traverse forward to the sink + * port of the module net whose source is the input + * We will find the instance name which is the parent of the sink port, and search the + * net id through the instance_name_to_net_map + * The the net id does not match the net id of this input, we will disable the sink port! + * + * Parent_module + * +--------------------------------------------- + * | + * | +--------------------------------------------+ + * | | MUX child_module | + * | | +-------------+ +-----------+ | + * | +--->| Routing |------>| | | + * input_pin0(netA) --->|----x--->| Multiplexer | netA | output_pin|-----+ + * | +-------------+ | | netA + * | | | + * + + * + *******************************************************************/ +void disable_analysis_module_output_pin_net_sinks(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_instance_name, + const ModuleId& child_module, + const size_t& child_instance, + const ModulePortId& child_module_port, + const size_t& child_module_pin, + const AtomNetId& mapped_net, + const std::map mux_instance_to_net_map) { + /* Validate file stream */ + valid_file_stream(fp); + + /* Find the module net which sources from this port! */ + ModuleNetId module_net = module_manager.module_instance_port_net(parent_module, child_module, child_instance, child_module_port, child_module_pin); + VTR_ASSERT(true == module_manager.valid_module_net_id(parent_module, module_net)); + + /* Touch each sink of the net! */ + for (const ModuleNetSinkId& sink_id : module_manager.module_net_sinks(parent_module, module_net)) { + ModuleId sink_module = module_manager.net_sink_modules(parent_module, module_net)[sink_id]; + size_t sink_instance = module_manager.net_sink_instances(parent_module, module_net)[sink_id]; + + /* Skip when sink module is the parent module, + * the output ports of parent modules have been disabled/enabled already! + */ + if (sink_module == parent_module) { + continue; + } + + std::string sink_instance_name = module_manager.instance_name(parent_module, sink_module, sink_instance); + bool disable_timing = false; + /* Check if this node is used by benchmark */ + if (AtomNetId::INVALID() == mapped_net) { + /* Disable all the sinks! */ + disable_timing = true; + } else { + std::map::const_iterator it = mux_instance_to_net_map.find(sink_instance_name); + if (it != mux_instance_to_net_map.end()) { + /* See if the net id matches. If does not match, we should disable! */ + if (mapped_net != mux_instance_to_net_map.at(sink_instance_name)) { + disable_timing = true; + } + } + } + + /* Time to write SDC command to disable timing or not */ + if (false == disable_timing) { + continue; + } + + BasicPort sink_port = module_manager.module_port(sink_module, module_manager.net_sink_ports(parent_module, module_net)[sink_id]); + sink_port.set_width(module_manager.net_sink_pins(parent_module, module_net)[sink_id], + module_manager.net_sink_pins(parent_module, module_net)[sink_id]); + + VTR_ASSERT(!sink_instance_name.empty()); + /* Get the input id that is used! Disable the unused inputs! */ + fp << "set_disable_timing "; + fp << parent_instance_name; + fp << sink_instance_name << "/"; + fp << generate_sdc_port(sink_port); + fp << std::endl; + } +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.h b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.h new file mode 100644 index 000000000..3e5582f94 --- /dev/null +++ b/openfpga/src/fpga_sdc/analysis_sdc_writer_utils.h @@ -0,0 +1,55 @@ +#ifndef ANALYSIS_SDC_WRITER_UTILS_H +#define ANALYSIS_SDC_WRITER_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include +#include +#include +#include "module_manager.h" +#include "rr_graph_obj.h" +#include "atom_netlist_fwd.h" +#include "vpr_routing_annotation.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +bool is_rr_node_to_be_disable_for_analysis(const VprRoutingAnnotation& routing_annotation, + const RRNodeId& cur_rr_node); + +void disable_analysis_module_input_pin_net_sinks(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_instance_name, + const ModulePortId& module_input_port, + const size_t& module_input_pin, + const AtomNetId& mapped_net, + const std::map mux_instance_to_net_map); + +void disable_analysis_module_input_port_net_sinks(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_instance_name, + const ModulePortId& module_input_port, + const AtomNetId& mapped_net, + const std::map mux_instance_to_net_map); + +void disable_analysis_module_output_pin_net_sinks(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& parent_module, + const std::string& parent_instance_name, + const ModuleId& child_module, + const size_t& child_instance, + const ModulePortId& child_module_port, + const size_t& child_module_pin, + const AtomNetId& mapped_net, + const std::map mux_instance_to_net_map); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/fpga_verilog/verilog_module_writer.cpp b/openfpga/src/fpga_verilog/verilog_module_writer.cpp index 15a5bc28d..59f863701 100644 --- a/openfpga/src/fpga_verilog/verilog_module_writer.cpp +++ b/openfpga/src/fpga_verilog/verilog_module_writer.cpp @@ -16,6 +16,8 @@ #include "openfpga_port.h" #include "openfpga_digest.h" +#include "openfpga_naming.h" + #include "module_manager_utils.h" #include "verilog_port_types.h" #include "verilog_writer_utils.h" @@ -371,7 +373,7 @@ void write_verilog_instance_to_file(std::fstream& fp, * if not, we use a default name _ */ if (true == module_manager.instance_name(parent_module, child_module, instance_id).empty()) { - fp << module_manager.module_name(child_module) << "_" << instance_id << "_" << " (" << std::endl; + fp << generate_instance_name(module_manager.module_name(child_module), instance_id) << " (" << std::endl; } else { fp << module_manager.instance_name(parent_module, child_module, instance_id) << " (" << std::endl; } diff --git a/openfpga/test_script/and_k6_frac.openfpga b/openfpga/test_script/and_k6_frac.openfpga index 1018887f1..4133dcea5 100644 --- a/openfpga/test_script/and_k6_frac.openfpga +++ b/openfpga/test_script/and_k6_frac.openfpga @@ -52,5 +52,8 @@ write_verilog_testbench --file /var/tmp/xtang/openfpga_test_src/SRC --reference_ # - Turn on every options here write_pnr_sdc --file /var/tmp/xtang/openfpga_test_src/SDC +# Write the SDC to run timing analysis for a mapped FPGA fabric +write_analysis_sdc --file /var/tmp/xtang/openfpga_test_src/SDC_analysis + # Finish and exit OpenFPGA exit