diff options
author | Keith Rothman <537074+litghost@users.noreply.github.com> | 2021-02-16 09:45:43 -0800 |
---|---|---|
committer | Keith Rothman <537074+litghost@users.noreply.github.com> | 2021-02-17 12:03:16 -0800 |
commit | c385321248c2bd181d42b7ce7a2f60baa365aa8d (patch) | |
tree | dfa19555d2bee253f20aff2b34cfdbe0bf624719 /fpga_interchange | |
parent | a7421399f776ed9aebfa7b940bbaf5f327244f17 (diff) | |
download | nextpnr-c385321248c2bd181d42b7ce7a2f60baa365aa8d.tar.gz nextpnr-c385321248c2bd181d42b7ce7a2f60baa365aa8d.tar.bz2 nextpnr-c385321248c2bd181d42b7ce7a2f60baa365aa8d.zip |
Add initial site router.
This site router likely cannot handle the full problem space. It may
need to be replaced with a more generalize approach as testing
continues.
Signed-off-by: Keith Rothman <537074+litghost@users.noreply.github.com>
Diffstat (limited to 'fpga_interchange')
-rw-r--r-- | fpga_interchange/arch.cc | 2 | ||||
-rw-r--r-- | fpga_interchange/arch.h | 62 | ||||
-rw-r--r-- | fpga_interchange/arch_pack_io.cc | 2 | ||||
-rw-r--r-- | fpga_interchange/site_router.cc | 753 |
4 files changed, 813 insertions, 6 deletions
diff --git a/fpga_interchange/arch.cc b/fpga_interchange/arch.cc index 41659861..58fa3c85 100644 --- a/fpga_interchange/arch.cc +++ b/fpga_interchange/arch.cc @@ -161,7 +161,7 @@ Arch::Arch(ArchArgs args) : args(args) 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]; + auto &type_definition = constraints.definitions[tile_type_index++]; for (const ConstraintTagPOD &tag : tile_type.tags) { type_definition.emplace_back(); auto &definition = type_definition.back(); diff --git a/fpga_interchange/arch.h b/fpga_interchange/arch.h index 88f35df4..cd987001 100644 --- a/fpga_interchange/arch.h +++ b/fpga_interchange/arch.h @@ -754,10 +754,28 @@ struct Arch : ArchAPI<ArchRanges> std::unordered_map<WireId, NetInfo *> reserved_wires; static constexpr size_t kMaxState = 8; + + struct TileStatus; + struct SiteRouter + { + SiteRouter(int16_t site) : site(site), dirty(false), site_ok(true) {} + + std::unordered_set<CellInfo *> cells_in_site; + const int16_t site; + + mutable bool dirty; + mutable bool site_ok; + + void bindBel(CellInfo *cell); + void unbindBel(CellInfo *cell); + bool checkSiteRouting(const Context *ctx, const TileStatus &tile_status) const; + }; + struct TileStatus { std::vector<ExclusiveStateGroup<kMaxState>> tags; std::vector<CellInfo *> boundcells; + std::vector<SiteRouter> sites; }; std::unordered_map<int32_t, TileStatus> tileStatus; @@ -829,11 +847,26 @@ struct Arch : ArchAPI<ArchRanges> auto &tile_type = chip_info->tile_types[chip_info->tiles[tile].type]; result.first->second.boundcells.resize(tile_type.bel_data.size()); result.first->second.tags.resize(default_tags.size()); + + result.first->second.sites.reserve(tile_type.number_sites); + for (size_t i = 0; i < tile_type.number_sites; ++i) { + result.first->second.sites.push_back(SiteRouter(i)); + } } return result.first->second; } + const SiteRouter &get_site_status(const TileStatus &tile_status, const BelInfoPOD &bel_data) const + { + return tile_status.sites.at(bel_data.site); + } + + SiteRouter &get_site_status(TileStatus &tile_status, const BelInfoPOD &bel_data) + { + return tile_status.sites.at(bel_data.site); + } + void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) override { NPNR_ASSERT(bel != BelId()); @@ -859,6 +892,8 @@ struct Arch : ArchAPI<ArchRanges> // really matters if the placer can choose IO port locations. } + get_site_status(tile_status, bel_data).bindBel(cell); + tile_status.boundcells[bel.index] = cell; cell->bel = bel; @@ -887,6 +922,9 @@ struct Arch : ArchAPI<ArchRanges> constraints.unbindBel(tile_status.tags.data(), get_cell_constraints(bel, cell->type)); } + const auto &bel_data = bel_info(chip_info, bel); + get_site_status(tile_status, bel_data).unbindBel(cell); + refreshUiBel(bel); } @@ -1414,6 +1452,20 @@ struct Arch : ArchAPI<ArchRanges> return bel_data.pin_map[cell_type_index] != -1; } + bool is_cell_valid_constraints(const CellInfo *cell, const TileStatus &tile_status, bool explain) const + { + if (io_port_types.count(cell->type)) { + return true; + } + + BelId bel = cell->bel; + NPNR_ASSERT(bel != BelId()); + + return constraints.isValidBelForCellType(getCtx(), get_constraint_prototype(bel), tile_status.tags.data(), + get_cell_constraints(bel, cell->type), + id(chip_info->tiles[bel.tile].name.get()), cell->name, bel, explain); + } + // Return true whether all Bels at a given location are valid bool isBelLocationValid(BelId bel) const override { @@ -1433,10 +1485,12 @@ struct Arch : ArchAPI<ArchRanges> return true; } - return constraints.isValidBelForCellType(getCtx(), get_constraint_prototype(bel), tile_status.tags.data(), - get_cell_constraints(bel, cell->type), - id(chip_info->tiles[bel.tile].name.get()), cell->name, bel, - explain_constraints); + if (!is_cell_valid_constraints(cell, tile_status, explain_constraints)) { + return false; + } + + auto &bel_data = bel_info(chip_info, bel); + return get_site_status(tile_status, bel_data).checkSiteRouting(getCtx(), tile_status); } } diff --git a/fpga_interchange/arch_pack_io.cc b/fpga_interchange/arch_pack_io.cc index cc1cfb93..16d0d05f 100644 --- a/fpga_interchange/arch_pack_io.cc +++ b/fpga_interchange/arch_pack_io.cc @@ -207,7 +207,7 @@ void Arch::pack_ports() for (CellInfo *cell : placed_cells) { NPNR_ASSERT(cell->bel != BelId()); - NPNR_ASSERT(isValidBelForCell(cell, cell->bel)); + NPNR_ASSERT(isBelLocationValid(cell->bel)); } } } diff --git a/fpga_interchange/site_router.cc b/fpga_interchange/site_router.cc new file mode 100644 index 00000000..e643311d --- /dev/null +++ b/fpga_interchange/site_router.cc @@ -0,0 +1,753 @@ +/* + * 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 "log.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +bool verbose_site_router(const Context *ctx) { return ctx->verbose; } + +void Arch::SiteRouter::bindBel(CellInfo *cell) +{ + auto result = cells_in_site.emplace(cell); + NPNR_ASSERT(result.second); + + dirty = true; +} + +void Arch::SiteRouter::unbindBel(CellInfo *cell) +{ + NPNR_ASSERT(cells_in_site.erase(cell) == 1); + + dirty = true; +} + +struct RouteNode +{ + void clear() + { + parent = std::list<RouteNode>::iterator(); + leafs.clear(); + pip = PipId(); + wire = WireId(); + } + + using Node = std::list<RouteNode>::iterator; + + Node parent; + std::vector<Node> leafs; + + PipId pip; // What pip was taken to reach this node. + WireId wire; // What wire is this routing node located at? +}; + +struct RouteNodeStorage +{ + // Free list of nodes. + std::list<RouteNode> nodes; + + // Either allocate a new node if no nodes are on the free list, or return + // an element from the free list. + std::list<RouteNode>::iterator alloc_node(std::list<RouteNode> &new_owner) + { + if (nodes.empty()) { + nodes.emplace_front(RouteNode()); + } + + auto ret = nodes.begin(); + new_owner.splice(new_owner.end(), nodes, ret); + + ret->clear(); + + return ret; + } + + // Return 1 node from the current owner to the free list. + void free_node(std::list<RouteNode> &owner, std::list<RouteNode>::iterator node) + { + nodes.splice(nodes.end(), owner, node); + } + + // Return all node from the current owner to the free list. + void free_nodes(std::list<RouteNode> &owner) + { + nodes.splice(nodes.end(), owner); + NPNR_ASSERT(owner.empty()); + } +}; + +struct SiteInformation +{ + const Context *ctx; + + const std::unordered_set<CellInfo *> &cells_in_site; + + SiteInformation(const Context *ctx, const std::unordered_set<CellInfo *> &cells_in_site) + : ctx(ctx), cells_in_site(cells_in_site) + { + } + + bool check_bel_pin(CellInfo *cell, const PortInfo &port_info, BelPin bel_pin) + { + WireId wire = ctx->getBelPinWire(bel_pin.bel, bel_pin.pin); + auto result = consumed_wires.emplace(wire, port_info.net); + if (!result.second) { + // This wire is already in use, make sure the net bound is + // the same net, otherwise there is a net conflict. + const NetInfo *other_net = result.first->second; + if (other_net != port_info.net) { + // We have a direct net conflict at the BEL pin, + // immediately short circuit the site routing check. + if (verbose_site_router(ctx)) { + log_info("Direct net conflict detected for cell %s:%s at bel %s, net %s != %s\n", + cell->name.c_str(ctx), cell->type.c_str(ctx), ctx->nameOfBel(cell->bel), + port_info.net->name.c_str(ctx), other_net->name.c_str(ctx)); + } + return false; + } + } + + nets_in_site.emplace(port_info.net); + + if (port_info.type == PORT_OUT) { + unrouted_source_wires.emplace(wire, std::unordered_set<WireId>()); + } else { + unrouted_sink_wires.emplace(wire); + } + + return true; + } + + bool check_initial_wires() + { + // Propigate from BEL pins to first wire, checking for trival routing + // conflicts. + // + // Populate initial consumed wires, and nets_in_site. + for (CellInfo *cell : cells_in_site) { + BelId bel = cell->bel; + for (const auto &pin_pair : cell->cell_bel_pins) { + const PortInfo &port = cell->ports.at(pin_pair.first); + for (IdString bel_pin_name : pin_pair.second) { + BelPin bel_pin; + bel_pin.bel = bel; + bel_pin.pin = bel_pin_name; + + if (!check_bel_pin(cell, port, bel_pin)) { + return false; + } + } + } + } + + // Populate nets_fully_within_site + for (const NetInfo *net : nets_in_site) { + if (ctx->is_net_within_site(*net)) { + nets_fully_within_site.emplace(net); + } + } + + // Remove sinks that are trivally routed. + std::vector<WireId> trivally_routed_sinks; + for (WireId sink_wire : unrouted_sink_wires) { + if (unrouted_source_wires.count(sink_wire) > 0) { + if (verbose_site_router(ctx)) { + log_info("Wire %s is trivally routed!\n", ctx->nameOfWire(sink_wire)); + } + trivally_routed_sinks.push_back(sink_wire); + } + } + + for (WireId sink_wire : trivally_routed_sinks) { + NPNR_ASSERT(unrouted_sink_wires.erase(sink_wire) == 1); + } + + // Remove sources that are routed now that trivally routed sinks are + // removed. + std::unordered_set<WireId> trivally_routed_sources; + for (const NetInfo *net : nets_fully_within_site) { + std::unordered_set<WireId> sink_wires_in_net; + bool already_routed = true; + for (const PortRef &user : net->users) { + for (const IdString pin : user.cell->cell_bel_pins.at(user.port)) { + WireId sink_wire = ctx->getBelPinWire(user.cell->bel, pin); + if (unrouted_sink_wires.count(sink_wire) > 0) { + sink_wires_in_net.emplace(sink_wire); + already_routed = false; + } + } + } + + if (already_routed) { + for (const IdString pin : net->driver.cell->cell_bel_pins.at(net->driver.port)) { + trivally_routed_sources.emplace(ctx->getBelPinWire(net->driver.cell->bel, pin)); + } + } else { + for (const IdString pin : net->driver.cell->cell_bel_pins.at(net->driver.port)) { + WireId source_wire = ctx->getBelPinWire(net->driver.cell->bel, pin); + unrouted_source_wires.at(source_wire) = sink_wires_in_net; + } + } + } + + for (WireId source_wire : trivally_routed_sources) { + NPNR_ASSERT(unrouted_source_wires.erase(source_wire) == 1); + } + + return true; + } + + // Checks if a source wire has been fully routed. + // + // Returns false if this wire is not an unrouted source wire. + bool check_source_routed(WireId wire) const + { + if (unrouted_source_wires.count(wire)) { + bool fully_routed = true; + for (WireId sink_wire : unrouted_source_wires.at(wire)) { + if (unrouted_sink_wires.count(sink_wire)) { + fully_routed = false; + } + } + + return fully_routed; + } else { + return false; + } + } + + // Removes an source wires that have been fully routed. + void remove_routed_sources() + { + std::vector<WireId> routed_wires; + for (auto &source_pair : unrouted_source_wires) { + if (check_source_routed(source_pair.first)) { + routed_wires.push_back(source_pair.first); + } + } + + for (WireId wire : routed_wires) { + NPNR_ASSERT(unrouted_source_wires.erase(wire) == 1); + } + } + + bool is_fully_routed() const { return unrouted_sink_wires.empty() && unrouted_source_wires.empty(); } + + bool select_route(WireId first_wire, RouteNode::Node node, const NetInfo *net, + std::unordered_set<WireId> *newly_consumed_wires) + { + + bool is_last_pip_site_port = ctx->is_site_port(node->pip); + do { + auto result = consumed_wires.emplace(node->wire, net); + if (!result.second && result.first->second != net) { + // Conflict, this wire is already in use and it's not + // doesn't match! + return false; + } + + // By selecting a route, other sinks are potentially now routed. + unrouted_sink_wires.erase(node->wire); + + newly_consumed_wires->emplace(node->wire); + + node = node->parent; + } while (node != RouteNode::Node()); + + if (unrouted_source_wires.count(first_wire)) { + // By selecting a route to a site pip, this source wire is routed. + if (is_last_pip_site_port) { + NPNR_ASSERT(unrouted_source_wires.erase(first_wire)); + } else if (is_net_within_site(net)) { + // For nets that are completely contained within the site, it + // is possible that by selecting this route it is now fully + // routed. Check now. + if (check_source_routed(first_wire)) { + NPNR_ASSERT(unrouted_source_wires.erase(first_wire)); + } + } + } + + return true; + } + + // Map of currently occupied wires and their paired net. + std::unordered_map<WireId, const NetInfo *> consumed_wires; + + // Set of nets in site + std::unordered_set<const NetInfo *> nets_in_site; + + // Map from source wire to sink wires within this site. + // If all sink wires are routed, the source is also routed! + std::unordered_map<WireId, std::unordered_set<WireId>> unrouted_source_wires; + std::unordered_set<WireId> unrouted_sink_wires; + + // Set of nets are fully contained within the site. + std::unordered_set<const NetInfo *> nets_fully_within_site; + + bool is_net_within_site(const NetInfo *net) const { return nets_fully_within_site.count(net); } +}; + +struct SiteExpansionLoop +{ + const Context *const ctx; + RouteNodeStorage *const node_storage; + + using Node = RouteNode::Node; + + SiteExpansionLoop(const Context *ctx, RouteNodeStorage *node_storage) : ctx(ctx), node_storage(node_storage) + { + NPNR_ASSERT(node_storage != nullptr); + } + + ~SiteExpansionLoop() { node_storage->free_nodes(nodes); } + + // Storage for nodes + std::list<RouteNode> nodes; + + WireId first_wire; + const NetInfo *net_for_wire; + std::unordered_map<RouteNode *, Node> completed_routes; + std::unordered_map<WireId, std::vector<Node>> wire_to_nodes; + + Node new_node(WireId wire, PipId pip, Node parent) + { + auto node = node_storage->alloc_node(nodes); + node->wire = wire; + node->pip = pip; + node->parent = parent; + + return node; + } + + void free_node(Node node) { node_storage->free_node(nodes, node); } + + // Expand from wire specified, either downhill or uphill. + // + // Expands until it reaches another net of it's own (e.g. source to sink + // within site) or a site port (e.g. out to routing network). + void expand(WireId wire, const SiteInformation *site_info) + { + + bool downhill = site_info->unrouted_source_wires.count(wire) != 0; + if (!downhill) { + NPNR_ASSERT(site_info->unrouted_sink_wires.count(wire) != 0); + } + + first_wire = wire; + net_for_wire = site_info->consumed_wires.at(first_wire); + + if (verbose_site_router(ctx)) { + log_info("Expanding net %s from %s\n", net_for_wire->name.c_str(ctx), ctx->nameOfWire(first_wire)); + } + + completed_routes.clear(); + wire_to_nodes.clear(); + node_storage->free_nodes(nodes); + + auto node = new_node(first_wire, PipId(), /*parent=*/Node()); + wire_to_nodes[first_wire].push_back(node); + + std::vector<Node> nodes_to_expand; + nodes_to_expand.push_back(node); + + auto do_expand = [&](Node parent_node, PipId pip, WireId wire) { + if (wire == first_wire) { + // No simple loops + // FIXME: May need to detect more complicated loops! + return; + } + + if (ctx->is_site_port(pip)) { + if (verbose_site_router(ctx)) { + log_info("Expanded net %s reaches %s\n", net_for_wire->name.c_str(ctx), ctx->nameOfPip(pip)); + } + auto node = new_node(wire, pip, parent_node); + completed_routes.emplace(&*node, node); + return; + } + + auto iter = site_info->consumed_wires.find(wire); + if (iter != site_info->consumed_wires.end()) { + // This wire already belongs to a net! + if (iter->second == net_for_wire) { + // If this wire is the same net, this is a valid complete + // route. + if (!downhill && site_info->unrouted_source_wires.count(wire)) { + // This path is from a sink to a source, it is a complete route. + auto node = new_node(wire, pip, parent_node); + if (verbose_site_router(ctx)) { + log_info("Expanded net %s reaches source %s\n", net_for_wire->name.c_str(ctx), + ctx->nameOfWire(wire)); + } + completed_routes.emplace(&*node, node); + } else if (downhill && site_info->is_net_within_site(net_for_wire)) { + // This path is from a sink to a source, it is a complete route to 1 sinks. + auto node = new_node(wire, pip, parent_node); + if (verbose_site_router(ctx)) { + log_info("Expanded net %s reaches sink %s\n", net_for_wire->name.c_str(ctx), + ctx->nameOfWire(wire)); + } + completed_routes.emplace(&*node, node); + } + } else { + // Net conflict, do not expand further. + return; + } + } + + // This wire is not a destination, and is not directly occupied, + // put it on the expansion list. + nodes_to_expand.push_back(new_node(wire, pip, parent_node)); + }; + + while (!nodes_to_expand.empty()) { + Node node_to_expand = nodes_to_expand.back(); + nodes_to_expand.pop_back(); + + if (downhill) { + for (PipId pip : ctx->getPipsDownhill(node_to_expand->wire)) { + WireId wire = ctx->getPipDstWire(pip); + do_expand(node_to_expand, pip, wire); + } + } else { + for (PipId pip : ctx->getPipsUphill(node_to_expand->wire)) { + WireId wire = ctx->getPipSrcWire(pip); + do_expand(node_to_expand, pip, wire); + } + } + } + } + + // Remove any routes that use specified wire. + void remove_wire(WireId wire) + { + auto iter = wire_to_nodes.find(wire); + if (iter == wire_to_nodes.end()) { + // This wire was not in use, done! + return; + } + + // We need to prune the tree of nodes starting from any node that + // uses the specified wire. Create a queue of nodes to follow to + // gather all nodes that need to be removed. + std::list<RouteNode> nodes_to_follow; + for (Node node : iter->second) { + nodes_to_follow.splice(nodes_to_follow.end(), nodes, node); + } + + // Follow all nodes to their end, mark that node to be eventually removed. + std::list<RouteNode> nodes_to_remove; + while (!nodes_to_follow.empty()) { + Node node = nodes_to_follow.begin(); + nodes_to_remove.splice(nodes_to_remove.end(), nodes_to_follow, node); + + for (Node child_node : node->leafs) { + nodes_to_follow.splice(nodes_to_follow.end(), nodes, child_node); + } + } + + // Check if any nodes being removed are a completed route. + for (RouteNode &node : nodes_to_remove) { + completed_routes.erase(&node); + } + + // Move all nodes to be removed to the free list. + node_storage->free_nodes(nodes_to_remove); + NPNR_ASSERT(nodes_to_follow.empty()); + NPNR_ASSERT(nodes_to_remove.empty()); + } +}; + +bool route_site(const Context *ctx, SiteInformation *site_info) +{ + // All nets need to route: + // - From sources to an output site pin or sink wire. + // - From sink to an input site pin. + + std::unordered_set<WireId> unrouted_wires; + + for (auto wire_pair : site_info->unrouted_source_wires) { + auto result = unrouted_wires.emplace(wire_pair.first); + NPNR_ASSERT(result.second); + } + for (WireId wire : site_info->unrouted_sink_wires) { + auto result = unrouted_wires.emplace(wire); + if (!result.second) { + log_error("Found sink wire %s already in unrouted_wires set. unrouted_source_wires.count() == %zu\n", + ctx->nameOfWire(wire), site_info->unrouted_source_wires.count(wire)); + } + } + + // All done! + if (unrouted_wires.empty()) { + return true; + } + + // Expand from first wires to all pontential routes (either net pair or + // site pin). + RouteNodeStorage node_storage; + std::vector<SiteExpansionLoop> expansions; + expansions.reserve(unrouted_wires.size()); + + for (WireId wire : unrouted_wires) { + expansions.emplace_back(SiteExpansionLoop(ctx, &node_storage)); + + SiteExpansionLoop &wire_router = expansions.back(); + wire_router.expand(wire, site_info); + + // It is not possible to route this wire at all, fail early. + if (wire_router.completed_routes.empty()) { + return false; + } + } + + std::unordered_set<WireId> newly_consumed_wires; + std::unordered_map<WireId, SiteExpansionLoop *> wire_to_expansion; + for (auto &expansion : expansions) { + // This is a special case, where the expansion found exactly 1 solution. + // That solution must be conflict free, or the site is unroutable. + if (expansion.completed_routes.size() == 1) { + auto node = expansion.completed_routes.begin()->second; + if (!site_info->select_route(expansion.first_wire, node, expansion.net_for_wire, &newly_consumed_wires)) { + // Conflict! + return false; + } + } else { + auto result = wire_to_expansion.emplace(expansion.first_wire, &expansion); + NPNR_ASSERT(result.second); + } + } + + if (wire_to_expansion.empty()) { + // All routes have been assigned with congestion! + return true; + } + + // At this point some expansions have multiple results. Build congestion + // information, and pick non-conflicted routes for remaining expansions. + std::vector<WireId> completed_wires; + do { + // Before anything, remove routes that have been consumed in previous + // iteration. + for (auto &expansion_wire : wire_to_expansion) { + auto &expansion = *expansion_wire.second; + for (WireId consumed_wire : newly_consumed_wires) { + const NetInfo *net_for_wire = site_info->consumed_wires.at(consumed_wire); + if (net_for_wire != expansion.net_for_wire) { + expansion.remove_wire(consumed_wire); + } + + // By removing that wire, this expansion now has no solutions! + if (expansion.completed_routes.empty()) { + return false; + } + } + } + + // Check if there are any more trival solutions. + completed_wires.clear(); + newly_consumed_wires.clear(); + + for (auto &expansion_wire : wire_to_expansion) { + auto &expansion = *expansion_wire.second; + if (expansion.completed_routes.size() == 1) { + auto node = expansion.completed_routes.begin()->second; + if (!site_info->select_route(expansion.first_wire, node, expansion.net_for_wire, + &newly_consumed_wires)) { + // Conflict! + return false; + } + + // Mark this expansion as done! + completed_wires.push_back(expansion_wire.first); + } + } + + // Remove trival solutions from unsolved routing. + for (WireId wire : completed_wires) { + NPNR_ASSERT(wire_to_expansion.erase(wire) == 1); + } + + // All expansions have been selected for! + if (wire_to_expansion.empty()) { + break; + } + + // At least 1 trival solution was selected, re-prune. + if (!newly_consumed_wires.empty()) { + // Prune remaining solutions. + continue; + } + + std::unordered_map<WireId, std::unordered_set<const NetInfo *>> wire_congestion; + + for (auto &expansion_wire : wire_to_expansion) { + auto &expansion = *expansion_wire.second; + + for (auto pair : expansion.completed_routes) { + auto node = pair.second; + + do { + wire_congestion[node->wire].emplace(expansion.net_for_wire); + node = node->parent; + } while (node != RouteNode::Node()); + } + } + + for (auto &expansion_wire : wire_to_expansion) { + auto &expansion = *expansion_wire.second; + + RouteNode::Node uncongestion_route; + + for (auto pair : expansion.completed_routes) { + auto node = pair.second; + uncongestion_route = node; + + do { + if (wire_congestion[node->wire].size() > 1) { + uncongestion_route = RouteNode::Node(); + break; + } + node = node->parent; + } while (node != RouteNode::Node()); + + if (uncongestion_route != RouteNode::Node()) { + break; + } + } + + if (uncongestion_route != RouteNode::Node()) { + // Select a trivally uncongestion route if possible. + NPNR_ASSERT(site_info->select_route(expansion.first_wire, uncongestion_route, expansion.net_for_wire, + &newly_consumed_wires)); + completed_wires.push_back(expansion.first_wire); + } + } + + // Remove trival solutions from unsolved routing. + for (WireId wire : completed_wires) { + NPNR_ASSERT(wire_to_expansion.erase(wire) == 1); + } + + // All expansions have been selected for! + if (wire_to_expansion.empty()) { + break; + } + + // At least 1 trival solution was selected, re-prune. + if (!newly_consumed_wires.empty()) { + // Prune remaining solutions. + continue; + } + + // FIXME: Actually de-congest non-trival site routing. + // + // The simplistic solution (only select when 1 solution is available) + // will likely solve initial problems. Once that is show to be wrong, + // come back with something more general. + NPNR_ASSERT(false); + + } while (!wire_to_expansion.empty()); + + return true; +} + +bool Arch::SiteRouter::checkSiteRouting(const Context *ctx, const Arch::TileStatus &tile_status) const +{ + if (!dirty) { + return site_ok; + } + + dirty = false; + + if (cells_in_site.size() == 0) { + site_ok = true; + return site_ok; + } + + site_ok = false; + + // Make sure all cells in this site belong! + auto iter = cells_in_site.begin(); + NPNR_ASSERT((*iter)->bel != BelId()); + auto tile = (*iter)->bel.tile; + + if (verbose_site_router(ctx)) { + log_info("Checking site routing for site %s\n", + ctx->chip_info->sites[ctx->chip_info->tiles[tile].sites[site]].name.get()); + } + + for (CellInfo *cell : cells_in_site) { + // All cells in the site must be placed. + NPNR_ASSERT(cell->bel != BelId()); + + // Sanity check that all cells in this site are part of the same site. + NPNR_ASSERT(tile == cell->bel.tile); + NPNR_ASSERT(site == bel_info(ctx->chip_info, cell->bel).site); + + // As a first pass make sure each assigned cell in site is valid by + // constraints. + if (!ctx->is_cell_valid_constraints(cell, tile_status, verbose_site_router(ctx))) { + if (verbose_site_router(ctx)) { + log_info("Sanity check failed, cell_type %s at %s has an invalid constraints, so site is not good\n", + cell->type.c_str(ctx), ctx->nameOfBel(cell->bel)); + } + site_ok = false; + return site_ok; + } + } + // + // FIXME: Populate "consumed_wires" with all VCC/GND tied in the site. + // This will allow route_site to leverage site local constant sources. + // + // FIXME: Handle case where a constant is requested, but use of an + // inverter is possible. This is the place to handle "bestConstant" + // (e.g. route VCC's over GND's, etc). + // + // FIXME: Enable some LUT rotation! + // Default cell/bel pin map always uses high pins, which will generate + // conflicts where there are none!!! + + SiteInformation site_info(ctx, cells_in_site); + + // Push from cell pins to the first WireId from each cell pin. + if (!site_info.check_initial_wires()) { + site_ok = false; + return site_ok; + } + + site_ok = route_site(ctx, &site_info); + if (verbose_site_router(ctx)) { + if (site_ok) { + site_info.remove_routed_sources(); + NPNR_ASSERT(site_info.is_fully_routed()); + log_info("Site %s is routable\n", + ctx->chip_info->sites[ctx->chip_info->tiles[tile].sites[site]].name.get()); + } else { + log_info("Site %s is not routable\n", + ctx->chip_info->sites[ctx->chip_info->tiles[tile].sites[site]].name.get()); + } + } + + return site_ok; +} + +NEXTPNR_NAMESPACE_END |