aboutsummaryrefslogtreecommitdiffstats
path: root/fpga_interchange/lookahead.cc
diff options
context:
space:
mode:
authorKeith Rothman <537074+litghost@users.noreply.github.com>2021-03-22 17:46:00 -0700
committerKeith Rothman <537074+litghost@users.noreply.github.com>2021-03-23 08:16:50 -0700
commit8d1eb0a1950816d4dcaae40fb230acff0d1afeef (patch)
tree4c0fac8969789f144c2296e4a3208565a57597f7 /fpga_interchange/lookahead.cc
parent9ef412c2cc623ef84d8fb866734f3892fc6f127c (diff)
downloadnextpnr-8d1eb0a1950816d4dcaae40fb230acff0d1afeef.tar.gz
nextpnr-8d1eb0a1950816d4dcaae40fb230acff0d1afeef.tar.bz2
nextpnr-8d1eb0a1950816d4dcaae40fb230acff0d1afeef.zip
Initial lookahead for FPGA interchange.
Currently the lookahead is disabled by default because of the time to compute and RAM usage. However it does appear to work reasonably well in testing. Further effort is required to lower RAM usage after initial computation, and explore trade-off for cheaper time to compute. Signed-off-by: Keith Rothman <537074+litghost@users.noreply.github.com>
Diffstat (limited to 'fpga_interchange/lookahead.cc')
-rw-r--r--fpga_interchange/lookahead.cc1548
1 files changed, 1548 insertions, 0 deletions
diff --git a/fpga_interchange/lookahead.cc b/fpga_interchange/lookahead.cc
new file mode 100644
index 00000000..cd05c16f
--- /dev/null
+++ b/fpga_interchange/lookahead.cc
@@ -0,0 +1,1548 @@
+/*
+ * 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 "lookahead.h"
+
+#include <boost/filesystem.hpp>
+#include <boost/safe_numerics/safe_integer.hpp>
+#include <capnp/message.h>
+#include <capnp/serialize.h>
+#include <kj/filesystem.h>
+#include <kj/std/iostream.h>
+#include <queue>
+#include <sstream>
+#include <zlib.h>
+
+#include "context.h"
+#include "flat_wire_map.h"
+#include "log.h"
+#include "sampler.h"
+#include "scope_lock.h"
+
+#if defined(NEXTPNR_USE_TBB)
+#include <tbb/parallel_for_each.h>
+#endif
+
+NEXTPNR_NAMESPACE_BEGIN
+
+static constexpr size_t kNumberSamples = 4;
+static constexpr int32_t kMaxExploreDist = 20;
+
+// Initial only explore with a depth of this.
+static constexpr int32_t kInitialExploreDepth = 30;
+
+struct RoutingNode
+{
+ WireId wire_to_expand;
+ delay_t cost;
+ int32_t depth;
+
+ bool operator<(const RoutingNode &other) const { return cost < other.cost; }
+};
+
+struct PipAndCost
+{
+ PipId upstream_pip;
+ delay_t cost_from_src;
+ int32_t depth;
+};
+
+static void expand_input(const Context *ctx, WireId input_wire, HashTables::HashMap<TypeWireId, delay_t> *input_costs)
+{
+ HashTables::HashSet<WireId> seen;
+ std::priority_queue<RoutingNode> to_expand;
+
+ RoutingNode initial;
+ initial.cost = 0;
+ initial.wire_to_expand = input_wire;
+
+ to_expand.push(initial);
+
+ while (!to_expand.empty()) {
+ RoutingNode node = to_expand.top();
+ to_expand.pop();
+
+ auto result = seen.emplace(node.wire_to_expand);
+ if (!result.second) {
+ // We've already done an expansion at this wire.
+ continue;
+ }
+
+ for (PipId pip : ctx->getPipsUphill(node.wire_to_expand)) {
+ if (ctx->is_pip_synthetic(pip)) {
+ continue;
+ }
+ WireId new_wire = ctx->getPipSrcWire(pip);
+ if (new_wire == WireId()) {
+ continue;
+ }
+
+ RoutingNode next_node;
+ next_node.wire_to_expand = new_wire;
+ next_node.cost = node.cost + ctx->getPipDelay(pip).maxDelay() + ctx->getWireDelay(new_wire).maxDelay();
+
+ if (ctx->is_site_port(pip)) {
+ // Done with expansion, record the path if cheaper.
+ // Only the first path to each wire will be the cheapest.
+
+ // Get local tile wire at pip dest. Using getPipSrcWire may
+ // return a node wire, which is not correct here.
+ TypeWireId route_to;
+ route_to.type = ctx->chip_info->tiles[pip.tile].type;
+ route_to.index = loc_info(ctx->chip_info, pip).pip_data[pip.index].src_index;
+ if (route_to.index >= 0) {
+ auto result = input_costs->emplace(route_to, next_node.cost);
+ if (!result.second && result.first->second > next_node.cost) {
+ result.first->second = next_node.cost;
+ }
+ }
+ } else {
+ to_expand.push(next_node);
+ }
+ }
+ }
+}
+
+static void update_site_to_site_costs(const Context *ctx, WireId first_wire,
+ const HashTables::HashMap<WireId, PipAndCost> &best_path,
+ HashTables::HashMap<TypeWirePair, delay_t> *site_to_site_cost)
+{
+ for (auto &best_pair : best_path) {
+ WireId last_wire = best_pair.first;
+ TypeWirePair pair;
+ pair.dst = TypeWireId(ctx, last_wire);
+
+ PipAndCost pip_and_cost = best_pair.second;
+ delay_t cost_from_src = pip_and_cost.cost_from_src;
+
+ WireId cursor;
+ do {
+ cursor = ctx->getPipSrcWire(pip_and_cost.upstream_pip);
+
+ pair.src = TypeWireId(ctx, cursor);
+
+ delay_t cost = cost_from_src;
+
+ // Only use the delta cost from cursor to last_wire, not the full
+ // cost from first_wire to last_wire.
+ if (cursor != first_wire) {
+ pip_and_cost = best_path.at(cursor);
+ cost -= pip_and_cost.cost_from_src;
+ }
+
+ NPNR_ASSERT(cost >= 0);
+
+ auto cost_result = site_to_site_cost->emplace(pair, cost);
+ if (!cost_result.second) {
+ // Update point to point cost if cheaper.
+ if (cost_result.first->second > cost) {
+ cost_result.first->second = cost;
+ }
+ }
+ } while (cursor != first_wire);
+ }
+}
+
+static void expand_output(const Context *ctx, WireId output_wire, Lookahead::OutputSiteWireCost *output_cost,
+ HashTables::HashMap<TypeWirePair, delay_t> *site_to_site_cost)
+{
+ HashTables::HashSet<WireId> seen;
+ std::priority_queue<RoutingNode> to_expand;
+
+ RoutingNode initial;
+ initial.cost = 0;
+ initial.wire_to_expand = output_wire;
+
+ to_expand.push(initial);
+
+ HashTables::HashMap<WireId, PipAndCost> best_path;
+
+ while (!to_expand.empty()) {
+ RoutingNode node = to_expand.top();
+ to_expand.pop();
+
+ auto result = seen.emplace(node.wire_to_expand);
+ if (!result.second) {
+ // We've already done an expansion at this wire.
+ continue;
+ }
+
+ for (PipId pip : ctx->getPipsDownhill(node.wire_to_expand)) {
+ if (ctx->is_pip_synthetic(pip)) {
+ continue;
+ }
+ WireId new_wire = ctx->getPipDstWire(pip);
+ if (new_wire == WireId()) {
+ continue;
+ }
+
+ RoutingNode next_node;
+ next_node.wire_to_expand = new_wire;
+ next_node.cost = node.cost + ctx->getPipDelay(pip).maxDelay() + ctx->getWireDelay(new_wire).maxDelay();
+
+ if (ctx->is_site_port(pip)) {
+ // Done with expansion, record the path if cheaper.
+
+ // Get local tile wire at pip dest. Using getPipDstWire may
+ // return a node wire, which is not correct here.
+ TypeWireId route_from;
+ route_from.type = ctx->chip_info->tiles[pip.tile].type;
+ route_from.index = loc_info(ctx->chip_info, pip).pip_data[pip.index].dst_index;
+ if (route_from.index != -1 && output_cost != nullptr && next_node.cost < output_cost->cost) {
+ output_cost->cost = next_node.cost;
+ output_cost->cheapest_route_from = route_from;
+ }
+ } else {
+ to_expand.push(next_node);
+
+ auto result = best_path.emplace(new_wire, PipAndCost());
+ PipAndCost &pip_and_cost = result.first->second;
+ if (result.second || pip_and_cost.cost_from_src > next_node.cost) {
+ pip_and_cost.upstream_pip = pip;
+ pip_and_cost.cost_from_src = next_node.cost;
+ }
+ }
+ }
+ }
+
+ update_site_to_site_costs(ctx, output_wire, best_path, site_to_site_cost);
+}
+
+static void expand_input_type(const Context *ctx, DeterministicRNG *rng, const Sampler &tiles_of_type,
+ TypeWireId input_wire, std::vector<Lookahead::InputSiteWireCost> *input_costs)
+{
+ HashTables::HashMap<TypeWireId, delay_t> input_costs_map;
+ for (size_t region = 0; region < tiles_of_type.number_of_regions(); ++region) {
+ size_t tile = tiles_of_type.get_sample_from_region(region, [rng]() -> int32_t { return rng->rng(); });
+
+ NPNR_ASSERT(ctx->chip_info->tiles[tile].type == input_wire.type);
+ WireId wire = canonical_wire(ctx->chip_info, tile, input_wire.index);
+
+ expand_input(ctx, wire, &input_costs_map);
+ }
+
+ input_costs->clear();
+ input_costs->reserve(input_costs_map.size());
+ for (const auto &input_pair : input_costs_map) {
+ input_costs->emplace_back();
+ auto &input_cost = input_costs->back();
+ input_cost.route_to = input_pair.first;
+ input_cost.cost = input_pair.second;
+ }
+}
+
+struct DelayStorage
+{
+ HashTables::HashMap<TypeWirePair, HashTables::HashMap<std::pair<int32_t, int32_t>, delay_t>> storage;
+ int32_t max_explore_depth;
+};
+
+static bool has_multiple_inputs(const Context *ctx, WireId wire)
+{
+ size_t pip_count = 0;
+ for (PipId pip : ctx->getPipsUphill(wire)) {
+ (void)pip;
+ pip_count += 1;
+ }
+
+ return pip_count > 1;
+}
+
+static void update_results(const Context *ctx, const FlatWireMap<PipAndCost> &best_path, WireId src_wire,
+ WireId sink_wire, DelayStorage *storage)
+{
+ TypeWireId src_wire_type(ctx, src_wire);
+
+ int src_tile;
+ if (src_wire.tile == -1) {
+ src_tile = ctx->chip_info->nodes[src_wire.index].tile_wires[0].tile;
+ } else {
+ src_tile = src_wire.tile;
+ }
+
+ int32_t src_x, src_y;
+ ctx->get_tile_x_y(src_tile, &src_x, &src_y);
+
+ TypeWirePair wire_pair;
+ wire_pair.src = src_wire_type;
+
+ // The first couple wires from the site pip are usually boring, don't record
+ // them.
+ bool out_of_infeed = false;
+
+ // Starting from end of result, walk backwards and record the path into
+ // the delay storage.
+ WireId cursor = sink_wire;
+ HashTables::HashSet<WireId> seen;
+ while (cursor != src_wire) {
+ // No loops allowed in routing!
+ auto result = seen.emplace(cursor);
+ NPNR_ASSERT(result.second);
+
+ if (!out_of_infeed && has_multiple_inputs(ctx, cursor)) {
+ out_of_infeed = true;
+ }
+
+ TypeWireId dst_wire_type(ctx, cursor);
+ wire_pair.dst = dst_wire_type;
+
+ int dst_tile;
+ if (cursor.tile == -1) {
+ dst_tile = ctx->chip_info->nodes[cursor.index].tile_wires[0].tile;
+ } else {
+ dst_tile = cursor.tile;
+ }
+ int32_t dst_x;
+ int32_t dst_y;
+ ctx->get_tile_x_y(dst_tile, &dst_x, &dst_y);
+
+ std::pair<int32_t, int32_t> dx_dy;
+ dx_dy.first = dst_x - src_x;
+ dx_dy.second = dst_y - src_y;
+
+ const PipAndCost &pip_and_cost = best_path.at(cursor);
+ if (out_of_infeed) {
+ auto &delta_data = storage->storage[wire_pair];
+ auto result2 = delta_data.emplace(dx_dy, pip_and_cost.cost_from_src);
+ if (!result2.second) {
+ if (result2.first->second > pip_and_cost.cost_from_src) {
+ result2.first->second = pip_and_cost.cost_from_src;
+ }
+ }
+ }
+
+ cursor = ctx->getPipSrcWire(pip_and_cost.upstream_pip);
+ }
+}
+
+static void expand_routing_graph_from_wire(const Context *ctx, WireId first_wire, FlatWireMap<PipAndCost> *best_path,
+ DelayStorage *storage)
+{
+ HashTables::HashSet<WireId> seen;
+ std::priority_queue<RoutingNode> to_expand;
+
+ int src_tile;
+ if (first_wire.tile == -1) {
+ src_tile = ctx->chip_info->nodes[first_wire.index].tile_wires[0].tile;
+ } else {
+ src_tile = first_wire.tile;
+ }
+
+ int32_t src_x, src_y;
+ ctx->get_tile_x_y(src_tile, &src_x, &src_y);
+
+ RoutingNode initial;
+ initial.cost = 0;
+ initial.wire_to_expand = first_wire;
+ initial.depth = 0;
+
+ to_expand.push(initial);
+
+ best_path->clear();
+ size_t skip_count = 0;
+
+ while (!to_expand.empty()) {
+ RoutingNode node = to_expand.top();
+ to_expand.pop();
+
+ auto result = seen.emplace(node.wire_to_expand);
+ if (!result.second) {
+ // We've already done an expansion at this wire.
+ skip_count += 1;
+ continue;
+ }
+
+ bool has_site_pip = false;
+ for (PipId pip : ctx->getPipsDownhill(node.wire_to_expand)) {
+ if (ctx->is_pip_synthetic(pip)) {
+ continue;
+ }
+
+ // Don't expand edges that are site pips, but do record how we
+ // got to the pip before the site pip!
+ if (ctx->is_site_port(pip)) {
+ has_site_pip = true;
+ continue;
+ }
+
+ WireId new_wire = ctx->getPipDstWire(pip);
+ if (new_wire == WireId()) {
+ continue;
+ }
+
+ RoutingNode next_node;
+ next_node.wire_to_expand = new_wire;
+ next_node.cost = node.cost + ctx->getPipDelay(pip).maxDelay() + ctx->getWireDelay(new_wire).maxDelay();
+ next_node.depth = node.depth + 1;
+
+ // Record best path.
+ PipAndCost pip_and_cost;
+ pip_and_cost.upstream_pip = pip;
+ pip_and_cost.cost_from_src = next_node.cost;
+ pip_and_cost.depth = next_node.depth;
+ auto result = best_path->emplace(new_wire, pip_and_cost);
+ bool is_best_path = true;
+ if (!result.second) {
+ if (result.first.second->cost_from_src > next_node.cost) {
+ result.first.second->cost_from_src = next_node.cost;
+ result.first.second->upstream_pip = pip;
+ result.first.second->depth = next_node.depth;
+ } else {
+ is_best_path = false;
+ }
+ }
+
+ Loc dst = ctx->getPipLocation(pip);
+ int32_t dst_x = dst.x;
+ int32_t dst_y = dst.y;
+ if (is_best_path && std::abs(dst_x - src_x) < kMaxExploreDist &&
+ std::abs(dst_y - src_y) < kMaxExploreDist && next_node.depth < storage->max_explore_depth) {
+ to_expand.push(next_node);
+ }
+ }
+
+ if (has_site_pip) {
+ update_results(ctx, *best_path, first_wire, node.wire_to_expand, storage);
+ }
+ }
+}
+
+static bool has_multiple_outputs(const Context *ctx, WireId wire)
+{
+ size_t pip_count = 0;
+ for (PipId pip : ctx->getPipsDownhill(wire)) {
+ (void)pip;
+ pip_count += 1;
+ }
+
+ return pip_count > 1;
+}
+
+static void expand_routing_graph(const Context *ctx, DeterministicRNG *rng, const Sampler &tiles_of_type,
+ TypeWireId wire_type, HashTables::HashSet<TypeWireSet> *types_explored,
+ DelayStorage *storage, HashTables::HashSet<TypeWireId> *types_deferred,
+ FlatWireMap<PipAndCost> *best_path)
+{
+ HashTables::HashSet<TypeWireSet> new_types_explored;
+
+ for (size_t region = 0; region < tiles_of_type.number_of_regions(); ++region) {
+ size_t tile = tiles_of_type.get_sample_from_region(region, [rng]() -> int32_t { return rng->rng(); });
+
+ NPNR_ASSERT(ctx->chip_info->tiles[tile].type == wire_type.type);
+
+ WireId wire = canonical_wire(ctx->chip_info, tile, wire_type.index);
+ TypeWireSet wire_set(ctx, wire);
+
+ if (!has_multiple_outputs(ctx, wire)) {
+ types_deferred->emplace(wire_type);
+ continue;
+ }
+
+ new_types_explored.emplace(wire_set);
+
+ expand_routing_graph_from_wire(ctx, wire, best_path, storage);
+ }
+
+ for (const TypeWireSet &new_wire_set : new_types_explored) {
+ types_explored->emplace(new_wire_set);
+ }
+}
+
+static WireId follow_pip_chain(const Context *ctx, WireId wire, delay_t *delay_out)
+{
+ delay_t delay = 0;
+ WireId cursor = wire;
+ while (true) {
+ WireId next;
+ size_t pip_count = 0;
+ delay_t next_delay = delay;
+ for (PipId pip : ctx->getPipsDownhill(cursor)) {
+ pip_count += 1;
+ next = ctx->getPipDstWire(pip);
+ next_delay += ctx->getPipDelay(pip).maxDelay() + ctx->getWireDelay(next).maxDelay();
+ }
+
+ if (pip_count > 1) {
+ *delay_out = delay;
+ return cursor;
+ }
+
+ if (next == WireId()) {
+ return WireId();
+ }
+
+ delay = next_delay;
+
+ cursor = next;
+ }
+
+ // Unreachable?
+ NPNR_ASSERT(false);
+}
+
+static WireId follow_pip_chain_target(const Context *ctx, WireId wire, WireId target, delay_t *delay_out)
+{
+ delay_t delay = 0;
+ WireId cursor = wire;
+ while (cursor != target) {
+ WireId next;
+ size_t pip_count = 0;
+ delay_t next_delay = delay;
+ for (PipId pip : ctx->getPipsDownhill(cursor)) {
+ pip_count += 1;
+ next = ctx->getPipDstWire(pip);
+ next_delay += ctx->getPipDelay(pip).maxDelay() + ctx->getWireDelay(next).maxDelay();
+ }
+
+ if (pip_count > 1) {
+ *delay_out = delay;
+ return cursor;
+ }
+
+ if (next == WireId()) {
+ return WireId();
+ }
+
+ delay = next_delay;
+
+ cursor = next;
+ }
+
+ *delay_out = delay;
+ return cursor;
+}
+
+static WireId follow_pip_chain_up(const Context *ctx, WireId wire, delay_t *delay_out)
+{
+ delay_t delay = 0;
+ WireId cursor = wire;
+ while (true) {
+ WireId next;
+ size_t pip_count = 0;
+ delay_t next_delay = delay;
+ for (PipId pip : ctx->getPipsUphill(cursor)) {
+ pip_count += 1;
+ next = ctx->getPipSrcWire(pip);
+ next_delay += ctx->getPipDelay(pip).maxDelay() + ctx->getWireDelay(next).maxDelay();
+ }
+
+ if (pip_count > 1) {
+ *delay_out = delay;
+ return cursor;
+ }
+
+ if (next == WireId()) {
+ return WireId();
+ }
+
+ delay = next_delay;
+
+ cursor = next;
+ }
+
+ // Unreachable?
+ NPNR_ASSERT(false);
+}
+
+static void expand_deferred_routing_graph(const Context *ctx, DeterministicRNG *rng, const Sampler &tiles_of_type,
+ TypeWireId wire_type, HashTables::HashSet<TypeWireSet> *types_explored,
+ DelayStorage *storage, FlatWireMap<PipAndCost> *best_path)
+{
+ HashTables::HashSet<TypeWireSet> new_types_explored;
+
+ for (size_t region = 0; region < tiles_of_type.number_of_regions(); ++region) {
+ size_t tile = tiles_of_type.get_sample_from_region(region, [rng]() -> int32_t { return rng->rng(); });
+
+ NPNR_ASSERT(ctx->chip_info->tiles[tile].type == wire_type.type);
+
+ WireId wire = canonical_wire(ctx->chip_info, tile, wire_type.index);
+ TypeWireSet wire_set(ctx, wire);
+ if (types_explored->count(wire_set)) {
+ // Check if this wire set has been expanded.
+ continue;
+ }
+
+ delay_t delay;
+ WireId routing_wire = follow_pip_chain(ctx, wire, &delay);
+
+ // This wire doesn't go anywhere!
+ if (routing_wire == WireId()) {
+ continue;
+ }
+
+ TypeWireSet routing_wire_set(ctx, routing_wire);
+ if (types_explored->count(routing_wire_set)) {
+ continue;
+ }
+
+ new_types_explored.emplace(wire_set);
+ expand_routing_graph_from_wire(ctx, wire, best_path, storage);
+ }
+
+ for (const TypeWireSet &new_wire_set : new_types_explored) {
+ types_explored->emplace(new_wire_set);
+ }
+}
+
+static void expand_output_type(const Context *ctx, DeterministicRNG *rng, const Sampler &tiles_of_type,
+ TypeWireId output_wire, Lookahead::OutputSiteWireCost *output_cost,
+ HashTables::HashMap<TypeWirePair, delay_t> *site_to_site_cost)
+{
+ for (size_t region = 0; region < tiles_of_type.number_of_regions(); ++region) {
+ size_t tile = tiles_of_type.get_sample_from_region(region, [rng]() -> int32_t { return rng->rng(); });
+
+ NPNR_ASSERT(ctx->chip_info->tiles[tile].type == output_wire.type);
+ WireId wire = canonical_wire(ctx->chip_info, tile, output_wire.index);
+
+ expand_output(ctx, wire, output_cost, site_to_site_cost);
+ }
+}
+
+static constexpr bool kWriteLookaheadCsv = false;
+
+void write_lookahead_csv(const Context *ctx, const DelayStorage &all_tiles_storage)
+{
+ FILE *lookahead_data = fopen("lookahead.csv", "w");
+ NPNR_ASSERT(lookahead_data != nullptr);
+ fprintf(lookahead_data, "src_type,src_wire,dest_type,dest_wire,delta_x,delta_y,delay\n");
+ for (const auto &type_pair : all_tiles_storage.storage) {
+ auto &src_wire_type = type_pair.first.src;
+ auto &src_type_data = ctx->chip_info->tile_types[src_wire_type.type];
+ IdString src_type(src_type_data.name);
+ IdString src_wire(src_type_data.wire_data[src_wire_type.index].name);
+
+ auto &dst_wire_type = type_pair.first.dst;
+ auto &dst_type_data = ctx->chip_info->tile_types[dst_wire_type.type];
+ IdString dst_type(dst_type_data.name);
+ IdString dst_wire(dst_type_data.wire_data[dst_wire_type.index].name);
+
+ for (const auto &delta_pair : type_pair.second) {
+ fprintf(lookahead_data, "%s,%s,%s,%s,%d,%d,%d\n", src_type.c_str(ctx), src_wire.c_str(ctx),
+ dst_type.c_str(ctx), dst_wire.c_str(ctx), delta_pair.first.first, delta_pair.first.second,
+ delta_pair.second);
+ }
+ }
+
+ fclose(lookahead_data);
+}
+
+// Storage for tile type expansion for lookahead.
+struct ExpandLocals
+{
+ virtual ~ExpandLocals() {}
+ const std::vector<Sampler> *tiles_of_type;
+ DeterministicRNG *rng;
+ FlatWireMap<PipAndCost> *best_path;
+ DelayStorage *storage;
+ HashTables::HashSet<TypeWireSet> *explored;
+ HashTables::HashSet<TypeWireId> *deferred;
+
+ virtual void lock() {}
+ virtual void unlock() {}
+ virtual void copy_back(int32_t tile_type) {}
+};
+
+// Do tile type expansion for 1 tile.
+static void expand_tile_type(const Context *ctx, int32_t tile_type, ExpandLocals *locals)
+{
+ auto &type_data = ctx->chip_info->tile_types[tile_type];
+ if (ctx->verbose) {
+ ScopeLock<ExpandLocals> lock(locals);
+ log_info("Expanding all wires in type %s\n", IdString(type_data.name).c_str(ctx));
+ }
+
+ auto &tile_sampler = (*locals->tiles_of_type)[tile_type];
+ for (size_t wire_index = 0; wire_index < type_data.wire_data.size(); ++wire_index) {
+ auto &wire_data = type_data.wire_data[wire_index];
+ if (wire_data.site != -1) {
+ // Skip site wires
+ continue;
+ }
+
+ if (ctx->debug) {
+ ScopeLock<ExpandLocals> lock(locals);
+ log_info("Expanding wire %s in type %s (%d/%zu, seen %zu types, deferred %zu types)\n",
+ IdString(wire_data.name).c_str(ctx), IdString(type_data.name).c_str(ctx), tile_type,
+ ctx->chip_info->tile_types.size(), locals->explored->size(), locals->deferred->size());
+ }
+
+ TypeWireId wire;
+ wire.type = tile_type;
+ wire.index = wire_index;
+
+ expand_routing_graph(ctx, locals->rng, tile_sampler, wire, locals->explored, locals->storage, locals->deferred,
+ locals->best_path);
+ }
+
+ locals->copy_back(tile_type);
+}
+
+// Function that does all tile expansions serially.
+static void expand_tile_type_serial(const Context *ctx, const std::vector<int32_t> &tile_types,
+ const std::vector<Sampler> &tiles_of_type, DeterministicRNG *rng,
+ FlatWireMap<PipAndCost> *best_path, DelayStorage *storage,
+ HashTables::HashSet<TypeWireSet> *explored,
+ HashTables::HashSet<TypeWireId> *deferred, HashTables::HashSet<int32_t> *tiles_left)
+{
+
+ for (int32_t tile_type : tile_types) {
+ ExpandLocals locals;
+
+ locals.tiles_of_type = &tiles_of_type;
+ locals.rng = rng;
+ locals.best_path = best_path;
+ locals.storage = storage;
+ locals.explored = explored;
+ expand_tile_type(ctx, tile_type, &locals);
+
+ NPNR_ASSERT(tiles_left->erase(tile_type) == 1);
+ }
+
+ NPNR_ASSERT(tiles_left->empty());
+}
+
+// Additional storage for doing tile type expansion in parallel.
+struct TbbExpandLocals : public ExpandLocals
+{
+ const Context *ctx;
+ std::mutex *all_costs_mutex;
+
+ DelayStorage *all_tiles_storage;
+ HashTables::HashSet<TypeWireSet> *types_explored;
+ HashTables::HashSet<TypeWireId> *types_deferred;
+ HashTables::HashSet<int32_t> *tiles_left;
+
+ void lock() override { all_costs_mutex->lock(); }
+
+ void unlock() override { all_costs_mutex->unlock(); }
+
+ void copy_back(int32_t tile_type) override
+ {
+ ScopeLock<TbbExpandLocals> locker(this);
+
+ auto &type_data = ctx->chip_info->tile_types[tile_type];
+
+ // Copy per tile data by to over all data structures.
+ if (ctx->verbose) {
+ log_info("Expanded all wires in type %s, merging data back\n", IdString(type_data.name).c_str(ctx));
+ log_info("Testing %zu wires, saw %zu types, deferred %zu types\n", type_data.wire_data.size(),
+ explored->size(), deferred->size());
+ }
+
+ // Copy cheapest explored paths back to all_tiles_storage.
+ for (const auto &type_pair : storage->storage) {
+ auto &type_pair_data = all_tiles_storage->storage[type_pair.first];
+ for (const auto &delta_pair : type_pair.second) {
+ // See if this dx/dy already has data.
+ auto result = type_pair_data.emplace(delta_pair.first, delta_pair.second);
+ if (!result.second) {
+ // This was already in the map, check if this new result is
+ // better
+ if (delta_pair.second < result.first->second) {
+ result.first->second = delta_pair.second;
+ }
+ }
+ }
+ }
+
+ // Update explored and deferred sets.
+ for (auto &key : *explored) {
+ types_explored->emplace(key);
+ }
+ for (auto &key : *deferred) {
+ types_deferred->emplace(key);
+ }
+
+ NPNR_ASSERT(tiles_left->erase(tile_type));
+
+ if (ctx->verbose) {
+ log_info("Done merging data from type %s, %zu tiles left\n", IdString(type_data.name).c_str(ctx),
+ tiles_left->size());
+ }
+ }
+};
+
+// Wrapper method used if running expansion in parallel.
+//
+// expand_tile_type is invoked using thread local data, and then afterwards
+// the data is joined with the global data.
+static void expand_tile_type_parallel(const Context *ctx, int32_t tile_type, const std::vector<Sampler> &tiles_of_type,
+ DeterministicRNG *rng, std::mutex *all_costs_mutex,
+ DelayStorage *all_tiles_storage, HashTables::HashSet<TypeWireSet> *types_explored,
+ HashTables::HashSet<TypeWireId> *types_deferred,
+ HashTables::HashSet<int32_t> *tiles_left)
+{
+ TbbExpandLocals locals;
+
+ DeterministicRNG rng_copy = *rng;
+ FlatWireMap<PipAndCost> best_path(ctx);
+ HashTables::HashSet<TypeWireSet> explored;
+ HashTables::HashSet<TypeWireId> deferred;
+ DelayStorage storage;
+ storage.max_explore_depth = all_tiles_storage->max_explore_depth;
+
+ locals.tiles_of_type = &tiles_of_type;
+ locals.rng = &rng_copy;
+ locals.best_path = &best_path;
+ locals.storage = &storage;
+ locals.explored = &explored;
+ locals.deferred = &deferred;
+
+ locals.ctx = ctx;
+ locals.all_costs_mutex = all_costs_mutex;
+ locals.all_tiles_storage = all_tiles_storage;
+ locals.types_explored = types_explored;
+ locals.types_deferred = types_deferred;
+ locals.tiles_left = tiles_left;
+
+ expand_tile_type(ctx, tile_type, &locals);
+}
+
+void Lookahead::build_lookahead(const Context *ctx, DeterministicRNG *rng)
+{
+ auto start = std::chrono::high_resolution_clock::now();
+
+ if (ctx->verbose) {
+ log_info("Building lookahead, first gathering input and output site wires\n");
+ }
+
+ HashTables::HashSet<TypeWireId> input_site_ports;
+ for (BelId bel : ctx->getBels()) {
+ const auto &bel_data = bel_info(ctx->chip_info, bel);
+
+ for (IdString pin : ctx->getBelPins(bel)) {
+ WireId pin_wire = ctx->getBelPinWire(bel, pin);
+ if (pin_wire == WireId()) {
+ continue;
+ }
+
+ PortType type = ctx->getBelPinType(bel, pin);
+
+ if (type == PORT_IN && bel_data.category == BEL_CATEGORY_LOGIC) {
+ input_site_wires.emplace(TypeWireId(ctx, pin_wire), std::vector<InputSiteWireCost>());
+ } else if (type == PORT_OUT && bel_data.category == BEL_CATEGORY_LOGIC) {
+ output_site_wires.emplace(TypeWireId(ctx, pin_wire), OutputSiteWireCost());
+ } else if (type == PORT_OUT && bel_data.category == BEL_CATEGORY_SITE_PORT) {
+ input_site_ports.emplace(TypeWireId(ctx, pin_wire));
+ }
+ }
+ }
+
+ if (ctx->verbose) {
+ log_info("Have %zu input and %zu output site wire types. Creating tile type samplers\n",
+ input_site_wires.size(), output_site_wires.size());
+ }
+
+ std::vector<Sampler> tiles_of_type;
+ tiles_of_type.resize(ctx->chip_info->tile_types.ssize());
+
+ std::vector<size_t> indicies;
+ std::vector<std::pair<int32_t, int32_t>> xys;
+ indicies.reserve(ctx->chip_info->tiles.size());
+ xys.reserve(ctx->chip_info->tiles.size());
+
+ for (int32_t tile_type = 0; tile_type < ctx->chip_info->tile_types.ssize(); ++tile_type) {
+ indicies.clear();
+ xys.clear();
+
+ for (size_t tile = 0; tile < ctx->chip_info->tiles.size(); ++tile) {
+ if (ctx->chip_info->tiles[tile].type != tile_type) {
+ continue;
+ }
+
+ std::pair<size_t, size_t> xy;
+ ctx->get_tile_x_y(tile, &xy.first, &xy.second);
+
+ indicies.push_back(tile);
+ xys.push_back(xy);
+ }
+
+ auto &tile_sampler = tiles_of_type[tile_type];
+ tile_sampler.divide_samples(kNumberSamples, xys);
+
+ // Remap Sampler::indicies from 0 to number of tiles of type to
+ // absolute tile indicies.
+ for (size_t i = 0; i < indicies.size(); ++i) {
+ tile_sampler.indicies[i] = indicies[tile_sampler.indicies[i]];
+ }
+ }
+
+ if (ctx->verbose) {
+ log_info("Expanding input site wires\n");
+ }
+
+ // Expand backwards from each input site wire to find the cheapest
+ // non-site wire.
+ for (auto &input_pair : input_site_wires) {
+ expand_input_type(ctx, rng, tiles_of_type[input_pair.first.type], input_pair.first, &input_pair.second);
+ }
+
+ if (ctx->verbose) {
+ log_info("Expanding output site wires\n");
+ }
+
+ // Expand forward from each output site wire to find the cheapest
+ // non-site wire.
+ for (auto &output_pair : output_site_wires) {
+ output_pair.second.cost = std::numeric_limits<delay_t>::max();
+ expand_output_type(ctx, rng, tiles_of_type[output_pair.first.type], output_pair.first, &output_pair.second,
+ &site_to_site_cost);
+ }
+ for (TypeWireId input_site_port : input_site_ports) {
+ expand_output_type(ctx, rng, tiles_of_type[input_site_port.type], input_site_port, nullptr, &site_to_site_cost);
+ }
+
+ if (ctx->verbose) {
+ log_info("Expanding all wire types\n");
+ }
+
+ DelayStorage all_tiles_storage;
+ all_tiles_storage.max_explore_depth = kInitialExploreDepth;
+
+ // These are wire types that have been explored.
+ HashTables::HashSet<TypeWireSet> types_explored;
+
+ // These are wire types that have been deferred because they are trival
+ // copies of another wire type. These can be cheaply computed after the
+ // graph has been explored.
+ HashTables::HashSet<TypeWireId> types_deferred;
+
+ std::vector<int32_t> tile_types;
+ HashTables::HashSet<int32_t> tiles_left;
+ tile_types.reserve(ctx->chip_info->tile_types.size());
+ for (int32_t tile_type = 0; tile_type < ctx->chip_info->tile_types.ssize(); ++tile_type) {
+ tile_types.push_back(tile_type);
+ tiles_left.emplace(tile_type);
+ }
+
+ FlatWireMap<PipAndCost> best_path(ctx);
+
+ // Walk each tile type, and expand all non-site wires in the tile.
+ // Wires that are nodes will expand as if the node type is the first node
+ // in the wire.
+ //
+ // Wires that only have 1 output pip are deferred until the next loop,
+ // because generally those wires will get explored via another wire.
+ // The deferred will be expanded if this assumption doesn't hold.
+ bool expand_serially = true;
+#if defined(NEXTPNR_USE_TBB) // Run parallely
+ {
+ std::mutex all_costs_mutex;
+
+ expand_serially = false;
+ tbb::parallel_for_each(tile_types, [&](int32_t tile_type) {
+ expand_tile_type_parallel(ctx, tile_type, tiles_of_type, rng, &all_costs_mutex, &all_tiles_storage,
+ &types_explored, &types_deferred, &tiles_left);
+ });
+ }
+#else
+ // Supress warning that expand_tile_type_parallel if not running in
+ // parallel.
+ (void)expand_tile_type_parallel;
+#endif
+ if (expand_serially) {
+ expand_tile_type_serial(ctx, tile_types, tiles_of_type, rng, &best_path, &all_tiles_storage, &types_explored,
+ &types_deferred, &tiles_left);
+ }
+
+ // Check to see if deferred wire types were expanded. If they were not
+ // expanded, expand them now. If they were expanded, copy_types is
+ // populated with the wire types that can just copy the relevant data from
+ // another wire type.
+ for (TypeWireId wire_type : types_deferred) {
+ auto &type_data = ctx->chip_info->tile_types[wire_type.type];
+ auto &tile_sampler = tiles_of_type[wire_type.type];
+ auto &wire_data = type_data.wire_data[wire_type.index];
+
+ if (ctx->verbose) {
+ log_info("Expanding deferred wire %s in type %s (seen %zu types)\n", IdString(wire_data.name).c_str(ctx),
+ IdString(type_data.name).c_str(ctx), types_explored.size());
+ }
+
+ expand_deferred_routing_graph(ctx, rng, tile_sampler, wire_type, &types_explored, &all_tiles_storage,
+ &best_path);
+ }
+
+ auto end = std::chrono::high_resolution_clock::now();
+ if (ctx->verbose) {
+ log_info("Done with expansion, dt %02fs\n", std::chrono::duration<float>(end - start).count());
+ }
+
+ if (kWriteLookaheadCsv) {
+ write_lookahead_csv(ctx, all_tiles_storage);
+ end = std::chrono::high_resolution_clock::now();
+ if (ctx->verbose) {
+ log_info("Done writing data to disk, dt %02fs\n", std::chrono::duration<float>(end - start).count());
+ }
+ }
+
+#if defined(NEXTPNR_USE_TBB) // Run parallely
+ tbb::parallel_for_each(
+ all_tiles_storage.storage,
+ [&](const std::pair<TypeWirePair, HashTables::HashMap<std::pair<int32_t, int32_t>, delay_t>> &type_pair) {
+#else
+ for (const auto &type_pair : all_tiles_storage.storage) {
+#endif
+ cost_map.set_cost_map(ctx, type_pair.first, type_pair.second);
+#if defined(NEXTPNR_USE_TBB) // Run parallely
+ });
+#else
+ }
+#endif
+
+ end = std::chrono::high_resolution_clock::now();
+ if (ctx->verbose) {
+ log_info("build_lookahead time %.02fs\n", std::chrono::duration<float>(end - start).count());
+ }
+}
+
+constexpr static bool kUseGzipForLookahead = false;
+
+static void write_message(::capnp::MallocMessageBuilder &message, const std::string &filename)
+{
+ kj::Array<capnp::word> words = messageToFlatArray(message);
+ kj::ArrayPtr<kj::byte> bytes = words.asBytes();
+
+ boost::filesystem::path temp = boost::filesystem::unique_path();
+ log_info("Writing tempfile to %s\n", temp.c_str());
+
+ if (kUseGzipForLookahead) {
+ gzFile file = gzopen(temp.c_str(), "w");
+ NPNR_ASSERT(file != Z_NULL);
+
+ size_t bytes_written = 0;
+ int result;
+ while (bytes_written < bytes.size()) {
+ size_t bytes_remaining = bytes.size() - bytes_written;
+ size_t bytes_to_write = bytes_remaining;
+ if (bytes_to_write >= std::numeric_limits<int>::max()) {
+ bytes_to_write = std::numeric_limits<int>::max();
+ }
+ result = gzwrite(file, &bytes[0] + bytes_written, bytes_to_write);
+ if (result < 0) {
+ break;
+ }
+
+ bytes_written += result;
+ }
+
+ int error;
+ std::string error_str;
+ if (result < 0) {
+ error_str.assign(gzerror(file, &error));
+ }
+ NPNR_ASSERT(gzclose(file) == Z_OK);
+ if (bytes_written != bytes.size()) {
+ // Remove failed writes before reporting error.
+ boost::filesystem::remove(temp);
+ }
+
+ if (result < 0) {
+ log_error("Failed to write lookahead, error from gzip %s\n", error_str.c_str());
+ } else {
+ if (bytes_written != bytes.size()) {
+ log_error("Failed to write lookahead, wrote %d bytes, had %zu bytes\n", result, bytes.size());
+ } else {
+ // Written, move file into place
+ boost::filesystem::rename(temp, filename);
+ }
+ }
+ } else {
+ {
+ kj::Own<kj::Filesystem> fs = kj::newDiskFilesystem();
+
+ auto path = kj::Path::parse(temp);
+ auto file = fs->getCurrent().openFile(path, kj::WriteMode::CREATE);
+ file->writeAll(bytes);
+ }
+
+ boost::filesystem::rename(temp, filename);
+ }
+}
+
+bool Lookahead::read_lookahead(const std::string &chipdb_hash, const std::string &filename)
+{
+ capnp::ReaderOptions reader_options;
+ reader_options.traversalLimitInWords = 32llu * 1024llu * 1024llu * 1024llu;
+
+ if (kUseGzipForLookahead) {
+ gzFile file = gzopen(filename.c_str(), "r");
+ if (file == Z_NULL) {
+ return false;
+ }
+
+ std::vector<uint8_t> output_data;
+ output_data.resize(4096);
+ std::stringstream sstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
+ while (true) {
+ int ret = gzread(file, output_data.data(), output_data.size());
+ NPNR_ASSERT(ret >= 0);
+ if (ret > 0) {
+ sstream.write((const char *)output_data.data(), ret);
+ NPNR_ASSERT(sstream);
+ } else {
+ NPNR_ASSERT(ret == 0);
+ int error;
+ gzerror(file, &error);
+ NPNR_ASSERT(error == Z_OK);
+ break;
+ }
+ }
+
+ NPNR_ASSERT(gzclose(file) == Z_OK);
+
+ sstream.seekg(0);
+ kj::std::StdInputStream istream(sstream);
+ capnp::InputStreamMessageReader message_reader(istream, reader_options);
+
+ lookahead_storage::Lookahead::Reader lookahead = message_reader.getRoot<lookahead_storage::Lookahead>();
+ return from_reader(chipdb_hash, lookahead);
+ } else {
+ boost::iostreams::mapped_file_source file;
+ try {
+ file.open(filename.c_str());
+ } catch (std::ios_base::failure &fail) {
+ return false;
+ }
+
+ if (!file.is_open()) {
+ return false;
+ }
+
+ const char *data = reinterpret_cast<const char *>(file.data());
+ const kj::ArrayPtr<const ::capnp::word> words =
+ kj::arrayPtr(reinterpret_cast<const ::capnp::word *>(data), file.size() / sizeof(::capnp::word));
+ ::capnp::FlatArrayMessageReader reader(words, reader_options);
+ lookahead_storage::Lookahead::Reader lookahead = reader.getRoot<lookahead_storage::Lookahead>();
+ return from_reader(chipdb_hash, lookahead);
+ }
+}
+
+void Lookahead::write_lookahead(const std::string &chipdb_hash, const std::string &file) const
+{
+ ::capnp::MallocMessageBuilder message;
+
+ lookahead_storage::Lookahead::Builder lookahead = message.initRoot<lookahead_storage::Lookahead>();
+ to_builder(chipdb_hash, lookahead);
+ write_message(message, file);
+}
+
+void Lookahead::init(const Context *ctx, DeterministicRNG *rng)
+{
+ std::string lookahead_filename;
+ if (kUseGzipForLookahead) {
+ lookahead_filename = ctx->args.chipdb + ".lookahead.tgz";
+ } else {
+ lookahead_filename = ctx->args.chipdb + ".lookahead";
+ }
+
+ std::string chipdb_hash = ctx->get_chipdb_hash();
+
+ if (ctx->args.rebuild_lookahead || !read_lookahead(chipdb_hash, lookahead_filename)) {
+ build_lookahead(ctx, rng);
+ if (!ctx->args.dont_write_lookahead) {
+ write_lookahead(chipdb_hash, lookahead_filename);
+ }
+ }
+}
+
+static bool safe_add_i32(int32_t a, int32_t b, int32_t *out)
+{
+#if defined(__GNUG__) || defined(__clang__)
+ // GCC and clang have had __builtin_add_overflow for a while.
+ return !__builtin_add_overflow(a, b, out);
+#else
+ // MSVC and other don't have an intrinsic. Emit some more code.
+ bool safe_to_add;
+ if (b < 0) {
+ safe_to_add = a >= std::numeric_limits<int32_t>::min() - b;
+ } else {
+ safe_to_add = a <= std::numeric_limits<int32_t>::max() - b;
+ }
+ if (!safe_to_add) {
+ return false;
+ }
+ *out = a + b;
+ return true;
+#endif
+}
+
+static void saturating_incr(int32_t *acc, int32_t value)
+{
+ if (!safe_add_i32(*acc, value, acc)) {
+ if (value > 0) {
+ *acc = std::numeric_limits<int32_t>::max();
+ } else {
+ *acc = std::numeric_limits<int32_t>::min();
+ }
+ }
+}
+
+#define DEBUG_LOOKUP
+
+delay_t Lookahead::estimateDelay(const Context *ctx, WireId src, WireId dst) const
+{
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug) {
+ log_info("Looking up %s to %s\n", ctx->nameOfWire(src), ctx->nameOfWire(dst));
+ }
+#endif
+ delay_t delay = 0;
+
+ // Follow chain down, chasing wires with only 1 pip. Stop if dst is
+ // reached.
+ WireId orig_src = src;
+ src = follow_pip_chain_target(ctx, src, dst, &delay);
+ NPNR_ASSERT(delay >= 0);
+ if (src == WireId()) {
+ // This src wire is a dead end, tell router to avoid it!
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug) {
+ log_info("Source %s is a dead end!\n", ctx->nameOfWire(orig_src));
+ }
+#endif
+ return std::numeric_limits<delay_t>::max();
+ }
+
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug && src != orig_src) {
+ log_info("Moving src from %s to %s, delay = %d\n", ctx->nameOfWire(orig_src), ctx->nameOfWire(src), delay);
+ }
+#endif
+
+ if (src == dst) {
+ // Reached target already, done!
+ return delay;
+ }
+
+ if (ctx->is_same_site(src, dst)) {
+ // Check for site to site direct path.
+ TypeWirePair pair;
+
+ TypeWireId src_type(ctx, src);
+ pair.src = src_type;
+
+ TypeWireId dst_type(ctx, dst);
+ pair.dst = dst_type;
+
+ auto iter = site_to_site_cost.find(pair);
+ if (iter != site_to_site_cost.end()) {
+ NPNR_ASSERT(iter->second >= 0);
+ saturating_incr(&delay, iter->second);
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug) {
+ log_info("Found site to site direct path %s -> %s = %d\n", ctx->nameOfWire(src), ctx->nameOfWire(dst),
+ delay);
+ }
+#endif
+ return delay;
+ }
+ }
+
+ // At this point we know that the routing interconnect is needed, or
+ // the pair is unreachable.
+ orig_src = src;
+ TypeWireId src_type(ctx, src);
+
+ // Find the first routing wire from the src_type.
+ auto src_iter = output_site_wires.find(src_type);
+ if (src_iter != output_site_wires.end()) {
+ NPNR_ASSERT(src_iter->second.cost >= 0);
+ saturating_incr(&delay, src_iter->second.cost);
+ src_type = src_iter->second.cheapest_route_from;
+
+ src = canonical_wire(ctx->chip_info, src.tile, src_type.index);
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug) {
+ log_info("Moving src from %s to %s, delay = %d\n", ctx->nameOfWire(orig_src), ctx->nameOfWire(src), delay);
+ }
+#endif
+ }
+
+ // Make sure that the new wire is in the routing graph.
+ if (ctx->is_wire_in_site(src)) {
+#ifdef DEBUG_LOOKUP
+ // We've already tested for direct site to site routing, if src cannot
+ // reach outside of the routing network, this path is impossible.
+ if (ctx->debug) {
+ log_warning("Failed to reach routing network for src %s, got to %s\n", ctx->nameOfWire(orig_src),
+ ctx->nameOfWire(src));
+ }
+#endif
+ return std::numeric_limits<delay_t>::max();
+ }
+
+ if (src == dst) {
+ // Reached target already, done!
+ return delay;
+ }
+
+ // Find the first routing wire that reaches dst_type.
+ WireId orig_dst = dst;
+ TypeWireId dst_type(ctx, dst);
+
+ auto dst_iter = input_site_wires.find(dst_type);
+ if (dst_iter == input_site_wires.end()) {
+ // dst_type isn't an input site wire, just add point to point delay.
+ auto &dst_data = ctx->wire_info(dst);
+ if (dst_data.site != -1) {
+#ifdef DEBUG_LOOKUP
+ // We've already tested for direct site to site routing, if dst cannot
+ // be reached from the routing network, this path is impossible.
+ if (ctx->debug) {
+ log_warning("Failed to reach routing network for dst %s, got to %s\n", ctx->nameOfWire(orig_dst),
+ ctx->nameOfWire(dst));
+ }
+#endif
+ return std::numeric_limits<delay_t>::max();
+ }
+
+ // Follow chain up
+ WireId orig_dst = dst;
+ (void)orig_dst;
+
+ delay_t chain_delay;
+ dst = follow_pip_chain_up(ctx, dst, &chain_delay);
+ NPNR_ASSERT(chain_delay >= 0);
+ saturating_incr(&delay, chain_delay);
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug && dst != orig_dst) {
+ log_info("Moving dst from %s to %s, delay = %d\n", ctx->nameOfWire(orig_dst), ctx->nameOfWire(dst), delay);
+ }
+#endif
+
+ if (src == dst) {
+ // Reached target already, done!
+ return delay;
+ }
+
+ // Both src and dst are in the routing graph, lookup approx cost to go
+ // from src to dst.
+ int32_t delay_from_map = cost_map.get_delay(ctx, src, dst);
+ NPNR_ASSERT(delay_from_map >= 0);
+ saturating_incr(&delay, delay_from_map);
+
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug) {
+ log_info("Final delay = %d\n", delay);
+ }
+#endif
+
+ return delay;
+ } else {
+ // dst_type is an input site wire, try each possible routing path.
+ delay_t base_delay = delay;
+ delay_t cheapest_path = std::numeric_limits<delay_t>::max();
+
+ for (const InputSiteWireCost &input_cost : dst_iter->second) {
+ dst = orig_dst;
+ delay = base_delay;
+
+ NPNR_ASSERT(input_cost.cost >= 0);
+ saturating_incr(&delay, input_cost.cost);
+ dst_type = input_cost.route_to;
+
+ NPNR_ASSERT(dst_type.index != -1);
+ dst = canonical_wire(ctx->chip_info, dst.tile, dst_type.index);
+ NPNR_ASSERT(dst != WireId());
+
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug) {
+ log_info("Moving dst from %s to %s, delay = %d\n", ctx->nameOfWire(orig_dst), ctx->nameOfWire(dst),
+ delay);
+ }
+#endif
+
+ if (dst == src) {
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug) {
+ log_info("Possible delay = %d\n", delay);
+ }
+#endif
+ // Reached target already, done!
+ cheapest_path = std::min(delay, cheapest_path);
+ continue;
+ }
+
+ auto &dst_data = ctx->wire_info(dst);
+ if (dst_data.site != -1) {
+#ifdef DEBUG_LOOKUP
+ // We've already tested for direct site to site routing, if dst cannot
+ // be reached from the routing network, this path is impossible.
+ if (ctx->debug) {
+ log_warning("Failed to reach routing network for dst %s, got to %s\n", ctx->nameOfWire(orig_dst),
+ ctx->nameOfWire(dst));
+ }
+#endif
+ continue;
+ }
+
+ // Follow chain up
+ WireId orig_dst = dst;
+ (void)orig_dst;
+
+ delay_t chain_delay;
+ dst = follow_pip_chain_up(ctx, dst, &chain_delay);
+ NPNR_ASSERT(chain_delay >= 0);
+ saturating_incr(&delay, chain_delay);
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug && dst != orig_dst) {
+ log_info("Moving dst from %s to %s, delay = %d\n", ctx->nameOfWire(orig_dst), ctx->nameOfWire(dst),
+ delay);
+ }
+#endif
+
+ if (dst == WireId()) {
+ // This dst wire is a dead end, don't examine it!
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug) {
+ log_info("Dest %s is a dead end!\n", ctx->nameOfWire(dst));
+ }
+#endif
+ continue;
+ }
+
+ if (src == dst) {
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug) {
+ log_info("Possible delay = %d\n", delay);
+ }
+#endif
+ // Reached target already, done!
+ cheapest_path = std::min(delay, cheapest_path);
+ continue;
+ }
+
+ // Both src and dst are in the routing graph, lookup approx cost to go
+ // from src to dst.
+ int32_t delay_from_map = cost_map.get_delay(ctx, src, dst);
+ NPNR_ASSERT(delay_from_map >= 0);
+ saturating_incr(&delay, delay_from_map);
+ cheapest_path = std::min(delay, cheapest_path);
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug) {
+ log_info("Possible delay = %d\n", delay);
+ }
+#endif
+ }
+
+#ifdef DEBUG_LOOKUP
+ if (ctx->debug) {
+ log_info("Final delay = %d\n", delay);
+ }
+#endif
+
+ return cheapest_path;
+ }
+}
+
+bool Lookahead::from_reader(const std::string &chipdb_hash, lookahead_storage::Lookahead::Reader reader)
+{
+ std::string expected_hash = reader.getChipdbHash();
+ if (chipdb_hash != expected_hash) {
+ return false;
+ }
+
+ input_site_wires.clear();
+ output_site_wires.clear();
+ site_to_site_cost.clear();
+
+ for (auto input_reader : reader.getInputSiteWires()) {
+ TypeWireId key(input_reader.getKey());
+
+ auto result = input_site_wires.emplace(key, std::vector<InputSiteWireCost>());
+ NPNR_ASSERT(result.second);
+ std::vector<InputSiteWireCost> &costs = result.first->second;
+ auto value = input_reader.getValue();
+ costs.reserve(value.size());
+ for (auto cost : value) {
+ costs.emplace_back(InputSiteWireCost{TypeWireId(cost.getRouteTo()), cost.getCost()});
+ }
+ }
+
+ for (auto output_reader : reader.getOutputSiteWires()) {
+ TypeWireId key(output_reader.getKey());
+
+ auto result = output_site_wires.emplace(
+ key, OutputSiteWireCost{TypeWireId(output_reader.getCheapestRouteFrom()), output_reader.getCost()});
+ NPNR_ASSERT(result.second);
+ }
+
+ for (auto site_to_site_reader : reader.getSiteToSiteCost()) {
+ TypeWirePair key(site_to_site_reader.getKey());
+ auto result = site_to_site_cost.emplace(key, site_to_site_reader.getCost());
+ NPNR_ASSERT(result.second);
+ }
+
+ cost_map.from_reader(reader.getCostMap());
+
+ return true;
+}
+
+void Lookahead::to_builder(const std::string &chipdb_hash, lookahead_storage::Lookahead::Builder builder) const
+{
+ builder.setChipdbHash(chipdb_hash);
+
+ auto input_out = builder.initInputSiteWires(input_site_wires.size());
+ auto in = input_site_wires.begin();
+ for (auto out = input_out.begin(); out != input_out.end(); ++out, ++in) {
+ NPNR_ASSERT(in != input_site_wires.end());
+
+ const TypeWireId &key = in->first;
+ key.to_builder(out->getKey());
+
+ const std::vector<InputSiteWireCost> &costs = in->second;
+ auto value = out->initValue(costs.size());
+
+ auto value_in = costs.begin();
+ for (auto value_out = value.begin(); value_out != value.end(); ++value_out, ++value_in) {
+ value_in->route_to.to_builder(value_out->getRouteTo());
+ value_out->setCost(value_in->cost);
+ }
+ }
+
+ auto output_out = builder.initOutputSiteWires(output_site_wires.size());
+ auto out = output_site_wires.begin();
+ for (auto out2 = output_out.begin(); out2 != output_out.end(); ++out, ++out2) {
+ NPNR_ASSERT(out != output_site_wires.end());
+
+ const TypeWireId &key = out->first;
+ key.to_builder(out2->getKey());
+
+ const TypeWireId &cheapest_route_from = out->second.cheapest_route_from;
+ cheapest_route_from.to_builder(out2->getCheapestRouteFrom());
+
+ out2->setCost(out->second.cost);
+ }
+
+ auto site_out = builder.initSiteToSiteCost(site_to_site_cost.size());
+ auto site = site_to_site_cost.begin();
+ for (auto out2 = site_out.begin(); out2 != site_out.end(); ++out2, ++site) {
+ NPNR_ASSERT(site != site_to_site_cost.end());
+
+ const TypeWirePair &key = site->first;
+ key.to_builder(out2->getKey());
+ out2->setCost(site->second);
+ }
+
+ cost_map.to_builder(builder.getCostMap());
+}
+
+NEXTPNR_NAMESPACE_END