diff --git a/vpr/src/base/netlist_writer.cpp b/vpr/src/base/netlist_writer.cpp index 7a968949e..e03ddea8e 100644 --- a/vpr/src/base/netlist_writer.cpp +++ b/vpr/src/base/netlist_writer.cpp @@ -18,6 +18,7 @@ #include "vtr_version.h" #include "vpr_error.h" +#include "vpr_types.h" #include "netlist_walker.h" #include "netlist_writer.h" @@ -27,70 +28,76 @@ #include "atom_netlist_utils.h" #include "logic_vec.h" -//Overview -//======== -// -//This file implements the netlist writer which generates post-implementation (i.e. post-routing) netlists -//in BLIF and Verilog formats, along with an SDF file which can be used for timing back-annotation (e.g. for -//timing simulation). -// -//The externally facing function from this module is netlist_writer() which will generate the netlist and SDF -//output files. netlist_writer() assumes that a post-routing implementation is available and that there is a -//valid timing graph with real delays annotated (these delays are used to create the SDF). -// -//Implementation -//============== -// -//The netlist writer is primarily driven by the NetlistWriterVisitor class, which walks the netlist using the -//NetlistWalker (see netlist_walker.h). The netlist walker calls NetlistWriterVisitor's visit_atom_impl() -//method for every atom (i.e. primitive circuit element) in VPR's internal data structures. -// -//visit_atom_impl() then dispatches the atom to the appropriate NetlistWriterVisitor member function -//(e.g. LUTs to make_lut_instance(), Latches to make_latch_instance(), etc.). Each of the make_*_instance() -//functions records the external nets the atom connects to (see make_inst_wire()) and constructs a concrete -//'Instance' object to represent the primitive. NetlistWriterVisitor saves these representations for later -//output. -// -//'Instance' is an abstract class representing objects which know how to create thier own representation in -//BLIF, Verilog and SDF formats. Most primitives can be represented by the BlackBoxInst class, but special -//primitives like LUTs and Latchs have thier own implementations (LutInst, LatchInst) to handle some of their -//unique requirements. -// -//Once the entire netlist has been traversed the netlist walker will call NetlistWriterVisitor's finish_impl() -//method which kicks off the generation of the actual netlists and SDF files. NetlistWriterVisitor's print_*() -//methods setting up file-level global information (I/Os, net declarations etc.) and then ask each Instance to -//print itself in the appropriate format to the appropriate file. -// -//Name Escaping -//============= -//One of the challenges in generating netlists is producing consistent naming of netlist elements. -//In particular valid BLIF names, Verilog names and SDF names are not neccessarily the same. -//As a result we must escape invalid characters/identifiers when producing some formats. -// -//All name escaping is handled at the output stage (i.e. in the print_*() functions) since it is -//very format dependant. -// -//VPR stores names internally generally following BLIF conventions. As a result these names need -//to be escaped when generating Verilog or SDF. This is handled with escape_verilog_identifier() -//and escape_sdf_identifier() functions. -// -//Primitives -//========== -//Verilog netlist generation assumes the existance of appropriate primitives for the various -//atom types (i.e. a LUT_K module, a DFF module etc.). These are currently defined the file -///vtr_flow/primitives.v, where is the root of the VTR source tree. -// -//You will typically need to link with this file when attempting to simulate a post-implementation netlist. -//Also see the comments in primitives.v for important notes related to performing SDF back-annotated timing -//simulation. +/** + * @file + * + * This file implements the netlist writer which generates post-implementation (i.e. post-routing) netlists + * in BLIF and Verilog formats, along with an SDF file which can be used for timing back-annotation (e.g. for + * timing simulation). + * + * The externally facing function from this module is netlist_writer() which will generate the netlist and SDF + * output files. netlist_writer() assumes that a post-routing implementation is available and that there is a + * valid timing graph with real delays annotated (these delays are used to create the SDF). + * + * Implementation + * ============== + * + * The netlist writer is primarily driven by the NetlistWriterVisitor class, which walks the netlist using the + * NetlistWalker (see netlist_walker.h). The netlist walker calls NetlistWriterVisitor's visit_atom_impl() + * method for every atom (i.e. primitive circuit element) in VPR's internal data structures. + * + * visit_atom_impl() then dispatches the atom to the appropriate NetlistWriterVisitor member function + * (e.g. LUTs to make_lut_instance(), Latches to make_latch_instance(), etc.). Each of the make_*_instance() + * functions records the external nets the atom connects to (see make_inst_wire()) and constructs a concrete + * 'Instance' object to represent the primitive. NetlistWriterVisitor saves these representations for later + * output. + * + * 'Instance' is an abstract class representing objects which know how to create thier own representation in + * BLIF, Verilog and SDF formats. Most primitives can be represented by the BlackBoxInst class, but special + * primitives like LUTs and Latchs have thier own implementations (LutInst, LatchInst) to handle some of their + * unique requirements. + * + * Once the entire netlist has been traversed the netlist walker will call NetlistWriterVisitor's finish_impl() + * method which kicks off the generation of the actual netlists and SDF files. NetlistWriterVisitor's print_*() + * methods setting up file-level global information (I/Os, net declarations etc.) and then ask each Instance to + * print itself in the appropriate format to the appropriate file. + * + * Name Escaping + * ============= + * One of the challenges in generating netlists is producing consistent naming of netlist elements. + * In particular valid BLIF names, Verilog names and SDF names are not neccessarily the same. + * As a result we must escape invalid characters/identifiers when producing some formats. + * + * All name escaping is handled at the output stage (i.e. in the print_*() functions) since it is + * very format dependant. + * + * VPR stores names internally generally following BLIF conventions. As a result these names need + * to be escaped when generating Verilog or SDF. This is handled with escape_verilog_identifier() + * and escape_sdf_identifier() functions. + * + * Primitives + * ========== + * Verilog netlist generation assumes the existance of appropriate primitives for the various + * atom types (i.e. a LUT_K module, a DFF module etc.). These are currently defined the file + * `/vtr_flow/primitives.v`, where `` is the root of the VTR source tree. + * + * You will typically need to link with this file when attempting to simulate a post-implementation netlist. + * Also see the comments in primitives.v for important notes related to performing SDF back-annotated timing + * simulation. + */ -//Enable for extra output while calculating LUT masks +/* Enable for extra output while calculating LUT masks */ //#define DEBUG_LUT_MASK // //File local type declarations // +// This pair cointains the following values: +// - double: hold, setup or clock-to-q delays of the port +// - string: port name of the associated source clock pin of the sequential port +typedef std::pair sequential_port_delay_pair; + /*enum class PortType { * IN, * OUT, @@ -104,7 +111,7 @@ std::string indent(size_t depth); double get_delay_ps(double delay_sec); void print_blif_port(std::ostream& os, size_t& unconn_count, const std::string& port_name, const std::vector& nets, int depth); -void print_verilog_port(std::ostream& os, const std::string& port_name, const std::vector& nets, PortType type, int depth); +void print_verilog_port(std::ostream& os, size_t& unconn_count, const std::string& port_name, const std::vector& nets, PortType type, int depth); std::string create_unconn_net(size_t& unconn_count); std::string escape_verilog_identifier(const std::string id); @@ -121,12 +128,12 @@ std::string join_identifier(std::string lhs, std::string rhs); //A combinational timing arc class Arc { public: - Arc(std::string src_port, //Source of the arc - int src_ipin, //Source pin index - std::string snk_port, //Sink of the arc - int snk_ipin, //Sink pin index - float del, //Delay on this arc - std::string cond = "") //Condition associated with the arc + Arc(std::string src_port, ///> port_conns, //The port connections of this instance. Key: port name, Value: connected nets - std::vector timing_arc_values) //The timing arcs of this instance + public: ///> port_conns, /// timing_arc_values) /// port_conns, //Instance's port-to-net connections - Type type, //Type of this latch - vtr::LogicValue init_value, //Initial value of the latch - double tcq = std::numeric_limits::quiet_NaN(), //Clock-to-Q delay - double tsu = std::numeric_limits::quiet_NaN(), //Setup time - double thld = std::numeric_limits::quiet_NaN()) //Hold time + LatchInst(std::string inst_name, /// port_conns, ///::quiet_NaN(), ///::quiet_NaN(), ///::quiet_NaN()) /// port_connections_; Type type_; vtr::LogicValue initial_value_; - double tcq_; //Clock delay + tcq - double tsu_; //Setup time - double thld_; //Hold time + double tcq_; /// params, //Verilog parameters: Dictonary of - std::map attrs, //Instance attributes: Dictonary of - std::map> input_port_conns, //Port connections: Dictionary of - std::map> output_port_conns, //Port connections: Dictionary of - std::vector timing_arcs, //Combinational timing arcs - std::map ports_tsu, //Port setup checks - std::map ports_tcq) //Port clock-to-q delays + BlackBoxInst(std::string type_name, /// params, /// + std::map attrs, /// + std::map> input_port_conns, /// + std::map> output_port_conns, /// + std::vector timing_arcs, /// ports_tsu, /// ports_thld, /// ports_tcq) ///first; auto& nets = iter->second; - print_verilog_port(os, port_name, nets, PortType::INPUT, depth + 1); + print_verilog_port(os, unconn_count, port_name, nets, PortType::INPUT, depth + 1); if (!(iter == --input_port_conns_.end() && output_port_conns_.empty())) { os << ","; } @@ -630,7 +645,7 @@ class BlackBoxInst : public Instance { for (auto iter = output_port_conns_.begin(); iter != output_port_conns_.end(); ++iter) { auto& port_name = iter->first; auto& nets = iter->second; - print_verilog_port(os, port_name, nets, PortType::OUTPUT, depth + 1); + print_verilog_port(os, unconn_count, port_name, nets, PortType::OUTPUT, depth + 1); if (!(iter == --output_port_conns_.end())) { os << ","; } @@ -641,75 +656,77 @@ class BlackBoxInst : public Instance { } void print_sdf(std::ostream& os, int depth = 0) override { - os << indent(depth) << "(CELL\n"; - os << indent(depth + 1) << "(CELLTYPE \"" << type_name_ << "\")\n"; - os << indent(depth + 1) << "(INSTANCE " << escape_sdf_identifier(inst_name_) << ")\n"; - os << indent(depth + 1) << "(DELAY\n"; + if (!timing_arcs_.empty() || !ports_tcq_.empty() || !ports_tsu_.empty() || !ports_thld_.empty()) { + os << indent(depth) << "(CELL\n"; + os << indent(depth + 1) << "(CELLTYPE \"" << type_name_ << "\")\n"; + os << indent(depth + 1) << "(INSTANCE " << escape_sdf_identifier(inst_name_) << ")\n"; + os << indent(depth + 1) << "(DELAY\n"; - if (!timing_arcs_.empty() || !ports_tcq_.empty()) { - os << indent(depth + 2) << "(ABSOLUTE\n"; + if (!timing_arcs_.empty() || !ports_tcq_.empty()) { + os << indent(depth + 2) << "(ABSOLUTE\n"; - //Combinational paths - for (const auto& arc : timing_arcs_) { - double delay_ps = get_delay_ps(arc.delay()); + //Combinational paths + for (const auto& arc : timing_arcs_) { + double delay_ps = get_delay_ps(arc.delay()); - std::stringstream delay_triple; - delay_triple << "(" << delay_ps << ":" << delay_ps << ":" << delay_ps << ")"; + std::stringstream delay_triple; + delay_triple << "(" << delay_ps << ":" << delay_ps << ":" << delay_ps << ")"; - //Note that we explicitly do not escape the last array indexing so an SDF - //reader will treat the ports as multi-bit - // - //We also only put the last index in if the port has multiple bits - os << indent(depth + 3) << "(IOPATH "; - os << escape_sdf_identifier(arc.source_name()); - if (find_port_size(arc.source_name()) > 1) { - os << "[" << arc.source_ipin() << "]"; + //Note that we explicitly do not escape the last array indexing so an SDF + //reader will treat the ports as multi-bit + // + //We also only put the last index in if the port has multiple bits + 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 << " "; + os << escape_sdf_identifier(arc.sink_name()); + if (find_port_size(arc.sink_name()) > 1) { + os << "[" << arc.sink_ipin() << "]"; + } + os << " "; + os << delay_triple.str(); + os << ")\n"; } - os << " "; - os << escape_sdf_identifier(arc.sink_name()); - if (find_port_size(arc.sink_name()) > 1) { - os << "[" << arc.sink_ipin() << "]"; + + //Clock-to-Q delays + for (auto kv : ports_tcq_) { + double clock_to_q_ps = get_delay_ps(kv.second.first); + + std::stringstream delay_triple; + delay_triple << "(" << clock_to_q_ps << ":" << clock_to_q_ps << ":" << clock_to_q_ps << ")"; + + os << indent(depth + 3) << "(IOPATH (posedge " << escape_sdf_identifier(kv.second.second) << ") " << escape_sdf_identifier(kv.first) << " " << delay_triple.str() << " " << delay_triple.str() << ")\n"; } - os << " "; - os << delay_triple.str(); - os << ")\n"; + os << indent(depth + 2) << ")\n"; //ABSOLUTE } + os << indent(depth + 1) << ")\n"; //DELAY - //Clock-to-Q delays - for (auto kv : ports_tcq_) { - double clock_to_q_ps = get_delay_ps(kv.second); + if (!ports_tsu_.empty() || !ports_thld_.empty()) { + //Setup checks + os << indent(depth + 1) << "(TIMINGCHECK\n"; + for (auto kv : ports_tsu_) { + double setup_ps = get_delay_ps(kv.second.first); - std::stringstream delay_triple; - delay_triple << "(" << clock_to_q_ps << ":" << clock_to_q_ps << ":" << clock_to_q_ps << ")"; + std::stringstream delay_triple; + delay_triple << "(" << setup_ps << ":" << setup_ps << ":" << setup_ps << ")"; - os << indent(depth + 3) << "(IOPATH (posedge clock) " << escape_sdf_identifier(kv.first) << " " << delay_triple.str() << " " << delay_triple.str() << ")\n"; + os << indent(depth + 2) << "(SETUP " << escape_sdf_identifier(kv.first) << " (posedge " << escape_sdf_identifier(kv.second.second) << ") " << delay_triple.str() << ")\n"; + } + for (auto kv : ports_thld_) { + double hold_ps = get_delay_ps(kv.second.first); + + std::stringstream delay_triple; + delay_triple << "(" << hold_ps << ":" << hold_ps << ":" << hold_ps << ")"; + + os << indent(depth + 2) << "(HOLD " << escape_sdf_identifier(kv.first) << " (posedge " << escape_sdf_identifier(kv.second.second) << ") " << delay_triple.str() << ")\n"; + } + os << indent(depth + 1) << ")\n"; //TIMINGCHECK } - os << indent(depth + 2) << ")\n"; //ABSOLUTE + os << indent(depth) << ")\n"; //CELL } - os << indent(depth + 1) << ")\n"; //DELAY - - if (!ports_tsu_.empty() || !ports_thld_.empty()) { - //Setup checks - os << indent(depth + 1) << "(TIMINGCHECK\n"; - for (auto kv : ports_tsu_) { - double setup_ps = get_delay_ps(kv.second); - - std::stringstream delay_triple; - delay_triple << "(" << setup_ps << ":" << setup_ps << ":" << setup_ps << ")"; - - os << indent(depth + 2) << "(SETUP " << escape_sdf_identifier(kv.first) << " (posedge clock) " << delay_triple.str() << ")\n"; - } - for (auto kv : ports_thld_) { - double hold_ps = get_delay_ps(kv.second); - - std::stringstream delay_triple; - delay_triple << "(" << hold_ps << ":" << hold_ps << ":" << hold_ps << ")"; - - os << indent(depth + 2) << "(HOLD " << escape_sdf_identifier(kv.first) << " (posedge clock) " << delay_triple.str() << ")\n"; - } - os << indent(depth + 1) << ")\n"; //TIMINGCHECK - } - os << indent(depth) << ")\n"; //CELL } size_t find_port_size(std::string port_name) { @@ -736,19 +753,21 @@ class BlackBoxInst : public Instance { std::map> input_port_conns_; std::map> output_port_conns_; std::vector timing_arcs_; - std::map ports_tsu_; - std::map ports_thld_; - std::map ports_tcq_; + std::map ports_tsu_; + std::map ports_thld_; + std::map ports_tcq_; }; -//Assignment represents the logical connection between two nets -// -// This is synonomous with verilog's 'assign x = y' which connects -// two nets with logical identity, assigning the value of 'y' to 'x' +/** + * @brief Assignment represents the logical connection between two nets + * + * This is synonomous with verilog's 'assign x = y' which connects + * two nets with logical identity, assigning the value of 'y' to 'x' + */ class Assignment { public: - Assignment(std::string lval, //The left value (assigned to) - std::string rval) //The right value (assigned from) + Assignment(std::string lval, /// delay_calc) : verilog_os_(verilog_os) , blif_os_(blif_os) @@ -847,7 +868,7 @@ class NetlistWriterVisitor : public NetlistVisitor { } private: //Internal Helper functions - //Writes out the verilog netlist + ///@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"; @@ -909,17 +930,18 @@ class NetlistWriterVisitor : public NetlistVisitor { } //All the cell instances + size_t unconn_count = 0; verilog_os_ << "\n"; verilog_os_ << indent(depth + 1) << "//Cell instances\n"; for (auto& inst : cell_instances_) { - inst->print_verilog(verilog_os_, depth + 1); + inst->print_verilog(verilog_os_, unconn_count, depth + 1); } verilog_os_ << "\n"; verilog_os_ << indent(depth) << "endmodule\n"; } - //Writes out the blif netlist + ///@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"; blif_os_ << indent(depth) << ".model " << top_module_name_ << "\n"; @@ -972,7 +994,7 @@ class NetlistWriterVisitor : public NetlistVisitor { blif_os_ << indent(depth) << ".end\n"; } - //Writes out the SDF + ///@brief Writes out the SDF void print_sdf(int depth = 0) { sdf_os_ << indent(depth) << "(DELAYFILE\n"; sdf_os_ << indent(depth + 1) << "(SDFVERSION \"2.1\")\n"; @@ -1023,14 +1045,17 @@ class NetlistWriterVisitor : public NetlistVisitor { sdf_os_ << indent(depth) << ")\n"; } - //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, //The id of the net in the atom netlist - tatum::NodeId tnode_id, //The tnode associated with the primitive pin - std::string inst_name, //The name of the instance associated with the pin - PortType port_type, //The port direction - int port_idx, //The instance port index - int pin_idx) { //The instance pin index + /** + * @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, ///pb_graph_node; std::string io_name; @@ -1112,7 +1141,7 @@ class NetlistWriterVisitor : public NetlistVisitor { return io_name; } - //Returns an Instance object representing the LUT + ///@brief Returns an Instance object representing the LUT std::shared_ptr make_lut_instance(const t_pb* atom) { //Determine what size LUT int lut_size = find_num_inputs(atom); @@ -1189,7 +1218,7 @@ class NetlistWriterVisitor : public NetlistVisitor { return inst; } - //Returns an Instance object representing the Latch + ///@brief Returns an Instance object representing the Latch std::shared_ptr make_latch_instance(const t_pb* atom) { std::string inst_name = join_identifier("latch", atom->name); @@ -1240,9 +1269,11 @@ class NetlistWriterVisitor : public NetlistVisitor { return std::make_shared(inst_name, port_conns, type, init_value, tcq, tsu); } - //Returns an Instance object representing the RAM - // Note that the primtive interface to dual and single port rams is nearly identical, - // so we using a single function to handle both + /** + * @brief Returns an Instance object representing the RAM + * @note the primtive interface to dual and single port rams is nearly identical, + * so we using a single function to handle both + */ std::shared_ptr make_ram_instance(const t_pb* atom) { const auto& top_pb_route = find_top_pb_route(atom); const t_pb_graph_node* pb_graph_node = atom->pb_graph_node; @@ -1257,8 +1288,9 @@ class NetlistWriterVisitor : public NetlistVisitor { std::map> input_port_conns; std::map> output_port_conns; std::vector timing_arcs; - std::map ports_tsu; - std::map ports_tcq; + std::map ports_tsu; + std::map ports_thld; + std::map ports_tcq; params["ADDR_WIDTH"] = "0"; params["DATA_WIDTH"] = "0"; @@ -1314,7 +1346,7 @@ class NetlistWriterVisitor : public NetlistVisitor { } input_port_conns[port_name].push_back(net); - ports_tsu[port_name] = pin->tsu; + ports_tsu[port_name] = std::make_pair(pin->tsu, pin->associated_clock_pin->port->name); } } @@ -1350,7 +1382,7 @@ class NetlistWriterVisitor : public NetlistVisitor { "Unrecognized input port class '%s' for primitive '%s' (%s)\n", port_class.c_str(), atom->name, pb_type->name); } output_port_conns[port_name].push_back(net); - ports_tcq[port_name] = pin->tco_max; + ports_tcq[port_name] = std::make_pair(pin->tco_max, pin->associated_clock_pin->port->name); } } @@ -1381,10 +1413,10 @@ class NetlistWriterVisitor : public NetlistVisitor { } } - return std::make_shared(type, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_tcq); + return std::make_shared(type, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_thld, ports_tcq); } - //Returns an Instance object representing a Multiplier + ///@brief Returns an Instance object representing a Multiplier std::shared_ptr make_multiply_instance(const t_pb* atom) { auto& timing_ctx = g_vpr_ctx.timing(); @@ -1399,8 +1431,9 @@ class NetlistWriterVisitor : public NetlistVisitor { std::map> input_port_conns; std::map> output_port_conns; std::vector timing_arcs; - std::map ports_tsu; - std::map ports_tcq; + std::map ports_tsu; + std::map ports_thld; + std::map ports_tcq; params["WIDTH"] = "0"; @@ -1476,10 +1509,10 @@ class NetlistWriterVisitor : public NetlistVisitor { VTR_ASSERT(pb_graph_node->num_clock_ports == 0); //No clocks - return std::make_shared(type_name, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_tcq); + return std::make_shared(type_name, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_thld, ports_tcq); } - //Returns an Instance object representing an Adder + ///@brief Returns an Instance object representing an Adder std::shared_ptr make_adder_instance(const t_pb* atom) { auto& timing_ctx = g_vpr_ctx.timing(); @@ -1494,8 +1527,9 @@ class NetlistWriterVisitor : public NetlistVisitor { std::map> input_port_conns; std::map> output_port_conns; std::vector timing_arcs; - std::map ports_tsu; - std::map ports_tcq; + std::map ports_tsu; + std::map ports_thld; + std::map ports_tcq; params["WIDTH"] = "0"; @@ -1575,7 +1609,7 @@ class NetlistWriterVisitor : public NetlistVisitor { } } - return std::make_shared(type_name, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_tcq); + return std::make_shared(type_name, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_thld, ports_tcq); } std::shared_ptr make_blackbox_instance(const t_pb* atom) { @@ -1583,6 +1617,7 @@ class NetlistWriterVisitor : public NetlistVisitor { const t_pb_graph_node* pb_graph_node = atom->pb_graph_node; const t_pb_type* pb_type = pb_graph_node->pb_type; + auto& timing_ctx = g_vpr_ctx.timing(); std::string type_name = pb_type->model->name; std::string inst_name = join_identifier(type_name, atom->name); std::map params; @@ -1590,8 +1625,17 @@ class NetlistWriterVisitor : public NetlistVisitor { std::map> input_port_conns; std::map> output_port_conns; std::vector timing_arcs; - std::map ports_tsu; - std::map ports_tcq; + + // Maps to store a sink's port with the corresponding timing edge to that sink + // - key : string corresponding to the port's name + // - value : pair with the delay and the associated clock pin port name + // + // tsu : Setup + // thld: Hold + // tcq : Clock-to-Q + std::map ports_tsu; + std::map ports_thld; + std::map ports_tcq; //Delay matrix[sink_tnode] -> tuple of source_port_name, pin index, delay std::map>> tnode_delay_matrix; @@ -1603,19 +1647,35 @@ class NetlistWriterVisitor : public NetlistVisitor { const t_port* port = pin->port; int cluster_pin_idx = pin->pin_count_in_cluster; - auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id; std::string net; - if (!atom_net_id) { + if (!top_pb_route.count(cluster_pin_idx)) { //Disconnected + net = ""; } else { //Connected + auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id; + VTR_ASSERT(atom_net_id); + auto src_tnode = find_tnode(atom, cluster_pin_idx); net = make_inst_wire(atom_net_id, src_tnode, inst_name, PortType::INPUT, iport, ipin); + //Delays + // + //We record the source's sink tnodes and their delays here + for (tatum::EdgeId edge : timing_ctx.graph->node_out_edges(src_tnode)) { + double delay = delay_calc_->max_edge_delay(*timing_ctx.graph, edge); + + auto sink_tnode = timing_ctx.graph->edge_sink_node(edge); + tnode_delay_matrix[sink_tnode].emplace_back(port->name, ipin, delay); + } } input_port_conns[port->name].push_back(net); + if (pin->type == PB_PIN_SEQUENTIAL) { + if (!std::isnan(pin->tsu)) ports_tsu[port->name] = std::make_pair(pin->tsu, pin->associated_clock_pin->port->name); + if (!std::isnan(pin->thld)) ports_thld[port->name] = std::make_pair(pin->thld, pin->associated_clock_pin->port->name); + } } } @@ -1626,19 +1686,29 @@ class NetlistWriterVisitor : public NetlistVisitor { const t_port* port = pin->port; int cluster_pin_idx = pin->pin_count_in_cluster; - auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id; std::string net; - if (!atom_net_id) { + if (!top_pb_route.count(cluster_pin_idx)) { //Disconnected net = ""; } else { //Connected + auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id; + VTR_ASSERT(atom_net_id); + auto inode = find_tnode(atom, cluster_pin_idx); net = make_inst_wire(atom_net_id, inode, inst_name, PortType::OUTPUT, iport, ipin); + //Record the timing arcs + for (auto& data_tuple : tnode_delay_matrix[inode]) { + auto src_name = std::get<0>(data_tuple); + auto src_ipin = std::get<1>(data_tuple); + auto delay = std::get<2>(data_tuple); + timing_arcs.emplace_back(src_name, src_ipin, port->name, ipin, delay); + } } output_port_conns[port->name].push_back(net); + if (pin->type == PB_PIN_SEQUENTIAL && !std::isnan(pin->tco_max)) ports_tcq[port->name] = std::make_pair(pin->tco_max, pin->associated_clock_pin->port->name); } } @@ -1649,16 +1719,16 @@ class NetlistWriterVisitor : public NetlistVisitor { const t_port* port = pin->port; int cluster_pin_idx = pin->pin_count_in_cluster; - auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id; - - VTR_ASSERT(atom_net_id); //Must have a clock std::string net; - if (!atom_net_id) { + if (!top_pb_route.count(cluster_pin_idx)) { //Disconnected net = ""; } else { //Connected + auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id; + VTR_ASSERT(atom_net_id); //Must have a clock + auto src_tnode = find_tnode(atom, cluster_pin_idx); net = make_inst_wire(atom_net_id, src_tnode, inst_name, PortType::CLOCK, iport, ipin); } @@ -1677,16 +1747,16 @@ class NetlistWriterVisitor : public NetlistVisitor { attrs[attr.first] = attr.second; } - return std::make_shared(type_name, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_tcq); + return std::make_shared(type_name, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_thld, ports_tcq); } - //Returns the top level pb_route associated with the given pb + ///@brief Returns the top level pb_route associated with the given pb const t_pb_routes& find_top_pb_route(const t_pb* curr) { const t_pb* top_pb = find_top_cb(curr); return top_pb->pb_route; } - //Returns the top complex block which contains the given pb + ///@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 @@ -1698,7 +1768,7 @@ class NetlistWriterVisitor : public NetlistVisitor { return curr; } - //Returns the tnode ID of the given atom's connected cluster pin + ///@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(); @@ -1715,7 +1785,7 @@ class NetlistWriterVisitor : public NetlistVisitor { return tnode_id; } - //Returns a LogicVec representing the LUT mask of the given LUT atom + ///@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 auto& atom_ctx = g_vpr_ctx.atom(); @@ -1746,17 +1816,19 @@ class NetlistWriterVisitor : public NetlistVisitor { return lut_mask; } - //Helper function for load_lut_mask() which determines how the LUT inputs were - //permuted compared to the input BLIF - // - // Since the LUT inputs may have been rotated from the input blif specification we need to - // figure out this permutation to reflect the physical implementation connectivity. - // - // We return a permutation map (which is a list of swaps from index to index) - // which is then applied to do the rotation of the lutmask. - // - // The net in the atom netlist which was originally connected to pin i, is connected - // to pin permute[i] in the implementation. + /** + * @brief Helper function for load_lut_mask() which determines how the LUT inputs were + * permuted compared to the input BLIF + * + * Since the LUT inputs may have been rotated from the input blif specification we need to + * figure out this permutation to reflect the physical implementation connectivity. + * + * We return a permutation map (which is a list of swaps from index to index) + * which is then applied to do the rotation of the lutmask. + * + * The net in the atom netlist which was originally connected to pin i, is connected + * to pin permute[i] in the implementation. + */ std::vector determine_lut_permutation(size_t num_inputs, const t_pb* atom_pb) { auto& atom_ctx = g_vpr_ctx.atom(); @@ -1792,7 +1864,17 @@ class NetlistWriterVisitor : public NetlistVisitor { if (impl_input_net_id) { //If there is a valid net connected in the implementation AtomNetId logical_net_id = atom_ctx.nlist.port_net(port_id, orig_index); - VTR_ASSERT(impl_input_net_id == logical_net_id); + + // Fatal error should be flagged when the net marked in implementation + // does not match the net marked in input netlist + if (impl_input_net_id != logical_net_id) { + VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER, + "Unmatch:\n\tlogical net is '%s' at pin '%lu'\n\timplmented net is '%s' at pin '%s'\n", + atom_ctx.nlist.net_name(logical_net_id).c_str(), + size_t(orig_index), + atom_ctx.nlist.net_name(impl_input_net_id).c_str(), + gpin->to_string().c_str()); + } //Mark the permutation. // The net originally located at orig_index in the atom netlist @@ -1831,9 +1913,10 @@ class NetlistWriterVisitor : public NetlistVisitor { return permute; } - //Helper function for load_lut_mask() which determines if the - //names is encodeing the ON (returns true) or OFF (returns false) - //set. + /** + * @brief Helper function for load_lut_mask() which determines if the + * names is encodeing the ON (returns true) or OFF (returns false) set. + */ bool names_encodes_on_set(vtr::t_linked_vptr* names_row_ptr) { //Determine the truth (output value) for this row // By default we assume the on-set is encoded to correctly handle @@ -1871,9 +1954,11 @@ class NetlistWriterVisitor : public NetlistVisitor { return encoding_on_set; } - //Helper function for load_lut_mask() - // - //Converts the given names_row string to a LogicVec + /** + * @brief Helper function for load_lut_mask() + * + * Converts the given names_row string to a LogicVec + */ LogicVec names_row_to_logic_vec(const std::string names_row, size_t num_inputs, bool encoding_on_set) { //Get an iterator to the last character (i.e. the output value) auto output_val_iter = names_row.end() - 1; @@ -1915,7 +2000,7 @@ class NetlistWriterVisitor : public NetlistVisitor { return input_values; } - //Returns the total number of input pins on the given pb + ///@brief Returns the total number of input pins on the given pb int find_num_inputs(const t_pb* pb) { int count = 0; for (int i = 0; i < pb->pb_graph_node->num_input_ports; i++) { @@ -1923,7 +2008,7 @@ class NetlistWriterVisitor : public NetlistVisitor { } return count; } - //Returns the logical net ID + ///@brief Returns the logical net ID AtomNetId find_atom_input_logical_net(const t_pb* atom, int atom_input_idx) { const t_pb_graph_node* pb_node = atom->pb_graph_node; @@ -1936,7 +2021,7 @@ class NetlistWriterVisitor : public NetlistVisitor { return atom_net_id; } - //Returns the name of the routing segment between two wires + ///@brief Returns the name of the routing segment between two wires std::string interconnect_name(std::string driver_wire, std::string sink_wire) { std::string name = join_identifier("routing_segment", driver_wire); name = join_identifier(name, "to"); @@ -1945,7 +2030,7 @@ class NetlistWriterVisitor : public NetlistVisitor { return name; } - //Returns the delay in pico-seconds from source_tnode to sink_tnode + ///@brief Returns the delay in pico-seconds from source_tnode to sink_tnode double get_delay_ps(tatum::NodeId source_tnode, tatum::NodeId sink_tnode) { auto& timing_ctx = g_vpr_ctx.timing(); @@ -1958,11 +2043,11 @@ class NetlistWriterVisitor : public NetlistVisitor { } private: //Data - std::string top_module_name_; //Name of the top level module (i.e. the circuit) - std::vector inputs_; //Name of circuit inputs - std::vector outputs_; //Name of circuit outputs - std::vector assignments_; //Set of assignments (i.e. net-to-net connections) - std::vector> cell_instances_; //Set of cell instances + std::string top_module_name_; /// inputs_; /// outputs_; /// assignments_; ///> cell_instances_; /// delay_calc) { std::string verilog_filename = basename + "_post_synthesis.v"; std::string blif_filename = basename + "_post_synthesis.blif"; @@ -2013,7 +2098,7 @@ void netlist_writer(const std::string basename, std::shared_ptr& nets, int depth) { if (nets.size() == 1) { //If only a single bit port, don't include port indexing @@ -2067,10 +2154,12 @@ void print_blif_port(std::ostream& os, size_t& unconn_count, const std::string& } } -//Pretty-Prints a verilog port to the given output stream -// -// Handles special cases like multi-bit and disconnected ports -void print_verilog_port(std::ostream& os, const std::string& port_name, const std::vector& nets, PortType type, int depth) { +/** + * @brief Pretty-Prints a verilog port to the given output stream + * + * Handles special cases like multi-bit and disconnected ports + */ +void print_verilog_port(std::ostream& os, size_t& unconn_count, const std::string& port_name, const std::vector& nets, PortType type, int depth) { //Port name os << indent(depth) << "." << port_name << "("; @@ -2083,7 +2172,7 @@ void print_verilog_port(std::ostream& os, const std::string& port_name, const st os << "1'b0"; } else { VTR_ASSERT(type == PortType::OUTPUT); - os << ""; + os << "1'bX"; } } else { //Connected @@ -2118,7 +2207,7 @@ void print_verilog_port(std::ostream& os, const std::string& port_name, const st os << ")"; } -//Escapes the given identifier to be safe for verilog +///@brief Escapes the given identifier to be safe for verilog std::string escape_verilog_identifier(const std::string identifier) { //Verilog allows escaped identifiers // @@ -2135,7 +2224,7 @@ std::string escape_verilog_identifier(const std::string identifier) { return escaped_name; } -//Returns true if c is categorized as a special character in SDF +///@brief Returns true if c is categorized as a special character in SDF bool is_special_sdf_char(char c) { //From section 3.2.5 of IEEE1497 Part 3 (i.e. the SDF spec) //Special characters run from: @@ -2165,7 +2254,7 @@ bool is_special_sdf_char(char c) { return false; } -//Escapes the given identifier to be safe for sdf +///@brief Escapes the given identifier to be safe for sdf std::string escape_sdf_identifier(const std::string identifier) { //SDF allows escaped characters // @@ -2184,7 +2273,7 @@ std::string escape_sdf_identifier(const std::string identifier) { return escaped_name; } -//Joins two identifier strings +///@brief Joins two identifier strings std::string join_identifier(std::string lhs, std::string rhs) { return lhs + '_' + rhs; } diff --git a/vpr/src/base/netlist_writer.h b/vpr/src/base/netlist_writer.h index 141ce6a2b..8f3b4e8a6 100644 --- a/vpr/src/base/netlist_writer.h +++ b/vpr/src/base/netlist_writer.h @@ -8,11 +8,13 @@ #include "AnalysisDelayCalculator.h" -//Writes out the post-synthesis implementation netlists in BLIF and Verilog formats, -//along with an SDF for delay annotations. -// -//All written filenames end in {basename}_post_synthesis.{fmt} where {basename} is the -//basename argument and {fmt} is the file format (e.g. v, blif, sdf) +/** + * @brief Writes out the post-synthesis implementation netlists in BLIF and Verilog formats, + * along with an SDF for delay annotations. + * + * All written filenames end in {basename}_post_synthesis.{fmt} where {basename} is the + * basename argument and {fmt} is the file format (e.g. v, blif, sdf) + */ void netlist_writer(const std::string basename, std::shared_ptr delay_calc); #endif