text: #include <fstream>
  #include <iostream>
  #include <sstream>
  #include <set>
  #include <map>
  #include <utility>
  #include <vector>
  #include <string>
  #include <algorithm>
  #include <bitset>
  #include <memory>
  #include <unordered_set>
  #include <cmath>
  
  #include "vtr_assert.h"
  #include "vtr_util.h"
  #include "vtr_log.h"
  #include "vtr_logic.h"
  #include "vtr_version.h"
  
  #include "vpr_error.h"
  
  #include "netlist_walker.h"
  #include "netlist_writer.h"
  
  #include "globals.h"
  #include "atom_netlist.h"
  #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>/vtr_flow/primitives.v, where <vtr> 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
  //#define DEBUG_LUT_MASK
  
  //
  //File local type declarations
  //
  
  /*enum class PortType {
   * IN,
   * OUT,
   * CLOCK
   * };*/
  
  //
  // File local function declarations
  //
  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<std::string>& nets, int depth);
  void print_verilog_port(std::ostream& os, const std::string& port_name, const std::vector<std::string>& nets, PortType type, int depth);
  
  std::string create_unconn_net(size_t& unconn_count);
  std::string escape_verilog_identifier(const std::string& id);
  std::string escape_sdf_identifier(const std::string& id);
  bool is_special_sdf_char(char c);
  std::string join_identifier(const std::string& lhs, const std::string& rhs);
  
  //
  //
  // File local class and function implementations
  //
  //
  
  //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
          : source_name_(std::move(src_port))
          , source_ipin_(src_ipin)
          , sink_name_(std::move(snk_port))
          , sink_ipin_(snk_ipin)
          , delay_(del)
          , condition_(std::move(cond)) {}
  
      //Accessors
      std::string source_name() const { return source_name_; }
      int source_ipin() const { return source_ipin_; }
      std::string sink_name() const { return sink_name_; }
      int sink_ipin() const { return sink_ipin_; }
      double delay() const { return delay_; }
      std::string condition() const { return condition_; }
  
    private:
      std::string source_name_;
      int source_ipin_;
      std::string sink_name_;
      int sink_ipin_;
      double delay_;
      std::string condition_;
  };
  
  //Instance is an interface used to represent an element instantiated in a netlist
  //
  //Instances know how to describe themselves in BLIF, Verilog and SDF
  //
  //This should be subclassed to implement support for new primitive types (although
  //see BlackBoxInst for a general implementation for a black box primitive)
  class Instance {
    public:
      virtual ~Instance() = default;
  
      //Print the current instance in blif format
      //
      //  os - The output stream to print to
      //  unconn_count - The current count of unconnected nets.  BLIF has limitations requiring
      //                 unconnected nets to be used to represent unconnected ports.  To allow
      //                 unique naming of these nets unconn_count is used to uniquify these names.
      //                 Whenever creating an unconnected net (and using unconn_count to uniquify
      //                 its name in the file) unconn_count should be incremented.
      //  depth - Current indentation depth.  This is used to figure-out how much indentation
      //          should be applied.  This is purely for cosmetic formatting.  Use indent() for
      //          generating consistent indentation.
      virtual void print_blif(std::ostream& os, size_t& unconn_count, int depth = 0) = 0;
  
      //Print the current instanse in Verilog, see print_blif() for argument descriptions
      virtual void print_verilog(std::ostream& os, int depth = 0) = 0;
  
      //Print the current instanse in SDF, see print_blif() for argument descriptions
      virtual void print_sdf(std::ostream& os, int depth = 0) = 0;
  };
  
  //An instance representing a Look-Up Table
  class LutInst : public Instance {
    public:                                                               //Public methods
      LutInst(size_t lut_size,                                            //The LUT size
              LogicVec lut_mask,                                          //The LUT mask representing the logic function
              std::string inst_name,                                      //The name of this instance
              std::map<std::string, std::vector<std::string>> port_conns, //The port connections of this instance. Key: port name, Value: connected nets
              std::vector<Arc> timing_arc_values)                         //The timing arcs of this instance
          : type_("LUT_K")
          , lut_size_(lut_size)
          , lut_mask_(std::move(lut_mask))
          , inst_name_(std::move(inst_name))
          , port_conns_(std::move(port_conns))
          , timing_arcs_(std::move(timing_arc_values)) {
      }
  
      //Accessors
      const std::vector<Arc>& timing_arcs() { return timing_arcs_; }
      std::string instance_name() { return inst_name_; }
      std::string type() { return type_; }
  
    public: //Instance interface method implementations
      void print_verilog(std::ostream& os, int depth) override {
          //Instantiate the lut
          os << indent(depth) << type_ << " #(\n";
  
          os << indent(depth + 1) << ".K(" << lut_size_ << "),\n";
  
          std::stringstream param_ss;
          param_ss << lut_mask_;
          os << indent(depth + 1) << ".LUT_MASK(" << param_ss.str() << ")\n";
  
          os << indent(depth) << ") " << escape_verilog_identifier(inst_name_) << " (\n";
  
          VTR_ASSERT(port_conns_.count("in"));
          VTR_ASSERT(port_conns_.count("out"));
          VTR_ASSERT(port_conns_.size() == 2);
  
          print_verilog_port(os, "in", port_conns_["in"], PortType::INPUT, depth + 1);
          os << ","
             << "\n";
          print_verilog_port(os, "out", port_conns_["out"], PortType::OUTPUT, depth + 1);
          os << "\n";
  
          os << indent(depth) << ");\n\n";
      }
  
      void print_blif(std::ostream& os, size_t& unconn_count, int depth) override {
          os << indent(depth) << ".names ";
  
          //Input nets
          for (const auto& net : port_conns_["in"]) {
              if (net == "") {
                  //Disconnected
                  os << create_unconn_net(unconn_count) << " ";
              } else {
                  os << net << " ";
              }
          }
  
          VTR_ASSERT(port_conns_["out"].size() == 1);
  
          //Output net
          auto out_net = port_conns_["out"][0];
          if (out_net == "") {
              //Disconnected
              os << create_unconn_net(unconn_count) << " ";
          } else {
              os << out_net << " ";
          }
          os << "\n";
  
          //Write out the truth table.
          //
          // We can write out the truth table as either a set of minterms (where the function is true),
          // or a set of maxterms (where the function is false). We choose the representation which produces
          // the fewest terms (and is smallest to represent in BLIF).
  
          //Count the number of set minterms
          size_t num_set_minterms = 0;
          for (size_t i = 0; i < lut_mask_.size(); ++i) {
              if (lut_mask_[i] == vtr::LogicValue::TRUE) {
                  ++num_set_minterms;
              }
          }
  
          vtr::LogicValue output_value;
          if (num_set_minterms < lut_mask_.size() / 2) {
              //Less than half the minterms are true, so
              //output minterms
              output_value = vtr::LogicValue::TRUE;
          } else {
              //Half-or-more minterms are true, so
              //output maxterms
              output_value = vtr::LogicValue::FALSE;
          }
  
          size_t minterms_set = 0;
          size_t maxterms_set = 0;
          for (size_t minterm = 0; minterm < lut_mask_.size(); minterm++) {
              if (lut_mask_[minterm] == output_value) {
                  //Convert the minterm to a string of bits
                  std::string bit_str = std::bitset<64>(minterm).to_string();
  
                  //Because BLIF puts the input values in order from LSB (left) to MSB (right), we need
                  //to reverse the string
                  std::reverse(bit_str.begin(), bit_str.end());
  
                  //Trim to the LUT size
                  std::string input_values(bit_str.begin(), bit_str.begin() + (lut_size_));
  
                  //Add the row as true
                  os << indent(depth) << input_values;
  
                  //Specify whether this is a minterm (on-set) or max-term (off-set)
                  if (output_value == vtr::LogicValue::TRUE) {
                      os << " 1\n";
                      minterms_set++;
                  } else {
                      VTR_ASSERT(output_value == vtr::LogicValue::FALSE);
                      os << " 0\n";
                      maxterms_set++;
                  }
              }
          }
          if (minterms_set == 0 && maxterms_set == 0) {
              //Handle the always true/false case
              for (size_t i = 0; i < port_conns_["in"].size(); ++i) {
                  os << "-"; //Don't care for all inputs
              }
  
              if (output_value == vtr::LogicValue::TRUE) {
                  //Always false
                  os << " 0\n";
              } else {
                  VTR_ASSERT(output_value == vtr::LogicValue::FALSE);
                  //Always true
                  os << " 1\n";
              }
          }
          os << "\n";
      }
  
      void print_sdf(std::ostream& os, int depth) override {
          os << indent(depth) << "(CELL\n";
          os << indent(depth + 1) << "(CELLTYPE \"" << type() << "\")\n";
          os << indent(depth + 1) << "(INSTANCE " << escape_sdf_identifier(instance_name()) << ")\n";
  
          if (!timing_arcs().empty()) {
              os << indent(depth + 1) << "(DELAY\n";
              os << indent(depth + 2) << "(ABSOLUTE\n";
  
              for (auto& arc : timing_arcs()) {
                  double delay_ps = arc.delay();
  
                  std::stringstream delay_triple;
                  delay_triple << "(" << delay_ps << ":" << delay_ps << ":" << delay_ps << ")";
  
                  os << indent(depth + 3) << "(IOPATH ";
                  //Note we do not escape the last index of multi-bit signals since they are used to
                  //match multi-bit ports
                  os << escape_sdf_identifier(arc.source_name()) << "[" << arc.source_ipin() << "]"
                     << " ";
  
                  VTR_ASSERT(arc.sink_ipin() == 0); //Should only be one output
                  os << escape_sdf_identifier(arc.sink_name()) << " ";
                  os << delay_triple.str() << " " << delay_triple.str() << ")\n";
              }
              os << indent(depth + 2) << ")\n";
              os << indent(depth + 1) << ")\n";
          }
  
          os << indent(depth) << ")\n";
          os << indent(depth) << "\n";
      }
  
    private:
      std::string type_;
      size_t lut_size_;
      LogicVec lut_mask_;
      std::string inst_name_;
      std::map<std::string, std::vector<std::string>> port_conns_;
      std::vector<Arc> timing_arcs_;
  };
  
  class LatchInst : public Instance {
    public: //Public types
      //Types of latches (defined by BLIF)
      enum class Type {
          RISING_EDGE,
          FALLING_EDGE,
          ACTIVE_HIGH,
          ACTIVE_LOW,
          ASYNCHRONOUS,
      };
      friend std::ostream& operator<<(std::ostream& os, const Type& type) {
          if (type == Type::RISING_EDGE)
              os << "re";
          else if (type == Type::FALLING_EDGE)
              os << "fe";
          else if (type == Type::ACTIVE_HIGH)
              os << "ah";
          else if (type == Type::ACTIVE_LOW)
              os << "al";
          else if (type == Type::ASYNCHRONOUS)
              os << "as";
          else
              VTR_ASSERT(false);
          return os;
      }
      friend std::istream& operator>>(std::istream& is, Type& type) {
          std::string tok;
          is >> tok;
          if (tok == "re")
              type = Type::RISING_EDGE;
          else if (tok == "fe")
              type = Type::FALLING_EDGE;
          else if (tok == "ah")
              type = Type::ACTIVE_HIGH;
          else if (tok == "al")
              type = Type::ACTIVE_LOW;
          else if (tok == "as")
              type = Type::ASYNCHRONOUS;
          else
              VTR_ASSERT(false);
          return is;
      }
  
    public:
      LatchInst(std::string inst_name,                                  //Name of this instance
                std::map<std::string, std::string> 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<double>::quiet_NaN(),  //Clock-to-Q delay
                double tsu = std::numeric_limits<double>::quiet_NaN(),  //Setup time
                double thld = std::numeric_limits<double>::quiet_NaN()) //Hold time
          : instance_name_(std::move(inst_name))
          , port_connections_(std::move(port_conns))
          , type_(type)
          , initial_value_(init_value)
          , tcq_(tcq)
          , tsu_(tsu)
          , thld_(thld) {}
  
      void print_blif(std::ostream& os, size_t& /*unconn_count*/, int depth = 0) override {
          os << indent(depth) << ".latch"
             << " ";
  
          //Input D port
          auto d_port_iter = port_connections_.find("D");
          VTR_ASSERT(d_port_iter != port_connections_.end());
          os << d_port_iter->second << " ";
  
          //Output Q port
          auto q_port_iter = port_connections_.find("Q");
          VTR_ASSERT(q_port_iter != port_connections_.end());
          os << q_port_iter->second << " ";
  
          //Latch type
          os << type_ << " "; //Type, i.e. rising-edge
  
          //Control input
          auto control_port_iter = port_connections_.find("clock");
          VTR_ASSERT(control_port_iter != port_connections_.end());
          os << control_port_iter->second << " "; //e.g. clock
          os << (int)initial_value_ << " ";       //Init value: e.g. 2=don't care
          os << "\n";
      }
  
      void print_verilog(std::ostream& os, int depth = 0) override {
          //Currently assume a standard DFF
          VTR_ASSERT(type_ == Type::RISING_EDGE);
  
          os << indent(depth) << "DFF"
             << " #(\n";
          os << indent(depth + 1) << ".INITIAL_VALUE(";
          if (initial_value_ == vtr::LogicValue::TRUE)
              os << "1'b1";
          else if (initial_value_ == vtr::LogicValue::FALSE)
              os << "1'b0";
          else if (initial_value_ == vtr::LogicValue::DONT_CARE)
              os << "1'bx";
          else if (initial_value_ == vtr::LogicValue::UNKOWN)
              os << "1'bx";
          else
              VTR_ASSERT(false);
          os << ")\n";
          os << indent(depth) << ") " << escape_verilog_identifier(instance_name_) << " (\n";
  
          for (auto iter = port_connections_.begin(); iter != port_connections_.end(); ++iter) {
              os << indent(depth + 1) << "." << iter->first << "(" << escape_verilog_identifier(iter->second) << ")";
  
              if (iter != --port_connections_.end()) {
                  os << ", ";
              }
              os << "\n";
          }
          os << indent(depth) << ");";
          os << "\n";
      }
  
      void print_sdf(std::ostream& os, int depth = 0) override {
          VTR_ASSERT(type_ == Type::RISING_EDGE);
  
          os << indent(depth) << "(CELL\n";
          os << indent(depth + 1) << "(CELLTYPE \""
             << "DFF"
             << "\")\n";
          os << indent(depth + 1) << "(INSTANCE " << escape_sdf_identifier(instance_name_) << ")\n";
  
          //Clock to Q
          if (!std::isnan(tcq_)) {
              os << indent(depth + 1) << "(DELAY\n";
              os << indent(depth + 2) << "(ABSOLUTE\n";
              double delay_ps = get_delay_ps(tcq_);
  
              std::stringstream delay_triple;
              delay_triple << "(" << delay_ps << ":" << delay_ps << ":" << delay_ps << ")";
  
              os << indent(depth + 3) << "(IOPATH "
                 << "(posedge clock) Q " << delay_triple.str() << " " << delay_triple.str() << ")\n";
              os << indent(depth + 2) << ")\n";
              os << indent(depth + 1) << ")\n";
          }
  
          //Setup/Hold
          if (!std::isnan(tsu_) || !std::isnan(thld_)) {
              os << indent(depth + 1) << "(TIMINGCHECK\n";
              if (!std::isnan(tsu_)) {
                  std::stringstream setup_triple;
                  double setup_ps = get_delay_ps(tsu_);
                  setup_triple << "(" << setup_ps << ":" << setup_ps << ":" << setup_ps << ")";
                  os << indent(depth + 2) << "(SETUP D (posedge clock) " << setup_triple.str() << ")\n";
              }
              if (!std::isnan(thld_)) {
                  std::stringstream hold_triple;
                  double hold_ps = get_delay_ps(thld_);
                  hold_triple << "(" << hold_ps << ":" << hold_ps << ":" << hold_ps << ")";
                  os << indent(depth + 2) << "(HOLD D (posedge clock) " << hold_triple.str() << ")\n";
              }
          }
          os << indent(depth + 1) << ")\n";
          os << indent(depth) << ")\n";
          os << indent(depth) << "\n";
      }
  
    private:
      std::string instance_name_;
      std::map<std::string, std::string> port_connections_;
      Type type_;
      vtr::LogicValue initial_value_;
      double tcq_;  //Clock delay + tcq
      double tsu_;  //Setup time
      double thld_; //Hold time
  };
  
  class BlackBoxInst : public Instance {
    public:
      BlackBoxInst(std::string type_name,                                             //Instance type
                   std::string inst_name,                                             //Instance name
                   std::map<std::string, std::string> params,                         //Verilog parameters: Dictonary of <param_name,value>
                   std::map<std::string, std::string> attrs,                          //Instance attributes: Dictonary of <attr_name,value>
                   std::map<std::string, std::vector<std::string>> input_port_conns,  //Port connections: Dictionary of <port,nets>
                   std::map<std::string, std::vector<std::string>> output_port_conns, //Port connections: Dictionary of <port,nets>
                   std::vector<Arc> timing_arcs,                                      //Combinational timing arcs
                   std::map<std::string, double> ports_tsu,                           //Port setup checks
                   std::map<std::string, double> ports_tcq)                           //Port clock-to-q delays
          : type_name_(std::move(type_name))
          , inst_name_(std::move(inst_name))
          , params_(std::move(params))
          , attrs_(std::move(attrs))
          , input_port_conns_(std::move(input_port_conns))
          , output_port_conns_(std::move(output_port_conns))
          , timing_arcs_(std::move(timing_arcs))
          , ports_tsu_(std::move(ports_tsu))
          , ports_tcq_(std::move(ports_tcq)) {}
  
      void print_blif(std::ostream& os, size_t& unconn_count, int depth = 0) override {
          os << indent(depth) << ".subckt " << type_name_ << " \\"
             << "\n";
  
          //Input ports
          for (auto iter = input_port_conns_.begin(); iter != input_port_conns_.end(); ++iter) {
              const auto& port_name = iter->first;
              const auto& nets = iter->second;
              print_blif_port(os, unconn_count, port_name, nets, depth + 1);
  
              if (!(iter == --input_port_conns_.end() && output_port_conns_.empty())) {
                  os << " \\";
              }
              os << "\n";
          }
  
          //Output ports
          for (auto iter = output_port_conns_.begin(); iter != output_port_conns_.end(); ++iter) {
              const auto& port_name = iter->first;
              const auto& nets = iter->second;
              print_blif_port(os, unconn_count, port_name, nets, depth + 1);
  
              if (!(iter == --output_port_conns_.end())) {
                  os << " \\";
              }
              os << "\n";
          }
  
          // Params
          for (auto iter = params_.begin(); iter != params_.end(); ++iter) {
              os << ".param " << iter->first << " " << iter->second << "\n";
          }
  
          // Attrs
          for (auto iter = attrs_.begin(); iter != attrs_.end(); ++iter) {
              os << ".attr " << iter->first << " " << iter->second << "\n";
          }
  
          os << "\n";
      }
  
      void print_verilog(std::ostream& os, int depth = 0) override {
          //Instance type
          os << indent(depth) << type_name_ << " #(\n";
  
          //Verilog parameters
          for (auto iter = params_.begin(); iter != params_.end(); ++iter) {
              os << indent(depth + 1) << "." << iter->first << "(" << iter->second << ")";
              if (iter != --params_.end()) {
                  os << ",";
              }
              os << "\n";
          }
  
          //Instance name
          os << indent(depth) << ") " << escape_verilog_identifier(inst_name_) << " (\n";
  
          //Input Port connections
          for (auto iter = input_port_conns_.begin(); iter != input_port_conns_.end(); ++iter) {
              auto& port_name = iter->first;
              auto& nets = iter->second;
              print_verilog_port(os, port_name, nets, PortType::INPUT, depth + 1);
              if (!(iter == --input_port_conns_.end() && output_port_conns_.empty())) {
                  os << ",";
              }
              os << "\n";
          }
  
          //Output Port connections
          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);
              if (!(iter == --output_port_conns_.end())) {
                  os << ",";
              }
              os << "\n";
          }
          os << indent(depth) << ");\n";
          os << "\n";
      }
  
      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()) {
              os << indent(depth + 2) << "(ABSOLUTE\n";
  
              //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 << ")";
  
                  //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";
              }
  
              //Clock-to-Q delays
              for (const auto& kv : ports_tcq_) {
                  double clock_to_q_ps = get_delay_ps(kv.second);
  
                  std::stringstream delay_triple;
                  delay_triple << "(" << clock_to_q_ps << ":" << clock_to_q_ps << ":" << clock_to_q_ps << ")";
  
                  os << indent(depth + 3) << "(IOPATH (posedge clock) " << escape_sdf_identifier(kv.first) << " " << delay_triple.str() << " " << delay_triple.str() << ")\n";
              }
              os << indent(depth + 2) << ")\n"; //ABSOLUTE
          }
          os << indent(depth + 1) << ")\n"; //DELAY
  
          if (!ports_tsu_.empty() || !ports_thld_.empty()) {
              //Setup checks
              os << indent(depth + 1) << "(TIMINGCHECK\n";
              for (const 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 (const 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(const std::string& port_name) {
          auto iter = input_port_conns_.find(port_name);
          if (iter != input_port_conns_.end()) {
              return iter->second.size();
          }
  
          iter = output_port_conns_.find(port_name);
          if (iter != output_port_conns_.end()) {
              return iter->second.size();
          }
          VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
                          "Could not find port %s on %s of type %s\n", port_name.c_str(), inst_name_.c_str(), type_name_.c_str());
  
          return -1; //Suppress warning
      }
  
    private:
      std::string type_name_;
      std::string inst_name_;
      std::map<std::string, std::string> params_;
      std::map<std::string, std::string> attrs_;
      std::map<std::string, std::vector<std::string>> input_port_conns_;
      std::map<std::string, std::vector<std::string>> output_port_conns_;
      std::vector<Arc> timing_arcs_;
      std::map<std::string, double> ports_tsu_;
      std::map<std::string, double> ports_thld_;
      std::map<std::string, double> 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'
  class Assignment {
    public:
      Assignment(std::string lval, //The left value (assigned to)
                 std::string rval) //The right value (assigned from)
          : lval_(std::move(lval))
          , rval_(std::move(rval)) {}
  
      void print_verilog(std::ostream& os, const std::string& indent) {
          os << indent << "assign " << escape_verilog_identifier(lval_) << " = " << escape_verilog_identifier(rval_) << ";\n";
      }
      void print_blif(std::ostream& os, const std::string& indent) {
          os << indent << ".names " << rval_ << " " << lval_ << "\n";
          os << indent << "1 1\n";
      }
  
    private:
      std::string lval_;
      std::string rval_;
  };
  
  //A class which writes post-synthesis netlists (Verilog and BLIF) and the SDF
  //
  //It implements the NetlistVisitor interface used by NetlistWalker (see netlist_walker.h)
  class NetlistWriterVisitor : public NetlistVisitor {
    public:                                          //Public interface
      NetlistWriterVisitor(std::ostream& verilog_os, //Output stream for verilog netlist
                           std::ostream& blif_os,    //Output stream for blif netlist
                           std::ostream& sdf_os,     //Output stream for SDF
                           std::shared_ptr<const AnalysisDelayCalculator> delay_calc)
          : verilog_os_(verilog_os)
          , blif_os_(blif_os)
          , sdf_os_(sdf_os)
          , delay_calc_(std::move(delay_calc)) {
          auto& atom_ctx = g_vpr_ctx.atom();
  
          //Initialize the pin to tnode look-up
          for (AtomPinId pin : atom_ctx.nlist.pins()) {
              AtomBlockId blk = atom_ctx.nlist.pin_block(pin);
              ClusterBlockId clb_idx = atom_ctx.lookup.atom_clb(blk);
  
              const t_pb_graph_pin* gpin = atom_ctx.lookup.atom_pin_pb_graph_pin(pin);
              VTR_ASSERT(gpin);
              int pb_pin_idx = gpin->pin_count_in_cluster;
  
              tatum::NodeId tnode_id = atom_ctx.lookup.atom_pin_tnode(pin);
  
              auto key = std::make_pair(clb_idx, pb_pin_idx);
              auto value = std::make_pair(key, tnode_id);
              auto ret = pin_id_to_tnode_lookup_.insert(value);
              VTR_ASSERT_MSG(ret.second, "Was inserted");
          }
      }
  
      //Non copyable/assignable/moveable
      NetlistWriterVisitor(NetlistWriterVisitor& other) = delete;
      NetlistWriterVisitor(NetlistWriterVisitor&& other) = delete;
      NetlistWriterVisitor& operator=(NetlistWriterVisitor& rhs) = delete;
      NetlistWriterVisitor& operator=(NetlistWriterVisitor&& rhs) = delete;
  
    private: //Internal types
    private: //NetlistVisitor interface functions
      void visit_top_impl(const char* top_level_name) override {
          top_module_name_ = top_level_name;
      }
  
      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)) {
              inputs_.emplace_back(make_io(atom, PortType::INPUT));
          } else if (model->name == std::string(MODEL_OUTPUT)) {
              outputs_.emplace_back(make_io(atom, PortType::OUTPUT));
          } 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));
          }
      }
  
      void finish_impl() override {
          print_verilog();
          print_blif();
          print_sdf();
      }
  
    private: //Internal Helper functions
      //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";
  
          //Primary Inputs
          for (auto iter = inputs_.begin(); iter != inputs_.end(); ++iter) {
              verilog_os_ << indent(depth + 1) << "input " << escape_verilog_identifier(*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) {
              verilog_os_ << indent(depth + 1) << "output " << escape_verilog_identifier(*iter);
              if (iter + 1 != outputs_.end()) {
                  verilog_os_ << ",";
              }
              verilog_os_ << "\n";
          }
          verilog_os_ << indent(depth) << ");\n";
  
          //Wire declarations
          verilog_os_ << "\n";
          verilog_os_ << indent(depth + 1) << "//Wires\n";
          for (auto& kv : logical_net_drivers_) {
              verilog_os_ << indent(depth + 1) << "wire " << escape_verilog_identifier(kv.second.first) << ";\n";
          }
          for (auto& kv : logical_net_sinks_) {
              for (auto& wire_tnode_pair : kv.second) {
                  verilog_os_ << indent(depth + 1) << "wire " << escape_verilog_identifier(wire_tnode_pair.first) << ";\n";
              }
          }
  
          //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));
          }
  
          //Interconnect between cell instances
          verilog_os_ << "\n";
          verilog_os_ << indent(depth + 1) << "//Interconnect\n";
          for (const auto& kv : logical_net_sinks_) {
              auto atom_net_id = kv.first;
              auto driver_iter = logical_net_drivers_.find(atom_net_id);
              VTR_ASSERT(driver_iter != logical_net_drivers_.end());
              const auto& driver_wire = driver_iter->second.first;
  
              for (auto& sink_wire_tnode_pair : kv.second) {
                  std::string inst_name = interconnect_name(driver_wire, sink_wire_tnode_pair.first);
                  verilog_os_ << indent(depth + 1) << "fpga_interconnect " << escape_verilog_identifier(inst_name) << " (\n";
                  verilog_os_ << indent(depth + 2) << ".datain(" << escape_verilog_identifier(driver_wire) << "),\n";
                  verilog_os_ << indent(depth + 2) << ".dataout(" << escape_verilog_identifier(sink_wire_tnode_pair.first) << ")\n";
                  verilog_os_ << indent(depth + 1) << ");\n\n";
              }
          }
  
          //All the cell instances
          verilog_os_ << "\n";
          verilog_os_ << indent(depth + 1) << "//Cell instances\n";
          for (auto& inst : cell_instances_) {
              inst->print_verilog(verilog_os_, depth + 1);
          }
  
          verilog_os_ << "\n";
          verilog_os_ << indent(depth) << "endmodule\n";
      }
  
      //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";
  
          //Primary inputs
          blif_os_ << indent(depth) << ".inputs ";
          for (auto iter = inputs_.begin(); iter != inputs_.end(); ++iter) {
              blif_os_ << *iter << " ";
          }
          blif_os_ << "\n";
  
          //Primary outputs
          blif_os_ << indent(depth) << ".outputs ";
          for (auto iter = outputs_.begin(); iter != outputs_.end(); ++iter) {
              blif_os_ << *iter << " ";
          }
          blif_os_ << "\n";
  
          //I/O assignments
          blif_os_ << "\n";
          blif_os_ << indent(depth) << "#IO assignments\n";
          for (auto& assign : assignments_) {
              assign.print_blif(blif_os_, indent(depth));
          }
  
          //All interconnect between elements
          blif_os_ << "\n";
          blif_os_ << indent(depth) << "#Interconnect\n";
          for (const auto& kv : logical_net_sinks_) {
              auto atom_net_id = kv.first;
              auto driver_iter = logical_net_drivers_.find(atom_net_id);
              VTR_ASSERT(driver_iter != logical_net_drivers_.end());
              const auto& driver_wire = driver_iter->second.first;
  
              for (auto& sink_wire_tnode_pair : kv.second) {
                  blif_os_ << indent(depth) << ".names " << driver_wire << " " << sink_wire_tnode_pair.first << "\n";
                  blif_os_ << indent(depth) << "1 1\n";
              }
          }
  
          //The cells
          blif_os_ << "\n";
          blif_os_ << indent(depth) << "#Cell instances\n";
          size_t unconn_count = 0;
          for (auto& inst : cell_instances_) {
              inst->print_blif(blif_os_, unconn_count);
          }
  
          blif_os_ << "\n";
          blif_os_ << indent(depth) << ".end\n";
      }
  
      //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";
          sdf_os_ << indent(depth + 1) << "(DESIGN \"" << top_module_name_ << "\")\n";
          sdf_os_ << indent(depth + 1) << "(VENDOR \"verilog-to-routing\")\n";
          sdf_os_ << indent(depth + 1) << "(PROGRAM \"vpr\")\n";
          sdf_os_ << indent(depth + 1) << "(VERSION \"" << vtr::VERSION << "\")\n";
          sdf_os_ << indent(depth + 1) << "(DIVIDER /)\n";
          sdf_os_ << indent(depth + 1) << "(TIMESCALE 1 ps)\n";
          sdf_os_ << "\n";
  
          //Interconnect
          for (const auto& kv : logical_net_sinks_) {
              auto atom_net_id = kv.first;
              auto driver_iter = logical_net_drivers_.find(atom_net_id);
              VTR_ASSERT(driver_iter != logical_net_drivers_.end());
              auto driver_wire = driver_iter->second.first;
              auto driver_tnode = driver_iter->second.second;
  
              for (auto& sink_wire_tnode_pair : kv.second) {
                  auto sink_wire = sink_wire_tnode_pair.first;
                  auto sink_tnode = sink_wire_tnode_pair.second;
  
                  sdf_os_ << indent(depth + 1) << "(CELL\n";
                  sdf_os_ << indent(depth + 2) << "(CELLTYPE \"fpga_interconnect\")\n";
                  sdf_os_ << indent(depth + 2) << "(INSTANCE " << escape_sdf_identifier(interconnect_name(driver_wire, sink_wire)) << ")\n";
                  sdf_os_ << indent(depth + 2) << "(DELAY\n";
                  sdf_os_ << indent(depth + 3) << "(ABSOLUTE\n";
  
                  double delay = get_delay_ps(driver_tnode, sink_tnode);
  
                  std::stringstream delay_triple;
                  delay_triple << "(" << delay << ":" << delay << ":" << delay << ")";
  
                  sdf_os_ << indent(depth + 4) << "(IOPATH datain dataout " << delay_triple.str() << " " << delay_triple.str() << ")\n";
                  sdf_os_ << indent(depth + 3) << ")\n";
                  sdf_os_ << indent(depth + 2) << ")\n";
                  sdf_os_ << indent(depth + 1) << ")\n";
                  sdf_os_ << indent(depth) << "\n";
              }
          }
  
          //Cells
          for (const auto& inst : cell_instances_) {
              inst->print_sdf(sdf_os_, depth + 1);
          }
  
          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
  
          std::string wire_name = std::move(inst_name);
          if (port_type == PortType::INPUT) {
              wire_name = join_identifier(wire_name, "input");
          } else if (port_type == PortType::CLOCK) {
              wire_name = join_identifier(wire_name, "clock");
          } else {
              VTR_ASSERT(port_type == PortType::OUTPUT);
              wire_name = join_identifier(wire_name, "output");
          }
  
          wire_name = join_identifier(wire_name, std::to_string(port_idx));
          wire_name = join_identifier(wire_name, std::to_string(pin_idx));
  
          auto value = std::make_pair(wire_name, tnode_id);
          if (port_type == PortType::INPUT || port_type == PortType::CLOCK) {
              //Add the sink
              logical_net_sinks_[atom_net_id].push_back(value);
  
          } else {
              //Add the driver
              VTR_ASSERT(port_type == PortType::OUTPUT);
  
              auto ret = logical_net_drivers_.insert(std::make_pair(atom_net_id, value));
              VTR_ASSERT(ret.second); //Was inserted, drivers are unique
          }
  
          return wire_name;
      }
  
      //Returns the name of a circuit-level Input/Output
      //The I/O is recorded and instantiated by the top level output routines
      std::string make_io(const t_pb* atom, //The implementation primitive representing the I/O
                          PortType dir) {   //The IO direction
  
          const t_pb_graph_node* pb_graph_node = atom->pb_graph_node;
  
          std::string io_name;
          int cluster_pin_idx = -1;
          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 = atom->name;
  
          } 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 = atom->name + 4;
          }
  
          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, io_name, wire_dir, 0, 0);
  
              //Connect the wires to to I/Os with assign statements
              if (wire_dir == PortType::INPUT) {
                  assignments_.emplace_back(io_name, wire_name);
              } else {
                  assignments_.emplace_back(wire_name, io_name);
              }
          }
  
          return io_name;
      }
  
      //Returns an Instance object representing the LUT
      std::shared_ptr<Instance> make_lut_instance(const t_pb* atom) {
          //Determine what size LUT
          int lut_size = find_num_inputs(atom);
  
          //Determine the truth table
          auto lut_mask = load_lut_mask(lut_size, atom);
  
          //Determine the instance name
          auto inst_name = join_identifier("lut", atom->name);
  
          //Determine the port connections
          std::map<std::string, std::vector<std::string>> port_conns;
  
          const t_pb_graph_node* pb_graph_node = atom->pb_graph_node;
          VTR_ASSERT(pb_graph_node->num_input_ports == 1); //LUT has one input port
  
          const auto& top_pb_route = find_top_pb_route(atom);
  
          VTR_ASSERT(pb_graph_node->num_output_ports == 1);                                 //LUT has one output port
          VTR_ASSERT(pb_graph_node->num_output_pins[0] == 1);                               //LUT has one output pin
          int sink_cluster_pin_idx = pb_graph_node->output_pins[0][0].pin_count_in_cluster; //Unique pin index in cluster
  
          //Add inputs connections
          std::vector<Arc> timing_arcs;
          for (int pin_idx = 0; pin_idx < pb_graph_node->num_input_pins[0]; pin_idx++) {
              int cluster_pin_idx = pb_graph_node->input_pins[0][pin_idx].pin_count_in_cluster; //Unique pin index in cluster
  
              std::string net;
              if (!top_pb_route.count(cluster_pin_idx)) {
                  //Disconnected
                  net = "";
              } else {
                  //Connected to a net
                  auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id; //Connected net in atom netlist
                  VTR_ASSERT(atom_net_id);
  
                  //Look up the tnode associated with this pin (used for delay calculation)
                  tatum::NodeId src_tnode_id = find_tnode(atom, cluster_pin_idx);
                  tatum::NodeId sink_tnode_id = find_tnode(atom, sink_cluster_pin_idx);
  
                  net = make_inst_wire(atom_net_id, src_tnode_id, inst_name, PortType::INPUT, 0, pin_idx);
  
                  //Record the timing arc
                  float delay = get_delay_ps(src_tnode_id, sink_tnode_id);
  
                  Arc timing_arc("in", pin_idx, "out", 0, delay);
  
                  timing_arcs.push_back(timing_arc);
              }
              port_conns["in"].push_back(net);
          }
  
          //Add the single output connection
          {
              auto atom_net_id = top_pb_route[sink_cluster_pin_idx].atom_net_id; //Connected net in atom netlist
  
              std::string net;
              if (!atom_net_id) {
                  //Disconnected
                  net = "";
              } else {
                  //Connected to a net
  
                  //Look up the tnode associated with this pin (used for delay calculation)
                  tatum::NodeId tnode_id = find_tnode(atom, sink_cluster_pin_idx);
  
                  net = make_inst_wire(atom_net_id, tnode_id, inst_name, PortType::OUTPUT, 0, 0);
              }
              port_conns["out"].push_back(net);
          }
  
          auto inst = std::make_shared<LutInst>(lut_size, lut_mask, inst_name, port_conns, timing_arcs);
  
          return inst;
      }
  
      //Returns an Instance object representing the Latch
      std::shared_ptr<Instance> make_latch_instance(const t_pb* atom) {
          std::string inst_name = join_identifier("latch", atom->name);
  
          const auto& top_pb_route = find_top_pb_route(atom);
          const t_pb_graph_node* pb_graph_node = atom->pb_graph_node;
  
          //We expect a single input, output and clock ports
          VTR_ASSERT(pb_graph_node->num_input_ports == 1);
          VTR_ASSERT(pb_graph_node->num_output_ports == 1);
          VTR_ASSERT(pb_graph_node->num_clock_ports == 1);
          VTR_ASSERT(pb_graph_node->num_input_pins[0] == 1);
          VTR_ASSERT(pb_graph_node->num_output_pins[0] == 1);
          VTR_ASSERT(pb_graph_node->num_clock_pins[0] == 1);
  
          //The connections
          std::map<std::string, std::string> port_conns;
  
          //Input (D)
          int input_cluster_pin_idx = pb_graph_node->input_pins[0][0].pin_count_in_cluster; //Unique pin index in cluster
          VTR_ASSERT(top_pb_route.count(input_cluster_pin_idx));
          auto input_atom_net_id = top_pb_route[input_cluster_pin_idx].atom_net_id; //Connected net in atom netlist
          std::string input_net = make_inst_wire(input_atom_net_id, find_tnode(atom, input_cluster_pin_idx), inst_name, PortType::INPUT, 0, 0);
          port_conns["D"] = input_net;
  
          double tsu = pb_graph_node->input_pins[0][0].tsu;
  
          //Output (Q)
          int output_cluster_pin_idx = pb_graph_node->output_pins[0][0].pin_count_in_cluster; //Unique pin index in cluster
          VTR_ASSERT(top_pb_route.count(output_cluster_pin_idx));
          auto output_atom_net_id = top_pb_route[output_cluster_pin_idx].atom_net_id; //Connected net in atom netlist
          std::string output_net = make_inst_wire(output_atom_net_id, find_tnode(atom, output_cluster_pin_idx), inst_name, PortType::OUTPUT, 0, 0);
          port_conns["Q"] = output_net;
  
          double tcq = pb_graph_node->output_pins[0][0].tco_max;
  
          //Clock (control)
          int control_cluster_pin_idx = pb_graph_node->clock_pins[0][0].pin_count_in_cluster; //Unique pin index in cluster
          VTR_ASSERT(top_pb_route.count(control_cluster_pin_idx));
          auto control_atom_net_id = top_pb_route[control_cluster_pin_idx].atom_net_id; //Connected net in atom netlist
          std::string control_net = make_inst_wire(control_atom_net_id, find_tnode(atom, control_cluster_pin_idx), inst_name, PortType::CLOCK, 0, 0);
          port_conns["clock"] = control_net;
  
          //VPR currently doesn't store enough information to determine these attributes,
          //for now assume reasonable defaults.
          LatchInst::Type type = LatchInst::Type::RISING_EDGE;
          vtr::LogicValue init_value = vtr::LogicValue::FALSE;
  
          return std::make_shared<LatchInst>(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
      std::shared_ptr<Instance> 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;
          const t_pb_type* pb_type = pb_graph_node->pb_type;
  
          VTR_ASSERT(pb_type->class_type == MEMORY_CLASS);
  
          std::string type = pb_type->model->name;
          std::string inst_name = join_identifier(type, atom->name);
          std::map<std::string, std::string> params;
          std::map<std::string, std::string> attrs;
          std::map<std::string, std::vector<std::string>> input_port_conns;
          std::map<std::string, std::vector<std::string>> output_port_conns;
          std::vector<Arc> timing_arcs;
          std::map<std::string, double> ports_tsu;
          std::map<std::string, double> ports_tcq;
  
          params["ADDR_WIDTH"] = "0";
          params["DATA_WIDTH"] = "0";
  
          //Process the input ports
          for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) {
              for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) {
                  const t_pb_graph_pin* pin = &pb_graph_node->input_pins[iport][ipin];
                  const t_port* port = pin->port;
                  std::string port_class = port->port_class;
  
                  int cluster_pin_idx = pin->pin_count_in_cluster;
                  std::string net;
                  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);
                      net = make_inst_wire(atom_net_id, find_tnode(atom, cluster_pin_idx), inst_name, PortType::INPUT, iport, ipin);
                  }
  
                  //RAMs use a port class specification to identify the purpose of each port
                  std::string port_name;
                  if (port_class == "address") {
                      port_name = "addr";
                      params["ADDR_WIDTH"] = std::to_string(port->num_pins);
                  } else if (port_class == "address1") {
                      port_name = "addr1";
                      params["ADDR_WIDTH"] = std::to_string(port->num_pins);
                  } else if (port_class == "address2") {
                      port_name = "addr2";
                      params["ADDR_WIDTH"] = std::to_string(port->num_pins);
                  } else if (port_class == "data_in") {
                      port_name = "data";
                      params["DATA_WIDTH"] = std::to_string(port->num_pins);
                  } else if (port_class == "data_in1") {
                      port_name = "data1";
                      params["DATA_WIDTH"] = std::to_string(port->num_pins);
                  } else if (port_class == "data_in2") {
                      port_name = "data2";
                      params["DATA_WIDTH"] = std::to_string(port->num_pins);
                  } else if (port_class == "write_en") {
                      port_name = "we";
                  } else if (port_class == "write_en1") {
                      port_name = "we1";
                  } else if (port_class == "write_en2") {
                      port_name = "we2";
                  } else {
                      VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
                                      "Unrecognized input port class '%s' for primitive '%s' (%s)\n", port_class.c_str(), atom->name, pb_type->name);
                  }
  
                  input_port_conns[port_name].push_back(net);
                  ports_tsu[port_name] = pin->tsu;
              }
          }
  
          //Process the output ports
          for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) {
              for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) {
                  const t_pb_graph_pin* pin = &pb_graph_node->output_pins[iport][ipin];
                  const t_port* port = pin->port;
                  std::string port_class = port->port_class;
  
                  int cluster_pin_idx = pin->pin_count_in_cluster;
  
                  std::string net;
                  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);
                      net = make_inst_wire(atom_net_id, find_tnode(atom, cluster_pin_idx), inst_name, PortType::OUTPUT, iport, ipin);
                  }
  
                  std::string port_name;
                  if (port_class == "data_out") {
                      port_name = "out";
                  } else if (port_class == "data_out1") {
                      port_name = "out1";
                  } else if (port_class == "data_out2") {
                      port_name = "out2";
                  } else {
                      VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
                                      "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;
              }
          }
  
          //Process the clock ports
          for (int iport = 0; iport < pb_graph_node->num_clock_ports; ++iport) {
              VTR_ASSERT(pb_graph_node->num_clock_pins[iport] == 1); //Expect a single clock port
  
              for (int ipin = 0; ipin < pb_graph_node->num_clock_pins[iport]; ++ipin) {
                  const t_pb_graph_pin* pin = &pb_graph_node->clock_pins[iport][ipin];
                  const t_port* port = pin->port;
                  std::string port_class = port->port_class;
  
                  int cluster_pin_idx = pin->pin_count_in_cluster;
                  VTR_ASSERT(top_pb_route.count(cluster_pin_idx));
                  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 = make_inst_wire(atom_net_id, find_tnode(atom, cluster_pin_idx), inst_name, PortType::CLOCK, iport, ipin);
  
                  if (port_class == "clock") {
                      VTR_ASSERT(pb_graph_node->num_clock_pins[iport] == 1); //Expect a single clock pin
                      input_port_conns["clock"].push_back(net);
                  } else {
                      VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
                                      "Unrecognized input port class '%s' for primitive '%s' (%s)\n", port_class.c_str(), atom->name, pb_type->name);
                  }
              }
          }
  
          return std::make_shared<BlackBoxInst>(type, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_tcq);
      }
  
      //Returns an Instance object representing a Multiplier
      std::shared_ptr<Instance> make_multiply_instance(const t_pb* atom) {
          auto& timing_ctx = g_vpr_ctx.timing();
  
          const auto& top_pb_route = find_top_pb_route(atom);
          const t_pb_graph_node* pb_graph_node = atom->pb_graph_node;
          const t_pb_type* pb_type = pb_graph_node->pb_type;
  
          std::string type_name = pb_type->model->name;
          std::string inst_name = join_identifier(type_name, atom->name);
          std::map<std::string, std::string> params;
          std::map<std::string, std::string> attrs;
          std::map<std::string, std::vector<std::string>> input_port_conns;
          std::map<std::string, std::vector<std::string>> output_port_conns;
          std::vector<Arc> timing_arcs;
          std::map<std::string, double> ports_tsu;
          std::map<std::string, double> ports_tcq;
  
          params["WIDTH"] = "0";
  
          //Delay matrix[sink_tnode] -> tuple of source_port_name, pin index, delay
          std::map<tatum::NodeId, std::vector<std::tuple<std::string, int, double>>> tnode_delay_matrix;
  
          //Process the input ports
          for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) {
              for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) {
                  const t_pb_graph_pin* pin = &pb_graph_node->input_pins[iport][ipin];
                  const t_port* port = pin->port;
                  params["WIDTH"] = std::to_string(port->num_pins); //Assume same width on all ports
  
                  int cluster_pin_idx = pin->pin_count_in_cluster;
  
                  std::string net;
                  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 souce sink tnodes and thier 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);
              }
          }
  
          //Process the output ports
          for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) {
              for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) {
                  const t_pb_graph_pin* pin = &pb_graph_node->output_pins[iport][ipin];
                  const t_port* port = pin->port;
  
                  int cluster_pin_idx = pin->pin_count_in_cluster;
                  std::string net;
                  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);
              }
          }
  
          VTR_ASSERT(pb_graph_node->num_clock_ports == 0); //No clocks
  
          return std::make_shared<BlackBoxInst>(type_name, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_tcq);
      }
  
      //Returns an Instance object representing an Adder
      std::shared_ptr<Instance> make_adder_instance(const t_pb* atom) {
          auto& timing_ctx = g_vpr_ctx.timing();
  
          const auto& top_pb_route = find_top_pb_route(atom);
          const t_pb_graph_node* pb_graph_node = atom->pb_graph_node;
          const t_pb_type* pb_type = pb_graph_node->pb_type;
  
          std::string type_name = pb_type->model->name;
          std::string inst_name = join_identifier(type_name, atom->name);
          std::map<std::string, std::string> params;
          std::map<std::string, std::string> attrs;
          std::map<std::string, std::vector<std::string>> input_port_conns;
          std::map<std::string, std::vector<std::string>> output_port_conns;
          std::vector<Arc> timing_arcs;
          std::map<std::string, double> ports_tsu;
          std::map<std::string, double> ports_tcq;
  
          params["WIDTH"] = "0";
  
          //Delay matrix[sink_tnode] -> tuple of source_port_name, pin index, delay
          std::map<tatum::NodeId, std::vector<std::tuple<std::string, int, double>>> tnode_delay_matrix;
  
          //Process the input ports
          for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) {
              for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) {
                  const t_pb_graph_pin* pin = &pb_graph_node->input_pins[iport][ipin];
                  const t_port* port = pin->port;
  
                  if (port->name == std::string("a") || port->name == std::string("b")) {
                      params["WIDTH"] = std::to_string(port->num_pins); //Assume same width on all ports
                  }
  
                  int cluster_pin_idx = pin->pin_count_in_cluster;
  
                  std::string net;
                  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);
  
                      //Connected
                      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 souce sink tnodes and thier 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);
              }
          }
  
          //Process the output ports
          for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) {
              for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) {
                  const t_pb_graph_pin* pin = &pb_graph_node->output_pins[iport][ipin];
                  const t_port* port = pin->port;
  
                  int cluster_pin_idx = pin->pin_count_in_cluster;
  
                  std::string net;
                  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);
              }
          }
  
          return std::make_shared<BlackBoxInst>(type_name, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_tcq);
      }
  
      std::shared_ptr<Instance> make_blackbox_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;
          const t_pb_type* pb_type = pb_graph_node->pb_type;
  
          std::string type_name = pb_type->model->name;
          std::string inst_name = join_identifier(type_name, atom->name);
          std::map<std::string, std::string> params;
          std::map<std::string, std::string> attrs;
          std::map<std::string, std::vector<std::string>> input_port_conns;
          std::map<std::string, std::vector<std::string>> output_port_conns;
          std::vector<Arc> timing_arcs;
          std::map<std::string, double> ports_tsu;
          std::map<std::string, double> ports_tcq;
  
          //Delay matrix[sink_tnode] -> tuple of source_port_name, pin index, delay
          std::map<tatum::NodeId, std::vector<std::tuple<std::string, int, double>>> tnode_delay_matrix;
  
          //Process the input ports
          for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) {
              for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) {
                  const t_pb_graph_pin* pin = &pb_graph_node->input_pins[iport][ipin];
                  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) {
                      //Disconnected
  
                  } else {
                      //Connected
                      auto src_tnode = find_tnode(atom, cluster_pin_idx);
                      net = make_inst_wire(atom_net_id, src_tnode, inst_name, PortType::INPUT, iport, ipin);
                  }
  
                  input_port_conns[port->name].push_back(net);
              }
          }
  
          //Process the output ports
          for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) {
              for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) {
                  const t_pb_graph_pin* pin = &pb_graph_node->output_pins[iport][ipin];
                  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) {
                      //Disconnected
                      net = "";
                  } else {
                      //Connected
                      auto inode = find_tnode(atom, cluster_pin_idx);
                      net = make_inst_wire(atom_net_id, inode, inst_name, PortType::OUTPUT, iport, ipin);
                  }
  
                  output_port_conns[port->name].push_back(net);
              }
          }
  
          //Process the clock ports
          for (int iport = 0; iport < pb_graph_node->num_clock_ports; ++iport) {
              for (int ipin = 0; ipin < pb_graph_node->num_clock_pins[iport]; ++ipin) {
                  const t_pb_graph_pin* pin = &pb_graph_node->clock_pins[iport][ipin];
                  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) {
                      //Disconnected
                      net = "";
                  } else {
                      //Connected
                      auto src_tnode = find_tnode(atom, cluster_pin_idx);
                      net = make_inst_wire(atom_net_id, src_tnode, inst_name, PortType::CLOCK, iport, ipin);
                  }
  
                  input_port_conns[port->name].push_back(net);
              }
          }
  
          auto& atom_ctx = g_vpr_ctx.atom();
          AtomBlockId blk_id = atom_ctx.lookup.pb_atom(atom);
          for (const auto& param : atom_ctx.nlist.block_params(blk_id)) {
              params[param.first] = param.second;
          }
  
          for (const auto& attr : atom_ctx.nlist.block_attrs(blk_id)) {
              attrs[attr.first] = attr.second;
          }
  
          return std::make_shared<BlackBoxInst>(type_name, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_tcq);
      }
  
      //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
      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;
      }
  
      //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();
  
          AtomBlockId blk_id = atom_ctx.lookup.pb_atom(atom);
          ClusterBlockId clb_index = atom_ctx.lookup.atom_clb(blk_id);
  
          auto key = std::make_pair(clb_index, cluster_pin_idx);
          auto iter = pin_id_to_tnode_lookup_.find(key);
          VTR_ASSERT(iter != pin_id_to_tnode_lookup_.end());
  
          tatum::NodeId tnode_id = iter->second;
          VTR_ASSERT(tnode_id);
  
          return tnode_id;
      }
  
      //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();
  
          const t_model* model = atom_ctx.nlist.block_model(atom_ctx.lookup.pb_atom(atom));
          VTR_ASSERT(model->name == std::string(MODEL_NAMES));
  
  #ifdef DEBUG_LUT_MASK
          std::cout << "Loading LUT mask for: " << atom->name << std::endl;
  #endif
  
          //Figure out how the inputs were permuted (compared to the input netlist)
          std::vector<int> permute = determine_lut_permutation(num_inputs, atom);
  
          //Retrieve the truth table
          const auto& truth_table = atom_ctx.nlist.block_truth_table(atom_ctx.lookup.pb_atom(atom));
  
          //Apply the permutation
          auto permuted_truth_table = permute_truth_table(truth_table, num_inputs, permute);
  
          //Convert to lut mask
          LogicVec lut_mask = truth_table_to_lut_mask(permuted_truth_table, num_inputs);
  
  #ifdef DEBUG_LUT_MASK
          std::cout << "\tLUT_MASK: " << lut_mask << std::endl;
  #endif
  
          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.
      std::vector<int> determine_lut_permutation(size_t num_inputs, const t_pb* atom_pb) {
          auto& atom_ctx = g_vpr_ctx.atom();
  
          std::vector<int> permute(num_inputs, OPEN);
  
  #ifdef DEBUG_LUT_MASK
          std::cout << "\tInit Permute: {";
          for (size_t i = 0; i < permute.size(); i++) {
              std::cout << permute[i] << ", ";
          }
          std::cout << "}" << std::endl;
  #endif
  
          //Determine the permutation
          //
          //We walk through the logical inputs to this atom (i.e. in the original truth table/netlist)
          //and find the corresponding input in the implementation atom (i.e. in the current netlist)
          auto ports = atom_ctx.nlist.block_input_ports(atom_ctx.lookup.pb_atom(atom_pb));
          if (ports.size() == 1) {
              const t_pb_graph_node* gnode = atom_pb->pb_graph_node;
              VTR_ASSERT(gnode->num_input_ports == 1);
              VTR_ASSERT(gnode->num_input_pins[0] >= (int)num_inputs);
  
              AtomPortId port_id = *ports.begin();
  
              for (size_t ipin = 0; ipin < num_inputs; ++ipin) {
                  AtomNetId impl_input_net_id = find_atom_input_logical_net(atom_pb, ipin); //The net currently connected to input j
  
                  //Find the original pin index
                  const t_pb_graph_pin* gpin = &gnode->input_pins[0][ipin];
                  BitIndex orig_index = atom_pb->atom_pin_bit_index(gpin);
  
                  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);
  
                      //Mark the permutation.
                      //  The net originally located at orig_index in the atom netlist
                      //  was moved to ipin in the implementation
                      permute[orig_index] = ipin;
                  }
              }
          } else {
              //May have no inputs on a constant generator
              VTR_ASSERT(ports.size() == 0);
          }
  
          //Fill in any missing values in the permutation (i.e. zeros)
          std::set<int> perm_indicies(permute.begin(), permute.end());
          size_t unused_index = 0;
          for (size_t i = 0; i < permute.size(); i++) {
              if (permute[i] == OPEN) {
                  while (perm_indicies.count(unused_index)) {
                      unused_index++;
                  }
                  permute[i] = unused_index;
                  perm_indicies.insert(unused_index);
              }
          }
  
  #ifdef DEBUG_LUT_MASK
          std::cout << "\tPermute: {";
          for (size_t k = 0; k < permute.size(); k++) {
              std::cout << permute[k] << ", ";
          }
          std::cout << "}" << std::endl;
  
          std::cout << "\tBLIF = Input ->  Rotated" << std::endl;
          std::cout << "\t------------------------" << std::endl;
  #endif
          return permute;
      }
  
      //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
          // constant true/false
          //
          // False output:
          //      .names j
          //
          // True output:
          //      .names j
          //      1
          bool encoding_on_set = true;
  
          //We may get a nullptr if the output is always false
          if (names_row_ptr) {
              //Determine whether the truth table stores the ON or OFF set
              //
              //  In blif, the 'output values' of a .names must be either '1' or '0', and must be consistent
              //  within a single .names -- that is a single .names can encode either the ON or OFF set
              //  (of which only one will be encoded in a single .names)
              //
              const std::string names_first_row = (const char*)names_row_ptr->data_vptr;
              auto names_first_row_output_iter = names_first_row.end() - 1;
  
              if (*names_first_row_output_iter == '1') {
                  encoding_on_set = true;
              } else if (*names_first_row_output_iter == '0') {
                  encoding_on_set = false;
              } else {
                  VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
                                  "Invalid .names truth-table character '%c'. Must be one of '1', '0' or '-'. \n", *names_first_row_output_iter);
              }
          }
  
          return encoding_on_set;
      }
  
      //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;
  
          //Sanity-check, the 2nd last character should be a space
          auto space_iter = names_row.end() - 2;
          VTR_ASSERT(*space_iter == ' ');
  
          //Extract the truth (output value) for this row
          if (*output_val_iter == '1') {
              VTR_ASSERT(encoding_on_set);
          } else if (*output_val_iter == '0') {
              VTR_ASSERT(!encoding_on_set);
          } else {
              VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
                              "Invalid .names encoding both ON and OFF set\n");
          }
  
          //Extract the input values for this row
          LogicVec input_values(num_inputs, vtr::LogicValue::FALSE);
          size_t i = 0;
          //Walk through each input in the input cube for this row
          while (names_row[i] != ' ') {
              vtr::LogicValue input_val = vtr::LogicValue::UNKOWN;
              if (names_row[i] == '1') {
                  input_val = vtr::LogicValue::TRUE;
              } else if (names_row[i] == '0') {
                  input_val = vtr::LogicValue::FALSE;
              } else if (names_row[i] == '-') {
                  input_val = vtr::LogicValue::DONT_CARE;
              } else {
                  VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
                                  "Invalid .names truth-table character '%c'. Must be one of '1', '0' or '-'. \n", names_row[i]);
              }
  
              input_values[i] = input_val;
              i++;
          }
          return input_values;
      }
  
      //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++) {
              count += pb->pb_graph_node->num_input_pins[i];
          }
          return count;
      }
      //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;
  
          int cluster_pin_idx = pb_node->input_pins[0][atom_input_idx].pin_count_in_cluster;
          const auto& top_pb_route = find_top_pb_route(atom);
          AtomNetId atom_net_id;
          if (top_pb_route.count(cluster_pin_idx)) {
              atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id;
          }
          return atom_net_id;
      }
  
      //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", std::move(driver_wire));
          name = join_identifier(name, "to");
          name = join_identifier(name, std::move(sink_wire));
  
          return name;
      }
  
      //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();
  
          tatum::EdgeId edge = timing_ctx.graph->find_edge(source_tnode, sink_tnode);
          VTR_ASSERT(edge);
  
          double delay_sec = delay_calc_->max_edge_delay(*timing_ctx.graph, edge);
  
          return ::get_delay_ps(delay_sec); //Class overload hides file-scope by default
      }
  
    private:                                                  //Data
      std::string top_module_name_;                           //Name of the top level module (i.e. the circuit)
      std::vector<std::string> inputs_;                       //Name of circuit inputs
      std::vector<std::string> outputs_;                      //Name of circuit outputs
      std::vector<Assignment> assignments_;                   //Set of assignments (i.e. net-to-net connections)
      std::vector<std::shared_ptr<Instance>> cell_instances_; //Set of cell instances
  
      //Drivers of logical nets.
      // Key: logic net id, Value: pair of wire_name and tnode_id
      std::map<AtomNetId, std::pair<std::string, tatum::NodeId>> logical_net_drivers_;
  
      //Sinks of logical nets.
      // Key: logical net id, Value: vector wire_name tnode_id pairs
      std::map<AtomNetId, std::vector<std::pair<std::string, tatum::NodeId>>> logical_net_sinks_;
      std::map<std::string, float> logical_net_sink_delays_;
  
      //Output streams
      std::ostream& verilog_os_;
      std::ostream& blif_os_;
      std::ostream& sdf_os_;
  
      //Look-up from pins to tnodes
      std::map<std::pair<ClusterBlockId, int>, tatum::NodeId> pin_id_to_tnode_lookup_;
  
      std::shared_ptr<const AnalysisDelayCalculator> delay_calc_;
  };
  
  //
  // Externally Accessible Functions
  //
  
  //Main routing for this file. See netlist_writer.h for details.
  void netlist_writer(const std::string& basename, std::shared_ptr<const AnalysisDelayCalculator> delay_calc) {
      std::string verilog_filename = basename + "_post_synthesis.v";
      std::string blif_filename = basename + "_post_synthesis.blif";
      std::string sdf_filename = basename + "_post_synthesis.sdf";
  
      VTR_LOG("Writing Implementation Netlist: %s\n", verilog_filename.c_str());
      VTR_LOG("Writing Implementation Netlist: %s\n", blif_filename.c_str());
      VTR_LOG("Writing Implementation SDF    : %s\n", sdf_filename.c_str());
  
      std::ofstream verilog_os(verilog_filename);
      std::ofstream blif_os(blif_filename);
      std::ofstream sdf_os(sdf_filename);
  
      NetlistWriterVisitor visitor(verilog_os, blif_os, sdf_os, std::move(delay_calc));
  
      NetlistWalker nl_walker(visitor);
  
      nl_walker.walk();
  }
  
  //
  // File-scope function implementations
  //
  
  //Returns a blank string for indenting the given depth
  std::string indent(size_t depth) {
      std::string indent_ = "    ";
      std::string new_indent;
      for (size_t i = 0; i < depth; ++i) {
          new_indent += indent_;
      }
      return new_indent;
  }
  
  //Returns the delay in pico-seconds from a floating point delay
  double get_delay_ps(double delay_sec) {
      return delay_sec * 1e12; //Scale to picoseconds
  }
  
  //Returns the name of a unique unconnected net
  std::string create_unconn_net(size_t& unconn_count) {
      //We increment unconn_count by reference so each
      //call generates a unique name
      return "__vpr__unconn" + std::to_string(unconn_count++);
  }
  
  //Pretty-Prints a blif port to the given output stream
  //
  //  Handles special cases like multi-bit and disconnected ports
  void print_blif_port(std::ostream& os, size_t& unconn_count, const std::string& port_name, const std::vector<std::string>& nets, int depth) {
      if (nets.size() == 1) {
          //If only a single bit port, don't include port indexing
          os << indent(depth) << port_name << "=";
          if (nets[0].empty()) {
              os << create_unconn_net(unconn_count);
          } else {
              os << nets[0];
          }
      } else {
          //Include bit indexing for multi-bit ports
          for (int ipin = 0; ipin < (int)nets.size(); ++ipin) {
              os << indent(depth) << port_name << "[" << ipin << "]"
                 << "=";
  
              if (nets[ipin].empty()) {
                  os << create_unconn_net(unconn_count);
              } else {
                  os << nets[ipin];
              }
              if (ipin != (int)nets.size() - 1) {
                  os << " "
                     << "\\"
                     << "\n";
              }
          }
      }
  }
  
  //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<std::string>& nets, PortType type, int depth) {
      //Port name
      os << indent(depth) << "." << port_name << "(";
  
      //Pins
      if (nets.size() == 1) {
          //Single-bit port
          if (nets[0].empty()) {
              //Disconnected
              if (type == PortType::INPUT || type == PortType::CLOCK) {
                  os << "1'b0";
              } else {
                  VTR_ASSERT(type == PortType::OUTPUT);
                  os << "";
              }
          } else {
              //Connected
              os << escape_verilog_identifier(nets[0]);
          }
      } else {
          //A multi-bit port, we explicitly concat the single-bit nets to build the port,
          //taking care to print MSB on left and LSB on right
          os << "{"
             << "\n";
          for (int ipin = (int)nets.size() - 1; ipin >= 0; --ipin) { //Reverse order to match endianess
              os << indent(depth + 1);
              if (nets[ipin].empty()) {
                  //Disconnected
                  if (type == PortType::INPUT || type == PortType::CLOCK) {
                      os << "1'b0";
                  } else {
                      VTR_ASSERT(type == PortType::OUTPUT);
                      os << "";
                  }
              } else {
                  //Connected
                  os << escape_verilog_identifier(nets[ipin]);
              }
              if (ipin != 0) {
                  os << ",";
                  os << "\n";
              }
          }
          os << "}";
      }
      os << ")";
  }
  
  //Escapes the given identifier to be safe for verilog
  std::string escape_verilog_identifier(const std::string& identifier) {
      //Verilog allows escaped identifiers
      //
      //The escaped identifiers start with a literal back-slash '\'
      //followed by the identifier and are terminated by white space
      //
      //We pre-pend the escape back-slash and append a space to avoid
      //the identifier gobbling up adjacent characters like commas which
      //are not actually part of the identifier
      std::string prefix = "\\";
      std::string suffix = " ";
      std::string escaped_name = prefix + identifier + suffix;
  
      return escaped_name;
  }
  
  //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:
      //    ! to # (ASCII decimal 33-35)
      //    % to / (ASCII decimal 37-47)
      //    : to @ (ASCII decimal 58-64)
      //    [ to ^ (ASCII decimal 91-94)
      //    ` to ` (ASCII decimal 96)
      //    { to ~ (ASCII decimal 123-126)
      //
      //Note that the spec defines _ (decimal code 95) and $ (decimal code 36)
      //as non-special alphanumeric characters.
      //
      //However it inconsistently also lists $ in the list of special characters.
      //Since the spec allows for non-special characters to be escaped (they are treated
      //normally), we treat $ as a special character to be safe.
      //
      //Note that the spec appears to have rendering errors in the PDF availble
      //on IEEE Xplore, listing the 'LEFT-POINTING DOUBLE ANGLE QUOTATION MARK'
      //character (decimal code 171) in place of the APOSTROPHE character '
      //with decimal code 39 in the special character list. We assume code 39.
      if ((c >= 33 && c <= 35) || (c == 36) || // $
          (c >= 37 && c <= 47) || (c >= 58 && c <= 64) || (c >= 91 && c <= 94) || (c == 96) || (c >= 123 && c <= 126)) {
          return true;
      }
  
      return false;
  }
  
  //Escapes the given identifier to be safe for sdf
  std::string escape_sdf_identifier(const std::string& identifier) {
      //SDF allows escaped characters
      //
      //We look at each character in the string and escape it if it is
      //a special character
      std::string escaped_name;
  
      for (char c : identifier) {
          if (is_special_sdf_char(c)) {
              //Escape the special character
              escaped_name += '\\';
          }
          escaped_name += c;
      }
  
      return escaped_name;
  }
  
  //Joins two identifier strings
  std::string join_identifier(const std::string& lhs, const std::string& rhs) {
      return lhs + '_' + rhs;
  }
  