#include <algorithm>
#include <numeric>

#include "netlist_utils.h"
#include "vtr_assert.h"
#include "vtr_log.h"
#include "vpr_error.h"
/*
 *
 * NetlistIdRemapper class implementation
 *
 */
template<typename BlockId, typename PortId, typename PinId, typename NetId>
BlockId NetlistIdRemapper<BlockId, PortId, PinId, NetId>::new_block_id(BlockId old_id) const {
    return block_id_map_[old_id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PortId NetlistIdRemapper<BlockId, PortId, PinId, NetId>::new_port_id(PortId old_id) const {
    return port_id_map_[old_id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PinId NetlistIdRemapper<BlockId, PortId, PinId, NetId>::new_pin_id(PinId old_id) const {
    return pin_id_map_[old_id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
NetId NetlistIdRemapper<BlockId, PortId, PinId, NetId>::new_net_id(NetId old_id) const {
    return net_id_map_[old_id];
}

/*
 *
 * Netlist class implementation
 *
 */

template<typename BlockId, typename PortId, typename PinId, typename NetId>
Netlist<BlockId, PortId, PinId, NetId>::Netlist(std::string name, std::string id)
    : netlist_name_(name)
    , netlist_id_(id) {}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
Netlist<BlockId, PortId, PinId, NetId>::~Netlist() = default;

/*
 *
 * Netlist
 *
 */
template<typename BlockId, typename PortId, typename PinId, typename NetId>
const std::string& Netlist<BlockId, PortId, PinId, NetId>::netlist_name() const {
    return netlist_name_;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
const std::string& Netlist<BlockId, PortId, PinId, NetId>::netlist_id() const {
    return netlist_id_;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::is_dirty() const {
    return dirty_;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::is_compressed() const {
    return !is_dirty();
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::net_is_ignored(const NetId id) const {
    VTR_ASSERT_SAFE(valid_net_id(id));

    return net_is_ignored_[id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::net_is_global(const NetId id) const {
    VTR_ASSERT_SAFE(valid_net_id(id));

    return net_is_global_[id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::print_stats() const {
    VTR_LOG("Blocks  %zu capacity/size: %.2f\n", block_ids_.size(), float(block_ids_.capacity()) / block_ids_.size());
    VTR_LOG("Ports   %zu capacity/size: %.2f\n", port_ids_.size(), float(port_ids_.capacity()) / port_ids_.size());
    VTR_LOG("Pins    %zu capacity/size: %.2f\n", pin_ids_.size(), float(pin_ids_.capacity()) / pin_ids_.size());
    VTR_LOG("Nets    %zu capacity/size: %.2f\n", net_ids_.size(), float(net_ids_.capacity()) / net_ids_.size());
    VTR_LOG("Strings %zu capacity/size: %.2f\n", string_ids_.size(), float(string_ids_.capacity()) / string_ids_.size());
}

/*
 *
 * Blocks
 *
 */
template<typename BlockId, typename PortId, typename PinId, typename NetId>
const std::string& Netlist<BlockId, PortId, PinId, NetId>::block_name(const BlockId blk_id) const {
    StringId str_id = block_names_[blk_id];
    return strings_[str_id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::block_is_combinational(const BlockId blk_id) const {
    VTR_ASSERT_SAFE(valid_block_id(blk_id));

    return block_clock_pins(blk_id).size() == 0;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::attr_range Netlist<BlockId, PortId, PinId, NetId>::block_attrs(const BlockId blk_id) const {
    VTR_ASSERT_SAFE(valid_block_id(blk_id));

    return vtr::make_range(block_attrs_[blk_id].begin(), block_attrs_[blk_id].end());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::param_range Netlist<BlockId, PortId, PinId, NetId>::block_params(const BlockId blk_id) const {
    VTR_ASSERT_SAFE(valid_block_id(blk_id));

    return vtr::make_range(block_params_[blk_id].begin(), block_params_[blk_id].end());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::pin_range Netlist<BlockId, PortId, PinId, NetId>::block_pins(const BlockId blk_id) const {
    VTR_ASSERT_SAFE(valid_block_id(blk_id));

    return vtr::make_range(block_pins_[blk_id].begin(), block_pins_[blk_id].end());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::pin_range Netlist<BlockId, PortId, PinId, NetId>::block_input_pins(const BlockId blk_id) const {
    VTR_ASSERT_SAFE(valid_block_id(blk_id));

    auto begin = block_pins_[blk_id].begin();

    auto end = block_pins_[blk_id].begin() + block_num_input_pins_[blk_id];

    return vtr::make_range(begin, end);
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::pin_range Netlist<BlockId, PortId, PinId, NetId>::block_output_pins(const BlockId blk_id) const {
    VTR_ASSERT_SAFE(valid_block_id(blk_id));

    auto begin = block_pins_[blk_id].begin() + block_num_input_pins_[blk_id];

    auto end = begin + block_num_output_pins_[blk_id];

    return vtr::make_range(begin, end);
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::pin_range Netlist<BlockId, PortId, PinId, NetId>::block_clock_pins(const BlockId blk_id) const {
    VTR_ASSERT_SAFE(valid_block_id(blk_id));

    auto begin = block_pins_[blk_id].begin()
                 + block_num_input_pins_[blk_id]
                 + block_num_output_pins_[blk_id];

    auto end = begin + block_num_clock_pins_[blk_id];

    VTR_ASSERT_SAFE(end == block_pins_[blk_id].end());

    return vtr::make_range(begin, end);
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::port_range Netlist<BlockId, PortId, PinId, NetId>::block_ports(const BlockId blk_id) const {
    VTR_ASSERT_SAFE(valid_block_id(blk_id));

    return vtr::make_range(block_ports_[blk_id].begin(), block_ports_[blk_id].end());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::port_range Netlist<BlockId, PortId, PinId, NetId>::block_input_ports(const BlockId blk_id) const {
    VTR_ASSERT_SAFE(valid_block_id(blk_id));

    auto begin = block_ports_[blk_id].begin();

    auto end = block_ports_[blk_id].begin() + block_num_input_ports_[blk_id];

    return vtr::make_range(begin, end);
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::port_range Netlist<BlockId, PortId, PinId, NetId>::block_output_ports(const BlockId blk_id) const {
    VTR_ASSERT_SAFE(valid_block_id(blk_id));

    auto begin = block_ports_[blk_id].begin() + block_num_input_ports_[blk_id];

    auto end = begin + block_num_output_ports_[blk_id];

    return vtr::make_range(begin, end);
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::port_range Netlist<BlockId, PortId, PinId, NetId>::block_clock_ports(const BlockId blk_id) const {
    VTR_ASSERT_SAFE(valid_block_id(blk_id));

    auto begin = block_ports_[blk_id].begin()
                 + block_num_input_ports_[blk_id]
                 + block_num_output_ports_[blk_id];

    auto end = begin + block_num_clock_ports_[blk_id];

    VTR_ASSERT_SAFE(end == block_ports_[blk_id].end());

    return vtr::make_range(begin, end);
}

/*
 *
 * Ports
 *
 */
template<typename BlockId, typename PortId, typename PinId, typename NetId>
const std::string& Netlist<BlockId, PortId, PinId, NetId>::port_name(const PortId port_id) const {
    VTR_ASSERT_SAFE(valid_port_id(port_id));

    StringId str_id = port_names_[port_id];
    return strings_[str_id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
BlockId Netlist<BlockId, PortId, PinId, NetId>::port_block(const PortId port_id) const {
    VTR_ASSERT_SAFE(valid_port_id(port_id));

    return port_blocks_[port_id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::pin_range Netlist<BlockId, PortId, PinId, NetId>::port_pins(const PortId port_id) const {
    VTR_ASSERT_SAFE(valid_port_id(port_id));

    return vtr::make_range(port_pins_[port_id].begin(), port_pins_[port_id].end());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PinId Netlist<BlockId, PortId, PinId, NetId>::port_pin(const PortId port_id, const BitIndex port_bit) const {
    //Convenience look-up bypassing port
    return find_pin(port_id, port_bit);
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
NetId Netlist<BlockId, PortId, PinId, NetId>::port_net(const PortId port_id, const BitIndex port_bit) const {
    //port_pin() will validate that port_bit and port_id are valid so don't
    //check redundently here

    //Convenience look-up bypassing port and pin
    PinId pin_id = port_pin(port_id, port_bit);
    if (pin_id) {
        return pin_net(pin_id);
    } else {
        return NetId::INVALID();
    }
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
BitIndex Netlist<BlockId, PortId, PinId, NetId>::port_width(const PortId port_id) const {
    VTR_ASSERT_SAFE(valid_port_id(port_id));

    return port_widths_[port_id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PortType Netlist<BlockId, PortId, PinId, NetId>::port_type(const PortId port_id) const {
    VTR_ASSERT_SAFE(valid_port_id(port_id));

    return port_types_[port_id];
}

/*
 *
 * Pins
 *
 */
template<typename BlockId, typename PortId, typename PinId, typename NetId>
std::string Netlist<BlockId, PortId, PinId, NetId>::pin_name(const PinId pin_id) const {
    BlockId blk = pin_block(pin_id);
    PortId port = pin_port(pin_id);

    return block_name(blk) + "." + port_name(port) + "[" + std::to_string(pin_port_bit(pin_id)) + "]";
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PinType Netlist<BlockId, PortId, PinId, NetId>::pin_type(const PinId pin_id) const {
    auto port_id = pin_port(pin_id);

    PinType type = PinType::OPEN;
    switch (port_type(port_id)) {
        case PortType::INPUT: /*fallthrough */
        case PortType::CLOCK:
            type = PinType::SINK;
            break;
        case PortType::OUTPUT:
            type = PinType::DRIVER;
            break;
        default:
            VTR_ASSERT_OPT_MSG(false, "Valid port type");
    }
    return type;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
NetId Netlist<BlockId, PortId, PinId, NetId>::pin_net(const PinId pin_id) const {
    VTR_ASSERT_SAFE(valid_pin_id(pin_id));

    return pin_nets_[pin_id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
int Netlist<BlockId, PortId, PinId, NetId>::pin_net_index(const PinId pin_id) const {
    VTR_ASSERT_SAFE(valid_pin_id(pin_id));

    return pin_net_indices_[pin_id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PortId Netlist<BlockId, PortId, PinId, NetId>::pin_port(const PinId pin_id) const {
    VTR_ASSERT_SAFE(valid_pin_id(pin_id));

    return pin_ports_[pin_id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
BitIndex Netlist<BlockId, PortId, PinId, NetId>::pin_port_bit(const PinId pin_id) const {
    VTR_ASSERT_SAFE(valid_pin_id(pin_id));

    return pin_port_bits_[pin_id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
BlockId Netlist<BlockId, PortId, PinId, NetId>::pin_block(const PinId pin_id) const {
    //Convenience lookup bypassing the port
    VTR_ASSERT_SAFE(valid_pin_id(pin_id));

    PortId port_id = pin_port(pin_id);
    return port_block(port_id);
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PortType Netlist<BlockId, PortId, PinId, NetId>::pin_port_type(const PinId pin_id) const {
    PortId port_id = pin_port(pin_id);

    return port_type(port_id);
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::pin_is_constant(const PinId pin_id) const {
    VTR_ASSERT_SAFE(valid_pin_id(pin_id));

    return pin_is_constant_[pin_id];
}

/*
 *
 * Nets
 *
 */
template<typename BlockId, typename PortId, typename PinId, typename NetId>
const std::string& Netlist<BlockId, PortId, PinId, NetId>::net_name(const NetId net_id) const {
    VTR_ASSERT_SAFE(valid_net_id(net_id));

    StringId str_id = net_names_[net_id];
    return strings_[str_id];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::pin_range Netlist<BlockId, PortId, PinId, NetId>::net_pins(const NetId net_id) const {
    VTR_ASSERT_SAFE(valid_net_id(net_id));

    return vtr::make_range(net_pins_[net_id].begin(), net_pins_[net_id].end());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PinId Netlist<BlockId, PortId, PinId, NetId>::net_pin(const NetId net_id, int net_pin_index) const {
    VTR_ASSERT_SAFE(valid_net_id(net_id));
    VTR_ASSERT_SAFE_MSG(net_pin_index >= 0 && size_t(net_pin_index) < net_pins_[net_id].size(), "Pin index must be in range");

    return net_pins_[net_id][net_pin_index];
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
BlockId Netlist<BlockId, PortId, PinId, NetId>::net_pin_block(const NetId net_id, int net_pin_index) const {
    auto pin_id = net_pin(net_id, net_pin_index);

    return pin_block(pin_id);
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PinId Netlist<BlockId, PortId, PinId, NetId>::net_driver(const NetId net_id) const {
    VTR_ASSERT_SAFE(valid_net_id(net_id));

    if (net_pins_[net_id].size() > 0) {
        return net_pins_[net_id][0];
    } else {
        return PinId::INVALID();
    }
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
BlockId Netlist<BlockId, PortId, PinId, NetId>::net_driver_block(const NetId net_id) const {
    auto driver_pin_id = net_driver(net_id);
    if (driver_pin_id) {
        return pin_block(driver_pin_id);
    }
    return BlockId::INVALID();
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::pin_range Netlist<BlockId, PortId, PinId, NetId>::net_sinks(const NetId net_id) const {
    VTR_ASSERT_SAFE(valid_net_id(net_id));

    return vtr::make_range(++net_pins_[net_id].begin(), net_pins_[net_id].end());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::net_is_constant(const NetId net_id) const {
    VTR_ASSERT_SAFE(valid_net_id(net_id));

    //Look-up the driver
    auto driver_pin_id = net_driver(net_id);
    if (driver_pin_id) {
        //Valid driver, see it is constant
        return pin_is_constant(driver_pin_id);
    }

    //No valid driver so can't be const
    return false;
}

/*
 *
 * Aggregates
 *
 */
template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::block_range Netlist<BlockId, PortId, PinId, NetId>::blocks() const {
    return vtr::make_range(block_ids_.begin(), block_ids_.end());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::port_range Netlist<BlockId, PortId, PinId, NetId>::ports() const {
    return vtr::make_range(port_ids_.begin(), port_ids_.end());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::pin_range Netlist<BlockId, PortId, PinId, NetId>::pins() const {
    return vtr::make_range(pin_ids_.begin(), pin_ids_.end());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::net_range Netlist<BlockId, PortId, PinId, NetId>::nets() const {
    return vtr::make_range(net_ids_.begin(), net_ids_.end());
}

/*
 *
 * Lookups
 *
 */
template<typename BlockId, typename PortId, typename PinId, typename NetId>
BlockId Netlist<BlockId, PortId, PinId, NetId>::find_block(const std::string& name) const {
    auto str_id = find_string(name);
    if (!str_id) {
        return BlockId::INVALID();
    } else {
        return find_block(str_id);
    }
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
BlockId Netlist<BlockId, PortId, PinId, NetId>::find_block_by_name_fragment(const std::string& name_substring) const {
    BlockId matching_blk_id = BlockId::INVALID();
    const std::string blk_name;

    // go through all the blocks in the netlist
    for (auto blk_id = block_ids_.begin(); blk_id != block_ids_.end(); blk_id++) {
        // get the corresponding block name
        blk_name = &strings_[block_names_[*blk_id]];
        // check whether the current block name contains the input string within it
        if (blk_name.find(name_substring) != std::string::npos) {
            matching_blk_id = blk_id;
            break;
        }
    }
    return matching_blk_id;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PortId Netlist<BlockId, PortId, PinId, NetId>::find_port(const BlockId blk_id, const std::string& name) const {
    VTR_ASSERT_SAFE(valid_block_id(blk_id));

    //Since we only know the port name, we must search all the ports
    for (auto port_id : block_ports(blk_id)) {
        if (port_name(port_id) == name) {
            return port_id;
        }
    }
    return PortId::INVALID();
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
NetId Netlist<BlockId, PortId, PinId, NetId>::find_net(const std::string& name) const {
    auto str_id = find_string(name);
    if (!str_id) {
        return NetId::INVALID();
    } else {
        return find_net(str_id);
    }
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PinId Netlist<BlockId, PortId, PinId, NetId>::find_pin(const PortId port_id, BitIndex port_bit) const {
    VTR_ASSERT_SAFE(valid_port_id(port_id));
    VTR_ASSERT_SAFE(valid_port_bit(port_id, port_bit));

    //Pins are stored in ascending order of bit index,
    //so we can binary search for the specific bit
    auto port_bit_cmp = [&](const PinId pin_id, BitIndex bit_index) {
        return pin_port_bit(pin_id) < bit_index;
    };

    auto pins_rng = port_pins(port_id);

    //Finds the location where the pin with bit index port_bit should be located (if it exists)
    auto iter = std::lower_bound(pins_rng.begin(), pins_rng.end(), port_bit, port_bit_cmp);

    if (iter == pins_rng.end() || pin_port_bit(*iter) != port_bit) {
        //Either the end of the pins (i.e. not found), or
        //the value does not match (indicating a gap in the indices, so also not found)
        return PinId::INVALID();
    } else {
        //Found it
        VTR_ASSERT_SAFE(pin_port_bit(*iter) == port_bit);
        return *iter;
    }
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PinId Netlist<BlockId, PortId, PinId, NetId>::find_pin(const std::string name) const {
    //We don't store a fast look-up for pin names, so do a slow linear search
    for (PinId pin : pins()) {
        if (pin_name(pin) == name) {
            return pin;
        }
    }
    return PinId::INVALID();
}

/*
 *
 * Validation
 *
 */
//Top-level verification method, checks that the sizes
//references, and lookups are all consistent.
template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::verify() const {
    bool valid = true;

    //Verify data structure consistency
    valid &= verify_sizes();
    valid &= verify_refs();
    valid &= verify_lookups();

    //Verify logical consistency
    valid &= verify_block_invariants();

    return valid;
}

//Checks that the sizes of internal data structures
//are consistent. Should take constant time.
template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::verify_sizes() const {
    bool valid = true;
    valid &= validate_block_sizes();
    valid &= validate_port_sizes();
    valid &= validate_pin_sizes();
    valid &= validate_net_sizes();
    valid &= validate_string_sizes();
    return valid;
}

//Checks that all cross-references are consistent.
//Should take linear time.
template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::verify_refs() const {
    bool valid = true;
    valid &= validate_block_port_refs();
    valid &= validate_port_pin_refs();
    valid &= validate_block_pin_refs();
    valid &= validate_net_pin_refs();
    valid &= validate_string_refs();

    return valid;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::verify_lookups() const {
    //Verify that fast look-ups are consistent

    //Blocks
    for (auto blk_id : blocks()) {
        const auto& name = block_name(blk_id);
        if (find_block(name) != blk_id) {
            VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Block lookup by name mismatch");
        }
    }

    //Ports
    for (auto port_id : port_ids_) {
        auto blk_id = port_block(port_id);
        const auto& name = port_name(port_id);
        if (find_port(blk_id, name) != port_id) {
            VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Port lookup by name mismatch");
        }
    }

    //Pins
    for (auto pin_id : pin_ids_) {
        auto port_id = pin_port(pin_id);
        auto bit = pin_port_bit(pin_id);
        if (find_pin(port_id, bit) != pin_id) {
            VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Pin lookup by name mismatch");
        }
    }

    //Nets
    for (auto net_id : nets()) {
        const auto& name = net_name(net_id);
        if (find_net(name) != net_id) {
            VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Net lookup by name mismatch");
        }
    }

    //Strings
    for (auto str_id : string_ids_) {
        const auto& name = strings_[str_id];
        if (find_string(name) != str_id) {
            VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "String lookup by name mismatch");
        }
    }
    return true;
}

/*
 *
 * Mutators
 *
 */
template<typename BlockId, typename PortId, typename PinId, typename NetId>
BlockId Netlist<BlockId, PortId, PinId, NetId>::create_block(const std::string name) {
    //Must have a non-empty name
    VTR_ASSERT_MSG(!name.empty(), "Non-Empty block name");

    //Check if the block has already been created
    StringId name_id = create_string(name);
    BlockId blk_id = find_block(name_id);

    if (blk_id == BlockId::INVALID()) {
        //Not found, create it

        //Reserve an id
        blk_id = BlockId(block_ids_.size());
        block_ids_.push_back(blk_id);

        //Initialize the data
        block_names_.push_back(name_id);
        block_attrs_.emplace_back();
        block_params_.emplace_back();

        //Initialize the look-ups
        block_name_to_block_id_.insert(name_id, blk_id);

        block_pins_.emplace_back();
        block_num_input_pins_.push_back(0);
        block_num_output_pins_.push_back(0);
        block_num_clock_pins_.push_back(0);

        block_ports_.emplace_back();
        block_num_input_ports_.push_back(0);
        block_num_output_ports_.push_back(0);
        block_num_clock_ports_.push_back(0);
    }

    //Check post-conditions: values
    VTR_ASSERT(valid_block_id(blk_id));
    VTR_ASSERT(block_name(blk_id) == name);
    VTR_ASSERT_SAFE(find_block(name) == blk_id);

    return blk_id;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PortId Netlist<BlockId, PortId, PinId, NetId>::create_port(const BlockId blk_id, const std::string name, BitIndex width, PortType type) {
    //Check pre-conditions
    VTR_ASSERT_MSG(valid_block_id(blk_id), "Valid block id");

    //See if the port already exists
    StringId name_id = create_string(name);
    PortId port_id = find_port(blk_id, name);
    if (!port_id) { //Not found, create it
                    //Reserve an id
        port_id = PortId(port_ids_.size());
        port_ids_.push_back(port_id);

        //Initialize the per-port-instance data
        port_blocks_.push_back(blk_id);
        port_names_.push_back(name_id);
        port_widths_.push_back(width);
        port_types_.push_back(type);

        //Allocate the pins, initialize to invalid Ids
        port_pins_.emplace_back();
    }

    //Check post-conditions: values
    VTR_ASSERT(valid_port_id(port_id));
    VTR_ASSERT(port_block(port_id) == blk_id);
    VTR_ASSERT(port_width(port_id) == width);

    return port_id;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
PinId Netlist<BlockId, PortId, PinId, NetId>::create_pin(const PortId port_id, BitIndex port_bit, const NetId net_id, const PinType type, bool is_const) {
    //Check pre-conditions (valid ids)
    VTR_ASSERT_MSG(valid_port_id(port_id), "Valid port id");
    VTR_ASSERT_MSG(valid_port_bit(port_id, port_bit), "Valid port bit");
    VTR_ASSERT_MSG(valid_net_id(net_id), "Valid net id");

    //See if the pin already exists
    PinId pin_id = find_pin(port_id, port_bit);

    if (!pin_id) {
        //Not found, create it

        //Reserve an id
        pin_id = PinId(pin_ids_.size());
        pin_ids_.push_back(pin_id);

        //Initialize the pin data
        pin_ports_.push_back(port_id);
        pin_port_bits_.push_back(port_bit);
        pin_nets_.push_back(net_id);
        pin_is_constant_.push_back(is_const);

        //Add the pin to the net
        int pins_net_index = associate_pin_with_net(pin_id, type, net_id);

        //Save the net index of the pin
        pin_net_indices_.push_back(pins_net_index);

        //Add the pin to the port
        associate_pin_with_port(pin_id, port_id);

        //Add the pin to the block
        associate_pin_with_block(pin_id, port_type(port_id), port_block(port_id));
    }

    //Check post-conditions: values
    VTR_ASSERT(valid_pin_id(pin_id));
    VTR_ASSERT(pin_port(pin_id) == port_id);
    VTR_ASSERT(pin_port_bit(pin_id) == port_bit);
    VTR_ASSERT(pin_net(pin_id) == net_id);
    VTR_ASSERT(pin_is_constant(pin_id) == is_const);
    VTR_ASSERT(pin_type(pin_id) == type);
    VTR_ASSERT_SAFE(find_pin(port_id, port_bit) == pin_id);

    return pin_id;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
NetId Netlist<BlockId, PortId, PinId, NetId>::create_net(const std::string name) {
    //Creates an empty net (or returns an existing one)
    VTR_ASSERT_MSG(!name.empty(), "Valid net name");

    //Check if the net has already been created
    StringId name_id = create_string(name);
    NetId net_id = find_net(name_id);
    if (net_id == NetId::INVALID()) {
        //Not found, create it

        //Reserve an id
        net_id = NetId(net_ids_.size());
        net_ids_.push_back(net_id);

        //Initialize the data
        net_names_.push_back(name_id);

        //Initialize the look-ups
        net_name_to_net_id_.insert(name_id, net_id);

        //Initialize with no driver
        net_pins_.emplace_back();
        net_pins_[net_id].emplace_back(PinId::INVALID());

        net_is_ignored_.push_back(false);

        net_is_global_.push_back(false);

        VTR_ASSERT(net_pins_[net_id].size() == 1);
        VTR_ASSERT(net_pins_[net_id][0] == PinId::INVALID());
    }

    //Check post-conditions: values
    VTR_ASSERT(valid_net_id(net_id));
    VTR_ASSERT(net_name(net_id) == name);
    VTR_ASSERT(find_net(name) == net_id);

    return net_id;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
NetId Netlist<BlockId, PortId, PinId, NetId>::add_net(const std::string name, PinId driver, std::vector<PinId> sinks) {
    //Creates a net with a full set of pins
    VTR_ASSERT_MSG(!find_net(name), "Net should not exist");

    //Create the empty net
    NetId net_id = create_net(name);

    //Set the driver and sinks of the net
    auto& dest_pins = net_pins_[net_id];
    dest_pins[0] = driver;
    dest_pins.insert(dest_pins.end(),
                     std::make_move_iterator(sinks.begin()),
                     std::make_move_iterator(sinks.end()));

    //Associate each pin with the net
    int net_index = 0;
    pin_nets_[driver] = net_id;
    pin_net_indices_[driver] = net_index++;
    for (auto sink : sinks) {
        pin_nets_[sink] = net_id;
        pin_net_indices_[sink] = net_index++;
    }

    return net_id;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::set_pin_is_constant(const PinId pin_id, const bool value) {
    VTR_ASSERT(valid_pin_id(pin_id));

    pin_is_constant_[pin_id] = value;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::set_pin_net(const PinId pin, PinType type, const NetId net) {
    VTR_ASSERT(valid_pin_id(pin));

    VTR_ASSERT((type == PinType::DRIVER && pin_port_type(pin) == PortType::OUTPUT)
               || (type == PinType::SINK && (pin_port_type(pin) == PortType::INPUT || pin_port_type(pin) == PortType::CLOCK)));

    NetId orig_net = pin_net(pin);
    if (orig_net) {
        //Clean up the pin reference on the original net
        remove_net_pin(orig_net, pin);
    }

    //Mark the pin's net
    pin_nets_[pin] = net;

    //Add the pin to the net
    int pins_net_index = associate_pin_with_net(pin, type, net);

    //Save the pin's index within the net
    pin_net_indices_[pin] = pins_net_index;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::set_block_name(const BlockId blk_id, const std::string new_name) {
    VTR_ASSERT(valid_block_id(blk_id));

    //Names must be unique -- no duplicates allowed
    BlockId existing_blk_id = find_block(new_name);
    if (existing_blk_id) {
        VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Can not re-name block '%s' to '%s' (a block named '%s' already exists).",
                        block_name(blk_id).c_str(),
                        new_name.c_str(),
                        new_name.c_str());
    }

    //Remove old name-look-up
    {
        StringId old_string = find_string(block_name(blk_id));
        block_name_to_block_id_[old_string] = BlockId::INVALID();
    }

    //Re-name the block
    StringId new_string = create_string(new_name);
    block_names_[blk_id] = new_string;

    //Update name-look-up
    block_name_to_block_id_.insert(new_string, blk_id);
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::set_block_attr(const BlockId blk_id, const std::string& name, const std::string& value) {
    VTR_ASSERT(valid_block_id(blk_id));

    block_attrs_[blk_id][name] = value;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::set_block_param(const BlockId blk_id, const std::string& name, const std::string& value) {
    VTR_ASSERT(valid_block_id(blk_id));

    block_params_[blk_id][name] = value;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::set_net_is_ignored(NetId net_id, bool state) {
    VTR_ASSERT(valid_net_id(net_id));

    net_is_ignored_[net_id] = state;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::set_net_is_global(NetId net_id, bool state) {
    VTR_ASSERT(valid_net_id(net_id));

    net_is_global_[net_id] = state;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::merge_nets(const NetId driver_net, const NetId sink_net) {
    VTR_ASSERT(valid_net_id(driver_net));
    VTR_ASSERT(valid_net_id(sink_net));

    //Sink net must not have a driver pin
    PinId sink_driver = net_driver(sink_net);
    if (sink_driver) {
        VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Can not merge nets '%s' and '%s' (sink net '%s' should have no driver, but is driven by pin '%s')",
                        net_name(driver_net).c_str(),
                        net_name(sink_net).c_str(),
                        net_name(sink_net).c_str(),
                        pin_name(sink_driver).c_str());
    }

    //We allow the driver net to (potentially) have no driver yet,
    //so we don't check to ensure it exists

    //
    //Merge the nets
    //

    //Move the sinks to the driver net
    for (PinId sink_pin : net_sinks(sink_net)) {
        //Update pin -> net references, also adds pins to driver_net
        set_pin_net(sink_pin, pin_type(sink_pin), driver_net);
    }

    //Remove the sink net
    // Note that we drop the sink net's pin references first,
    // this ensures remove_net() will only clean-up the net
    // data, and will not modify the (already moved) pins
    net_pins_[sink_net].clear(); //Drop sink_net's pin references
    remove_net(sink_net);
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::remove_block(const BlockId blk_id) {
    VTR_ASSERT(valid_block_id(blk_id));

    //Remove the ports
    for (PortId block_port : block_ports(blk_id)) {
        remove_port(block_port);
    }

    //Invalidate look-up
    StringId name_id = block_names_[blk_id];
    block_name_to_block_id_.insert(name_id, BlockId::INVALID());

    //Call derived class' remove()
    remove_block_impl(blk_id);

    //Mark as invalid
    block_ids_[blk_id] = BlockId::INVALID();

    //Mark netlist dirty
    dirty_ = true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::remove_port(const PortId port_id) {
    VTR_ASSERT(valid_port_id(port_id));

    //Remove the pins
    for (auto pin : port_pins(port_id)) {
        if (valid_pin_id(pin)) {
            remove_pin(pin);
        }
    }

    //Mark as invalid
    port_ids_[port_id] = PortId::INVALID();

    //Call derived class' remove()
    remove_port_impl(port_id);

    //Mark netlist dirty
    dirty_ = true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::remove_pin(const PinId pin_id) {
    VTR_ASSERT(valid_pin_id(pin_id));

    //Find the associated net
    NetId net = pin_net(pin_id);

    //Remove the pin from the associated net/port/block
    remove_net_pin(net, pin_id);

    //Mark as invalid
    pin_ids_[pin_id] = PinId::INVALID();

    //Call derived class' remove()
    remove_pin_impl(pin_id);

    //Mark netlist dirty
    dirty_ = true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::remove_net(const NetId net_id) {
    VTR_ASSERT(valid_net_id(net_id));

    //Disassociate the pins from the net
    for (auto pin_id : net_pins(net_id)) {
        if (pin_id) {
            pin_nets_[pin_id] = NetId::INVALID();
            pin_net_indices_[pin_id] = INVALID_INDEX;
        }
    }
    //Invalidate look-up
    StringId name_id = net_names_[net_id];
    net_name_to_net_id_.insert(name_id, NetId::INVALID());

    //Mark as invalid
    net_ids_[net_id] = NetId::INVALID();

    //Call derived class' remove()
    remove_net_impl(net_id);

    //Mark netlist dirty
    dirty_ = true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::remove_net_pin(const NetId net_id, const PinId pin_id) {
    //Remove a net-pin connection
    //
    //Note that during sweeping either the net or pin could be invalid (i.e. already swept)
    //so we check before trying to use them

    if (valid_net_id(net_id)) {
        //Warning: this is slow!
        auto iter = std::find(net_pins_[net_id].begin(), net_pins_[net_id].end(), pin_id); //Linear search
        VTR_ASSERT(iter != net_pins_[net_id].end());

        if (net_driver(net_id) == pin_id) {
            //Mark no driver
            net_pins_[net_id][0] = PinId::INVALID();
        } else {
            //Remove sink
            net_pins_[net_id].erase(iter); //Linear remove
        }

        //Note: since we fully update the net we don't need to mark the netlist dirty_
    }

    //Disassociate the pin with the net
    if (valid_pin_id(pin_id)) {
        pin_nets_[pin_id] = NetId::INVALID();
        pin_net_indices_[pin_id] = INVALID_INDEX;

        //Mark netlist dirty, since we are leaving an invalid net id
        dirty_ = true;
    }
}

/*
 *
 * Internal utilities
 *
 */
template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::IdRemapper Netlist<BlockId, PortId, PinId, NetId>::remove_and_compress() {
    remove_unused();
    return compress();
}

//Compress the various netlist components to remove invalid entries
// Note: this invalidates all Ids
template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::IdRemapper Netlist<BlockId, PortId, PinId, NetId>::compress() {
    //Build the mappings from old to new id's, potentially
    //re-ordering for improved cache locality
    //
    //The vectors passed as parameters are initialized as a mapping from old to new index
    // e.g. block_id_map[old_id] == new_id
    IdRemapper id_remapper = build_id_maps();

    clean_nets(id_remapper.net_id_map_);
    clean_pins(id_remapper.pin_id_map_);
    clean_ports(id_remapper.port_id_map_);
    clean_blocks(id_remapper.block_id_map_);
    //TODO: clean strings
    //TODO: iterative cleaning?

    //Now we re-build all the cross references
    // Note: net references must be rebuilt (to remove pins) before
    //       the pin references can be rebuilt (to account for index changes
    //       due to pins being removed from the net)
    rebuild_block_refs(id_remapper.pin_id_map_, id_remapper.port_id_map_);
    rebuild_port_refs(id_remapper.block_id_map_, id_remapper.pin_id_map_);
    rebuild_net_refs(id_remapper.pin_id_map_);
    rebuild_pin_refs(id_remapper.port_id_map_, id_remapper.net_id_map_);

    //Re-build the lookups
    rebuild_lookups();

    //Resize containers to exact size
    shrink_to_fit();

    //Netlist is now clean
    dirty_ = false;

    return id_remapper;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::remove_unused() {
    //Mark any nets/pins/ports/blocks which are not in use as invalid
    //so they will be removed

    bool found_unused;
    do {
        found_unused = false;
        //Nets with no connected pins
        for (auto net_id : net_ids_) {
            if (net_id
                && !net_driver(net_id)
                && net_sinks(net_id).size() == 0) {
                remove_net(net_id);
                found_unused = true;
            }
        }

        //Pins with no connected nets
        for (auto pin_id : pin_ids_) {
            if (pin_id && !pin_net(pin_id)) {
                remove_pin(pin_id);
                found_unused = true;
            }
        }

        //Empty ports
        for (auto port_id : port_ids_) {
            if (port_id && port_pins(port_id).size() == 0) {
                remove_port(port_id);
                found_unused = true;
            }
        }

        //Blocks with no pins
        for (auto blk_id : block_ids_) {
            if (blk_id && block_pins(blk_id).size() == 0) {
                remove_block(blk_id);
                found_unused = true;
            }
        }

    } while (found_unused);
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::IdRemapper Netlist<BlockId, PortId, PinId, NetId>::build_id_maps() {
    IdRemapper id_remapper;
    id_remapper.block_id_map_ = compress_ids(block_ids_);
    id_remapper.port_id_map_ = compress_ids(port_ids_);
    id_remapper.pin_id_map_ = compress_ids(pin_ids_);
    id_remapper.net_id_map_ = compress_ids(net_ids_);
    return id_remapper;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::clean_blocks(const vtr::vector_map<BlockId, BlockId>& block_id_map) {
    //Update all the block values
    block_ids_ = clean_and_reorder_ids(block_id_map);
    block_names_ = clean_and_reorder_values(block_names_, block_id_map);

    block_pins_ = clean_and_reorder_values(block_pins_, block_id_map);
    block_num_input_pins_ = clean_and_reorder_values(block_num_input_pins_, block_id_map);
    block_num_output_pins_ = clean_and_reorder_values(block_num_output_pins_, block_id_map);
    block_num_clock_pins_ = clean_and_reorder_values(block_num_clock_pins_, block_id_map);

    block_ports_ = clean_and_reorder_values(block_ports_, block_id_map);
    block_num_input_ports_ = clean_and_reorder_values(block_num_input_ports_, block_id_map);
    block_num_output_ports_ = clean_and_reorder_values(block_num_output_ports_, block_id_map);
    block_num_clock_ports_ = clean_and_reorder_values(block_num_clock_ports_, block_id_map);

    block_attrs_ = clean_and_reorder_values(block_attrs_, block_id_map);
    block_params_ = clean_and_reorder_values(block_params_, block_id_map);

    clean_blocks_impl(block_id_map);

    VTR_ASSERT(validate_block_sizes());

    VTR_ASSERT_MSG(are_contiguous(block_ids_), "Ids should be contiguous");
    VTR_ASSERT_MSG(all_valid(block_ids_), "All Ids should be valid");
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::clean_ports(const vtr::vector_map<PortId, PortId>& port_id_map) {
    //Update all the port values
    port_ids_ = clean_and_reorder_ids(port_id_map);
    port_names_ = clean_and_reorder_values(port_names_, port_id_map);
    port_blocks_ = clean_and_reorder_values(port_blocks_, port_id_map);
    port_widths_ = clean_and_reorder_values(port_widths_, port_id_map);
    port_types_ = clean_and_reorder_values(port_types_, port_id_map);
    port_pins_ = clean_and_reorder_values(port_pins_, port_id_map);

    clean_ports_impl(port_id_map);

    VTR_ASSERT(validate_port_sizes());

    VTR_ASSERT_MSG(are_contiguous(port_ids_), "Ids should be contiguous");
    VTR_ASSERT_MSG(all_valid(port_ids_), "All Ids should be valid");
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::clean_pins(const vtr::vector_map<PinId, PinId>& pin_id_map) {
    //Update all the pin values
    pin_ids_ = clean_and_reorder_ids(pin_id_map);
    pin_ports_ = clean_and_reorder_values(pin_ports_, pin_id_map);

    //It is sufficient to merely clean and re-order the pin_port_bits_, since
    //the size of a port (and hence the pin's bit index in the port) does not change
    //during cleaning
    pin_port_bits_ = clean_and_reorder_values(pin_port_bits_, pin_id_map);
    pin_nets_ = clean_and_reorder_values(pin_nets_, pin_id_map);

    //Note that clean and re-order serves only to resize pin_net_indices_, the
    //actual net references are wrong (since net size may have changed during
    //cleaning). They will be updated in rebuild_pin_refs()
    pin_net_indices_ = clean_and_reorder_values(pin_net_indices_, pin_id_map);
    pin_is_constant_ = clean_and_reorder_values(pin_is_constant_, pin_id_map);

    clean_pins_impl(pin_id_map);

    VTR_ASSERT(validate_pin_sizes());

    VTR_ASSERT_MSG(are_contiguous(pin_ids_), "Ids should be contiguous");
    VTR_ASSERT_MSG(all_valid(pin_ids_), "All Ids should be valid");
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::clean_nets(const vtr::vector_map<NetId, NetId>& net_id_map) {
    //Update all the net values
    net_ids_ = clean_and_reorder_ids(net_id_map);
    net_names_ = clean_and_reorder_values(net_names_, net_id_map);
    net_pins_ = clean_and_reorder_values(net_pins_, net_id_map); //Note: actual pin refs are updated in rebuild_net_refs()
    net_is_ignored_ = clean_and_reorder_values(net_is_ignored_, net_id_map);
    net_is_global_ = clean_and_reorder_values(net_is_global_, net_id_map);

    clean_nets_impl(net_id_map);

    VTR_ASSERT(validate_net_sizes());

    VTR_ASSERT_MSG(are_contiguous(net_ids_), "Ids should be contiguous");
    VTR_ASSERT_MSG(all_valid(net_ids_), "All Ids should be valid");
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::rebuild_lookups() {
    //We iterate through the reverse-lookups and update the values (i.e. ids)
    //to the new id values

    //Blocks
    block_name_to_block_id_.clear();
    for (auto blk_id : blocks()) {
        const auto& key = block_names_[blk_id];
        block_name_to_block_id_.insert(key, blk_id);
    }

    //Nets
    net_name_to_net_id_.clear();
    for (auto net_id : nets()) {
        const auto& key = net_names_[net_id];
        net_name_to_net_id_.insert(key, net_id);
    }
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::rebuild_block_refs(const vtr::vector_map<PinId, PinId>& pin_id_map,
                                                                const vtr::vector_map<PortId, PortId>& port_id_map) {
    //Update the pin id references held by blocks
    for (auto blk_id : blocks()) {
        //Before update the references, we need to know how many are valid,
        //so we can also update the numbers of input/output/clock pins

        //Note that we take special care to not modify the pin counts until after
        //they have all been counted (since the block_*_pins() functions depend on
        //the counts).
        size_t num_input_pins = count_valid_refs(block_input_pins(blk_id), pin_id_map);
        size_t num_output_pins = count_valid_refs(block_output_pins(blk_id), pin_id_map);
        size_t num_clock_pins = count_valid_refs(block_clock_pins(blk_id), pin_id_map);

        std::vector<PinId>& pin_collection = block_pins_[blk_id];

        pin_collection = update_valid_refs(pin_collection, pin_id_map);
        block_num_input_pins_[blk_id] = num_input_pins;
        block_num_output_pins_[blk_id] = num_output_pins;
        block_num_clock_pins_[blk_id] = num_clock_pins;

        VTR_ASSERT_SAFE_MSG(all_valid(pin_collection), "All Ids should be valid");
        VTR_ASSERT(pin_collection.size() == size_t(block_num_input_pins_[blk_id] + block_num_output_pins_[blk_id] + block_num_clock_pins_[blk_id]));

        //Similarly for ports
        size_t num_input_ports = count_valid_refs(block_input_ports(blk_id), port_id_map);
        size_t num_output_ports = count_valid_refs(block_output_ports(blk_id), port_id_map);
        size_t num_clock_ports = count_valid_refs(block_clock_ports(blk_id), port_id_map);

        std::vector<PortId>& blk_ports = block_ports_[blk_id];

        blk_ports = update_valid_refs(blk_ports, port_id_map);
        block_num_input_ports_[blk_id] = num_input_ports;
        block_num_output_ports_[blk_id] = num_output_ports;
        block_num_clock_ports_[blk_id] = num_clock_ports;

        VTR_ASSERT_SAFE_MSG(all_valid(blk_ports), "All Ids should be valid");
        VTR_ASSERT(blk_ports.size() == size_t(block_num_input_ports_[blk_id] + block_num_output_ports_[blk_id] + block_num_clock_ports_[blk_id]));
    }

    rebuild_block_refs_impl(pin_id_map, port_id_map);

    VTR_ASSERT(validate_block_sizes());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::rebuild_port_refs(const vtr::vector_map<BlockId, BlockId>& block_id_map,
                                                               const vtr::vector_map<PinId, PinId>& pin_id_map) {
    //Update block and pin references held by ports
    port_blocks_ = update_valid_refs(port_blocks_, block_id_map);
    VTR_ASSERT_SAFE_MSG(all_valid(port_blocks_), "All Ids should be valid");

    VTR_ASSERT(port_blocks_.size() == port_ids_.size());

    for (auto& pin_collection : port_pins_) {
        pin_collection = update_valid_refs(pin_collection, pin_id_map);
        VTR_ASSERT_SAFE_MSG(all_valid(pin_collection), "All Ids should be valid");
    }

    rebuild_port_refs_impl(block_id_map, pin_id_map);

    VTR_ASSERT(validate_port_sizes());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::rebuild_pin_refs(const vtr::vector_map<PortId, PortId>& port_id_map,
                                                              const vtr::vector_map<NetId, NetId>& net_id_map) {
    //Update port and net references held by pins
    pin_ports_ = update_all_refs(pin_ports_, port_id_map);
    VTR_ASSERT_SAFE_MSG(all_valid(pin_ports_), "All Ids should be valid");

    //NOTE: we do not need to update pin_port_bits_ since a pin's index within it's
    //port will not change (since the port width's don't change when cleaned)

    pin_nets_ = update_all_refs(pin_nets_, net_id_map);
    VTR_ASSERT_SAFE_MSG(all_valid(pin_nets_), "All Ids should be valid");

    //We must carefully re-build the pin net indices from scratch, since cleaning
    //may have changed the index of the pin within the net (e.g. if invalid pins
    //were removed)
    //
    //Note that for this to work correctly, the net references must have already been re-built!
    for (auto net : nets()) {
        int i = 0;
        for (auto pin : net_pins(net)) {
            pin_net_indices_[pin] = i;
            ++i;
        }
    }

    rebuild_pin_refs_impl(port_id_map, net_id_map);

    VTR_ASSERT(validate_pin_sizes());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::rebuild_net_refs(const vtr::vector_map<PinId, PinId>& pin_id_map) {
    //Update pin references held by nets
    for (auto& pin_collection : net_pins_) {
        //We take special care to preserve the driver index, since an INVALID id is used
        //to indicate an undriven net it should not be dropped during the update
        pin_collection = update_valid_refs(pin_collection, pin_id_map, {NET_DRIVER_INDEX});

        VTR_ASSERT_SAFE_MSG(all_valid(pin_collection), "All sinks should be valid");
    }

    rebuild_net_refs_impl(pin_id_map);

    VTR_ASSERT(validate_net_sizes());
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::shrink_to_fit() {
    //Block data
    block_ids_.shrink_to_fit();
    block_names_.shrink_to_fit();

    block_pins_.shrink_to_fit();
    for (std::vector<PinId>& pin_collection : block_pins_) {
        pin_collection.shrink_to_fit();
    }
    block_num_input_pins_.shrink_to_fit();
    block_num_output_pins_.shrink_to_fit();
    block_num_clock_pins_.shrink_to_fit();

    block_ports_.shrink_to_fit();
    for (auto& blk_ports : block_ports_) {
        blk_ports.shrink_to_fit();
    }
    block_num_input_ports_.shrink_to_fit();
    block_num_output_ports_.shrink_to_fit();
    block_num_clock_ports_.shrink_to_fit();

    VTR_ASSERT(validate_block_sizes());

    //Port data
    port_ids_.shrink_to_fit();
    port_blocks_.shrink_to_fit();
    port_widths_.shrink_to_fit();
    port_types_.shrink_to_fit();
    port_pins_.shrink_to_fit();
    for (auto& pin_collection : port_pins_) {
        pin_collection.shrink_to_fit();
    }
    VTR_ASSERT(validate_port_sizes());

    //Pin data
    pin_ids_.shrink_to_fit();
    pin_ports_.shrink_to_fit();
    pin_port_bits_.shrink_to_fit();
    pin_nets_.shrink_to_fit();
    pin_is_constant_.shrink_to_fit();
    VTR_ASSERT(validate_pin_sizes());

    //Net data
    net_ids_.shrink_to_fit();
    net_names_.shrink_to_fit();
    net_pins_.shrink_to_fit();
    net_is_ignored_.shrink_to_fit();
    net_is_global_.shrink_to_fit();
    VTR_ASSERT(validate_net_sizes());

    //String data
    string_ids_.shrink_to_fit();
    strings_.shrink_to_fit();
    VTR_ASSERT(validate_string_sizes());
}

/*
 *
 * Sanity Checks
 *
 */
template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::valid_block_id(BlockId block_id) const {
    if (block_id == BlockId::INVALID()
        || !block_ids_.contains(block_id)
        || block_ids_[block_id] != block_id) {
        return false;
    }
    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::valid_port_id(PortId port_id) const {
    if (port_id == PortId::INVALID()
        || !port_ids_.contains(port_id)
        || port_ids_[port_id] != port_id) {
        return false;
    }
    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::valid_port_bit(PortId port_id, BitIndex port_bit) const {
    VTR_ASSERT_SAFE(valid_port_id(port_id));
    if (port_bit >= port_width(port_id)) {
        return false;
    }
    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::valid_pin_id(PinId pin_id) const {
    if (pin_id == PinId::INVALID()
        || !pin_ids_.contains(pin_id)
        || pin_ids_[pin_id] != pin_id) {
        return false;
    }
    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::valid_net_id(NetId net_id) const {
    if (net_id == NetId::INVALID()
        || !net_ids_.contains(net_id)
        || net_ids_[net_id] != net_id) {
        return false;
    }
    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::valid_string_id(typename Netlist<BlockId, PortId, PinId, NetId>::StringId string_id) const {
    if (string_id == StringId::INVALID()
        || !string_ids_.contains(string_id)
        || string_ids_[string_id] != string_id) {
        return false;
    }
    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::validate_block_sizes() const {
    size_t num_blocks = blocks().size();
    if (block_names_.size() != num_blocks
        || block_pins_.size() != num_blocks
        || block_num_input_pins_.size() != num_blocks
        || block_num_output_pins_.size() != num_blocks
        || block_num_clock_pins_.size() != num_blocks
        || block_ports_.size() != num_blocks
        || block_num_input_ports_.size() != num_blocks
        || block_num_output_ports_.size() != num_blocks
        || block_num_clock_ports_.size() != num_blocks
        || block_attrs_.size() != num_blocks
        || block_params_.size() != num_blocks
        || !validate_block_sizes_impl(num_blocks)) {
        VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Inconsistent block data sizes");
    }
    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::validate_port_sizes() const {
    size_t num_ports = ports().size();
    if (port_names_.size() != num_ports
        || port_blocks_.size() != num_ports
        || port_pins_.size() != num_ports
        || !validate_port_sizes_impl(num_ports)) {
        VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Inconsistent port data sizes");
    }
    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::validate_pin_sizes() const {
    size_t num_pins = pins().size();
    if (pin_ports_.size() != num_pins
        || pin_port_bits_.size() != num_pins
        || pin_nets_.size() != num_pins
        || pin_net_indices_.size() != num_pins
        || pin_is_constant_.size() != num_pins
        || !validate_pin_sizes_impl(num_pins)) {
        VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Inconsistent pin data sizes");
    }
    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::validate_net_sizes() const {
    size_t num_nets = nets().size();
    if (net_names_.size() != num_nets
        || net_pins_.size() != num_nets
        || net_is_ignored_.size() != num_nets
        || net_is_global_.size() != num_nets
        || !validate_net_sizes_impl(num_nets)) {
        VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Inconsistent net data sizes");
    }
    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::validate_string_sizes() const {
    if (strings_.size() != string_ids_.size()) {
        VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Inconsistent string data sizes");
    }
    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::validate_block_port_refs() const {
    //Verify that all block <-> port references are consistent

    //Track how many times we've seen each port from the blocks
    vtr::vector_map<PortId, unsigned> seen_port_ids(port_ids_.size());

    for (auto blk_id : blocks()) {
        for (auto in_port_id : block_input_ports(blk_id)) {
            if (blk_id != port_block(in_port_id)) {
                VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Block-input port cross-reference does not match");
            }
            ++seen_port_ids[in_port_id];
        }
        for (auto out_port_id : block_output_ports(blk_id)) {
            if (blk_id != port_block(out_port_id)) {
                VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Block-output port cross-reference does not match");
            }
            ++seen_port_ids[out_port_id];
        }
        for (auto clock_port_id : block_clock_ports(blk_id)) {
            if (blk_id != port_block(clock_port_id)) {
                VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Block-clock port cross-reference does not match");
            }
            ++seen_port_ids[clock_port_id];
        }
    }

    //Check that we have neither orphaned ports (i.e. that aren't referenced by a block)
    //nor shared ports (i.e. referenced by multiple blocks)
    auto is_one = [](unsigned val) {
        return val == 1;
    };
    if (!std::all_of(seen_port_ids.begin(), seen_port_ids.end(), is_one)) {
        VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Port not referenced by a single block");
    }

    if (std::accumulate(seen_port_ids.begin(), seen_port_ids.end(), 0u) != port_ids_.size()) {
        VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Found orphaned port (not referenced by a block)");
    }

    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::validate_block_pin_refs() const {
    //Verify that block pin refs are consistent
    //(e.g. inputs are inputs, outputs are outputs etc.)
    for (auto blk_id : blocks()) {
        //Check that only input pins are found when iterating through inputs
        for (auto pin_id : block_input_pins(blk_id)) {
            auto port_id = pin_port(pin_id);

            auto type = port_type(port_id);
            if (type != PortType::INPUT) {
                VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Non-input pin in block input pins");
            }
        }

        //Check that only output pins are found when iterating through outputs
        for (auto pin_id : block_output_pins(blk_id)) {
            auto port_id = pin_port(pin_id);

            auto type = port_type(port_id);
            if (type != PortType::OUTPUT) {
                VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Non-output pin in block output pins");
            }
        }

        //Check that only clock pins are found when iterating through clocks
        for (auto pin_id : block_clock_pins(blk_id)) {
            auto port_id = pin_port(pin_id);

            auto type = port_type(port_id);
            if (type != PortType::CLOCK) {
                VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Non-clock pin in block clock pins");
            }
        }
    }
    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::validate_port_pin_refs() const {
    //Check that port <-> pin references are consistent

    //Track how many times we've seen each pin from the ports
    vtr::vector_map<PinId, unsigned> seen_pin_ids(pin_ids_.size());

    for (auto port_id : port_ids_) {
        bool first_bit = true;
        BitIndex prev_bit_index = 0;
        for (auto pin_id : port_pins(port_id)) {
            if (pin_port(pin_id) != port_id) {
                VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Port-pin cross-reference does not match");
            }
            if (pin_port_bit(pin_id) >= port_width(port_id)) {
                VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Out-of-range port bit index");
            }
            ++seen_pin_ids[pin_id];

            BitIndex port_bit_index = pin_port_bit(pin_id);

            //Verify that the port bit index is legal
            if (!valid_port_bit(port_id, port_bit_index)) {
                VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Invalid pin bit index in port");
            }

            //Verify that the pins are listed in increasing order of port bit index,
            //we rely on this property to perform fast binary searches for pins with specific bit
            //indices
            if (first_bit) {
                prev_bit_index = port_bit_index;
                first_bit = false;
            } else if (prev_bit_index >= port_bit_index) {
                VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Port pin indices are not in ascending order");
            }
        }
    }

    //Check that we have neither orphaned pins (i.e. that aren't referenced by a port)
    //nor shared pins (i.e. referenced by multiple ports)
    auto is_one = [](unsigned val) {
        return val == 1;
    };
    if (!std::all_of(seen_pin_ids.begin(), seen_pin_ids.end(), is_one)) {
        VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Pins referenced by zero or multiple ports");
    }

    if (std::accumulate(seen_pin_ids.begin(), seen_pin_ids.end(), 0u) != pin_ids_.size()) {
        VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Found orphaned pins (not referenced by a port)");
    }

    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::validate_net_pin_refs() const {
    //Check that net <-> pin references are consistent

    //Track how many times we've seen each pin from the ports
    vtr::vector_map<PinId, unsigned> seen_pin_ids(pin_ids_.size());

    for (auto net_id : nets()) {
        auto pins_rng = net_pins(net_id);
        for (auto iter = pins_rng.begin(); iter != pins_rng.end(); ++iter) {
            int pin_index = std::distance(pins_rng.begin(), iter);
            auto pin_id = *iter;

            if (iter == pins_rng.begin()) {
                //The first net pin is the driver, which may be invalid
                //if there is no driver.
                if (pin_id) {
                    VTR_ASSERT(pin_index == NET_DRIVER_INDEX);
                    if (pin_type(pin_id) != PinType::DRIVER) {
                        VPR_FATAL_ERROR(VPR_ERROR_NETLIST,
                                        "Driver pin %zu not found at expected index in net %zu",
                                        size_t(pin_id), size_t(net_id));
                    }
                }
            } else {
                //Any non-driver (i.e. sink) pins must be valid
                if (!pin_id) {
                    VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Invalid pin found in net sinks");
                }

                if (pin_type(pin_id) != PinType::SINK) {
                    VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Invalid pin type found in net sinks");
                }
            }

            if (pin_id) {
                //Verify the cross reference if the pin_id is valid (i.e. a sink or a valid driver)
                if (pin_net(pin_id) != net_id) {
                    VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Net-pin cross-reference does not match");
                }

                if (pin_net_index(pin_id) != pin_index) {
                    VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Pin's net index cross-reference does not match actual net index");
                }

                //We only record valid seen pins since we may see multiple undriven nets with invalid IDs
                ++seen_pin_ids[pin_id];
            }
        }
    }

    //Check that we have neither orphaned pins (i.e. that aren't referenced by a port)
    //nor shared pins (i.e. referenced by multiple ports)
    auto is_one = [](unsigned val) {
        return val == 1;
    };
    if (!std::all_of(seen_pin_ids.begin(), seen_pin_ids.end(), is_one)) {
        VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Found pins referenced by zero or multiple nets");
    }

    if (std::accumulate(seen_pin_ids.begin(), seen_pin_ids.end(), 0u) != pin_ids_.size()) {
        VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Found orphaned pins (not referenced by a net)");
    }

    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::validate_string_refs() const {
    for (const auto& str_id : block_names_) {
        if (!valid_string_id(str_id)) {
            VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Invalid block name string reference");
        }
    }
    for (const auto& str_id : port_names_) {
        if (!valid_string_id(str_id)) {
            VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Invalid port name string reference");
        }
    }
    for (const auto& str_id : net_names_) {
        if (!valid_string_id(str_id)) {
            VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Invalid net name string reference");
        }
    }
    return true;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
bool Netlist<BlockId, PortId, PinId, NetId>::verify_block_invariants() const {
    for (auto blk_id : blocks()) {
        //Check block type
        //Find any connected clock
        NetId clk_net_id;
        for (auto pin_id : block_clock_pins(blk_id)) {
            if (pin_id) {
                clk_net_id = pin_net(pin_id);
                break;
            }
        }

        if (block_is_combinational(blk_id)) {
            //Non-sequential types must not have a clock
            if (clk_net_id) {
                VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Block '%s' is a non-sequential type but has a clock '%s'",
                                block_name(blk_id).c_str(), net_name(clk_net_id).c_str());
            }

        } else {
            //Sequential types must have a clock
            if (!clk_net_id) {
                VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Block '%s' is sequential type but has no clock",
                                block_name(blk_id).c_str());
            }
        }

        //Check that block pins and ports are consistent (i.e. same number of pins)
        size_t num_block_pins = block_pins(blk_id).size();

        size_t total_block_port_pins = 0;
        for (auto port_id : block_ports(blk_id)) {
            total_block_port_pins += port_pins(port_id).size();
        }

        if (num_block_pins != total_block_port_pins) {
            VPR_FATAL_ERROR(VPR_ERROR_NETLIST, "Block pins and port pins do not match on block '%s'",
                            block_name(blk_id).c_str());
        }
    }

    return true;
}

/*
 *
 * Internal utilities
 *
 */
template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::StringId Netlist<BlockId, PortId, PinId, NetId>::find_string(const std::string& str) const {
    auto iter = string_to_string_id_.find(str);
    if (iter != string_to_string_id_.end()) {
        StringId str_id = iter->second;

        VTR_ASSERT_SAFE(str_id);
        VTR_ASSERT_SAFE(strings_[str_id] == str);

        return str_id;
    } else {
        return StringId::INVALID();
    }
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
BlockId Netlist<BlockId, PortId, PinId, NetId>::find_block(const typename Netlist<BlockId, PortId, PinId, NetId>::StringId name_id) const {
    VTR_ASSERT_SAFE(valid_string_id(name_id));
    auto iter = block_name_to_block_id_.find(name_id);
    if (iter != block_name_to_block_id_.end()) {
        BlockId blk_id = *iter;

        //Check post-conditions
        if (blk_id) {
            VTR_ASSERT_SAFE(valid_block_id(blk_id));
            VTR_ASSERT_SAFE(block_names_[blk_id] == name_id);
        }

        return blk_id;
    } else {
        return BlockId::INVALID();
    }
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::associate_pin_with_block(const PinId pin_id, const PortType type, const BlockId blk_id) {
    //Get an iterator pointing to where we want to insert
    pin_iterator iter;
    if (type == PortType::INPUT) {
        iter = block_input_pins(blk_id).end();
        ++block_num_input_pins_[blk_id];
    } else if (type == PortType::OUTPUT) {
        iter = block_output_pins(blk_id).end();
        ++block_num_output_pins_[blk_id];
    } else {
        VTR_ASSERT(type == PortType::CLOCK);
        iter = block_clock_pins(blk_id).end();
        ++block_num_clock_pins_[blk_id];
    }

    //Insert the element just before iter
    auto new_iter = block_pins_[blk_id].insert(iter, pin_id);

    //Inserted value should be the last valid range element
    if (type == PortType::INPUT) {
        VTR_ASSERT(new_iter + 1 == block_input_pins(blk_id).end());
        VTR_ASSERT(new_iter + 1 == block_output_pins(blk_id).begin());
    } else if (type == PortType::OUTPUT) {
        VTR_ASSERT(new_iter + 1 == block_output_pins(blk_id).end());
        VTR_ASSERT(new_iter + 1 == block_clock_pins(blk_id).begin());
    } else {
        VTR_ASSERT(type == PortType::CLOCK);
        VTR_ASSERT(new_iter + 1 == block_clock_pins(blk_id).end());
        VTR_ASSERT(new_iter + 1 == block_pins(blk_id).end());
    }
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
int Netlist<BlockId, PortId, PinId, NetId>::associate_pin_with_net(const PinId pin_id, const PinType type, const NetId net_id) {
    //Add the pin to the net
    if (type == PinType::DRIVER) {
        VTR_ASSERT_MSG(net_pins_[net_id].size() > 0, "Must be space for net's pin");
        VTR_ASSERT_MSG(net_pins_[net_id][0] == PinId::INVALID(), "Must be no existing net driver");

        net_pins_[net_id][0] = pin_id; //Set driver

        return 0; //Driver always at index 0
    } else {
        VTR_ASSERT(type == PinType::SINK);

        net_pins_[net_id].emplace_back(pin_id); //Add sink

        return net_pins_[net_id].size() - 1; //Index of inserted pin
    }
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::associate_pin_with_port(const PinId pin_id, const PortId port_id) {
    //A ports pins are stored in ascending order of port bit index, to allow for fast look-ups.
    //As a result we need to ensure that even if pins are created out-of-order (with respect
    //to port bit) they are still inserted in the port in sorted order.
    //
    //As a result we find the correct position using std::lower_bound and insert there.
    //Note that in the typical case (when port pins are created in order) this will be
    //the end of the port's pins and the insertion will be efficient. Only in the (more
    //unusual) case where the pins are created out-of-order will we need to do a slow
    //insert in the middle of the vector.
    auto port_bit_cmp = [&](const PinId pin, BitIndex bit_index) {
        return pin_port_bit(pin) < bit_index;
    };
    auto iter = std::lower_bound(port_pins_[port_id].begin(), port_pins_[port_id].end(), pin_port_bit(pin_id), port_bit_cmp);
    port_pins_[port_id].insert(iter, pin_id);
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
void Netlist<BlockId, PortId, PinId, NetId>::associate_port_with_block(const PortId port_id, const PortType type, const BlockId blk_id) {
    //Associate the port with the blocks inputs/outputs/clocks

    //Get an iterator pointing to where we want to insert
    port_iterator iter;
    if (type == PortType::INPUT) {
        iter = block_input_ports(blk_id).end();
        ++block_num_input_ports_[blk_id];
    } else if (type == PortType::OUTPUT) {
        iter = block_output_ports(blk_id).end();
        ++block_num_output_ports_[blk_id];
    } else {
        VTR_ASSERT(type == PortType::CLOCK);
        iter = block_clock_ports(blk_id).end();
        ++block_num_clock_ports_[blk_id];
    }

    //Insert the element just before iter
    auto new_iter = block_ports_[blk_id].insert(iter, port_id);

    //Inserted value should be the last valid range element
    if (type == PortType::INPUT) {
        VTR_ASSERT(new_iter + 1 == block_input_ports(blk_id).end());
        VTR_ASSERT(new_iter + 1 == block_output_ports(blk_id).begin());
    } else if (type == PortType::OUTPUT) {
        VTR_ASSERT(new_iter + 1 == block_output_ports(blk_id).end());
        VTR_ASSERT(new_iter + 1 == block_clock_ports(blk_id).begin());
    } else {
        VTR_ASSERT(type == PortType::CLOCK);
        VTR_ASSERT(new_iter + 1 == block_clock_ports(blk_id).end());
        VTR_ASSERT(new_iter + 1 == block_ports(blk_id).end());
    }
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
typename Netlist<BlockId, PortId, PinId, NetId>::StringId Netlist<BlockId, PortId, PinId, NetId>::create_string(const std::string& str) {
    StringId str_id = find_string(str);
    if (!str_id) {
        //Not found, create

        //Reserve an id
        str_id = StringId(string_ids_.size());
        string_ids_.push_back(str_id);

        //Store the reverse look-up
        auto key = str;
        string_to_string_id_[key] = str_id;

        //Initialize the data
        strings_.emplace_back(str);
    }

    //Check post-conditions: sizes
    VTR_ASSERT(string_to_string_id_.size() == string_ids_.size());
    VTR_ASSERT(strings_.size() == string_ids_.size());

    //Check post-conditions: values
    VTR_ASSERT(strings_[str_id] == str);
    VTR_ASSERT_SAFE(find_string(str) == str_id);

    return str_id;
}

template<typename BlockId, typename PortId, typename PinId, typename NetId>
NetId Netlist<BlockId, PortId, PinId, NetId>::find_net(const typename Netlist<BlockId, PortId, PinId, NetId>::StringId name_id) const {
    VTR_ASSERT_SAFE(valid_string_id(name_id));
    auto iter = net_name_to_net_id_.find(name_id);
    if (iter != net_name_to_net_id_.end()) {
        NetId net_id = *iter;

        if (net_id) {
            //Check post-conditions
            VTR_ASSERT_SAFE(valid_net_id(net_id));
            VTR_ASSERT_SAFE(net_names_[net_id] == name_id);
        }

        return net_id;
    } else {
        return NetId::INVALID();
    }
}
