/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2018 Claire Xenia Wolf * Copyright (C) 2018-19 gatecat * Copyright (C) 2021 Symbiflow Authors * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "arch.h" #include #include #include #include #include #include #include #include #include "constraints.impl.h" #include "fpga_interchange.h" #include "log.h" #include "nextpnr.h" #include "placer1.h" #include "placer_heap.h" #include "router1.h" #include "router2.h" #include "timing.h" #include "util.h" #include "xdc.h" // Include tcl.h late because it messed with defines and let them leave the // scope of the header. #include // #define DEBUG_BINDING // #define USE_LOOKAHEAD // #define DEBUG_CELL_PIN_MAPPING // Define to enable some idempotent sanity checks for some important // operations prior to placement and routing. #define IDEMPOTENT_CHECK NEXTPNR_NAMESPACE_BEGIN struct SiteBelPair { std::string site; IdString bel; SiteBelPair() {} SiteBelPair(std::string site, IdString bel) : site(site), bel(bel) {} bool operator==(const SiteBelPair &other) const { return site == other.site && bel == other.bel; } unsigned int hash() const { return mkhash(std::hash()(site), bel.hash()); } }; static std::pair split_identifier_name_dot(const std::string &name) { size_t first_dot = name.find('.'); NPNR_ASSERT(first_dot != std::string::npos); return std::make_pair(name.substr(0, first_dot), name.substr(first_dot + 1)); }; // ----------------------------------------------------------------------- void IdString::initialize_arch(const BaseCtx *ctx) {} // ----------------------------------------------------------------------- static const ChipInfoPOD *get_chip_info(const RelPtr *ptr) { return ptr->get(); } static std::string sha1_hash(const char *data, size_t size) { boost::uuids::detail::sha1 hasher; hasher.process_bytes(data, size); // unsigned int[5] boost::uuids::detail::sha1::digest_type digest; hasher.get_digest(digest); std::ostringstream buf; for (int i = 0; i < 5; ++i) buf << std::hex << std::setfill('0') << std::setw(8) << digest[i]; return buf.str(); } Arch::Arch(ArchArgs args) : args(args), disallow_site_routing(false) { try { blob_file.open(args.chipdb); if (args.chipdb.empty() || !blob_file.is_open()) log_error("Unable to read chipdb %s\n", args.chipdb.c_str()); const char *blob = reinterpret_cast(blob_file.data()); chipdb_hash = sha1_hash(blob, blob_file.size()); chip_info = get_chip_info(reinterpret_cast *>(blob)); } catch (...) { log_error("Unable to read chipdb %s\n", args.chipdb.c_str()); } if (chip_info->version != kExpectedChipInfoVersion) { log_error("Expected chipdb with version %d found version %d\n", kExpectedChipInfoVersion, chip_info->version); } // Read strings from constids into IdString database, checking that list // is unique and matches expected constid value. const RelSlice> &constids = *chip_info->constids; for (size_t i = 0; i < constids.size(); ++i) { IdString::initialize_add(this, constids[i].get(), i + 1); } id_GND = id("GND"); id_VCC = id("VCC"); // Sanity check cell name ids. const CellMapPOD &cell_map = *chip_info->cell_map; int32_t first_cell_id = cell_map.cell_names[0]; for (int32_t i = 0; i < cell_map.cell_names.ssize(); ++i) { log_assert(cell_map.cell_names[i] == i + first_cell_id); } io_port_types.emplace(this->id("$nextpnr_ibuf")); io_port_types.emplace(this->id("$nextpnr_obuf")); io_port_types.emplace(this->id("$nextpnr_iobuf")); io_port_types.emplace(this->id("$nextpnr_inv")); if (!this->args.package.empty()) { IdString package = this->id(this->args.package); package_index = -1; for (size_t i = 0; i < chip_info->packages.size(); ++i) { if (IdString(chip_info->packages[i].package) == package) { NPNR_ASSERT(package_index == -1); package_index = i; } } if (package_index == -1) { log_error("Could not find package '%s' in chipdb.\n", this->args.package.c_str()); } } else { // Default to first package. NPNR_ASSERT(chip_info->packages.size() > 0); if (chip_info->packages.size() == 1) { IdString package_name(chip_info->packages[0].package); this->args.package = package_name.str(this); package_index = 0; } else { log_info( "Package must be specified (with --package arg) when multiple packages are available, packages:\n"); for (const auto &package : chip_info->packages) { log_info(" - %s\n", IdString(package.package).c_str(this)); } log_error("--package is required!\n"); } } pool site_bel_pads; for (const auto &package_pin : chip_info->packages[package_index].pins) { IdString site(package_pin.site); IdString bel(package_pin.bel); site_bel_pads.emplace(SiteBelPair(site.str(this), bel)); } for (BelId bel : getBels()) { auto &bel_data = bel_info(chip_info, bel); const SiteInstInfoPOD &site = get_site_inst(bel); auto iter = site_bel_pads.find(SiteBelPair(site.site_name.get(), IdString(bel_data.name))); if (iter != site_bel_pads.end()) { pads.emplace(bel); } } explain_constraints = false; int tile_type_index = 0; size_t max_tag_count = 0; for (const TileTypeInfoPOD &tile_type : chip_info->tile_types) { max_tag_count = std::max(max_tag_count, tile_type.tags.size()); auto &type_definition = constraints.definitions[tile_type_index++]; for (const ConstraintTagPOD &tag : tile_type.tags) { type_definition.emplace_back(); auto &definition = type_definition.back(); definition.prefix = IdString(tag.tag_prefix); definition.default_state = IdString(tag.default_state); NPNR_ASSERT(tag.states.size() < kMaxState); definition.states.reserve(tag.states.size()); for (auto state : tag.states) { definition.states.push_back(IdString(state)); } } // Logic BELs (e.g. placable BELs) should always appear first in the // bel data list. // // When iterating over BELs this property is depended on to skip // non-placable BELs (e.g. routing BELs and site ports). bool in_logic_bels = true; for (const BelInfoPOD &bel_info : tile_type.bel_data) { if (in_logic_bels && bel_info.category != BEL_CATEGORY_LOGIC) { in_logic_bels = false; } if (!in_logic_bels) { NPNR_ASSERT(bel_info.category != BEL_CATEGORY_LOGIC); } } } // Initially LutElement vectors for each tile type. tile_type_index = 0; max_lut_cells = 0; max_lut_pins = 0; lut_elements.resize(chip_info->tile_types.size()); for (const TileTypeInfoPOD &tile_type : chip_info->tile_types) { std::vector &elements = lut_elements[tile_type_index++]; elements.reserve(tile_type.lut_elements.size()); int lut_cells_count = 0; for (auto &lut_element : tile_type.lut_elements) { elements.emplace_back(); LutElement &element = elements.back(); element.width = lut_element.width; for (auto &lut_bel : lut_element.lut_bels) { IdString name(lut_bel.name); auto result = element.lut_bels.emplace(name, LutBel()); NPNR_ASSERT(result.second); LutBel &lut = result.first->second; lut.name = name; lut.low_bit = lut_bel.low_bit; lut.high_bit = lut_bel.high_bit; lut.pins.reserve(lut_bel.pins.size()); for (size_t i = 0; i < lut_bel.pins.size(); ++i) { IdString pin(lut_bel.pins[i]); lut.pins.push_back(pin); lut.pin_to_index[pin] = i; } lut.output_pin = IdString(lut_bel.out_pin); lut_cells_count++; max_lut_pins = std::max((int)lut_bel.pins.size(), max_lut_pins); } element.compute_pin_order(); } max_lut_cells = std::max(lut_cells_count, max_lut_cells); } // Map lut cell types to their LutCellPOD wire_lut = nullptr; for (const LutCellPOD &lut_cell : chip_info->cell_map->lut_cells) { IdString cell_type(lut_cell.cell); auto result = lut_cells.emplace(cell_type, &lut_cell); NPNR_ASSERT(result.second); if (lut_cell.input_pins.size() == 1) { // Only really expecting 1 single input LUT type! NPNR_ASSERT(wire_lut == nullptr); wire_lut = &lut_cell; } } raw_bin_constant = std::regex("[01]+", std::regex_constants::ECMAScript | std::regex_constants::optimize); verilog_bin_constant = std::regex("([0-9]+)'b([01]+)", std::regex_constants::ECMAScript | std::regex_constants::optimize); verilog_hex_constant = std::regex("([0-9]+)'h([0-9a-fA-F]+)", std::regex_constants::ECMAScript | std::regex_constants::optimize); default_tags.resize(max_tag_count); } void Arch::init() { #ifdef USE_LOOKAHEAD lookahead.init(getCtx(), getCtx()); #endif dedicated_interconnect.init(getCtx()); cell_parameters.init(getCtx()); for (size_t tile_type = 0; tile_type < chip_info->tile_types.size(); ++tile_type) { pseudo_pip_data.init_tile_type(getCtx(), tile_type); } // Warn if there is no preferred constant net defined in the architecture IdString const_net_name(getCtx()->chip_info->constants->best_constant_net); if (const_net_name == IdString()) { log_warning("The architecture does not specify preferred constant net. Using VCC as default.\n"); } } // ----------------------------------------------------------------------- std::string Arch::getChipName() const { return chip_info->name.get(); } // ----------------------------------------------------------------------- IdString Arch::archArgsToId(ArchArgs args) const { return IdString(); } // ----------------------------------------------------------------------- void Arch::setup_byname() const { by_name_mutex.lock(); if (tile_by_name.empty()) { for (int i = 0; i < chip_info->tiles.ssize(); i++) { tile_by_name[id(chip_info->tiles[i].name.get())] = i; } } if (site_by_name.empty()) { for (int i = 0; i < chip_info->tiles.ssize(); i++) { auto &tile = chip_info->tiles[i]; auto &tile_type = chip_info->tile_types[tile.type]; for (size_t j = 0; j < tile_type.site_types.size(); j++) { auto &site = chip_info->sites[tile.sites[j]]; site_by_name[id(site.name.get())] = std::make_pair(i, j); } } } by_name_mutex.unlock(); } BelId Arch::getBelByName(IdStringList name) const { BelId ret; if (name.ids.size() != 2) { return BelId(); } setup_byname(); int tile, site; std::tie(tile, site) = site_by_name.at(name.ids[0]); auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type]; IdString belname = name.ids[1]; for (int i = 0; i < tile_info.bel_data.ssize(); i++) { if (tile_info.bel_data[i].site == site && tile_info.bel_data[i].name == belname.index) { ret.tile = tile; ret.index = i; break; } } return ret; } BelRange Arch::getBelsByTile(int x, int y) const { BelRange br; br.b.cursor_tile = get_tile_index(x, y); br.e.cursor_tile = br.b.cursor_tile; br.b.cursor_index = 0; br.e.cursor_index = chip_info->tile_types[chip_info->tiles[br.b.cursor_tile].type].bel_data.size(); br.b.chip = chip_info; br.e.chip = chip_info; if (br.b != br.e) { ++br.e; } return br; } WireId Arch::getBelPinWire(BelId bel, IdString pin) const { NPNR_ASSERT(bel != BelId()); int pin_index = get_bel_pin_index(bel, pin); auto &bel_data = bel_info(chip_info, bel); NPNR_ASSERT(pin_index >= 0 && pin_index < bel_data.num_bel_wires); const int32_t *wires = bel_data.wires.get(); int32_t wire_index = wires[pin_index]; if (wire_index < 0) { // This BEL pin is not connected. return WireId(); } else { return canonical_wire(chip_info, bel.tile, wire_index); } } PortType Arch::getBelPinType(BelId bel, IdString pin) const { NPNR_ASSERT(bel != BelId()); int pin_index = get_bel_pin_index(bel, pin); auto &bel_data = bel_info(chip_info, bel); NPNR_ASSERT(pin_index >= 0 && pin_index < bel_data.num_bel_wires); const int32_t *types = bel_data.types.get(); return PortType(types[pin_index]); } // ----------------------------------------------------------------------- WireId Arch::getWireByName(IdStringList name) const { WireId ret; if (name.ids.size() != 2) { return WireId(); } setup_byname(); auto iter = site_by_name.find(name.ids[0]); if (iter != site_by_name.end()) { int tile; int site; std::tie(tile, site) = iter->second; auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type]; IdString wirename = name.ids[1]; for (int i = 0; i < tile_info.wire_data.ssize(); i++) { if (tile_info.wire_data[i].site == site && tile_info.wire_data[i].name == wirename.index) { ret.tile = tile; ret.index = i; break; } } } else { int tile = tile_by_name.at(name.ids[0]); auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type]; IdString wirename = name.ids[1]; for (int i = 0; i < tile_info.wire_data.ssize(); i++) { if (tile_info.wire_data[i].site == -1 && tile_info.wire_data[i].name == wirename.index) { int32_t node = chip_info->tiles[tile].tile_wire_to_node[i]; if (node == -1) { // Not a nodal wire ret.tile = tile; ret.index = i; } else { // Is a nodal wire, set tile to -1 ret.tile = -1; ret.index = node; } break; } } } return ret; } IdString Arch::getWireType(WireId wire) const { int tile = wire.tile, index = wire.index; if (tile == -1) { // Nodal wire const TileWireRefPOD &wr = chip_info->nodes[wire.index].tile_wires[0]; tile = wr.tile; index = wr.index; } auto &w2t = chip_info->tiles[tile].tile_wire_to_type; if (index >= w2t.ssize()) return IdString(); int wire_type = w2t[index]; if (wire_type == -1) return IdString(); return IdString(chip_info->wire_types[wire_type].name); } bool Arch::is_site_wire(WireId wire) const { if (wire.tile == -1) return false; const auto &tile_type = loc_info(chip_info, wire); return tile_type.wire_data[wire.index].site != -1; } WireCategory Arch::get_wire_category(WireId wire) const { int tile = wire.tile, index = wire.index; if (tile == -1) { // Nodal wire const TileWireRefPOD &wr = chip_info->nodes[wire.index].tile_wires[0]; tile = wr.tile; index = wr.index; } auto &w2t = chip_info->tiles[tile].tile_wire_to_type; if (index >= w2t.ssize()) return WIRE_CAT_GENERAL; int wire_type = w2t[index]; if (wire_type == -1) return WIRE_CAT_GENERAL; return WireCategory(chip_info->wire_types[wire_type].category); } std::vector> Arch::getWireAttrs(WireId wire) const { return {}; } // ----------------------------------------------------------------------- PipId Arch::getPipByName(IdStringList name) const { // PIP name structure: // Tile PIP: /. // Site PIP: // // Site pin: / // Psuedo site PIP: /. setup_byname(); if (name.ids.size() == 3) { // This is a Site PIP. IdString site_name = name.ids[0]; IdString belname = name.ids[1]; IdString pinname = name.ids[2]; int tile; int site; std::tie(tile, site) = site_by_name.at(site_name); auto tile_type_idx = chip_info->tiles[tile].type; auto &tile_info = chip_info->tile_types[tile_type_idx]; std::array ids{name.ids[0], belname}; BelId bel = getBelByName(IdStringList(ids)); NPNR_ASSERT(bel != BelId()); int pin_index = get_bel_pin_index(bel, pinname); NPNR_ASSERT(pin_index >= 0); for (int i = 0; i < tile_info.pip_data.ssize(); i++) { if (tile_info.pip_data[i].site == site && tile_info.pip_data[i].bel == bel.index && tile_info.pip_data[i].extra_data == pin_index) { PipId ret; ret.tile = tile; ret.index = i; return ret; } } } else { auto iter = site_by_name.find(name.ids[0]); if (iter != site_by_name.end()) { // This is either a site pin or a psuedo site pip. // psuedo site pips are /. // site pins are / int tile; int site; std::tie(tile, site) = iter->second; auto tile_type_idx = chip_info->tiles[tile].type; auto &tile_info = chip_info->tile_types[tile_type_idx]; std::string pip_second = name.ids[1].str(this); auto split = pip_second.find('.'); if (split == std::string::npos) { // This is a site pin! BelId bel = getBelByName(name); NPNR_ASSERT(bel != BelId()); for (int i = 0; i < tile_info.pip_data.ssize(); i++) { if (tile_info.pip_data[i].site == site && tile_info.pip_data[i].bel == bel.index) { PipId ret; ret.tile = tile; ret.index = i; return ret; } } } else { // This is a psuedo site pip! IdString src_site_wire = id(pip_second.substr(0, split)); IdString dst_site_wire = id(pip_second.substr(split + 1)); int32_t src_index = -1; int32_t dst_index = -1; for (int i = 0; i < tile_info.wire_data.ssize(); i++) { if (tile_info.wire_data[i].site == site && tile_info.wire_data[i].name == src_site_wire.index) { src_index = i; if (dst_index != -1) { break; } } if (tile_info.wire_data[i].site == site && tile_info.wire_data[i].name == dst_site_wire.index) { dst_index = i; if (src_index != -1) { break; } } } NPNR_ASSERT(src_index != -1); NPNR_ASSERT(dst_index != -1); for (int i = 0; i < tile_info.pip_data.ssize(); i++) { if (tile_info.pip_data[i].site == site && tile_info.pip_data[i].src_index == src_index && tile_info.pip_data[i].dst_index == dst_index) { PipId ret; ret.tile = tile; ret.index = i; return ret; } } } } else { int tile = tile_by_name.at(name.ids[0]); size_t tile_type_idx = chip_info->tiles[tile].type; auto &tile_info = chip_info->tile_types[tile_type_idx]; std::string pip_second = name.ids[1].str(this); auto spn = split_identifier_name_dot(pip_second); auto src_wire_name = id(spn.first); auto dst_wire_name = id(spn.second); int32_t src_index = -1; int32_t dst_index = -1; for (int i = 0; i < tile_info.wire_data.ssize(); i++) { if (tile_info.wire_data[i].site == -1 && tile_info.wire_data[i].name == src_wire_name.index) { src_index = i; if (dst_index != -1) { break; } } if (tile_info.wire_data[i].site == -1 && tile_info.wire_data[i].name == dst_wire_name.index) { dst_index = i; if (src_index != -1) { break; } } } NPNR_ASSERT(src_index != -1); NPNR_ASSERT(dst_index != -1); for (int i = 0; i < tile_info.pip_data.ssize(); i++) { if (tile_info.pip_data[i].src_index == src_index && tile_info.pip_data[i].dst_index == dst_index) { PipId ret; ret.tile = tile; ret.index = i; return ret; } } } } return PipId(); } IdStringList Arch::getPipName(PipId pip) const { // PIP name structure: // Tile PIP: /. // Psuedo site PIP: /. // Site PIP: // // Site pin: / NPNR_ASSERT(pip != PipId()); auto &tile = chip_info->tiles[pip.tile]; auto &tile_type = loc_info(chip_info, pip); auto &pip_info = tile_type.pip_data[pip.index]; if (pip_info.site != -1) { // This is either a site pin or a site pip. auto &site = get_site_inst(pip); auto &bel = tile_type.bel_data[pip_info.bel]; IdString bel_name(bel.name); if (bel.category == BEL_CATEGORY_LOGIC) { // This is a psuedo pip IdString src_wire_name = IdString(tile_type.wire_data[pip_info.src_index].name); IdString dst_wire_name = IdString(tile_type.wire_data[pip_info.dst_index].name); IdString pip = id(src_wire_name.str(this) + "." + dst_wire_name.str(this)); std::array ids{id(site.name.get()), pip}; return IdStringList(ids); } else if (bel.category == BEL_CATEGORY_ROUTING) { // This is a site pip. IdString pin_name(bel.ports[pip_info.extra_data]); std::array ids{id(site.name.get()), bel_name, pin_name}; return IdStringList(ids); } else { NPNR_ASSERT(bel.category == BEL_CATEGORY_SITE_PORT); // This is a site pin, just the name of the BEL is a unique identifier. std::array ids{id(site.name.get()), bel_name}; return IdStringList(ids); } } else { // This is a tile pip. IdString src_wire_name = IdString(tile_type.wire_data[pip_info.src_index].name); IdString dst_wire_name = IdString(tile_type.wire_data[pip_info.dst_index].name); IdString pip = id(src_wire_name.str(this) + "." + dst_wire_name.str(this)); std::array ids{id(std::string(tile.name.get())), pip}; return IdStringList(ids); } } IdString Arch::getPipType(PipId pip) const { return id("PIP"); } std::vector> Arch::getPipAttrs(PipId pip) const { return {}; } // ----------------------------------------------------------------------- BelId Arch::getBelByLocation(Loc loc) const { BelId bi; if (loc.x >= chip_info->width || loc.y >= chip_info->height) return BelId(); bi.tile = get_tile_index(loc); auto &li = loc_info(chip_info, bi); if (loc.z >= li.bel_data.ssize()) { return BelId(); } else { bi.index = loc.z; return bi; } } std::vector> Arch::getBelAttrs(BelId bel) const { return {}; } // ----------------------------------------------------------------------- BoundingBox Arch::getRouteBoundingBox(WireId src, WireId dst) const { int dst_tile = dst.tile == -1 ? chip_info->nodes[dst.index].tile_wires[0].tile : dst.tile; int src_tile = src.tile == -1 ? chip_info->nodes[src.index].tile_wires[0].tile : src.tile; int src_x, src_y; get_tile_x_y(src_tile, &src_x, &src_y); int x0 = src_x, x1 = src_x, y0 = src_y, y1 = src_y; int dst_x, dst_y; get_tile_x_y(dst_tile, &dst_x, &dst_y); auto expand = [&](int x, int y) { x0 = std::min(x0, x); x1 = std::max(x1, x); y0 = std::min(y0, y); y1 = std::max(y1, y); }; expand(dst_x, dst_y); if (source_locs.count(src)) expand(source_locs.at(src).x, source_locs.at(src).y); if (sink_locs.count(dst)) { expand(sink_locs.at(dst).x, sink_locs.at(dst).y); } return {x0, y0, x1, y1}; } bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; } // ----------------------------------------------------------------------- bool Arch::pack() { decode_lut_cells(); expand_macros(); merge_constant_nets(); pack_ports(); pack_default_conns(); pack_cluster(); return true; } static void prepare_for_placement(Context *ctx) { ctx->remove_site_routing(); // Re-map BEL pins without constant pins for (BelId bel : ctx->getBels()) { CellInfo *cell = ctx->getBoundBelCell(bel); if (cell != nullptr && cell->cell_mapping != -1) { ctx->map_cell_pins(cell, cell->cell_mapping, /*bind_constants=*/false); } } } bool Arch::place() { // Before placement, ripup placement specific bindings and unmask all cell // pins. getCtx()->check(); prepare_for_placement(getCtx()); getCtx()->check(); #ifdef IDEMPOTENT_CHECK prepare_for_placement(getCtx()); getCtx()->check(); #endif place_constraints(); place_globals(); std::string placer = str_or_default(settings, id("placer"), defaultPlacer); if (placer == "heap") { PlacerHeapCfg cfg(getCtx()); cfg.criticalityExponent = 7; cfg.alpha = 0.08; cfg.beta = 0.4; cfg.placeAllAtOnce = true; cfg.hpwl_scale_x = 1; cfg.hpwl_scale_y = 2; cfg.spread_scale_x = 2; cfg.spread_scale_y = 1; cfg.solverTolerance = 0.6e-6; if (!placer_heap(getCtx(), cfg)) return false; } else if (placer == "sa") { if (!placer1(getCtx(), Placer1Cfg(getCtx()))) return false; } else { log_error("FPGA interchange architecture does not support placer '%s'\n", placer.c_str()); } getCtx()->attrs[getCtx()->id("step")] = std::string("place"); archInfoToAttributes(); // Print site LUT mapping caching stats if (!getCtx()->arch_args.disable_lut_mapping_cache) { log_info("Site LUT mapping cache stats:\n"); log_info(" miss ratio: %.1f%%\n", getCtx()->site_lut_mapping_cache.getMissRatio() * 100.0f); log_info(" peak size : %zuMB (%zu items)\n", getCtx()->site_lut_mapping_cache.getSizeMB(), getCtx()->site_lut_mapping_cache.getCount()); } getCtx()->check(); return true; } static void prepare_sites_for_routing(Context *ctx) { // Reset site routing and remove masked cell pins from previous router run // (if any). ctx->remove_site_routing(); // Re-map BEL pins with constant pins for (BelId bel : ctx->getBels()) { CellInfo *cell = ctx->getBoundBelCell(bel); if (cell != nullptr && cell->cell_mapping != -1) { ctx->map_cell_pins(cell, cell->cell_mapping, /*bind_constants=*/true); } } // Clear the site routing cache. This is because routing at this stage is done with the extra constraint of blocked // pins to ensure a routeable pin choice. ctx->site_routing_cache.clear(); // Clear the LUT mapping cache ctx->site_lut_mapping_cache.clear(); // Have site router bind site routing (via bindPip and bindWire). // This is important so that the pseudo pips are correctly blocked prior // to handing the design to the generalized router algorithms. for (auto &tile_pair : ctx->tileStatus) { for (auto &site_router : tile_pair.second.sites) { if (site_router.cells_in_site.empty()) { continue; } site_router.bindSiteRouting(ctx); } tile_pair.second.pseudo_pip_model.prepare_for_routing(ctx, tile_pair.second.sites); } // Fixup LUT vcc pins. IdString vcc_net_name(ctx->chip_info->constants->vcc_net_name); IdString gnd_net_name(ctx->chip_info->constants->gnd_net_name); IdString const_net_name(ctx->chip_info->constants->best_constant_net); NPNR_ASSERT(const_net_name == IdString() || const_net_name == vcc_net_name || const_net_name == gnd_net_name); // FIXME: Use VCC if the architecture does not device the best constant if (const_net_name == IdString()) { const_net_name = vcc_net_name; } for (BelId bel : ctx->getBels()) { CellInfo *cell = ctx->getBoundBelCell(bel); if (cell == nullptr) { continue; } for (const auto &it : cell->lut_cell.pin_connections) { const auto &bel_pin = it.first; const auto &conn = it.second; // Connected to an active signal or unconnected if (conn == LutCell::PinConnection::Signal || conn == LutCell::PinConnection::Unconnected) { continue; } // We can't rely on bel pins not clashing with cell names (for Xilinx they use different naming schemes, for // Nexus they are the same) so add a prefix to the bel pin name to disambiguate it IdString cell_pin = ctx->idf("%s_PHYS", ctx->nameOf(bel_pin)); PortInfo port_info; port_info.name = cell_pin; port_info.type = PORT_IN; port_info.net = nullptr; #ifdef DEBUG_LUT_MAPPING if (ctx->verbose) { log_info("%s must be tied to %s, tying now\n", ctx->nameOfWire(lut_pin_wire), LutCell::nameOfPinConnection(conn).c_str()); } #endif IdString tie_net_name; switch (conn) { case LutCell::PinConnection::Vcc: tie_net_name = vcc_net_name; break; case LutCell::PinConnection::Gnd: tie_net_name = gnd_net_name; break; case LutCell::PinConnection::Const: tie_net_name = const_net_name; break; default: // Should never happen NPNR_ASSERT_FALSE( stringf("Invalid LUT cell pin connection '%s'", LutCell::nameOfPinConnection(conn).c_str()) .c_str()); break; } auto result = cell->ports.emplace(cell_pin, port_info); if (result.second) { cell->cell_bel_pins[cell_pin].push_back(bel_pin); ctx->connectPort(tie_net_name, cell->name, cell_pin); cell->const_ports.emplace(cell_pin); } else { NPNR_ASSERT(result.first->second.net == ctx->getNetByAlias(tie_net_name)); auto result2 = cell->cell_bel_pins.emplace(cell_pin, std::vector({bel_pin})); NPNR_ASSERT(result2.first->second.at(0) == bel_pin); NPNR_ASSERT(result2.first->second.size() == 1); } } } } bool Arch::route() { getCtx()->check(); prepare_sites_for_routing(getCtx()); getCtx()->check(); #ifdef IDEMPOTENT_CHECK prepare_sites_for_routing(getCtx()); getCtx()->check(); #endif std::string router = str_or_default(settings, id("router"), defaultRouter); // Disallow site routing during general routing. This is because // "prepare_sites_for_routing" has already assigned routing for all sites // in the design, and if the router wants to route-thru a site, it *MUST* // use a pseudo-pip. // // It is not legal in the FPGA interchange to enter a site and not // terminate at a BEL pin. disallow_site_routing = true; route_globals(); bool result; if (router == "router1") { result = router1(getCtx(), Router1Cfg(getCtx())); } else if (router == "router2") { router2(getCtx(), Router2Cfg(getCtx())); result = true; } else { log_error("FPGA interchange architecture does not support router '%s'\n", router.c_str()); } disallow_site_routing = false; getCtx()->attrs[getCtx()->id("step")] = std::string("route"); archInfoToAttributes(); getCtx()->check(); // Now that routing is complete, unmask BEL pins. unmask_bel_pins(); getCtx()->check(); return result; } // ----------------------------------------------------------------------- std::vector Arch::getDecalGraphics(DecalId decal) const { return {}; } DecalXY Arch::getBelDecal(BelId bel) const { DecalXY decalxy; return decalxy; } DecalXY Arch::getWireDecal(WireId wire) const { DecalXY decalxy; return decalxy; } DecalXY Arch::getPipDecal(PipId pip) const { return {}; }; DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; }; // ----------------------------------------------------------------------- delay_t Arch::estimateDelay(WireId src, WireId dst) const { #ifdef USE_LOOKAHEAD return lookahead.estimateDelay(getCtx(), src, dst); #else // Note: Something is better than nothing when USE_LOOKAHEAD is not // defined. int src_tile = src.tile == -1 ? chip_info->nodes[src.index].tile_wires[0].tile : src.tile; int dst_tile = dst.tile == -1 ? chip_info->nodes[dst.index].tile_wires[0].tile : dst.tile; int src_x, src_y; get_tile_x_y(src_tile, &src_x, &src_y); int dst_x, dst_y; get_tile_x_y(dst_tile, &dst_x, &dst_y); delay_t base = 30 * std::min(std::abs(dst_x - src_x), 18) + 10 * std::max(std::abs(dst_x - src_x) - 18, 0) + 60 * std::min(std::abs(dst_y - src_y), 6) + 20 * std::max(std::abs(dst_y - src_y) - 6, 0) + 300; base = (base * 3) / 2; return base; #endif } delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const { // FIXME: Implement when adding timing-driven place and route. NPNR_UNUSED(src_pin); NPNR_UNUSED(dst_pin); int src_x, src_y; get_tile_x_y(src_bel.tile, &src_x, &src_y); int dst_x, dst_y; get_tile_x_y(dst_bel.tile, &dst_x, &dst_y); delay_t base = 30 * std::min(std::abs(dst_x - src_x), 18) + 10 * std::max(std::abs(dst_x - src_x) - 18, 0) + 60 * std::min(std::abs(dst_y - src_y), 6) + 20 * std::max(std::abs(dst_y - src_y) - 6, 0) + 300; base = (base * 3) / 2; return base; } bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const { // FIXME: Implement when adding timing-driven place and route. return false; } TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const { // FIXME: Implement when adding timing-driven place and route. return TMG_IGNORE; } TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const { // FIXME: Implement when adding timing-driven place and route. TimingClockingInfo info; return info; } // ----------------------------------------------------------------------- void Arch::read_logical_netlist(const std::string &filename) { FpgaInterchange::read_logical_netlist(getCtx(), filename); } void Arch::write_physical_netlist(const std::string &filename) const { FpgaInterchange::write_physical_netlist(getCtx(), filename); } void Arch::parse_xdc(const std::string &filename) { TclInterp interp(getCtx()); auto result = Tcl_EvalFile(interp.interp, filename.c_str()); if (result != TCL_OK) { log_error("Error in %s:%d => %s\n", filename.c_str(), Tcl_GetErrorLine(interp.interp), Tcl_GetStringResult(interp.interp)); } } std::string Arch::get_part() const { // FIXME: Need a map between device / package / speed grade and part. return chip_info->name.get() + args.package + "-1"; } // ----------------------------------------------------------------------- const std::string Arch::defaultPlacer = "heap"; const std::vector Arch::availablePlacers = {"sa", "heap"}; const std::string Arch::defaultRouter = "router2"; const std::vector Arch::availableRouters = {"router1", "router2"}; void Arch::map_cell_pins(CellInfo *cell, int32_t mapping, bool bind_constants) { cell->cell_mapping = mapping; if (cell->lut_cell.pins.empty()) { cell->cell_bel_pins.clear(); cell->masked_cell_bel_pins.clear(); } else { std::vector cell_pin_to_remove; for (auto port_pair : cell->cell_bel_pins) { if (!cell->lut_cell.lut_pins.count(port_pair.first)) { cell_pin_to_remove.push_back(port_pair.first); } } for (IdString cell_pin : cell_pin_to_remove) { NPNR_ASSERT(cell->cell_bel_pins.erase(cell_pin)); } } for (IdString const_port : cell->const_ports) { disconnectPort(cell->name, const_port); NPNR_ASSERT(cell->ports.erase(const_port)); } const CellBelMapPOD &cell_pin_map = chip_info->cell_map->cell_bel_map[mapping]; IdString gnd_net_name(chip_info->constants->gnd_net_name); IdString vcc_net_name(chip_info->constants->vcc_net_name); for (const auto &pin_map : cell_pin_map.common_pins) { IdString cell_pin(pin_map.cell_pin); IdString bel_pin(pin_map.bel_pin); // Skip assigned LUT pins, as they are already mapped! if (cell->lut_cell.lut_pins.count(cell_pin) && cell->cell_bel_pins.count(cell_pin)) { continue; } if (cell_pin == id_GND) { if (bind_constants) { PortInfo port_info; port_info.name = bel_pin; port_info.type = PORT_IN; port_info.net = nullptr; auto result = cell->ports.emplace(bel_pin, port_info); if (result.second) { cell->cell_bel_pins[bel_pin].push_back(bel_pin); connectPort(gnd_net_name, cell->name, bel_pin); cell->const_ports.emplace(bel_pin); } else { NPNR_ASSERT(result.first->second.net == getNetByAlias(gnd_net_name)); auto result2 = cell->cell_bel_pins.emplace(bel_pin, std::vector({bel_pin})); NPNR_ASSERT(result2.first->second.at(0) == bel_pin); NPNR_ASSERT(result2.first->second.size() == 1); } } continue; } if (cell_pin == id_VCC) { if (bind_constants) { PortInfo port_info; port_info.name = bel_pin; port_info.type = PORT_IN; port_info.net = nullptr; auto result = cell->ports.emplace(bel_pin, port_info); if (result.second) { cell->cell_bel_pins[bel_pin].push_back(bel_pin); connectPort(vcc_net_name, cell->name, bel_pin); cell->const_ports.emplace(bel_pin); } else { NPNR_ASSERT(result.first->second.net == getNetByAlias(vcc_net_name)); auto result2 = cell->cell_bel_pins.emplace(bel_pin, std::vector({bel_pin})); NPNR_ASSERT(result2.first->second.at(0) == bel_pin); NPNR_ASSERT(result2.first->second.size() == 1); } } continue; } cell->cell_bel_pins[cell_pin].push_back(bel_pin); } for (const auto ¶meter_pin_map : cell_pin_map.parameter_pins) { IdString param_key(parameter_pin_map.key); IdString param_value(parameter_pin_map.value); auto iter = cell->params.find(param_key); if (iter == cell->params.end()) { continue; } if (!cell_parameters.compare_property(getCtx(), cell->type, param_key, iter->second, param_value)) { continue; } #ifdef DEBUG_CELL_PIN_MAPPING log_info("parameter match on param_key %s\n", param_key.c_str(this)); #endif for (const auto &pin_map : parameter_pin_map.pins) { IdString cell_pin(pin_map.cell_pin); IdString bel_pin(pin_map.bel_pin); #ifdef DEBUG_CELL_PIN_MAPPING log_info(" %s => %s\n", cell_pin.c_str(this), bel_pin.c_str(this)); #endif // Skip assigned LUT pins, as they are already mapped! if (cell->lut_cell.lut_pins.count(cell_pin) && cell->cell_bel_pins.count(cell_pin)) { continue; } if (cell_pin == id_GND) { if (bind_constants) { PortInfo port_info; port_info.name = bel_pin; port_info.type = PORT_IN; port_info.net = nullptr; auto result = cell->ports.emplace(bel_pin, port_info); if (result.second) { cell->cell_bel_pins[bel_pin].push_back(bel_pin); connectPort(gnd_net_name, cell->name, bel_pin); cell->const_ports.emplace(bel_pin); } else { NPNR_ASSERT(result.first->second.net == getNetByAlias(gnd_net_name)); auto result2 = cell->cell_bel_pins.emplace(bel_pin, std::vector({bel_pin})); NPNR_ASSERT(result2.first->second.at(0) == bel_pin); NPNR_ASSERT(result2.first->second.size() == 1); } } continue; } if (cell_pin == id_VCC) { if (bind_constants) { PortInfo port_info; port_info.name = bel_pin; port_info.type = PORT_IN; port_info.net = nullptr; auto result = cell->ports.emplace(bel_pin, port_info); if (result.second) { cell->cell_bel_pins[bel_pin].push_back(bel_pin); connectPort(vcc_net_name, cell->name, bel_pin); cell->const_ports.emplace(bel_pin); } else { NPNR_ASSERT(result.first->second.net == getNetByAlias(vcc_net_name)); auto result2 = cell->cell_bel_pins.emplace(bel_pin, std::vector({bel_pin})); NPNR_ASSERT(result2.first->second.at(0) == bel_pin); NPNR_ASSERT(result2.first->second.size() == 1); } } continue; } cell->cell_bel_pins[cell_pin].push_back(bel_pin); } } #ifdef DEBUG_CELL_PIN_MAPPING log_info("Pin mapping for cell %s (type: %s)\n", cell->name.c_str(getCtx()), cell->type.c_str(getCtx())); for (auto &pin_pair : cell->cell_bel_pins) { log_info(" %s =>", pin_pair.first.c_str(getCtx())); for (IdString bel_pin : pin_pair.second) { log(" %s", bel_pin.c_str(getCtx())); } log("\n"); } #endif } void Arch::map_port_pins(BelId bel, CellInfo *cell) const { IdStringRange pins = getBelPins(bel); IdString pin = get_only_value(pins); NPNR_ASSERT(cell->ports.size() == 1); cell->cell_bel_pins[cell->ports.begin()->first].clear(); cell->cell_bel_pins[cell->ports.begin()->first].push_back(pin); } bool Arch::is_net_within_site(const NetInfo &net) const { if (net.driver.cell == nullptr || net.driver.cell->bel == BelId()) { return false; } BelId driver = net.driver.cell->bel; int32_t site = bel_info(chip_info, driver).site; NPNR_ASSERT(site >= 0); for (const auto &user : net.users) { if (user.cell == nullptr || user.cell->bel == BelId()) { return false; } BelId user_bel = user.cell->bel; if (user_bel.tile != driver.tile) { return false; } if (bel_info(chip_info, user_bel).site != site) { return false; } } return true; } size_t Arch::get_cell_type_index(IdString cell_type) const { const CellMapPOD &cell_map = *chip_info->cell_map; int cell_offset = cell_type.index - cell_map.cell_names[0]; if ((cell_offset < 0 || cell_offset >= cell_map.cell_names.ssize())) { log_error("Cell %s is not a placable element.\n", cell_type.c_str(this)); } NPNR_ASSERT(cell_map.cell_names[cell_offset] == cell_type.index); return cell_offset; } void Arch::merge_constant_nets() { NetInfo *gnd_net = nullptr; NetInfo *vcc_net = nullptr; bool need_gnd_source = false; bool need_vcc_source = false; IdString gnd_net_name(chip_info->constants->gnd_net_name); IdString gnd_cell_type(chip_info->constants->gnd_cell_name); IdString gnd_cell_port(chip_info->constants->gnd_cell_port); auto gnd_iter = nets.find(gnd_net_name); if (gnd_iter != nets.end()) { NPNR_ASSERT(gnd_iter->second->driver.cell != nullptr); NPNR_ASSERT(gnd_iter->second->driver.cell->type == gnd_cell_type); NPNR_ASSERT(gnd_iter->second->driver.port == gnd_cell_port); gnd_net = gnd_iter->second.get(); } else { gnd_net = createNet(gnd_net_name); need_gnd_source = true; } IdString vcc_net_name(chip_info->constants->vcc_net_name); IdString vcc_cell_type(chip_info->constants->vcc_cell_name); IdString vcc_cell_port(chip_info->constants->vcc_cell_port); auto vcc_iter = nets.find(vcc_net_name); if (vcc_iter != nets.end()) { NPNR_ASSERT(vcc_iter->second->driver.cell != nullptr); NPNR_ASSERT(vcc_iter->second->driver.cell->type == vcc_cell_type); NPNR_ASSERT(vcc_iter->second->driver.port == vcc_cell_port); vcc_net = vcc_iter->second.get(); } else { vcc_net = createNet(vcc_net_name); need_vcc_source = true; } std::vector other_gnd_nets; std::vector other_vcc_nets; for (auto &net_pair : nets) { if (net_pair.first == gnd_net_name) { NPNR_ASSERT(net_pair.second.get() == gnd_net); continue; } if (net_pair.first == vcc_net_name) { NPNR_ASSERT(net_pair.second.get() == vcc_net); continue; } NetInfo *net = net_pair.second.get(); if (net->driver.cell == nullptr) { continue; } if (net->driver.cell->type == gnd_cell_type) { NPNR_ASSERT(net->driver.port == gnd_cell_port); other_gnd_nets.push_back(net_pair.first); if (need_gnd_source) { IdString driver_cell = net->driver.cell->name; disconnectPort(driver_cell, gnd_cell_port); connectPort(gnd_net_name, driver_cell, gnd_cell_port); need_gnd_source = false; } NPNR_ASSERT(net->driver.port == gnd_cell_port); indexed_store users_copy = net->users; for (const PortRef &port_ref : users_copy) { IdString cell = port_ref.cell->name; disconnectPort(cell, port_ref.port); connectPort(gnd_net_name, cell, port_ref.port); } continue; } if (net->driver.cell->type == vcc_cell_type) { NPNR_ASSERT(net->driver.port == vcc_cell_port); other_vcc_nets.push_back(net_pair.first); if (need_vcc_source) { IdString driver_cell = net->driver.cell->name; disconnectPort(driver_cell, vcc_cell_port); connectPort(vcc_net_name, driver_cell, vcc_cell_port); need_vcc_source = false; } NPNR_ASSERT(net->driver.port == vcc_cell_port); indexed_store users_copy = net->users; for (const PortRef &port_ref : users_copy) { IdString cell = port_ref.cell->name; disconnectPort(cell, port_ref.port); connectPort(vcc_net_name, cell, port_ref.port); } } } for (IdString other_gnd_net : other_gnd_nets) { NetInfo *net = getNetByAlias(other_gnd_net); NPNR_ASSERT(net->users.empty()); if (net->driver.cell) { PortRef driver = net->driver; IdString cell_to_remove = driver.cell->name; disconnectPort(driver.cell->name, driver.port); NPNR_ASSERT(cells.erase(cell_to_remove)); } } for (IdString other_vcc_net : other_vcc_nets) { NetInfo *net = getNetByAlias(other_vcc_net); NPNR_ASSERT(net->users.empty()); if (net->driver.cell) { PortRef driver = net->driver; IdString cell_to_remove = driver.cell->name; disconnectPort(driver.cell->name, driver.port); NPNR_ASSERT(cells.erase(cell_to_remove)); } } for (IdString other_gnd_net : other_gnd_nets) { NPNR_ASSERT(nets.erase(other_gnd_net)); gnd_net->aliases.push_back(other_gnd_net); net_aliases[other_gnd_net] = gnd_net_name; } for (IdString other_vcc_net : other_vcc_nets) { NPNR_ASSERT(nets.erase(other_vcc_net)); vcc_net->aliases.push_back(other_vcc_net); net_aliases[other_vcc_net] = vcc_net_name; } if (need_gnd_source) { CellInfo *gnd_cell = createCell(gnd_cell_type, gnd_cell_type); gnd_cell->addOutput(gnd_cell_port); connectPort(gnd_net_name, gnd_cell_type, gnd_cell_port); } if (need_vcc_source) { CellInfo *vcc_cell = createCell(vcc_cell_type, vcc_cell_type); vcc_cell->addOutput(vcc_cell_port); connectPort(vcc_net_name, vcc_cell_type, vcc_cell_port); } } const std::vector &Arch::getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const { auto iter = cell_info->cell_bel_pins.find(pin); if (iter == cell_info->cell_bel_pins.end()) { return no_pins; } else { return iter->second; } } void Arch::report_invalid_bel(BelId bel, CellInfo *cell) const { int32_t mapping = bel_info(chip_info, bel).pin_map[get_cell_type_index(cell->type)]; NPNR_ASSERT(mapping < 0); log_error("Cell %s (%s) cannot be placed at BEL %s (mapping %d)\n", cell->name.c_str(this), cell->type.c_str(this), nameOfBel(bel), mapping); } void Arch::decode_lut_cells() { for (auto &cell_pair : cells) { CellInfo *cell = cell_pair.second.get(); auto iter = lut_cells.find(cell->type); if (iter == lut_cells.end()) { cell->lut_cell.pins.clear(); cell->lut_cell.equation.clear(); continue; } const LutCellPOD &lut_cell = *iter->second; cell->lut_cell.pins.reserve(lut_cell.input_pins.size()); for (uint32_t pin : lut_cell.input_pins) { cell->lut_cell.pins.push_back(IdString(pin)); cell->lut_cell.lut_pins.emplace(IdString(pin)); } IdString equation_parameter(lut_cell.parameter); const Property &equation = cell->params.at(equation_parameter); cell->lut_cell.equation.resize(1 << cell->lut_cell.pins.size()); cell->lut_cell.equation = cell_parameters.parse_int_like(getCtx(), cell->type, equation_parameter, equation); } } void Arch::remove_pip_pseudo_wires(PipId pip, NetInfo *net) { WireId wire; wire.tile = pip.tile; const PipInfoPOD &pip_data = pip_info(chip_info, pip); for (int32_t wire_index : pip_data.pseudo_cell_wires) { NPNR_ASSERT(wire_index != -1); wire.index = wire_index; auto iter = wire_to_net.find(wire); NPNR_ASSERT(iter != wire_to_net.end()); // This wire better already have been assigned to this net! if (iter->second != net) { if (iter->second == nullptr) { log_error("Wire %s part of pseudo pip %s but net is null\n", nameOfWire(wire), nameOfPip(pip)); } else { log_error("Wire %s part of pseudo pip %s but net is '%s' instead of net '%s'\n", nameOfWire(wire), nameOfPip(pip), iter->second->name.c_str(this), net->name.c_str(this)); } } auto wire_iter = net->wires.find(wire); if (wire_iter != net->wires.end()) { #ifdef DEBUG_BINDING if (getCtx()->verbose) { log_info("Removing %s from net %s, but it's in net wires\n", nameOfWire(wire), net->name.c_str(this)); } #endif // This wire is part of net->wires, make sure it has no pip, // but leave it alone. It will get cleaned up via // unbindWire. } else { // This wire is not in net->wires, update wire_to_net. #ifdef DEBUG_BINDING if (getCtx()->verbose) { log_info("Removing %s from net %s in remove_pip_pseudo_wires\n", nameOfWire(wire), net->name.c_str(this)); } #endif iter->second = nullptr; } } if (pip_data.pseudo_cell_wires.size() > 0) { get_tile_status(pip.tile).pseudo_pip_model.unbindPip(getCtx(), pip); } } void Arch::assign_net_to_wire(WireId wire, NetInfo *net, const char *src, bool require_empty) { #ifdef DEBUG_BINDING if (getCtx()->verbose) { log_info("Assigning wire %s to %s from %s\n", nameOfWire(wire), net->name.c_str(this), src); } #endif NPNR_ASSERT(net != nullptr); auto result = wire_to_net.emplace(wire, net); if (!result.second) { // This wire was already in the map, make sure this assignment was // legal. if (require_empty) { NPNR_ASSERT(result.first->second == nullptr); } else { NPNR_ASSERT(result.first->second == nullptr || result.first->second == net); } result.first->second = net; } } void Arch::unassign_wire(WireId wire) { NPNR_ASSERT(wire != WireId()); #ifdef DEBUG_BINDING if (getCtx()->verbose) { log_info("unassign_wire %s\n", nameOfWire(wire)); } #endif auto iter = wire_to_net.find(wire); NPNR_ASSERT(iter != wire_to_net.end()); NetInfo *net = iter->second; NPNR_ASSERT(net != nullptr); auto &net_wires = net->wires; auto it = net_wires.find(wire); NPNR_ASSERT(it != net_wires.end()); auto pip = it->second.pip; if (pip != PipId()) { #ifdef DEBUG_BINDING if (getCtx()->verbose) { log_info("Removing pip %s because it was used to reach wire %s\n", nameOfPip(pip), nameOfWire(wire)); } #endif auto pip_iter = pip_to_net.find(pip); NPNR_ASSERT(pip_iter != pip_to_net.end()); NPNR_ASSERT(pip_iter->second == net); pip_iter->second = nullptr; remove_pip_pseudo_wires(pip, net); } net_wires.erase(it); #ifdef DEBUG_BINDING if (getCtx()->verbose) { log_info("Removing %s from net %s in unassign_wire\n", nameOfWire(wire), net->name.c_str(this)); } #endif iter->second = nullptr; } void Arch::unbindPip(PipId pip) { NPNR_ASSERT(pip != PipId()); #ifdef DEBUG_BINDING if (getCtx()->verbose) { log_info("unbindPip %s\n", nameOfPip(pip)); } #endif auto pip_iter = pip_to_net.find(pip); NPNR_ASSERT(pip_iter != pip_to_net.end()); NetInfo *net = pip_iter->second; NPNR_ASSERT(net != nullptr); WireId dst = getPipDstWire(pip); auto wire_iter = wire_to_net.find(dst); NPNR_ASSERT(wire_iter != wire_to_net.end()); NPNR_ASSERT(wire_iter->second == net); remove_pip_pseudo_wires(pip, net); // Clear the net now. pip_iter->second = nullptr; #ifdef DEBUG_BINDING if (getCtx()->verbose) { log_info("Removing %s from net %s in unbindPip\n", nameOfWire(dst), net->name.c_str(this)); } #endif wire_iter->second = nullptr; NPNR_ASSERT(net->wires.erase(dst) == 1); refreshUiPip(pip); refreshUiWire(dst); } void Arch::bindPip(PipId pip, NetInfo *net, PlaceStrength strength) { NPNR_ASSERT(pip != PipId()); #ifdef DEBUG_BINDING if (getCtx()->verbose) { log_info("bindPip %s (%d/%d) to net %s\n", nameOfPip(pip), pip.tile, pip.index, net->name.c_str(this)); } #endif WireId dst = getPipDstWire(pip); NPNR_ASSERT(dst != WireId()); { // Pip should not already be assigned to anything. auto result = pip_to_net.emplace(pip, net); if (!result.second) { NPNR_ASSERT(result.first->second == nullptr); result.first->second = net; } } assign_net_to_wire(dst, net, "bindPip", /*require_empty=*/true); assign_pip_pseudo_wires(pip, net); { auto result = net->wires.emplace(dst, PipMap{pip, strength}); NPNR_ASSERT(result.second); } refreshUiPip(pip); refreshUiWire(dst); } void Arch::bindWire(WireId wire, NetInfo *net, PlaceStrength strength) { NPNR_ASSERT(wire != WireId()); #ifdef DEBUG_BINDING if (getCtx()->verbose) { log_info("bindWire %s to net %s\n", nameOfWire(wire), net->name.c_str(this)); } #endif assign_net_to_wire(wire, net, "bindWire", /*require_empty=*/true); auto &pip_map = net->wires[wire]; pip_map.pip = PipId(); pip_map.strength = strength; refreshUiWire(wire); } bool Arch::checkPipAvailForNet(PipId pip, const NetInfo *net) const { NPNR_ASSERT(pip != PipId()); auto pip_iter = pip_to_net.find(pip); if (pip_iter != pip_to_net.end() && pip_iter->second != nullptr) { bool pip_blocked = false; if (net == nullptr) { pip_blocked = true; } else { if (net != pip_iter->second) { pip_blocked = true; } } if (pip_blocked) { #ifdef DEBUG_BINDING if (getCtx()->verbose) { log_info("Pip %s (%d/%d) is not available, tied to net %s\n", getCtx()->nameOfPip(pip), pip.tile, pip.index, pip_iter->second->name.c_str(getCtx())); } #endif NPNR_ASSERT(pip_iter->first == pip); return false; } } WireId src = getPipSrcWire(pip); WireId dst = getPipDstWire(pip); auto wire_iter = wire_to_net.find(dst); if (wire_iter != wire_to_net.end()) { NetInfo *wire_net = wire_iter->second; if (wire_net != nullptr) { auto net_iter = wire_net->wires.find(dst); if (net_iter != wire_net->wires.end()) { if (net == nullptr) { #ifdef DEBUG_BINDING if (getCtx()->verbose) { log_info("Pip %s (%d/%d) is not available, dst wire %s is tied to net %s\n", getCtx()->nameOfPip(pip), pip.tile, pip.index, getCtx()->nameOfWire(dst), wire_net->name.c_str(getCtx())); } #endif // dst is already driven in this net, do not allow! return false; } else { #ifdef DEBUG_BINDING if (getCtx()->verbose && net_iter->second.pip != pip) { log_info("Pip %s (%d/%d) is not available, dst wire %s is tied to net %s\n", getCtx()->nameOfPip(pip), pip.tile, pip.index, getCtx()->nameOfWire(dst), wire_net->name.c_str(getCtx())); } #endif // This pip is available if this pip is already bound to // this. return net_iter->second.pip == pip; } } } } // If this pip is a route-though, make sure all of the route-though are valid. // A route-through is valid if: // - none of the pseudo-pip wires are bound to a net // - the pseudo pip wires are bound to the same net and there are no // lut elements in the tile type: this to prevent the router from choosing // a pseudo-pip on the same LUT and on a different input pin for the same net. const TileTypeInfoPOD &tile_type = loc_info(chip_info, pip); const PipInfoPOD &pip_data = tile_type.pip_data[pip.index]; WireId wire; wire.tile = pip.tile; for (int32_t wire_index : pip_data.pseudo_cell_wires) { wire.index = wire_index; NPNR_ASSERT(src != wire); NPNR_ASSERT(dst != wire); NetInfo *other_net = getConflictingWireNet(wire); bool is_null_net = other_net == nullptr; if (!is_null_net) { bool is_same_net = other_net == net; bool tile_has_luts = tile_type.lut_elements.size() > 0; if (!is_same_net || tile_has_luts) { #ifdef DEBUG_BINDING if (getCtx()->verbose) log_info("Pip %s is not available because wire %s is tied to a different net " "(other net: %s - orig net: %s) or the pseudo pip may traverses LUTs\n", getCtx()->nameOfPip(pip), getCtx()->nameOfWire(wire), other_net->name.c_str(getCtx()), net == nullptr ? "NULL net" : net->name.c_str(getCtx())); #endif return false; } } } auto tile_status_iter = tileStatus.find(pip.tile); if (pip_data.pseudo_cell_wires.size() > 0) { // FIXME: This pseudo pip check is incomplete, because constraint // failures will not be detected. However the current FPGA // interchange schema does not provide a cell type to place. if (tile_status_iter != tileStatus.end() && !tile_status_iter->second.pseudo_pip_model.checkPipAvail(getCtx(), pip)) { return false; } } if (pip_data.site != -1 && net != nullptr) { NPNR_ASSERT(net->driver.cell != nullptr); NPNR_ASSERT(net->driver.cell->bel != BelId()); auto &src_wire_data = tile_type.wire_data[pip_data.src_index]; auto &dst_wire_data = tile_type.wire_data[pip_data.dst_index]; bool valid_pip = false; if (pip.tile == net->driver.cell->bel.tile) { if (tile_status_iter == tileStatus.end()) { // there is no tile status and nothing blocks the validity of this PIP valid_pip = true; } else { const BelInfoPOD &bel_data = tile_type.bel_data[net->driver.cell->bel.index]; const SiteRouter &site_router = get_site_status(tile_status_iter->second, bel_data); const auto &pips = site_router.valid_pips; auto result = std::find(pips.begin(), pips.end(), pip); if (result != pips.end()) { valid_pip = true; } } } if (disallow_site_routing && !valid_pip) { // For now, if driver is not part of this site, and // disallow_site_routing is set, disallow the edge. return false; } // FIXME: This check isn't perfect. If a driver and sink are in the // same site, it is possible for the router to route-thru the site // ports without hitting a sink, which is not legal in the FPGA // interchange. if (!valid_pip) { // See if one users can enter this site. if (dst_wire_data.site == -1) { // This is an output site port, but not for the driver net. // Disallow. NPNR_ASSERT(src_wire_data.site == pip_data.site); } else { // This might be a valid pip, scan users. for (auto &user : net->users) { NPNR_ASSERT(user.cell != nullptr); if (user.cell->bel == BelId()) { continue; } auto &bel_data = bel_info(chip_info, user.cell->bel); if (bel_data.site == pip_data.site) { valid_pip = true; break; } } } } if (!valid_pip) { #ifdef DEBUG_BINDING if (getCtx()->verbose) { log_info("Pip %s is within a site and not available not right now\n", getCtx()->nameOfPip(pip)); } #endif return false; } } return true; } bool Arch::checkPipAvail(PipId pip) const { return checkPipAvailForNet(pip, nullptr); } std::string Arch::get_chipdb_hash() const { return chipdb_hash; } bool Arch::is_inverting(PipId pip) const { auto &tile_type = loc_info(chip_info, pip); auto &pip_info = tile_type.pip_data[pip.index]; if (pip_info.site == -1) { // FIXME: Some routing pips are inverters, but this is missing from // the chipdb. return false; } auto &bel_data = tile_type.bel_data[pip_info.bel]; // Is a fixed inverter if the non_inverting_pin is another pin. return bel_data.non_inverting_pin != pip_info.extra_data && bel_data.inverting_pin == pip_info.extra_data; } bool Arch::can_invert(PipId pip) const { auto &tile_type = loc_info(chip_info, pip); auto &pip_info = tile_type.pip_data[pip.index]; if (pip_info.site == -1) { return false; } auto &bel_data = tile_type.bel_data[pip_info.bel]; // Can optionally invert if this pip is both the non_inverting_pin and // inverting pin. return bel_data.non_inverting_pin == pip_info.extra_data && bel_data.inverting_pin == pip_info.extra_data; } void Arch::mask_bel_pins_on_site_wire(NetInfo *net, WireId wire) { std::vector bel_pins_to_mask; for (const PortRef &port_ref : net->users) { if (port_ref.cell->bel == BelId()) { continue; } NPNR_ASSERT(port_ref.cell != nullptr); auto iter = port_ref.cell->cell_bel_pins.find(port_ref.port); if (iter == port_ref.cell->cell_bel_pins.end()) { continue; } std::vector &cell_bel_pins = iter->second; bel_pins_to_mask.clear(); for (size_t bel_pin_idx = 0; bel_pin_idx < cell_bel_pins.size(); ++bel_pin_idx) { IdString bel_pin = cell_bel_pins.at(bel_pin_idx); WireId bel_pin_wire = getBelPinWire(port_ref.cell->bel, bel_pin); if (bel_pin_wire == wire) { bel_pins_to_mask.push_back(bel_pin_idx); } } if (!bel_pins_to_mask.empty()) { std::vector &masked_cell_bel_pins = port_ref.cell->masked_cell_bel_pins[port_ref.port]; // Remove in reverse order to preserve indicies. for (auto riter = bel_pins_to_mask.rbegin(); riter != bel_pins_to_mask.rend(); ++riter) { size_t bel_pin_idx = *riter; masked_cell_bel_pins.push_back(cell_bel_pins.at(bel_pin_idx)); cell_bel_pins.erase(cell_bel_pins.begin() + bel_pin_idx); } } } } void Arch::unmask_bel_pins() { for (auto &cell_pair : cells) { CellInfo *cell = cell_pair.second.get(); if (cell->masked_cell_bel_pins.empty()) { continue; } for (auto &mask_pair : cell->masked_cell_bel_pins) { IdString cell_port = mask_pair.first; const std::vector &bel_pins = mask_pair.second; std::vector &cell_bel_pins = cell->cell_bel_pins[cell_port]; cell_bel_pins.insert(cell_bel_pins.begin(), bel_pins.begin(), bel_pins.end()); } cell->masked_cell_bel_pins.clear(); } } void Arch::remove_site_routing() { pool wires_to_unbind; for (auto &net_pair : nets) { for (auto &wire_pair : net_pair.second->wires) { WireId wire = wire_pair.first; if (wire_pair.second.strength != STRENGTH_PLACER) { // Only looking for bound placer wires continue; } wires_to_unbind.emplace(wire); } } for (WireId wire : wires_to_unbind) { unbindWire(wire); } unmask_bel_pins(); IdString id_NEXTPNR_INV = id("$nextpnr_inv"); IdString id_I = id("I"); std::vector cells_to_remove; for (auto &cell_pair : cells) { CellInfo *cell = cell_pair.second.get(); if (cell->type != id_NEXTPNR_INV) { continue; } disconnectPort(cell_pair.first, id_I); cells_to_remove.push_back(cell_pair.first); tileStatus.at(cell->bel.tile).boundcells[cell->bel.index] = nullptr; } for (IdString cell_name : cells_to_remove) { NPNR_ASSERT(cells.erase(cell_name) == 1); } } void Arch::explain_bel_status(BelId bel) const { if (isBelLocationValid(bel)) { log_info("BEL %s is valid!\n", nameOfBel(bel)); return; } auto iter = tileStatus.find(bel.tile); NPNR_ASSERT(iter != tileStatus.end()); const TileStatus &tile_status = iter->second; const CellInfo *cell = tile_status.boundcells[bel.index]; if (!dedicated_interconnect.isBelLocationValid(bel, cell)) { dedicated_interconnect.explain_bel_status(bel, cell); return; } if (io_port_types.count(cell->type)) { return; } if (!is_cell_valid_constraints(cell, tile_status, /*explain_constraints=*/true)) { return; } auto &bel_data = bel_info(chip_info, bel); const SiteRouter &site = get_site_status(tile_status, bel_data); NPNR_ASSERT(!site.checkSiteRouting(getCtx(), tile_status)); site.explain(getCtx()); } DelayQuad Arch::getPipDelay(PipId pip) const { // FIXME: Implement when adding timing-driven place and route. const auto &pip_data = pip_info(chip_info, pip); // Scale pseudo-pips by the number of wires they consume to make them // more expensive than a single edge. This approximation exists soley to // make the non-timing driven solution avoid thinking that pseudo-pips // are the same cost as regular pips. return DelayQuad(100 * (1 + pip_data.pseudo_cell_wires.size())); } const DefaultCellConnsPOD *Arch::get_default_conns(IdString cell_type) const { for (const auto &conn : chip_info->constants->default_conns) { if (IdString(conn.cell_type) == cell_type) return &conn; } return nullptr; } void Arch::pack_default_conns() { IdString vcc_net_name(chip_info->constants->vcc_net_name); IdString gnd_net_name(chip_info->constants->gnd_net_name); Context *ctx = getCtx(); std::vector dead_nets; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); const DefaultCellConnsPOD *conns = get_default_conns(ci->type); if (conns == nullptr) continue; for (const auto &pin : conns->pins) { IdString pin_name(pin.pin_name); // pin missing, create it if (!ci->ports.count(pin_name)) ci->addInput(pin_name); const NetInfo *net = ci->ports.at(pin_name).net; if (net != nullptr) { // pin is connected, and driven, nothing to do if (net->driver.cell != nullptr) continue; // pin is connected but undriven, disconnect the existing net ctx->disconnectPort(ci->name, pin_name); // remove net if it has no remaining users if (net->users.empty()) dead_nets.push_back(net->name); } if (pin.value == PIN_VALUE_GND) ctx->connectPort(gnd_net_name, ci->name, pin_name); else if (pin.value == PIN_VALUE_VCC) ctx->connectPort(vcc_net_name, ci->name, pin_name); else NPNR_ASSERT(pin.value == PIN_VALUE_FLOAT); } } // Remove any left-behind nets with no users and no drivers for (auto net : dead_nets) ctx->nets.erase(net); } // Instance constraint templates. template void Arch::ArchConstraints::bindBel(Arch::ArchConstraints::TagState *, const Arch::ConstraintRange); template void Arch::ArchConstraints::unbindBel(Arch::ArchConstraints::TagState *, const Arch::ConstraintRange); template bool Arch::ArchConstraints::isValidBelForCellType(const Context *, uint32_t, const Arch::ArchConstraints::TagState *, const Arch::ConstraintRange, IdString, IdString, BelId, bool) const; NEXTPNR_NAMESPACE_END