/* * nextpnr -- Next Generation Place and Route * * 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 "site_arch.h" #include "site_arch.impl.h" NEXTPNR_NAMESPACE_BEGIN SiteInformation::SiteInformation(const Context *ctx, int32_t tile, int32_t site, const pool &cells_in_site) : ctx(ctx), tile(tile), tile_type(ctx->chip_info->tiles[tile].type), site(site), cells_in_site(cells_in_site) { } bool SiteArch::bindPip(const SitePip &pip, SiteNetInfo *net) { SiteWire src = getPipSrcWire(pip); SiteWire dst = getPipDstWire(pip); if (!bindWire(src, net)) { return false; } if (!bindWire(dst, net)) { unbindWire(src); return false; } auto result = net->wires.emplace(dst, SitePipMap{pip, 1}); if (!result.second) { if (result.first->second.pip != pip) { // Pip conflict! if (debug()) { log_info("Pip conflict binding pip %s to wire %s, conflicts with pip %s\n", nameOfPip(pip), nameOfWire(dst), nameOfPip(result.first->second.pip)); } unbindWire(src); unbindWire(dst); return false; } result.first->second.count += 1; } if (debug()) { log_info("Bound pip %s to wire %s\n", nameOfPip(pip), nameOfWire(dst)); } return true; } void SiteArch::unbindPip(const SitePip &pip) { SiteWire src = getPipSrcWire(pip); SiteWire dst = getPipDstWire(pip); if (debug()) { log_info("Unbinding pip %s from wire %s\n", nameOfPip(pip), nameOfWire(dst)); } SiteNetInfo *src_net = unbindWire(src); SiteNetInfo *dst_net = unbindWire(dst); NPNR_ASSERT(src_net == dst_net); auto iter = dst_net->wires.find(dst); NPNR_ASSERT(iter != dst_net->wires.end()); NPNR_ASSERT(iter->second.count >= 1); iter->second.count -= 1; if (iter->second.count == 0) { dst_net->wires.erase(iter); } } void SiteArch::archcheck() { for (SiteWire wire : getWires()) { for (SitePip pip : getPipsDownhill(wire)) { SiteWire wire2 = getPipSrcWire(pip); log_assert(wire == wire2); } for (SitePip pip : getPipsUphill(wire)) { SiteWire wire2 = getPipDstWire(pip); log_assert(wire == wire2); } } } SiteArch::SiteArch(const SiteInformation *site_info) : ctx(site_info->ctx), site_info(site_info), blocking_net(site_info->ctx->id("$nextpnr_blocked_net")) { // Build list of input and output site ports // // FIXME: This doesn't need to be computed over and over, move to // arch/chip db. const TileTypeInfoPOD &tile_type = loc_info(&site_info->chip_info(), *site_info); PipId pip; pip.tile = site_info->tile; for (size_t pip_index = 0; pip_index < tile_type.pip_data.size(); ++pip_index) { if (tile_type.pip_data[pip_index].site != site_info->site) { continue; } pip.index = pip_index; if (!site_info->is_site_port(pip)) { continue; } WireId src_wire = ctx->getPipSrcWire(pip); if (site_info->is_wire_in_site(src_wire)) { output_site_ports.push_back(pip); } else { input_site_ports.push_back(pip); } } // Create list of out of site sources and sinks. bool have_vcc_pins = false; bool have_gnd_pins = false; 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 (CellInfo *cell : site_info->cells_in_site) { for (const auto &pin_pair : cell->cell_bel_pins) { if (!cell->ports.count(pin_pair.first)) continue; const PortInfo &port = cell->ports.at(pin_pair.first); if (port.net != nullptr) { nets.emplace(port.net, SiteNetInfo{port.net}); } } for (const auto &conn : cell->lut_cell.pin_connections) { if (conn.second == LutCell::PinConnection::Vcc) { have_vcc_pins = true; } else if (conn.second == LutCell::PinConnection::Gnd) { have_gnd_pins = true; } else if (conn.second == LutCell::PinConnection::Const) { if (const_net_name == vcc_net_name) { have_vcc_pins = true; } else if (const_net_name == gnd_net_name) { have_gnd_pins = true; } } } } for (auto &net_pair : nets) { NetInfo *net = net_pair.first; SiteNetInfo &net_info = net_pair.second; // All nets require drivers if (net->driver.cell == nullptr) continue; bool net_driven_out_of_site = false; if (net->driver.cell->bel == BelId()) { // The driver of this site hasn't been placed, so treat it as // out of site. out_of_site_sources.push_back(SiteWire::make(site_info, PORT_OUT, net)); net_info.driver = out_of_site_sources.back(); net_driven_out_of_site = true; } else { if (!site_info->is_bel_in_site(net->driver.cell->bel)) { // The driver of this site has been placed, it is an out // of site source. out_of_site_sources.push_back(SiteWire::make(site_info, PORT_OUT, net)); // out_of_site_sources.back().wire = ctx->getNetinfoSourceWire(net); net_info.driver = out_of_site_sources.back(); net_driven_out_of_site = true; } else { net_info.driver = SiteWire::make(site_info, ctx->getNetinfoSourceWire(net)); } } if (net_driven_out_of_site) { // Because this net is driven from a source out of the site, // no out of site sink is required. continue; } // Examine net to determine if it has any users not in this site. bool net_used_out_of_site = false; for (const PortRef &user : net->users) { NPNR_ASSERT(user.cell != nullptr); if (user.cell->bel == BelId()) { // Because this net has a user that has not been placed, // and this net is being driven from this site, make sure // this net can be routed from this site. net_used_out_of_site = true; break; } if (!site_info->is_bel_in_site(user.cell->bel)) { net_used_out_of_site = true; break; } } if (net_used_out_of_site) { out_of_site_sinks.push_back(SiteWire::make(site_info, PORT_IN, net)); net_info.users.emplace(out_of_site_sinks.back()); } } // At this point all nets have a driver SiteWire, but user SiteWire's // within the site are not present. Add them now. for (auto &net_pair : nets) { NetInfo *net = net_pair.first; SiteNetInfo &net_info = net_pair.second; for (const PortRef &user : net->users) { if (!site_info->is_bel_in_site(user.cell->bel)) { // Only care about BELs within the site at this point. continue; } for (IdString bel_pin : ctx->getBelPinsForCellPin(user.cell, user.port)) { SiteWire wire = getBelPinWire(user.cell->bel, bel_pin); // Don't add users that are trivially routable! if (wire != net_info.driver) { #ifdef DEBUG_SITE_ARCH if (ctx->debug) { log_info("Add user %s because it isn't driver %s\n", nameOfWire(wire), nameOfWire(net_info.driver)); } #endif net_info.users.emplace(wire); } } } } NetInfo *vcc_net = ctx->nets.at(vcc_net_name).get(); auto vcc_iter = nets.find(vcc_net); if (vcc_iter == nets.end() && have_vcc_pins) { // VCC net isn't present, add it. SiteNetInfo net_info; net_info.net = vcc_net; net_info.driver.type = SiteWire::OUT_OF_SITE_SOURCE; net_info.driver.net = vcc_net; auto result = nets.emplace(vcc_net, net_info); NPNR_ASSERT(result.second); vcc_iter = result.first; } NetInfo *gnd_net = ctx->nets.at(gnd_net_name).get(); auto gnd_iter = nets.find(gnd_net); if (gnd_iter == nets.end() && have_gnd_pins) { // GND net isn't present, add it. SiteNetInfo net_info; net_info.net = gnd_net; net_info.driver.type = SiteWire::OUT_OF_SITE_SOURCE; net_info.driver.net = gnd_net; auto result = nets.emplace(gnd_net, net_info); NPNR_ASSERT(result.second); gnd_iter = result.first; } for (CellInfo *cell : site_info->cells_in_site) { for (const auto &it : cell->lut_cell.pin_connections) { const auto &pin = it.first; const auto &conn = it.second; if (conn == LutCell::PinConnection::Unconnected || conn == LutCell::PinConnection::Signal) { continue; } if (conn == LutCell::PinConnection::Vcc) { SiteWire wire = getBelPinWire(cell->bel, pin); vcc_iter->second.users.emplace(wire); } else if (conn == LutCell::PinConnection::Gnd) { SiteWire wire = getBelPinWire(cell->bel, pin); gnd_iter->second.users.emplace(wire); } else if (conn == LutCell::PinConnection::Const) { SiteWire wire = getBelPinWire(cell->bel, pin); if (const_net_name == vcc_net_name) { vcc_iter->second.users.emplace(wire); } if (const_net_name == gnd_net_name) { gnd_iter->second.users.emplace(wire); } } #ifdef DEBUG_LUT_MAPPING if (ctx->verbose) { log_info("Tying %s.%s to %s\n", cell->name.c_str(ctx), pin.c_str(ctx), LutCell::nameOfPinConnection(conn).c_str()); } #endif } } for (auto &net_pair : nets) { if (net_pair.first->driver.cell == nullptr) continue; SiteNetInfo *net_info = &net_pair.second; auto result = wire_to_nets.emplace(net_info->driver, SiteNetMap{net_info, 1}); // By this point, trivial congestion at sources should already by // avoided, and there should be no duplicates in the // driver/users data. NPNR_ASSERT(result.second); for (const auto &user : net_info->users) { result = wire_to_nets.emplace(user, SiteNetMap{net_info, 1}); NPNR_ASSERT(result.second); } } blocking_site_net.net = &blocking_net; } const char *SiteArch::nameOfWire(const SiteWire &wire) const { switch (wire.type) { case SiteWire::SITE_WIRE: return ctx->nameOfWire(wire.wire); case SiteWire::SITE_PORT_SINK: return ctx->nameOfWire(wire.wire); case SiteWire::SITE_PORT_SOURCE: return ctx->nameOfWire(wire.wire); case SiteWire::OUT_OF_SITE_SOURCE: { std::string &str = ctx->log_strs.next(); str = stringf("Out of site source for net %s", wire.net->name.c_str(ctx)); return str.c_str(); } case SiteWire::OUT_OF_SITE_SINK: { std::string &str = ctx->log_strs.next(); str = stringf("Out of sink source for net %s", wire.net->name.c_str(ctx)); return str.c_str(); } default: // Unreachable! NPNR_ASSERT(false); } } const char *SiteArch::nameOfPip(const SitePip &pip) const { switch (pip.type) { case SitePip::SITE_PIP: return ctx->nameOfPip(pip.pip); case SitePip::SITE_PORT: return ctx->nameOfPip(pip.pip); case SitePip::SOURCE_TO_SITE_PORT: { std::string &str = ctx->log_strs.next(); str = stringf("Out of site source for net %s => %s", pip.wire.net->name.c_str(ctx), ctx->nameOfWire(ctx->getPipSrcWire(pip.pip))); return str.c_str(); } case SitePip::SITE_PORT_TO_SINK: { std::string &str = ctx->log_strs.next(); str = stringf("%s => Out of site sink for net %s", ctx->nameOfWire(ctx->getPipDstWire(pip.pip)), pip.wire.net->name.c_str(ctx)); return str.c_str(); } case SitePip::SITE_PORT_TO_SITE_PORT: { std::string &str = ctx->log_strs.next(); str = stringf("%s => %s", ctx->nameOfWire(ctx->getPipSrcWire(pip.pip)), ctx->nameOfWire(ctx->getPipDstWire(pip.other_pip))); return str.c_str(); } default: // Unreachable! NPNR_ASSERT(false); } } const char *SiteArch::nameOfNet(const SiteNetInfo *net) const { return net->net->name.c_str(ctx); } bool SiteArch::debug() const { return ctx->debug; } SitePipUphillRange::SitePipUphillRange(const SiteArch *site_arch, SiteWire site_wire) : site_arch(site_arch), site_wire(site_wire) { switch (site_wire.type) { case SiteWire::SITE_WIRE: pip_range = site_arch->ctx->getPipsUphill(site_wire.wire); break; case SiteWire::OUT_OF_SITE_SOURCE: // No normal pips! break; case SiteWire::OUT_OF_SITE_SINK: // No normal pips! break; case SiteWire::SITE_PORT_SINK: // No normal pips! break; case SiteWire::SITE_PORT_SOURCE: // No normal pips! break; default: // Unreachable! NPNR_ASSERT(false); } } SitePip SitePipUphillIterator::operator*() const { switch (state) { case NORMAL_PIPS: return SitePip::make(site_arch->site_info, *iter); case PORT_SRC_TO_PORT_SINK: return SitePip::make(site_arch->site_info, site_arch->output_site_ports.at(cursor), site_wire.pip); case OUT_OF_SITE_SOURCES: return SitePip::make(site_arch->site_info, site_arch->out_of_site_sources.at(cursor), site_wire.pip); case OUT_OF_SITE_SINK_TO_PORT_SINK: return SitePip::make(site_arch->site_info, site_arch->output_site_ports.at(cursor), site_wire); case SITE_PORT: return SitePip::make(site_arch->site_info, site_wire.pip); default: // Unreachable! NPNR_ASSERT(false); } } SiteWire SiteWireIterator::operator*() const { WireId wire; PipId pip; SiteWire site_wire; switch (state) { case NORMAL_WIRES: wire.tile = site_arch->site_info->tile; wire.index = cursor; return SiteWire::make(site_arch->site_info, wire); case INPUT_SITE_PORTS: pip = site_arch->input_site_ports.at(cursor); site_wire = SiteWire::make_site_port(site_arch->site_info, pip, /*dst_wire=*/false); NPNR_ASSERT(site_wire.type == SiteWire::SITE_PORT_SOURCE); return site_wire; case OUTPUT_SITE_PORTS: pip = site_arch->output_site_ports.at(cursor); site_wire = SiteWire::make_site_port(site_arch->site_info, pip, /*dst_wire=*/true); NPNR_ASSERT(site_wire.type == SiteWire::SITE_PORT_SINK); return site_wire; case OUT_OF_SITE_SOURCES: return site_arch->out_of_site_sources.at(cursor); case OUT_OF_SITE_SINKS: return site_arch->out_of_site_sinks.at(cursor); default: // Unreachable! NPNR_ASSERT(false); } } SiteWireIterator SiteWireRange::begin() const { SiteWireIterator b; b.state = SiteWireIterator::BEGIN; b.site_arch = site_arch; b.tile_type = &loc_info(&site_arch->site_info->chip_info(), *site_arch->site_info); ++b; return b; } NEXTPNR_NAMESPACE_END