text: /**
   * @file
   * @brief BLIF Netlist Loader
   *
   * This loader handles loading a post-technology mapping fully flattened (i.e not
   * hierarchical) netlist in Berkely Logic Interchange Format (BLIF) file, and
   * builds a netlist data structure (AtomNetlist) from it.
   *
   * BLIF text parsing is handled by the blifparse library, while this file is responsible
   * for creating the netlist data structure.
   *
   * The main object of interest is the BlifAllocCallback struct, which implements the
   * blifparse callback interface.  The callback methods are then called when basic blif
   * primitives are encountered by the parser.  The callback methods then create the associated
   * netlist data structures.
   */
  #include <cstdio>
  #include <cstring>
  #include <ctime>
  #include <sstream>
  #include <unordered_set>
  #include <cctype> //std::isdigit
  
  #include "blifparse.hpp"
  #include "atom_netlist.h"
  
  #include "vtr_assert.h"
  #include "vtr_util.h"
  #include "vtr_log.h"
  #include "vtr_logic.h"
  #include "vtr_time.h"
  #include "vtr_digest.h"
  
  #include "vpr_types.h"
  #include "vpr_error.h"
  #include "globals.h"
  #include "read_blif.h"
  #include "arch_types.h"
  #include "echo_files.h"
  #include "hash.h"
  
  vtr::LogicValue to_vtr_logic_value(blifparse::LogicValue);
  
  struct BlifAllocCallback : public blifparse::Callback {
    public:
      BlifAllocCallback(e_circuit_format blif_format, AtomNetlist& main_netlist, const std::string netlist_id, const t_model* user_models, const t_model* library_models)
          : main_netlist_(main_netlist)
          , netlist_id_(netlist_id)
          , user_arch_models_(user_models)
          , library_arch_models_(library_models)
          , blif_format_(blif_format) {
          VTR_ASSERT(blif_format_ == e_circuit_format::BLIF
                     || blif_format_ == e_circuit_format::EBLIF);
          inpad_model_ = find_model(MODEL_INPUT);
          outpad_model_ = find_model(MODEL_OUTPUT);
  
          main_netlist_.set_block_types(inpad_model_, outpad_model_);
      }
  
      static constexpr const char* OUTPAD_NAME_PREFIX = "out:";
  
    public: //Callback interface
      void start_parse() override {}
  
      void finish_parse() override {
          //When parsing is finished we *move* the main netlist
          //into the user object. This ensures we never have two copies
          //(consuming twice the memory).
          size_t main_netlist_idx = determine_main_netlist_index();
          main_netlist_ = std::move(blif_models_[main_netlist_idx]);
      }
  
      void begin_model(std::string model_name) override {
          //Create a new model, and set it's name
  
          blif_models_.emplace_back(model_name, netlist_id_);
          blif_models_.back().set_block_types(inpad_model_, outpad_model_);
          blif_models_black_box_.emplace_back(false);
          ended_ = false;
          set_curr_block(AtomBlockId::INVALID()); //This statement doesn't define a block, so mark invalid
      }
  
      void inputs(std::vector<std::string> input_names) override {
          const t_model* blk_model = find_model(MODEL_INPUT);
  
          VTR_ASSERT_MSG(!blk_model->inputs, "Inpad model has an input port");
          VTR_ASSERT_MSG(blk_model->outputs, "Inpad model has no output port");
          VTR_ASSERT_MSG(blk_model->outputs->size == 1, "Inpad model has non-single-bit output port");
          VTR_ASSERT_MSG(!blk_model->outputs->next, "Inpad model has multiple output ports");
  
          std::string pin_name = blk_model->outputs->name;
          for (const auto& input : input_names) {
              AtomBlockId blk_id = curr_model().create_block(input, blk_model);
              AtomPortId port_id = curr_model().create_port(blk_id, blk_model->outputs);
              AtomNetId net_id = curr_model().create_net(input);
              curr_model().create_pin(port_id, 0, net_id, PinType::DRIVER);
          }
          set_curr_block(AtomBlockId::INVALID()); //This statement doesn't define a block, so mark invalid
      }
  
      void outputs(std::vector<std::string> output_names) override {
          const t_model* blk_model = find_model(MODEL_OUTPUT);
  
          VTR_ASSERT_MSG(!blk_model->outputs, "Outpad model has an output port");
          VTR_ASSERT_MSG(blk_model->inputs, "Outpad model has no input port");
          VTR_ASSERT_MSG(blk_model->inputs->size == 1, "Outpad model has non-single-bit input port");
          VTR_ASSERT_MSG(!blk_model->inputs->next, "Outpad model has multiple input ports");
  
          std::string pin_name = blk_model->inputs->name;
          for (const auto& output : output_names) {
              //Since we name blocks based on their drivers we need to uniquify outpad names,
              //which we do with a prefix
              AtomBlockId blk_id = curr_model().create_block(OUTPAD_NAME_PREFIX + output, blk_model);
              AtomPortId port_id = curr_model().create_port(blk_id, blk_model->inputs);
              AtomNetId net_id = curr_model().create_net(output);
              curr_model().create_pin(port_id, 0, net_id, PinType::SINK);
          }
          set_curr_block(AtomBlockId::INVALID()); //This statement doesn't define a block, so mark invalid
      }
  
      void names(std::vector<std::string> nets, std::vector<std::vector<blifparse::LogicValue>> so_cover) override {
          const t_model* blk_model = find_model(MODEL_NAMES);
  
          VTR_ASSERT_MSG(nets.size() > 0, "BLIF .names has no connections");
  
          VTR_ASSERT_MSG(blk_model->inputs, ".names model has no input port");
          VTR_ASSERT_MSG(!blk_model->inputs->next, ".names model has multiple input ports");
          if (static_cast<int>(nets.size()) - 1 > blk_model->inputs->size) {
              vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "BLIF .names input size (%zu) greater than .names model input size (%d)",
                        nets.size() - 1, blk_model->inputs->size);
          }
  
          VTR_ASSERT_MSG(blk_model->outputs, ".names has no output port");
          VTR_ASSERT_MSG(!blk_model->outputs->next, ".names model has multiple output ports");
          VTR_ASSERT_MSG(blk_model->outputs->size == 1, ".names model has non-single-bit output");
  
          //Convert the single-output cover to a netlist truth table
          AtomNetlist::TruthTable truth_table;
          for (const auto& row : so_cover) {
              truth_table.emplace_back();
              for (auto val : row) {
                  truth_table[truth_table.size() - 1].push_back(to_vtr_logic_value(val));
              }
          }
  
          AtomBlockId blk_id = curr_model().create_block(nets[nets.size() - 1], blk_model, truth_table);
          set_curr_block(blk_id);
  
          //Create inputs
          AtomPortId input_port_id = curr_model().create_port(blk_id, blk_model->inputs);
          for (size_t i = 0; i < nets.size() - 1; ++i) {
              AtomNetId net_id = curr_model().create_net(nets[i]);
  
              curr_model().create_pin(input_port_id, i, net_id, PinType::SINK);
          }
  
          //Figure out if the output is a constant generator
          bool output_is_const = false;
          if (truth_table.empty()
              || (truth_table.size() == 1 && truth_table[0].size() == 1 && truth_table[0][0] == vtr::LogicValue::FALSE)) {
              //An empty truth table in BLIF corresponds to a constant-zero
              //  e.g.
              //
              //  #gnd is a constant 0 generator
              //  .names gnd
              //
              //An single entry truth table with value '0' also corresponds to a constant-zero
              //  e.g.
              //
              //  #gnd2 is a constant 0 generator
              //  .names gnd2
              //  0
              //
              output_is_const = true;
              VTR_LOG("Found constant-zero generator '%s'\n", nets[nets.size() - 1].c_str());
          } else if (truth_table.size() == 1 && truth_table[0].size() == 1 && truth_table[0][0] == vtr::LogicValue::TRUE) {
              //A single-entry truth table with value '1' in BLIF corresponds to a constant-one
              //  e.g.
              //
              //  #vcc is a constant 1 generator
              //  .names vcc
              //  1
              //
              output_is_const = true;
              VTR_LOG("Found constant-one generator '%s'\n", nets[nets.size() - 1].c_str());
          }
  
          //Create output
          AtomNetId net_id = curr_model().create_net(nets[nets.size() - 1]);
          AtomPortId output_port_id = curr_model().create_port(blk_id, blk_model->outputs);
          curr_model().create_pin(output_port_id, 0, net_id, PinType::DRIVER, output_is_const);
      }
  
      void latch(std::string input, std::string output, blifparse::LatchType type, std::string control, blifparse::LogicValue init) override {
          if (type == blifparse::LatchType::UNSPECIFIED) {
              VTR_LOGF_WARN(filename_.c_str(), lineno_, "Treating latch '%s' of unspecified type as rising edge triggered\n", output.c_str());
          } else if (type != blifparse::LatchType::RISING_EDGE) {
              vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "Only rising edge latches supported\n");
          }
  
          if (control.empty()) {
              vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "Latch must have a clock\n");
          }
  
          const t_model* blk_model = find_model(MODEL_LATCH);
  
          VTR_ASSERT_MSG(blk_model->inputs, "Has one input port");
          VTR_ASSERT_MSG(blk_model->inputs->next, "Has two input port");
          VTR_ASSERT_MSG(!blk_model->inputs->next->next, "Has no more than two input port");
          VTR_ASSERT_MSG(blk_model->outputs, "Has one output port");
          VTR_ASSERT_MSG(!blk_model->outputs->next, "Has no more than one input port");
  
          const t_model_ports* d_model_port = blk_model->inputs;
          const t_model_ports* clk_model_port = blk_model->inputs->next;
          const t_model_ports* q_model_port = blk_model->outputs;
  
          VTR_ASSERT(d_model_port->name == std::string("D"));
          VTR_ASSERT(clk_model_port->name == std::string("clk"));
          VTR_ASSERT(q_model_port->name == std::string("Q"));
          VTR_ASSERT(d_model_port->size == 1);
          VTR_ASSERT(clk_model_port->size == 1);
          VTR_ASSERT(q_model_port->size == 1);
          VTR_ASSERT(clk_model_port->is_clock);
  
          //We set the initital value as a single entry in the 'truth_table' field
          AtomNetlist::TruthTable truth_table(1);
          truth_table[0].push_back(to_vtr_logic_value(init));
  
          AtomBlockId blk_id = curr_model().create_block(output, blk_model, truth_table);
          set_curr_block(blk_id);
  
          //The input
          AtomPortId d_port_id = curr_model().create_port(blk_id, d_model_port);
          AtomNetId d_net_id = curr_model().create_net(input);
          curr_model().create_pin(d_port_id, 0, d_net_id, PinType::SINK);
  
          //The output
          AtomPortId q_port_id = curr_model().create_port(blk_id, q_model_port);
          AtomNetId q_net_id = curr_model().create_net(output);
          curr_model().create_pin(q_port_id, 0, q_net_id, PinType::DRIVER);
  
          //The clock
          AtomPortId clk_port_id = curr_model().create_port(blk_id, clk_model_port);
          AtomNetId clk_net_id = curr_model().create_net(control);
          curr_model().create_pin(clk_port_id, 0, clk_net_id, PinType::SINK);
      }
  
      void subckt(std::string subckt_model, std::vector<std::string> ports, std::vector<std::string> nets) override {
          VTR_ASSERT(ports.size() == nets.size());
  
          const t_model* blk_model = find_model(subckt_model);
  
          //We name the subckt based on the net it's first output pin drives
          std::string subckt_name;
          for (size_t i = 0; i < ports.size(); ++i) {
              const t_model_ports* model_port = find_model_port(blk_model, ports[i]);
              VTR_ASSERT(model_port);
  
              if (model_port->dir == OUT_PORT) {
                  subckt_name = nets[i];
                  break;
              }
          }
  
          if (subckt_name.empty()) {
              //We need to name the block uniquely ourselves since there is no connected output
              subckt_name = unique_subckt_name();
  
              //Since this is unusual, warn the user
              VTR_LOGF_WARN(filename_.c_str(), lineno_,
                            "Subckt of type '%s' at %s:%d has no output pins, and has been named '%s'\n",
                            blk_model->name, filename_.c_str(), lineno_, subckt_name.c_str());
          }
  
          //The name for every block should be unique, check that there is no name conflict
          AtomBlockId blk_id = curr_model().find_block(subckt_name);
          if (blk_id) {
              const t_model* conflicting_model = curr_model().block_model(blk_id);
              vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_,
                        "Duplicate blocks named '%s' found in netlist."
                        " Existing block of type '%s' conflicts with subckt of type '%s'.",
                        subckt_name.c_str(), conflicting_model->name, subckt_model.c_str());
          }
  
          //Create the block
          blk_id = curr_model().create_block(subckt_name, blk_model);
          set_curr_block(blk_id);
  
          for (size_t i = 0; i < ports.size(); ++i) {
              //Check for consistency between model and ports
              const t_model_ports* model_port = find_model_port(blk_model, ports[i]);
              VTR_ASSERT(model_port);
  
              //Determine the pin type
              PinType pin_type = PinType::SINK;
              if (model_port->dir == OUT_PORT) {
                  pin_type = PinType::DRIVER;
              } else {
                  VTR_ASSERT_MSG(model_port->dir == IN_PORT, "Unexpected port type");
              }
  
              //Make the port
              std::string port_base;
              size_t port_bit;
              std::tie(port_base, port_bit) = split_index(ports[i]);
  
              AtomPortId port_id = curr_model().create_port(blk_id, find_model_port(blk_model, port_base));
  
              //Make the net
              AtomNetId net_id = curr_model().create_net(nets[i]);
  
              //Make the pin
              curr_model().create_pin(port_id, port_bit, net_id, pin_type);
          }
      }
  
      void blackbox() override {
          //We treat black-boxes as netlists during parsing so they should contain
          //only inpads/outpads
          for (const auto& blk_id : curr_model().blocks()) {
              auto blk_type = curr_model().block_type(blk_id);
              if (!(blk_type == AtomBlockType::INPAD || blk_type == AtomBlockType::OUTPAD)) {
                  vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "Unexpected primitives in blackbox model");
              }
          }
          set_curr_model_blackbox(true);
          set_curr_block(AtomBlockId::INVALID()); //This statement doesn't define a block, so mark invalid
      }
  
      void end_model() override {
          if (ended_) {
              vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "Unexpected .end");
          }
  
          merge_conn_nets();
  
          //Mark as ended
          ended_ = true;
  
          set_curr_block(AtomBlockId::INVALID()); //This statement doesn't define a block, so mark invalid
      }
  
      //BLIF Extensions
      void conn(std::string src, std::string dst) override {
          if (blif_format_ != e_circuit_format::EBLIF) {
              parse_error(lineno_, ".conn", "Supported only in extended BLIF format");
          }
  
          //We allow the .conn to create the nets if they don't exist,
          //however typically they will have already been defined.
          AtomNetId driver_net = curr_model().create_net(src);
          AtomNetId sink_net = curr_model().create_net(dst);
  
          //We eventually need to merge the driver and sink nets,
          //however we must defer that until all the net drivers
          //and sinks have been created (otherwise they may not
          //be properly merged depending on where the .conn is
          //delcared).
          //
          //As a result we record the nets to merge and do the actual merging at
          //the end of the .model
          curr_nets_to_merge_.emplace_back(driver_net, sink_net);
  
          set_curr_block(AtomBlockId::INVALID());
      }
  
      void cname(std::string cell_name) override {
          if (blif_format_ != e_circuit_format::EBLIF) {
              parse_error(lineno_, ".cname", "Supported only in extended BLIF format");
          }
  
          //Re-name the block
          curr_model().set_block_name(curr_block(), cell_name);
      }
  
      void attr(std::string name, std::string value) override {
          if (blif_format_ != e_circuit_format::EBLIF) {
              parse_error(lineno_, ".attr", "Supported only in extended BLIF format");
          }
  
          curr_model().set_block_attr(curr_block(), name, value);
      }
  
      void param(std::string name, std::string value) override {
          if (blif_format_ != e_circuit_format::EBLIF) {
              parse_error(lineno_, ".param", "Supported only in extended BLIF format");
          }
  
          curr_model().set_block_param(curr_block(), name, value);
      }
  
      //Utilities
      void filename(std::string fname) override { filename_ = fname; }
  
      void lineno(int line_num) override { lineno_ = line_num; }
  
      void parse_error(const int curr_lineno, const std::string& near_text, const std::string& msg) override {
          vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), curr_lineno,
                    "Error in blif file near '%s': %s\n", near_text.c_str(), msg.c_str());
      }
  
    public:
      //Retrieve the netlist
      size_t determine_main_netlist_index() {
          //Look through all the models loaded, to find the one which is non-blackbox (i.e. has real blocks
          //and is not a blackbox).  To check for errors we look at all models, even if we've already
          //found a non-blackbox model.
          int top_model_idx = -1; //Not valid
  
          for (int i = 0; i < static_cast<int>(blif_models_.size()); ++i) {
              if (!blif_models_black_box_[i]) {
                  //A non-blackbox model
                  if (top_model_idx == -1) {
                      //This is the top model
                      top_model_idx = i;
                  } else {
                      //We already have a top model
                      vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_,
                                "Found multiple models with primitives. "
                                "Only one model can contain primitives, the others must be blackboxes.");
                  }
              } else {
                  //Verify blackbox models match the architecture
                  verify_blackbox_model(blif_models_[i]);
              }
          }
  
          if (top_model_idx == -1) {
              vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_,
                        "No non-blackbox models found. The main model must not be a blackbox.");
          }
  
          //Return the main model
          VTR_ASSERT(top_model_idx >= 0);
          return static_cast<size_t>(top_model_idx);
      }
  
    private:
      const t_model* find_model(std::string name) {
          const t_model* arch_model = nullptr;
          for (const t_model* arch_models : {user_arch_models_, library_arch_models_}) {
              arch_model = arch_models;
              while (arch_model) {
                  if (name == arch_model->name) {
                      //Found it
                      break;
                  }
                  arch_model = arch_model->next;
              }
              if (arch_model) {
                  //Found it
                  break;
              }
          }
          if (!arch_model) {
              vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "Failed to find matching architecture model for '%s'\n",
                        name.c_str());
          }
          return arch_model;
      }
  
      const t_model_ports* find_model_port(const t_model* blk_model, std::string port_name) {
          //We need to handle both single, and multi-bit port names
          //
          //By convention multi-bit port names have the bit index stored in square brackets
          //at the end of the name. For example:
          //
          //   my_signal_name[2]
          //
          //indicates the 2nd bit of the port 'my_signal_name'.
          std::string trimmed_port_name;
          int bit_index;
  
          //Extract the index bit
          std::tie(trimmed_port_name, bit_index) = split_index(port_name);
  
          //We now have the valid bit index and port_name is the name excluding the index info
          VTR_ASSERT(bit_index >= 0);
  
          //We now look through all the ports on the model looking for the matching port
          for (const t_model_ports* ports : {blk_model->inputs, blk_model->outputs}) {
              const t_model_ports* curr_port = ports;
              while (curr_port) {
                  if (trimmed_port_name == curr_port->name) {
                      //Found a matching port, we need to verify the index
                      if (bit_index < curr_port->size) {
                          //Valid port index
                          return curr_port;
                      } else {
                          //Out of range
                          vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_,
                                    "Port '%s' on architecture model '%s' exceeds port width (%d bits)\n",
                                    port_name.c_str(), blk_model->name, curr_port->size);
                      }
                  }
                  curr_port = curr_port->next;
              }
          }
  
          //No match
          vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_,
                    "Found no matching port '%s' on architecture model '%s'\n",
                    port_name.c_str(), blk_model->name);
          return nullptr;
      }
  
      /** @brief Splits the index off a signal name and returns the base signal name
       *         (excluding the index) and the index as an integer. For example
       *
       * "my_signal_name[2]"   -> "my_signal_name", 2
       */
      std::pair<std::string, int> split_index(const std::string& signal_name) {
          int bit_index = 0;
  
          std::string trimmed_signal_name = signal_name;
  
          auto iter = --signal_name.end(); //Initialized to the last char
          if (*iter == ']') {
              //The name may end in an index
              //
              //To verify, we iterate back through the string until we find
              //an open square brackets, or non digit character
              --iter; //Advance past ']'
              while (iter != signal_name.begin() && std::isdigit(*iter))
                  --iter;
  
              //We are at the first non-digit character from the end (or the beginning of the string)
              if (*iter == '[') {
                  //We have a valid index in the open range (iter, --signal_name.end())
                  std::string index_str(iter + 1, --signal_name.end());
  
                  //Convert to an integer
                  std::stringstream ss(index_str);
                  ss >> bit_index;
                  VTR_ASSERT_MSG(!ss.fail() && ss.eof(), "Failed to extract signal index");
  
                  //Trim the signal name to exclude the final index
                  trimmed_signal_name = std::string(signal_name.begin(), iter);
              }
          }
          return std::make_pair(trimmed_signal_name, bit_index);
      }
  
      ///@brief Retieves a reference to the currently active .model
      AtomNetlist& curr_model() {
          if (blif_models_.empty() || ended_) {
              vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "Expected .model");
          }
  
          return blif_models_[blif_models_.size() - 1];
      }
  
      void set_curr_model_blackbox(bool val) {
          VTR_ASSERT(blif_models_.size() == blif_models_black_box_.size());
          blif_models_black_box_[blif_models_black_box_.size() - 1] = val;
      }
  
      bool verify_blackbox_model(AtomNetlist& blif_model) {
          const t_model* arch_model = find_model(blif_model.netlist_name());
  
          //Verify each port on the model
          //
          // We parse each .model as it's own netlist so the IOs
          // get converted to blocks
          for (auto blk_id : blif_model.blocks()) {
              //Check that the port directions match
              if (blif_model.block_type(blk_id) == AtomBlockType::INPAD) {
                  const auto& input_name = blif_model.block_name(blk_id);
  
                  //Find model port already verifies the port widths
                  const t_model_ports* arch_model_port = find_model_port(arch_model, input_name);
                  VTR_ASSERT(arch_model_port);
                  VTR_ASSERT(arch_model_port->dir == IN_PORT);
  
              } else {
                  VTR_ASSERT(blif_model.block_type(blk_id) == AtomBlockType::OUTPAD);
  
                  auto raw_output_name = blif_model.block_name(blk_id);
  
                  std::string output_name = vtr::replace_first(raw_output_name, OUTPAD_NAME_PREFIX, "");
  
                  //Find model port already verifies the port widths
                  const t_model_ports* arch_model_port = find_model_port(arch_model, output_name);
                  VTR_ASSERT(arch_model_port);
  
                  VTR_ASSERT(arch_model_port->dir == OUT_PORT);
              }
          }
          return true;
      }
  
      ///@brief Returns a different unique subck name each time it is called
      std::string unique_subckt_name() {
          return "unnamed_subckt" + std::to_string(unique_subckt_name_counter_++);
      }
  
      /** @brief Sets the current block which is being processed
       *
       * Used to determine which block a .cname, .param, .attr apply to
       */
      void set_curr_block(AtomBlockId blk) {
          curr_block_ = blk;
      }
  
      ///@brief Gets the current block which is being processed
      AtomBlockId curr_block() const {
          return curr_block_;
      }
  
      /**
       * @brief Merges all the recorded net pairs which need to be merged
       *
       * This should only be called at the end of a .model to ensure that
       * all the associated driver/sink pins have been delcared and connected
       * to their nets
       */
      void merge_conn_nets() {
          for (auto net_pair : curr_nets_to_merge_) {
              curr_model().merge_nets(net_pair.first, net_pair.second);
          }
  
          curr_nets_to_merge_.clear();
      }
  
    private:
      bool ended_ = true; //Initially no active .model
      std::string filename_ = "";
      int lineno_ = -1;
  
      std::vector<AtomNetlist> blif_models_;
      std::vector<bool> blif_models_black_box_;
  
      AtomNetlist& main_netlist_;    ///<User object we fill
      const std::string netlist_id_; ///<Unique identifier based on the contents of the blif file
      const t_model* user_arch_models_ = nullptr;
      const t_model* library_arch_models_ = nullptr;
      const t_model* inpad_model_;
      const t_model* outpad_model_;
  
      size_t unique_subckt_name_counter_ = 0;
  
      AtomBlockId curr_block_;
      std::vector<std::pair<AtomNetId, AtomNetId>> curr_nets_to_merge_;
  
      e_circuit_format blif_format_ = e_circuit_format::BLIF;
  };
  
  vtr::LogicValue to_vtr_logic_value(blifparse::LogicValue val) {
      vtr::LogicValue new_val = vtr::LogicValue::UNKOWN;
      switch (val) {
          case blifparse::LogicValue::TRUE:
              new_val = vtr::LogicValue::TRUE;
              break;
          case blifparse::LogicValue::FALSE:
              new_val = vtr::LogicValue::FALSE;
              break;
          case blifparse::LogicValue::DONT_CARE:
              new_val = vtr::LogicValue::DONT_CARE;
              break;
          case blifparse::LogicValue::UNKOWN:
              new_val = vtr::LogicValue::UNKOWN;
              break;
          default:
              VTR_ASSERT_OPT_MSG(false, "Unkown logic value");
      }
      return new_val;
  }
  
  AtomNetlist read_blif(e_circuit_format circuit_format,
                        const char* blif_file,
                        const t_model* user_models,
                        const t_model* library_models) {
      AtomNetlist netlist;
      std::string netlist_id = vtr::secure_digest_file(blif_file);
  
      BlifAllocCallback alloc_callback(circuit_format, netlist, netlist_id, user_models, library_models);
      blifparse::blif_parse_filename(blif_file, alloc_callback);
  
      return netlist;
  }
  