diff --git a/doc/src/vpr/command_line_usage.rst b/doc/src/vpr/command_line_usage.rst index 6342f89fbea..1f9f167fdea 100644 --- a/doc/src/vpr/command_line_usage.rst +++ b/doc/src/vpr/command_line_usage.rst @@ -1254,6 +1254,14 @@ Analysis Options **Default:** ``off`` +.. option:: --gen_post_implementation_merged_netlist { on | off } + + This option is based on ``--gen_post_synthesis_netlist``. + The difference is that ``--gen_post_implementation_merged_netlist`` generates a single verilog file with merged top module multi-bit ports of the implemented circuit. + The name of the file is ``_merged_post_implementation.v`` + + **Default:** ``off`` + .. option:: --post_synth_netlist_unconn_inputs { unconnected | nets | gnd | vcc } Controls how unconnected input cell ports are handled in the post-synthesis netlist diff --git a/vpr/src/base/SetupVPR.cpp b/vpr/src/base/SetupVPR.cpp index 5842fa7185e..f75745facae 100644 --- a/vpr/src/base/SetupVPR.cpp +++ b/vpr/src/base/SetupVPR.cpp @@ -623,6 +623,7 @@ static void SetupAnalysisOpts(const t_options& Options, t_analysis_opts& analysi } analysis_opts.gen_post_synthesis_netlist = Options.Generate_Post_Synthesis_Netlist; + analysis_opts.gen_post_implementation_merged_netlist = Options.Generate_Post_Implementation_Merged_Netlist; analysis_opts.timing_report_npaths = Options.timing_report_npaths; analysis_opts.timing_report_detail = Options.timing_report_detail; diff --git a/vpr/src/base/netlist_writer.cpp b/vpr/src/base/netlist_writer.cpp index 7eaba5d195a..041d5219e61 100644 --- a/vpr/src/base/netlist_writer.cpp +++ b/vpr/src/base/netlist_writer.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "vtr_assert.h" #include "vtr_util.h" @@ -695,12 +696,12 @@ class BlackBoxInst : public Instance { os << indent(depth + 3) << "(IOPATH "; os << escape_sdf_identifier(arc.source_name()); if (find_port_size(arc.source_name()) > 1) { - os << "[" << arc.source_ipin() << "]"; + os << "\\[" << arc.source_ipin() << "\\]"; } os << " "; os << escape_sdf_identifier(arc.sink_name()); if (find_port_size(arc.sink_name()) > 1) { - os << "[" << arc.sink_ipin() << "]"; + os << "\\[" << arc.sink_ipin() << "\\]"; } os << " "; os << delay_triple.str(); @@ -791,6 +792,11 @@ class Assignment { void print_verilog(std::ostream& os, std::string indent) { os << indent << "assign " << escape_verilog_identifier(lval_) << " = " << escape_verilog_identifier(rval_) << ";\n"; } + + void print_merged_verilog(std::ostream& os, std::string indent) { + os << indent << "assign " << lval_ << " = " << rval_ << ";\n"; + } + void print_blif(std::ostream& os, std::string indent) { os << indent << ".names " << rval_ << " " << lval_ << "\n"; os << indent << "1 1\n"; @@ -886,12 +892,8 @@ class NetlistWriterVisitor : public NetlistVisitor { print_sdf(); } - private: //Internal Helper functions - ///@brief Writes out the verilog netlist - void print_verilog(int depth = 0) { - verilog_os_ << indent(depth) << "//Verilog generated by VPR " << vtr::VERSION << " from post-place-and-route implementation\n"; - verilog_os_ << indent(depth) << "module " << top_module_name_ << " (\n"; - + protected: + virtual void print_primary_io(int depth) { //Primary Inputs for (auto iter = inputs_.begin(); iter != inputs_.end(); ++iter) { verilog_os_ << indent(depth + 1) << "input " << escape_verilog_identifier(*iter); @@ -900,7 +902,6 @@ class NetlistWriterVisitor : public NetlistVisitor { } verilog_os_ << "\n"; } - //Primary Outputs for (auto iter = outputs_.begin(); iter != outputs_.end(); ++iter) { verilog_os_ << indent(depth + 1) << "output " << escape_verilog_identifier(*iter); @@ -909,6 +910,22 @@ class NetlistWriterVisitor : public NetlistVisitor { } verilog_os_ << "\n"; } + } + + virtual void print_assignments(int depth) { + verilog_os_ << "\n"; + verilog_os_ << indent(depth + 1) << "//IO assignments\n"; + for (auto& assign : assignments_) { + assign.print_verilog(verilog_os_, indent(depth + 1)); + } + } + + ///@brief Writes out the verilog netlist + void print_verilog(int depth = 0) { + verilog_os_ << indent(depth) << "//Verilog generated by VPR " << vtr::VERSION << " from post-place-and-route implementation\n"; + verilog_os_ << indent(depth) << "module " << top_module_name_ << " (\n"; + + print_primary_io(depth); verilog_os_ << indent(depth) << ");\n"; //Wire declarations @@ -924,11 +941,7 @@ class NetlistWriterVisitor : public NetlistVisitor { } //connections between primary I/Os and their internal wires - verilog_os_ << "\n"; - verilog_os_ << indent(depth + 1) << "//IO assignments\n"; - for (auto& assign : assignments_) { - assign.print_verilog(verilog_os_, indent(depth + 1)); - } + print_assignments(depth); //Interconnect between cell instances verilog_os_ << "\n"; @@ -970,6 +983,7 @@ class NetlistWriterVisitor : public NetlistVisitor { verilog_os_ << indent(depth) << "endmodule\n"; } + private: //Internal Helper functions ///@brief Writes out the blif netlist void print_blif(int depth = 0) { blif_os_ << indent(depth) << "#BLIF generated by VPR " << vtr::VERSION << " from post-place-and-route implementation\n"; @@ -1074,47 +1088,6 @@ class NetlistWriterVisitor : public NetlistVisitor { sdf_os_ << indent(depth) << ")\n"; } - /** - * @brief Returns the name of a wire connecting a primitive and global net. - * - * The wire is recorded and instantiated by the top level output routines. - */ - std::string make_inst_wire(AtomNetId atom_net_id, /// make_lut_instance(const t_pb* atom) { //Determine what size LUT @@ -1785,18 +1800,6 @@ class NetlistWriterVisitor : public NetlistVisitor { return top_pb->pb_route; } - ///@brief Returns the top complex block which contains the given pb - const t_pb* find_top_cb(const t_pb* curr) { - //Walk up through the pb graph until curr - //has no parent, at which point it will be the top pb - const t_pb* parent = curr->parent_pb; - while (parent != nullptr) { - curr = parent; - parent = curr->parent_pb; - } - return curr; - } - ///@brief Returns the tnode ID of the given atom's connected cluster pin tatum::NodeId find_tnode(const t_pb* atom, int cluster_pin_idx) { auto& atom_ctx = g_vpr_ctx.atom(); @@ -1814,6 +1817,19 @@ class NetlistWriterVisitor : public NetlistVisitor { return tnode_id; } + private: + ///@brief Returns the top complex block which contains the given pb + const t_pb* find_top_cb(const t_pb* curr) { + //Walk up through the pb graph until curr + //has no parent, at which point it will be the top pb + const t_pb* parent = curr->parent_pb; + while (parent != nullptr) { + curr = parent; + parent = curr->parent_pb; + } + return curr; + } + ///@brief Returns a LogicVec representing the LUT mask of the given LUT atom LogicVec load_lut_mask(size_t num_inputs, //LUT size const t_pb* atom) { //LUT primitive @@ -2071,13 +2087,15 @@ class NetlistWriterVisitor : public NetlistVisitor { return ::get_delay_ps(delay_sec); //Class overload hides file-scope by default } - private: //Data - std::string top_module_name_; /// inputs_; /// outputs_; /// assignments_; ///> cell_instances_; ///> logical_net_drivers_; @@ -2088,7 +2106,10 @@ class NetlistWriterVisitor : public NetlistVisitor { std::map logical_net_sink_delays_; //Output streams + protected: std::ostream& verilog_os_; + + private: std::ostream& blif_os_; std::ostream& sdf_os_; @@ -2099,11 +2120,192 @@ class NetlistWriterVisitor : public NetlistVisitor { struct t_analysis_opts opts_; }; +/** + * @brief A class which writes post-implementation merged netlists (Verilog) + * + * It implements the NetlistVisitor interface used by NetlistWalker (see netlist_walker.h) + */ +class MergedNetlistWriterVisitor : public NetlistWriterVisitor { + public: //Public interface + MergedNetlistWriterVisitor(std::ostream& verilog_os, /// delay_calc, + struct t_analysis_opts opts) + : NetlistWriterVisitor(verilog_os, blif_os, sdf_os, delay_calc, opts) {} + + std::map portmap; + + void visit_atom_impl(const t_pb* atom) override { + auto& atom_ctx = g_vpr_ctx.atom(); + + auto atom_pb = atom_ctx.lookup.pb_atom(atom); + if (atom_pb == AtomBlockId::INVALID()) { + return; + } + const t_model* model = atom_ctx.nlist.block_model(atom_pb); + + if (model->name == std::string(MODEL_INPUT)) { + auto merged_io_name = make_io(atom, PortType::INPUT); + if (merged_io_name != "") + inputs_.emplace_back(merged_io_name); + } else if (model->name == std::string(MODEL_OUTPUT)) { + auto merged_io_name = make_io(atom, PortType::OUTPUT); + if (merged_io_name != "") + outputs_.emplace_back(merged_io_name); + } else if (model->name == std::string(MODEL_NAMES)) { + cell_instances_.push_back(make_lut_instance(atom)); + } else if (model->name == std::string(MODEL_LATCH)) { + cell_instances_.push_back(make_latch_instance(atom)); + } else if (model->name == std::string("single_port_ram")) { + cell_instances_.push_back(make_ram_instance(atom)); + } else if (model->name == std::string("dual_port_ram")) { + cell_instances_.push_back(make_ram_instance(atom)); + } else if (model->name == std::string("multiply")) { + cell_instances_.push_back(make_multiply_instance(atom)); + } else if (model->name == std::string("adder")) { + cell_instances_.push_back(make_adder_instance(atom)); + } else { + cell_instances_.push_back(make_blackbox_instance(atom)); + } + } + + /** + * @brief Returns the name of circuit-level Input/Output ports with multi-bit + * ports merged into one. + * + * The I/O is recorded and instantiated by the top level output routines + * @param atom The implementation primitive representing the I/O + * @param dir The IO direction + * @param portmap Map for keeping port names and width + */ + std::string make_io(const t_pb* atom, + PortType dir) { + const t_pb_graph_node* pb_graph_node = atom->pb_graph_node; + + std::string io_name; + std::string indexed_io_name; + int cluster_pin_idx = -1; + // regex for matching 3 groups: + // * 'out:' - optional + // * verilog identifier - mandatory + // * index - optional + std::string rgx = "(out:)?([a-zA-Z$_]+[a-zA-Z0-9$_]*)(\\[[0-9]+\\])?$"; + std::string name(atom->name); + std::regex regex(rgx); + std::smatch matches; + + if (dir == PortType::INPUT) { + VTR_ASSERT(pb_graph_node->num_output_ports == 1); //One output port + VTR_ASSERT(pb_graph_node->num_output_pins[0] == 1); //One output pin + cluster_pin_idx = pb_graph_node->output_pins[0][0].pin_count_in_cluster; //Unique pin index in cluster + + io_name = ""; + indexed_io_name = atom->name; + + if (std::regex_match(name, matches, regex)) { + if (std::find(inputs_.begin(), inputs_.end(), matches[2]) == inputs_.end()) { //Skip already existing multi-bit port names + io_name = matches[2]; + portmap[matches[2]] = 0; + } else { + portmap[matches[2]]++; + } + } + + } else { + VTR_ASSERT(pb_graph_node->num_input_ports == 1); //One input port + VTR_ASSERT(pb_graph_node->num_input_pins[0] == 1); //One input pin + cluster_pin_idx = pb_graph_node->input_pins[0][0].pin_count_in_cluster; //Unique pin index in cluster + + //Strip off the starting 'out:' that vpr adds to uniqify outputs + //this makes the port names match the input blif file + + io_name = ""; + indexed_io_name = atom->name + 4; + + if (std::regex_search(name, matches, regex)) { + if (std::find(outputs_.begin(), outputs_.end(), matches[2]) == outputs_.end()) { //Skip already existing multi-bit port names + portmap[matches[2]] = 0; + io_name = matches[2]; + } else { + portmap[matches[2]]++; + } + } + } + + const auto& top_pb_route = find_top_pb_route(atom); + + if (top_pb_route.count(cluster_pin_idx)) { + //Net exists + auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id; //Connected net in atom netlist + + //Port direction is inverted (inputs drive internal nets, outputs sink internal nets) + PortType wire_dir = (dir == PortType::INPUT) ? PortType::OUTPUT : PortType::INPUT; + + //Look up the tnode associated with this pin (used for delay calculation) + tatum::NodeId tnode_id = find_tnode(atom, cluster_pin_idx); + + auto wire_name = make_inst_wire(atom_net_id, tnode_id, indexed_io_name, wire_dir, 0, 0); + + //Connect the wires to to I/Os with assign statements + if (wire_dir == PortType::INPUT) { + assignments_.emplace_back(indexed_io_name, escape_verilog_identifier(wire_name)); + } else { + assignments_.emplace_back(escape_verilog_identifier(wire_name), indexed_io_name); + } + } + + return io_name; + } + + void print_primary_io(int depth) { + //Primary Inputs + for (auto iter = inputs_.begin(); iter != inputs_.end(); ++iter) { + //verilog_os_ << indent(depth + 1) << "input " << escape_verilog_identifier(*iter); + std::string range; + if (portmap[*iter] > 0) + verilog_os_ << indent(depth + 1) << "input [" << portmap[*iter] << ":0] " << *iter; + else + verilog_os_ << indent(depth + 1) << "input " << *iter; + if (iter + 1 != inputs_.end() || outputs_.size() > 0) { + verilog_os_ << ","; + } + verilog_os_ << "\n"; + } + + //Primary Outputs + for (auto iter = outputs_.begin(); iter != outputs_.end(); ++iter) { + std::string range; + if (portmap[*iter] > 0) + verilog_os_ << indent(depth + 1) << "output [" << portmap[*iter] << ":0] " << *iter; + else + verilog_os_ << indent(depth + 1) << "output " << *iter; + if (iter + 1 != outputs_.end()) { + verilog_os_ << ","; + } + verilog_os_ << "\n"; + } + } + + void print_assignments(int depth) { + verilog_os_ << "\n"; + verilog_os_ << indent(depth + 1) << "//IO assignments\n"; + for (auto& assign : assignments_) { + assign.print_merged_verilog(verilog_os_, indent(depth + 1)); + } + } + + void finish_impl() override { + // Don't write to blif and sdf streams + print_verilog(); + } +}; + // // Externally Accessible Functions // -///@brief Main routing for this file. See netlist_writer.h for details. +///@brief Main routine for this file. See netlist_writer.h for details. void netlist_writer(const std::string basename, std::shared_ptr delay_calc, struct t_analysis_opts opts) { std::string verilog_filename = basename + "_post_synthesis.v"; std::string blif_filename = basename + "_post_synthesis.blif"; @@ -2124,6 +2326,23 @@ void netlist_writer(const std::string basename, std::shared_ptr delay_calc, struct t_analysis_opts opts) { + std::string verilog_filename = basename + "_merged_post_implementation.v"; + + VTR_LOG("Writing Implementation Netlist: %s\n", verilog_filename.c_str()); + + std::ofstream verilog_os(verilog_filename); + // Don't write blif and sdf, pass dummy streams + std::ofstream blif_os; + std::ofstream sdf_os; + + MergedNetlistWriterVisitor visitor(verilog_os, blif_os, sdf_os, delay_calc, opts); + + NetlistWalker nl_walker(visitor); + + nl_walker.walk(); +} // // File-scope function implementations // diff --git a/vpr/src/base/netlist_writer.h b/vpr/src/base/netlist_writer.h index 9c4306de756..8a8a19976e1 100644 --- a/vpr/src/base/netlist_writer.h +++ b/vpr/src/base/netlist_writer.h @@ -17,4 +17,13 @@ */ void netlist_writer(const std::string basename, std::shared_ptr delay_calc, struct t_analysis_opts opts); +/** + * @brief Writes out the post implementation netlist in Verilog format. + * It has its top module ports merged into multi-bit ones. + * + * Written filename ends in {basename}_merged_post_implementation.v where {basename} is the + * basename argument. + */ +void merged_netlist_writer(const std::string basename, std::shared_ptr delay_calc, struct t_analysis_opts opts); + #endif diff --git a/vpr/src/base/read_options.cpp b/vpr/src/base/read_options.cpp index d233d209129..074e84a2e8c 100644 --- a/vpr/src/base/read_options.cpp +++ b/vpr/src/base/read_options.cpp @@ -2532,6 +2532,13 @@ argparse::ArgumentParser create_arg_parser(std::string prog_name, t_options& arg .default_value("off") .show_in(argparse::ShowIn::HELP_ONLY); + analysis_grp.add_argument(args.Generate_Post_Implementation_Merged_Netlist, "--gen_post_implementation_merged_netlist") + .help( + "Generates the post-implementation netlist with merged top module ports" + " Used for post-implementation simulation and verification") + .default_value("off") + .show_in(argparse::ShowIn::HELP_ONLY); + analysis_grp.add_argument(args.timing_report_npaths, "--timing_report_npaths") .help("Controls how many timing paths are reported.") .default_value("100") diff --git a/vpr/src/base/read_options.h b/vpr/src/base/read_options.h index 485c43fcdf1..d2edecc27ac 100644 --- a/vpr/src/base/read_options.h +++ b/vpr/src/base/read_options.h @@ -208,6 +208,7 @@ struct t_options { /* Analysis options */ argparse::ArgValue full_stats; argparse::ArgValue Generate_Post_Synthesis_Netlist; + argparse::ArgValue Generate_Post_Implementation_Merged_Netlist; argparse::ArgValue timing_report_npaths; argparse::ArgValue timing_report_detail; argparse::ArgValue timing_report_skew; diff --git a/vpr/src/base/vpr_api.cpp b/vpr/src/base/vpr_api.cpp index b3bd0406843..f4d759a862e 100644 --- a/vpr/src/base/vpr_api.cpp +++ b/vpr/src/base/vpr_api.cpp @@ -1282,6 +1282,11 @@ void vpr_analysis(t_vpr_setup& vpr_setup, const t_arch& Arch, const RouteStatus& vpr_setup.AnalysisOpts); } + //Write the post-implementation merged netlist + if (vpr_setup.AnalysisOpts.gen_post_implementation_merged_netlist) { + merged_netlist_writer(atom_ctx.nlist.netlist_name().c_str(), analysis_delay_calc, vpr_setup.AnalysisOpts); + } + //Do power analysis if (vpr_setup.PowerOpts.do_power) { vpr_power_estimation(vpr_setup, Arch, *timing_info, route_status); diff --git a/vpr/src/base/vpr_types.h b/vpr/src/base/vpr_types.h index 57186bc28e5..f469e76dbc4 100644 --- a/vpr/src/base/vpr_types.h +++ b/vpr/src/base/vpr_types.h @@ -1256,6 +1256,7 @@ struct t_analysis_opts { e_stage_action doAnalysis; bool gen_post_synthesis_netlist; + bool gen_post_implementation_merged_netlist; e_post_synth_netlist_unconn_handling post_synth_netlist_unconn_input_handling; e_post_synth_netlist_unconn_handling post_synth_netlist_unconn_output_handling;