/* * 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 "nextpnr.h" #include "design_utils.h" #include "dynamic_bitarray.h" #include "log.h" #include "site_routing_cache.h" #include "site_arch.h" #include "site_arch.impl.h" #include NEXTPNR_NAMESPACE_BEGIN bool verbose_site_router(const Context *ctx) { return ctx->debug; } bool verbose_site_router(const SiteArch *ctx) { return verbose_site_router(ctx->ctx); } void SiteRouter::bindBel(CellInfo *cell) { auto result = cells_in_site.emplace(cell); NPNR_ASSERT(result.second); dirty = true; } void SiteRouter::unbindBel(CellInfo *cell) { NPNR_ASSERT(cells_in_site.erase(cell) == 1); dirty = true; } bool check_initial_wires(const Context *ctx, SiteInformation *site_info) { // Propagate from BEL pins to first wire, checking for trivial routing // conflicts. dict wires; for (CellInfo *cell : site_info->cells_in_site) { BelId bel = cell->bel; 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) continue; for (IdString bel_pin_name : pin_pair.second) { BelPin bel_pin; bel_pin.bel = bel; bel_pin.pin = bel_pin_name; WireId wire = ctx->getBelPinWire(bel_pin.bel, bel_pin.pin); auto result = wires.emplace(wire, port.net); if (!result.second) { // This wire is already in use, make sure the net bound is // the same net, otherwise there is a trivial net // conflict. const NetInfo *other_net = result.first->second; if (other_net != port.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.net->name.c_str(ctx), other_net->name.c_str(ctx)); } return false; } } } } } return true; } static bool is_invalid_site_port(const SiteArch *ctx, const SiteNetInfo *net, const SitePip &pip) { // Blocked ports auto fnd_rsv = ctx->blocked_site_ports.find(pip.pip); if (fnd_rsv != ctx->blocked_site_ports.end() && !fnd_rsv->second.count(net->net)) return true; // Synthetic pips SyntheticType type = ctx->pip_synthetic_type(pip); PhysicalNetlist::PhysNetlist::NetType net_type = ctx->ctx->get_net_type(net->net); bool is_invalid = false; if (type == SYNTH_GND) { is_invalid = net_type != PhysicalNetlist::PhysNetlist::NetType::GND; } else if (type == SYNTH_VCC) { is_invalid = net_type != PhysicalNetlist::PhysNetlist::NetType::VCC; } return is_invalid; } struct SiteExpansionLoop { RouteNodeStorage *const node_storage; SiteExpansionLoop(RouteNodeStorage *node_storage) : node_storage(node_storage) { NPNR_ASSERT(node_storage != nullptr); } void clear() { node_storage->free_nodes(used_nodes); used_nodes.clear(); solution.clear(); net_driver = SiteWire(); } virtual ~SiteExpansionLoop() { node_storage->free_nodes(used_nodes); } // Storage for nodes std::vector used_nodes; bool expand_result; SiteWire net_driver; pool net_users; SiteRoutingSolution solution; Node new_node(const SiteWire &wire, const SitePip &pip, const Node *parent) { Node node = node_storage->alloc_node(); used_nodes.push_back(node.get_index()); node->wire = wire; node->pip = pip; if (parent != nullptr) { node->parent = (*parent).get_index(); node->flags = (*parent)->flags; node->depth = (*parent)->depth + 1; } if (pip.type == SitePip::SITE_PORT) { // Site ports should always have a parent! NPNR_ASSERT(parent != nullptr); if (wire.type == SiteWire::SITE_PORT_SINK) { NPNR_ASSERT((*parent)->wire.type == SiteWire::SITE_WIRE); NPNR_ASSERT(node->can_leave_site()); node->mark_left_site(); node->mark_left_site_after_entering(); } else if (wire.type == SiteWire::SITE_PORT_SOURCE) { // This is a backward walk, so this is considered entering // the site. NPNR_ASSERT((*parent)->wire.type == SiteWire::SITE_WIRE); NPNR_ASSERT(node->can_enter_site()); node->mark_entered_site(); } else { // See if this is a forward or backward walk. NPNR_ASSERT(wire.type == SiteWire::SITE_WIRE); if ((*parent)->wire.type == SiteWire::SITE_PORT_SINK) { // This is a backward walk, so this is considered leaving // the site. NPNR_ASSERT(node->can_leave_site()); node->mark_left_site(); node->mark_left_site_after_entering(); } else { NPNR_ASSERT((*parent)->wire.type == SiteWire::SITE_PORT_SOURCE); NPNR_ASSERT(node->can_enter_site()); node->mark_entered_site(); } } } return node; } // Expand from wire specified, always downhill. bool expand_net(const SiteArch *ctx, SiteRoutingCache *site_routing_cache, const SiteNetInfo *net, bool cache_disabled = false) { if (net->driver == net_driver && net->users == net_users) { return expand_result; } clear(); net_driver = net->driver; net_users = net->users; if (!cache_disabled && site_routing_cache->get_solution(ctx, *net, &solution)) { expand_result = true; return expand_result; } if (verbose_site_router(ctx)) { log_info("Expanding net %s from %s\n", ctx->nameOfNet(net), ctx->nameOfWire(net->driver)); } auto node = new_node(net->driver, SitePip(), /*parent=*/nullptr); pool targets; targets.insert(net->users.begin(), net->users.end()); if (verbose_site_router(ctx)) { log_info("%zu targets:\n", targets.size()); for (auto &target : targets) { log_info(" - %s\n", ctx->nameOfWire(target)); } } int32_t max_depth = 0; int32_t max_depth_seen = 0; std::vector nodes_to_expand; nodes_to_expand.push_back(node); std::vector completed_routes; while (!nodes_to_expand.empty()) { Node parent_node = nodes_to_expand.back(); nodes_to_expand.pop_back(); max_depth_seen = std::max(max_depth_seen, parent_node->depth); for (SitePip pip : ctx->getPipsDownhill(parent_node->wire)) { if (is_invalid_site_port(ctx, net, pip)) { if (verbose_site_router(ctx)) { log_info("Pip %s is not a valid site port for net %s, skipping\n", ctx->nameOfPip(pip), ctx->nameOfNet(net)); } continue; } SiteWire wire = ctx->getPipDstWire(pip); if (pip.type == SitePip::SITE_PORT) { if (wire.type == SiteWire::SITE_PORT_SINK) { if (!parent_node->can_leave_site()) { // This path has already left the site once, don't leave it again! if (verbose_site_router(ctx)) { log_info("%s is not a valid PIP for this path because it has already left the site\n", ctx->nameOfPip(pip)); } continue; } } else { NPNR_ASSERT(parent_node->wire.type == SiteWire::SITE_PORT_SOURCE); if (!parent_node->can_enter_site()) { // This path has already entered the site once, // don't enter it again! if (verbose_site_router(ctx)) { log_info( "%s is not a valid PIP for this path because it has already entered the site\n", ctx->nameOfPip(pip)); } continue; } } } auto wire_iter = ctx->wire_to_nets.find(wire); if (wire_iter != ctx->wire_to_nets.end() && wire_iter->second.net != net) { if (verbose_site_router(ctx)) { log_info("Wire %s is already tied to net %s, not exploring for net %s\n", ctx->nameOfWire(wire), ctx->nameOfNet(wire_iter->second.net), ctx->nameOfNet(net)); } continue; } auto node = new_node(wire, pip, &parent_node); if (!node->is_valid_node()) { if (verbose_site_router(ctx)) { log_info( "%s is not a valid PIP for this path because it has left the site after entering it.\n", ctx->nameOfPip(pip)); } continue; } if (targets.count(wire)) { completed_routes.push_back(node.get_index()); max_depth = std::max(max_depth, node->depth); } nodes_to_expand.push_back(node); } } // Make sure expansion reached all targets, otherwise this site is // already unroutable! solution.clear(); solution.store_solution(ctx, node_storage, net->driver, completed_routes); bool verify = solution.verify(ctx, *net); if (!verify) return false; for (size_t route : completed_routes) { SiteWire wire = node_storage->get_node(route)->wire; targets.erase(wire); } if (!cache_disabled && targets.empty()) { site_routing_cache->add_solutions(ctx, *net, solution); } // Return nodes back to the storage system. node_storage->free_nodes(used_nodes); used_nodes.clear(); expand_result = targets.empty(); return expand_result; } size_t num_solutions() const { return solution.num_solutions(); } const SiteWire &solution_sink(size_t idx) const { return solution.solution_sink(idx); } std::vector::const_iterator solution_begin(size_t idx) const { return solution.solution_begin(idx); } std::vector::const_iterator solution_end(size_t idx) const { return solution.solution_end(idx); } bool solution_inverted(size_t idx) const { return solution.solution_inverted(idx); } bool solution_can_invert(size_t idx) const { return solution.solution_can_invert(idx); } }; void print_current_state(const SiteArch *site_arch) { const Context *ctx = site_arch->ctx; auto &cells_in_site = site_arch->site_info->cells_in_site; const CellInfo *cell = *cells_in_site.begin(); BelId bel = cell->bel; const auto &bel_data = bel_info(ctx->chip_info, bel); const auto &site_inst = site_inst_info(ctx->chip_info, bel.tile, bel_data.site); log_info("Site %s\n", site_inst.name.get()); log_info(" Cells in site:\n"); for (CellInfo *cell : cells_in_site) { log_info(" - %s (%s) => %s\n", cell->name.c_str(ctx), cell->type.c_str(ctx), ctx->nameOfBel(cell->bel)); } log_info(" Nets in site:\n"); for (auto &net_pair : site_arch->nets) { auto *net = net_pair.first; log_info(" - %s, pins in site:\n", net->name.c_str(ctx)); if (net->driver.cell && cells_in_site.count(net->driver.cell)) { log_info(" - %s/%s (%s)\n", net->driver.cell->name.c_str(ctx), net->driver.port.c_str(ctx), net->driver.cell->type.c_str(ctx)); } for (const auto user : net->users) { if (user.cell && cells_in_site.count(user.cell)) { log_info(" - %s/%s (%s)\n", user.cell->name.c_str(ctx), user.port.c_str(ctx), user.cell->type.c_str(ctx)); } } } log_info(" Consumed wires:\n"); for (auto &wire_pair : site_arch->wire_to_nets) { const SiteWire &site_wire = wire_pair.first; if (site_wire.type != SiteWire::SITE_WIRE) { continue; } WireId wire = site_wire.wire; const NetInfo *net = wire_pair.second.net->net; log_info(" - %s is bound to %s\n", ctx->nameOfWire(wire), net->name.c_str(ctx)); } } struct PossibleSolutions { bool tested = false; SiteNetInfo *net = nullptr; std::vector::const_iterator pips_begin; std::vector::const_iterator pips_end; bool inverted = false; bool can_invert = false; PhysicalNetlist::PhysNetlist::NetType prefered_constant_net_type = PhysicalNetlist::PhysNetlist::NetType::SIGNAL; }; bool test_solution(SiteArch *ctx, SiteNetInfo *net, std::vector::const_iterator pips_begin, std::vector::const_iterator pips_end) { bool valid = true; std::vector::const_iterator good_pip_end = pips_begin; std::vector::const_iterator iter = pips_begin; SitePip pip; while (iter != pips_end) { pip = *iter; if (!ctx->bindPip(pip, net)) { valid = false; break; } ++iter; good_pip_end = iter; } // Unwind a bad solution if (!valid) { for (auto iter = pips_begin; iter != good_pip_end; ++iter) { ctx->unbindPip(*iter); } } else { NPNR_ASSERT(net->driver == ctx->getPipSrcWire(pip)); } return valid; } void remove_solution(SiteArch *ctx, std::vector::const_iterator pips_begin, std::vector::const_iterator pips_end) { for (auto iter = pips_begin; iter != pips_end; ++iter) { ctx->unbindPip(*iter); } } struct SolutionPreference { const SiteArch *ctx; const std::vector &solutions; SolutionPreference(const SiteArch *ctx, const std::vector &solutions) : ctx(ctx), solutions(solutions) { } bool non_inverting_preference(const PossibleSolutions &lhs, const PossibleSolutions &rhs) const { // If the LHS is non-inverting and the RHS is inverting, then put the // LHS first. if (!lhs.inverted && rhs.inverted) { return true; } // Better to have a path that can invert over a path that has no // option to invert. return (!lhs.can_invert) < (!rhs.can_invert); } bool inverting_preference(const PossibleSolutions &lhs, const PossibleSolutions &rhs) const { // If the LHS is inverting and the RHS is non-inverting, then put the // LHS first (because this is the inverting preferred case). if (lhs.inverted && !rhs.inverted) { return true; } // Better to have a path that can invert over a path that has no // option to invert. return (!lhs.can_invert) < (!rhs.can_invert); } bool operator()(size_t lhs_solution_idx, size_t rhs_solution_idx) const { const PossibleSolutions &lhs = solutions.at(lhs_solution_idx); const PossibleSolutions &rhs = solutions.at(rhs_solution_idx); NPNR_ASSERT(lhs.net == rhs.net); PhysicalNetlist::PhysNetlist::NetType net_type = ctx->ctx->get_net_type(lhs.net->net); if (net_type == PhysicalNetlist::PhysNetlist::NetType::SIGNAL) { return non_inverting_preference(lhs, rhs); } // All GND/VCC nets use out of site sources. Local constant sources // are still connected via synthetic edges to the global GND/VCC // network. NPNR_ASSERT(lhs.net->driver.type == SiteWire::OUT_OF_SITE_SOURCE); bool lhs_match_preference = net_type == lhs.prefered_constant_net_type; bool rhs_match_preference = net_type == rhs.prefered_constant_net_type; if (lhs_match_preference && !rhs_match_preference) { // Prefer solutions where the net type already matches the // prefered constant type. return true; } if (!lhs_match_preference && rhs_match_preference) { // Prefer solutions where the net type already matches the // prefered constant type. In this case the RHS is better, which // means that RHS < LHS, hence false here. return false; } NPNR_ASSERT(lhs_match_preference == rhs_match_preference); if (!lhs_match_preference) { // If the net type does not match the preference, then prefer // inverted solutions. return inverting_preference(lhs, rhs); } else { // If the net type does match the preference, then prefer // non-inverted solutions. return non_inverting_preference(lhs, rhs); } } }; static bool find_solution_via_backtrack(SiteArch *ctx, std::vector *solutions, std::vector> sinks_to_solutions, const std::vector &sinks, bool explain) { std::vector routed_sinks; std::vector solution_indicies; routed_sinks.resize(sinks_to_solutions.size(), 0); solution_indicies.resize(sinks_to_solutions.size(), 0); // Scan solutions, and remove any solutions that are invalid immediately // // Note: This result cannot be cached because some solutions may be // placement dependent. for (size_t solution_idx = 0; solution_idx < solutions->size(); ++solution_idx) { PossibleSolutions &solution = (*solutions)[solution_idx]; if (verbose_site_router(ctx) || explain) { log_info("Testing solution %zu\n", solution_idx); } if (test_solution(ctx, solution.net, solution.pips_begin, solution.pips_end)) { if (verbose_site_router(ctx) || explain) { log_info("Solution %zu is good\n", solution_idx); } remove_solution(ctx, solution.pips_begin, solution.pips_end); } else { if (verbose_site_router(ctx) || explain) { log_info("Solution %zu is not useable\n", solution_idx); } solution.tested = true; } } // Sort sinks_to_solutions so that preferred solutions are tested earlier // than less preferred solutions. // // See SolutionPreference implementation for details. for (size_t sink_idx = 0; sink_idx < sinks_to_solutions.size(); ++sink_idx) { std::vector &solutions_for_sink = sinks_to_solutions.at(sink_idx); std::stable_sort(solutions_for_sink.begin(), solutions_for_sink.end(), SolutionPreference(ctx, *solutions)); if (verbose_site_router(ctx) || explain) { log_info("Solutions for sink %s (%zu)\n", ctx->nameOfWire(sinks.at(sink_idx)), sink_idx); for (size_t solution_idx : solutions_for_sink) { const PossibleSolutions &solution = solutions->at(solution_idx); log_info("%zu: inverted = %d, can_invert = %d, tested = %d\n", solution_idx, solution.inverted, solution.can_invert, solution.tested); for (auto iter = solution.pips_begin; iter != solution.pips_end; ++iter) { log_info(" - %s\n", ctx->nameOfPip(*iter)); } } } } // Sort solutions by the number of possible solutions first. This allows // the backtrack to avoid the wide searches first. // First create a tuple vector of (sink_idx, number of valid solutions for sink). std::vector> solution_order; for (size_t sink_idx = 0; sink_idx < sinks_to_solutions.size(); ++sink_idx) { size_t solution_count = 0; for (size_t solution_idx : sinks_to_solutions[sink_idx]) { if (!(*solutions)[solution_idx].tested) { solution_count += 1; } } if (solution_count == 0) { if (verbose_site_router(ctx) || explain) { log_info("Sink %s has no solution in site\n", ctx->nameOfWire(sinks.at(sink_idx))); } return false; } solution_order.emplace_back(sink_idx, solution_count); } // Actually sort so that sinks with fewer valid solutions are tested // earlier. std::sort(solution_order.begin(), solution_order.end(), [](const std::pair &a, const std::pair &b) -> bool { return a.second < b.second; }); std::vector solution_stack; solution_stack.reserve(sinks_to_solutions.size()); if (verbose_site_router(ctx)) { log_info("Solving via backtrack with %zu solutions and %zu sinks\n", solutions->size(), sinks_to_solutions.size()); } // Simple backtrack explorer: // - Apply the next solution at stack index. // - If solution is valid, push solution onto stack, and advance stack // index at solution 0. // - If solution is not valid, pop the stack. // - At this level of the stack, advance to the next solution. If // there are not more solutions at this level, pop again. // - If stack is now empty, mark root solution as tested and invalid. // If root of stack has no more solutions, no solution is possible. while (true) { // Which sink is next to be tested? size_t sink_idx = solution_order[solution_stack.size()].first; size_t next_solution_to_test = solution_indicies[sink_idx]; if (verbose_site_router(ctx) || explain) { log_info("next %zu : %zu (of %zu)\n", sink_idx, next_solution_to_test, sinks_to_solutions[sink_idx].size()); } if (next_solution_to_test >= sinks_to_solutions[sink_idx].size()) { // We have exausted all solutions at this level of the stack! if (solution_stack.empty()) { // Search is done, failed!!! if (verbose_site_router(ctx) || explain) { log_info("No solution found via backtrack with %zu solutions and %zu sinks\n", solutions->size(), sinks_to_solutions.size()); } return false; } else { // This level of the stack is completely tapped out, pop back // to the next level up. size_t sink_idx = solution_order[solution_stack.size() - 1].first; size_t solution_idx = solution_stack.back(); if (verbose_site_router(ctx) || explain) { log_info("pop %zu : %zu\n", sink_idx, solution_idx); } solution_stack.pop_back(); // Remove the now tested bad solution at the previous level of // the stack. auto &solution = solutions->at(solution_idx); remove_solution(ctx, solution.pips_begin, solution.pips_end); // Because we had to pop up the stack, advance the index at // the level below us and start again. solution_indicies[sink_idx] += 1; continue; } } size_t solution_idx = sinks_to_solutions[sink_idx].at(next_solution_to_test); auto &solution = solutions->at(solution_idx); if (solution.tested) { // This solution was already determined to be no good, skip it. if (verbose_site_router(ctx) || explain) { log_info("skip %zu : %zu\n", sink_idx, solution_idx); } solution_indicies[sink_idx] += 1; continue; } if (verbose_site_router(ctx) || explain) { log_info("test %zu : %zu\n", sink_idx, solution_idx); } if (!test_solution(ctx, solution.net, solution.pips_begin, solution.pips_end)) { // This solution was no good, try the next one at this level of // the stack. solution_indicies[sink_idx] += 1; } else { // This solution was good, push onto the stack. if (verbose_site_router(ctx) || explain) { log_info("push %zu : %zu\n", sink_idx, solution_idx); } solution_stack.push_back(solution_idx); if (solution_stack.size() == sinks_to_solutions.size()) { // Found a valid solution, done! if (verbose_site_router(ctx)) { log_info("Solved via backtrack with %zu solutions and %zu sinks\n", solutions->size(), sinks_to_solutions.size()); } return true; } else { // Because we pushing to a new level of stack, restart the // search at this level. sink_idx = solution_order[solution_stack.size()].first; solution_indicies[sink_idx] = 0; } } } // Unreachable!!! NPNR_ASSERT(false); } static bool route_site(SiteArch *ctx, SiteRoutingCache *site_routing_cache, RouteNodeStorage *node_storage, bool explain, bool cache_disabled = false) { // Overview: // - Starting from each site net source, expand the site routing graph // and record all reachable sinks. // - Note: This step is cachable and is being cached. This cache // behaving well is critical to the performance of route_site. // - Convert site expansion solutions into flat solution map. // - Use backtrack algorithm to find conflict free solution to route site. // std::vector expansions; expansions.reserve(ctx->nets.size()); for (auto &net_pair : ctx->nets) { if (net_pair.first->driver.cell == nullptr) continue; SiteNetInfo *net = &net_pair.second; if (net->net->loop == nullptr) { net->net->loop = new SiteExpansionLoop(node_storage); } expansions.push_back(net->net->loop); SiteExpansionLoop *router = expansions.back(); if (!router->expand_net(ctx, site_routing_cache, net, cache_disabled)) { if (verbose_site_router(ctx) || explain) { log_info("Net %s expansion failed to reach all users, site is unroutable!\n", ctx->nameOfNet(net)); } return false; } } // Convert solutions into a flat solution set. // Create a flat sink list and map. std::vector sinks; dict sink_map; size_t number_solutions = 0; for (const auto *expansion : expansions) { number_solutions += expansion->num_solutions(); for (const SiteWire &unrouted_sink : expansion->net_users) { auto result = sink_map.emplace(unrouted_sink, sink_map.size()); NPNR_ASSERT(result.second); sinks.push_back(unrouted_sink); } } if (sink_map.empty()) { // All nets are trivially routed! return true; } std::vector solutions; solutions.reserve(number_solutions); // Create a map from flat sink index to flat solution index. std::vector> sinks_to_solutions; sinks_to_solutions.resize(sink_map.size()); // Actually flatten solutions. for (const auto *expansion : expansions) { for (size_t idx = 0; idx < expansion->num_solutions(); ++idx) { SiteWire wire = expansion->solution_sink(idx); auto begin = expansion->solution_begin(idx); auto end = expansion->solution_end(idx); NPNR_ASSERT(begin != end); size_t sink_idx = sink_map.at(wire); sinks_to_solutions.at(sink_idx).push_back(solutions.size()); solutions.emplace_back(); auto &solution = solutions.back(); solution.net = ctx->wire_to_nets.at(wire).net; solution.pips_begin = begin; solution.pips_end = end; solution.inverted = expansion->solution_inverted(idx); solution.can_invert = expansion->solution_can_invert(idx); for (auto iter = begin; iter != end; ++iter) { const SitePip &site_pip = *iter; NPNR_ASSERT(ctx->getPipDstWire(site_pip) == wire); wire = ctx->getPipSrcWire(site_pip); // If there is a input site port, mark on the solution what the // prefered constant net type is for this site port. if (site_pip.type == SitePip::SITE_PORT && wire.type == SiteWire::SITE_PORT_SOURCE) { solution.prefered_constant_net_type = ctx->prefered_constant_net_type(site_pip); } } NPNR_ASSERT(expansion->net_driver == wire); } } return find_solution_via_backtrack(ctx, &solutions, sinks_to_solutions, sinks, explain); } void check_routing(const SiteArch &site_arch) { for (auto &net_pair : site_arch.nets) { const NetInfo *net = net_pair.first; if (net->driver.cell == nullptr) continue; const SiteNetInfo &net_info = net_pair.second; for (const auto &user : net_info.users) { NPNR_ASSERT(site_arch.wire_to_nets.at(user).net->net == net); SiteWire cursor = user; while (cursor != net_info.driver) { auto iter = net_info.wires.find(cursor); if (iter == net_info.wires.end()) { log_error("Wire %s has no pip, but didn't reach driver wire %s\n", site_arch.nameOfWire(cursor), site_arch.nameOfWire(net_info.driver)); } const SitePip &site_pip = iter->second.pip; cursor = site_arch.getPipSrcWire(site_pip); } NPNR_ASSERT(cursor == net_info.driver); NPNR_ASSERT(site_arch.wire_to_nets.at(cursor).net->net == net); } } } static void apply_simple_routing(Context *ctx, const SiteArch &site_arch, NetInfo *net, const SiteNetInfo *site_net, const SiteWire &user) { SiteWire wire = user; while (wire != site_net->driver) { SitePip site_pip = site_net->wires.at(wire).pip; NPNR_ASSERT(site_arch.getPipDstWire(site_pip) == wire); if (site_pip.type == SitePip::SITE_PIP || site_pip.type == SitePip::SITE_PORT) { NetInfo *bound_net = ctx->getBoundPipNet(site_pip.pip); if (bound_net == nullptr) { ctx->bindPip(site_pip.pip, net, STRENGTH_PLACER); } else { NPNR_ASSERT(bound_net == net); } } wire = site_arch.getPipSrcWire(site_pip); } } static void apply_constant_routing(Context *ctx, const SiteArch &site_arch, NetInfo *net, const SiteNetInfo *site_net) { IdString gnd_net_name(ctx->chip_info->constants->gnd_net_name); NetInfo *gnd_net = ctx->nets.at(gnd_net_name).get(); IdString vcc_net_name(ctx->chip_info->constants->vcc_net_name); NetInfo *vcc_net = ctx->nets.at(vcc_net_name).get(); // This function is designed to operate only on the gnd or vcc net, and // assumes that the GND and VCC nets have been unified. NPNR_ASSERT(net == vcc_net || net == gnd_net); for (auto &user : site_net->users) { bool is_path_inverting = false, path_can_invert = false; SiteWire wire = user; PipId inverting_pip; PhysicalNetlist::PhysNetlist::NetType pref_const = PhysicalNetlist::PhysNetlist::NetType::SIGNAL; while (wire != site_net->driver) { SitePip pip = site_net->wires.at(wire).pip; NPNR_ASSERT(site_arch.getPipDstWire(pip) == wire); if (site_arch.isInverting(pip)) { // FIXME: Should be able to handle the general case of // multiple inverters, but that is harder (and annoying). Also // most sites won't allow for a double inversion, so just // disallow for now. NPNR_ASSERT(!is_path_inverting); is_path_inverting = true; NPNR_ASSERT(pip.type == SitePip::SITE_PIP); inverting_pip = pip.pip; } if (site_arch.canInvert(pip)) { path_can_invert = true; NPNR_ASSERT(pip.type == SitePip::SITE_PIP); inverting_pip = pip.pip; } wire = site_arch.getPipSrcWire(pip); pref_const = site_arch.prefered_constant_net_type(pip); } bool is_pref_const = true; if (pref_const == PhysicalNetlist::PhysNetlist::NetType::VCC && net == gnd_net) is_pref_const = false; else if (pref_const == PhysicalNetlist::PhysNetlist::NetType::GND && net == vcc_net) is_pref_const = false; if (!is_path_inverting && (!path_can_invert || is_pref_const)) { // This routing is boring, use base logic. apply_simple_routing(ctx, site_arch, net, site_net, user); continue; } NPNR_ASSERT(inverting_pip != PipId()); // This net is going to become two nets. // The portion of the net prior to the inverter is going to be bound // to the opposite net. For example, if the original net was gnd_net, // the portion prior to the inverter will not be the vcc_net. // // A new cell will be generated to sink the connection from the // opposite net. NetInfo *net_before_inverter; if (net == gnd_net) { net_before_inverter = vcc_net; } else { NPNR_ASSERT(net == vcc_net); net_before_inverter = gnd_net; } // First find a name for the new cell int count = 0; CellInfo *new_cell = nullptr; while (true) { std::string new_cell_name = stringf("%s_%s.%d", net->name.c_str(ctx), site_arch.nameOfWire(user), count); IdString new_cell_id = ctx->id(new_cell_name); if (ctx->cells.count(new_cell_id)) { count += 1; } else { new_cell = ctx->createCell(new_cell_id, ctx->id("$nextpnr_inv")); break; } } auto &tile_type = loc_info(ctx->chip_info, inverting_pip); auto &pip_data = tile_type.pip_data[inverting_pip.index]; NPNR_ASSERT(pip_data.site != -1); auto &bel_data = tile_type.bel_data[pip_data.bel]; BelId inverting_bel; inverting_bel.tile = inverting_pip.tile; inverting_bel.index = pip_data.bel; IdString in_port(bel_data.ports[pip_data.extra_data]); NPNR_ASSERT(bel_data.types[pip_data.extra_data] == PORT_IN); IdString id_I = ctx->id("I"); new_cell->addInput(id_I); new_cell->cell_bel_pins[id_I].push_back(in_port); new_cell->bel = inverting_bel; new_cell->belStrength = STRENGTH_PLACER; ctx->tileStatus.at(inverting_bel.tile).boundcells[inverting_bel.index] = new_cell; new_cell->connectPort(id_I, net_before_inverter); // The original BEL pin is now routed, but only through the inverter. // Because the cell/net model doesn't allow for multiple source pins // and the fact that the portion of the net after the inverter is // currently routed, all BEL pins on this site wire are going to be // masked from the router. NPNR_ASSERT(user.type == SiteWire::SITE_WIRE); ctx->mask_bel_pins_on_site_wire(net, user.wire); // Bind wires and pips to the two nets. bool after_inverter = true; wire = user; while (wire != site_net->driver) { SitePip site_pip = site_net->wires.at(wire).pip; NPNR_ASSERT(site_arch.getPipDstWire(site_pip) == wire); if (site_arch.isInverting(site_pip) || site_arch.canInvert(site_pip)) { NPNR_ASSERT(after_inverter); after_inverter = false; // Because this wire is just after the inverter, bind it to // the net without the pip, as this is a "source". NPNR_ASSERT(wire.type == SiteWire::SITE_WIRE); ctx->bindWire(wire.wire, net, STRENGTH_PLACER); } else { if (site_pip.type == SitePip::SITE_PIP || site_pip.type == SitePip::SITE_PORT) { if (after_inverter) { ctx->bindPip(site_pip.pip, net, STRENGTH_PLACER); } else { ctx->bindPip(site_pip.pip, net_before_inverter, STRENGTH_PLACER); } } } wire = site_arch.getPipSrcWire(site_pip); } } } static void apply_routing(Context *ctx, const SiteArch &site_arch, pool> &lut_thrus) { IdString gnd_net_name(ctx->chip_info->constants->gnd_net_name); NetInfo *gnd_net = ctx->nets.at(gnd_net_name).get(); IdString vcc_net_name(ctx->chip_info->constants->vcc_net_name); NetInfo *vcc_net = ctx->nets.at(vcc_net_name).get(); for (auto &net_pair : site_arch.nets) { NetInfo *net = net_pair.first; const SiteNetInfo *site_net = &net_pair.second; if (net == gnd_net || net == vcc_net) { apply_constant_routing(ctx, site_arch, net, site_net); } else { // If the driver wire is a site wire, bind it. if (site_net->driver.type == SiteWire::SITE_WIRE) { WireId driver_wire = site_net->driver.wire; if (ctx->getBoundWireNet(driver_wire) != net) { ctx->bindWire(driver_wire, net, STRENGTH_PLACER); } } for (auto &wire_pair : site_net->wires) { const SitePip &site_pip = wire_pair.second.pip; if (site_pip.type != SitePip::SITE_PIP && site_pip.type != SitePip::SITE_PORT) { continue; } auto &pip_data = pip_info(ctx->chip_info, site_pip.pip); BelId bel; bel.tile = site_pip.pip.tile; bel.index = pip_data.bel; const auto &bel_data = bel_info(ctx->chip_info, bel); // Detect and store LUT thrus for allowance check during routing if (bel_data.lut_element != -1) { WireId src_wire = ctx->getPipSrcWire(site_pip.pip); for (BelPin bel_pin : ctx->getWireBelPins(src_wire)) { if (bel_pin.bel != bel) continue; lut_thrus.insert(std::make_pair(bel_pin.pin, bel_pin.bel.index)); break; } } ctx->bindPip(site_pip.pip, net, STRENGTH_PLACER); } } } } static bool map_luts_in_site(const SiteInformation &site_info, pool> *blocked_wires) { const Context *ctx = site_info.ctx; bool enable_cache = !ctx->arch_args.disable_lut_mapping_cache; // Create a site LUT mapping key SiteLutMappingKey key = SiteLutMappingKey::create(site_info); // Get the solution from cache. If not found then compute it SiteLutMappingResult lutMapping; if (!enable_cache || !ctx->site_lut_mapping_cache.get(key, &lutMapping)) { const std::vector &lut_elements = ctx->lut_elements.at(site_info.tile_type); std::vector lut_mappers; lut_mappers.reserve(lut_elements.size()); for (size_t i = 0; i < lut_elements.size(); ++i) { lut_mappers.push_back(LutMapper(lut_elements[i])); } for (CellInfo *cell : site_info.cells_in_site) { if (cell->lut_cell.pins.empty()) { continue; } BelId bel = cell->bel; const auto &bel_data = bel_info(ctx->chip_info, bel); if (bel_data.lut_element != -1) { lut_mappers[bel_data.lut_element].cells.push_back(cell); } } bool res = true; lutMapping.blockedWires.clear(); for (LutMapper lut_mapper : lut_mappers) { if (lut_mapper.cells.empty()) { continue; } pool blocked_luts; if (!lut_mapper.remap_luts(ctx, &lutMapping, &blocked_luts)) { res = false; break; } for (const LutBel *lut_bel : blocked_luts) { lutMapping.blockedWires.emplace(std::make_pair(lut_bel->name, lut_bel->output_pin)); } } lutMapping.isValid = res; // Add the solution to the cache if (enable_cache) { ctx->site_lut_mapping_cache.add(key, lutMapping); } } // Apply the solution if valid if (lutMapping.isValid) { lutMapping.apply(site_info); blocked_wires->clear(); blocked_wires->insert(lutMapping.blockedWires.begin(), lutMapping.blockedWires.end()); } return lutMapping.isValid; } // Block outputs of unavailable LUTs to prevent site router from using them. static void block_lut_outputs(SiteArch *site_arch, const pool> &blocked_wires) { const Context *ctx = site_arch->site_info->ctx; auto &tile_info = ctx->chip_info->tile_types[site_arch->site_info->tile_type]; for (const auto &bel_pin_pair : blocked_wires) { IdString bel_name = bel_pin_pair.first; IdString bel_pin = bel_pin_pair.second; int32_t bel_index = -1; for (int32_t i = 0; i < tile_info.bel_data.ssize(); i++) { if (tile_info.bel_data[i].site == site_arch->site_info->site && tile_info.bel_data[i].name == bel_name.index) { bel_index = i; break; } } NPNR_ASSERT(bel_index != -1); BelId bel; bel.tile = site_arch->site_info->tile; bel.index = bel_index; SiteWire lut_output_wire = site_arch->getBelPinWire(bel, bel_pin); site_arch->bindWire(lut_output_wire, &site_arch->blocking_site_net); } } // Block wires corresponding to dedicated interconnections that are not // exposed to the general interconnect. // These wires cannot be chosen for the first element in a BEL chain, as they // would result in an unroutability error. static void block_cluster_wires(SiteArch *site_arch) { const Context *ctx = site_arch->site_info->ctx; auto &cells_in_site = site_arch->site_info->cells_in_site; for (auto &cell : cells_in_site) { if (cell->cluster == ClusterId()) continue; if (ctx->getClusterRootCell(cell->cluster) != cell) continue; Cluster cluster = ctx->clusters.at(cell->cluster); uint32_t cluster_id = cluster.index; auto &cluster_data = cluster_info(ctx->chip_info, cluster_id); if (cluster_data.chainable_ports.size() == 0) continue; IdString cluster_chain_input(cluster_data.chainable_ports[0].cell_sink); if (cluster_chain_input == IdString()) continue; auto &cell_bel_pins = cell->cell_bel_pins.at(cluster_chain_input); for (auto &bel_pin : cell_bel_pins) { SiteWire bel_pin_wire = site_arch->getBelPinWire(cell->bel, bel_pin); site_arch->bindWire(bel_pin_wire, &site_arch->blocking_site_net); } } } // Reserve site ports that are on dedicated rather than general interconnect static void reserve_site_ports(SiteArch *site_arch) { const Context *ctx = site_arch->site_info->ctx; site_arch->blocked_site_ports.clear(); for (PipId in_pip : site_arch->input_site_ports) { pool dedicated_nets; const int max_iters = 100; std::queue visit_queue; pool already_visited; WireId src = ctx->getPipSrcWire(in_pip); visit_queue.push(src); already_visited.insert(src); int iter = 0; while (!visit_queue.empty() && iter++ < max_iters) { WireId next = visit_queue.front(); visit_queue.pop(); for (auto bp : ctx->getWireBelPins(next)) { // Bel pins could mean dedicated routes CellInfo *bound = ctx->getBoundBelCell(bp.bel); if (bound == nullptr) continue; // Need to find the corresponding cell pin for (auto &port : bound->ports) { if (port.second.net == nullptr) continue; for (auto bel_pin : ctx->getBelPinsForCellPin(bound, port.first)) { if (bel_pin == bp.pin) dedicated_nets.insert(port.second.net); } } } for (auto pip : ctx->getPipsUphill(next)) { WireId next_src = ctx->getPipSrcWire(pip); if (already_visited.count(next_src)) continue; visit_queue.push(next_src); already_visited.insert(next_src); } } if (iter < max_iters) { if (ctx->debug) log_info("Blocking PIP %s\n", ctx->nameOfPip(in_pip)); // If we didn't search up to the iteration limit, assume this node is not reachable from general routing site_arch->blocked_site_ports[in_pip] = dedicated_nets; } } } // Recursively visit downhill PIPs until a SITE_PORT_SINK is reached. // Marks all PIPs for all valid paths. static bool visit_downhill_pips(const SiteArch *site_arch, const SiteWire &site_wire, std::vector &valid_pips) { bool valid_path_exists = false; for (SitePip site_pip : site_arch->getPipsDownhill(site_wire)) { const SiteWire &dst_wire = site_arch->getPipDstWire(site_pip); if (dst_wire.type == SiteWire::SITE_PORT_SINK) { valid_pips.push_back(site_pip.pip); return true; } bool path_ok = visit_downhill_pips(site_arch, dst_wire, valid_pips); valid_path_exists |= path_ok; if (path_ok) { valid_pips.push_back(site_pip.pip); } } return valid_path_exists; } // Checks all downhill PIPs starting from driver wires. // All valid PIPs are stored and returned in a vector. static void check_downhill_pips(Context *ctx, const SiteArch *site_arch, std::vector &valid_pips) { auto &cells_in_site = site_arch->site_info->cells_in_site; for (auto &net_pair : site_arch->nets) { NetInfo *net = net_pair.first; const SiteNetInfo *site_net = &net_pair.second; if (net->driver.cell && cells_in_site.count(net->driver.cell)) { const SiteWire &site_wire = site_net->driver; visit_downhill_pips(site_arch, site_wire, valid_pips); } } } bool SiteRouter::checkSiteRouting(const Context *ctx, const TileStatus &tile_status) const { // Overview: // - Make sure all cells in site satisfy the constraints. // - Ensure that the LUT equation elements in the site are legal // - Check that the site is routable. // Because site routing checks are expensive, cache them. // SiteRouter::bindBel/unbindBel should correctly invalid the cache by // setting dirty=true. if (!dirty) { return site_ok; } dirty = false; // Empty sites are trivially correct. 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->get_site_name(tile, site)); } 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; } } SiteInformation site_info(ctx, tile, site, cells_in_site); pool> blocked_wires; if (!map_luts_in_site(site_info, &blocked_wires)) { site_ok = false; return site_ok; } // Push from cell pins to the first WireId from each cell pin. // // If a site wire conflict occurs here, the site is trivially unroutable. if (!check_initial_wires(ctx, &site_info)) { site_ok = false; return site_ok; } // Construct a model of the site routing that is suitable for routing // algorithms. SiteArch site_arch(&site_info); // SiteArch::archcheck is a good sanity check on SiteArch and the chipdb, // but isn't cheap, so disabled for now. // // site_arch.archcheck(); block_lut_outputs(&site_arch, blocked_wires); block_cluster_wires(&site_arch); // Do a detailed routing check to see if the site has at least 1 valid // routing solution. site_ok = route_site(&site_arch, &ctx->site_routing_cache, &ctx->node_storage, /*explain=*/false); if (verbose_site_router(ctx)) { if (site_ok) { log_info("Site %s is routable\n", ctx->get_site_name(tile, site)); } else { log_info("Site %s is not routable\n", ctx->get_site_name(tile, site)); } } if (site_ok) { check_routing(site_arch); } return site_ok; } void SiteRouter::bindSiteRouting(Context *ctx) { NPNR_ASSERT(!dirty); NPNR_ASSERT(site_ok); // Make sure all cells in this site belong! auto iter = cells_in_site.begin(); NPNR_ASSERT((*iter)->bel != BelId()); auto tile = (*iter)->bel.tile; auto &tile_type = loc_info(ctx->chip_info, (*iter)->bel); // Unbind all bound site wires WireId wire; wire.tile = tile; for (size_t wire_index = 0; wire_index < tile_type.wire_data.size(); ++wire_index) { const TileWireInfoPOD &wire_data = tile_type.wire_data[wire_index]; if (wire_data.site != this->site) { continue; } wire.index = wire_index; NetInfo *net = ctx->getBoundWireNet(wire); if (net == nullptr) { continue; } auto &pip_map = net->wires.at(wire); if (pip_map.strength <= STRENGTH_STRONG) { ctx->unbindWire(wire); } } SiteInformation site_info(ctx, tile, site, cells_in_site); pool> blocked_wires; NPNR_ASSERT(map_luts_in_site(site_info, &blocked_wires)); SiteArch site_arch(&site_info); block_lut_outputs(&site_arch, blocked_wires); block_cluster_wires(&site_arch); reserve_site_ports(&site_arch); NPNR_ASSERT(route_site(&site_arch, &ctx->site_routing_cache, &ctx->node_storage, /*explain=*/false, /*cache_disabled=*/true)); check_routing(site_arch); apply_routing(ctx, site_arch, lut_thrus); check_downhill_pips(ctx, &site_arch, valid_pips); if (verbose_site_router(ctx)) { print_current_state(&site_arch); } } void SiteRouter::explain(const Context *ctx) const { NPNR_ASSERT(!dirty); if (site_ok) { return; } // Make sure all cells in this site belong! auto iter = cells_in_site.begin(); NPNR_ASSERT((*iter)->bel != BelId()); auto tile = (*iter)->bel.tile; SiteInformation site_info(ctx, tile, site, cells_in_site); SiteArch site_arch(&site_info); bool route_status = route_site(&site_arch, &ctx->site_routing_cache, &ctx->node_storage, /*explain=*/true); if (!route_status) { print_current_state(&site_arch); } } ArchNetInfo::~ArchNetInfo() { delete loop; } Arch::~Arch() { for (auto &net_pair : nets) { if (net_pair.second->loop) { net_pair.second->loop->clear(); } } } NEXTPNR_NAMESPACE_END