Merge pull request #641 from litghost/initial_lookahead

Initial lookahead for FPGA interchange.
This commit is contained in:
gatecat 2021-03-23 16:00:17 +00:00 committed by GitHub
commit 4d8dcab1d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2690 additions and 14 deletions

@ -1 +1 @@
Subproject commit cb6d16847dfca7f104205c83bbdc056303ac82a0 Subproject commit 8ec5b1739d7b91b84df3e92ccc70c7a7ee644089

View File

@ -43,6 +43,10 @@ if(WASI)
) )
else() else()
set(USE_THREADS ON) set(USE_THREADS ON)
find_package(TBB QUIET)
if (TBB_FOUND)
add_definitions(-DNEXTPNR_USE_TBB)
endif()
endif() endif()
if (NOT USE_THREADS) if (NOT USE_THREADS)
@ -243,6 +247,9 @@ endif()
if(PROFILER) if(PROFILER)
list(APPEND EXTRA_LIB_DEPS profiler) list(APPEND EXTRA_LIB_DEPS profiler)
endif() endif()
if(TBB_FOUND)
list(APPEND EXTRA_LIB_DEPS tbb)
endif()
foreach (family ${ARCH}) foreach (family ${ARCH})
message(STATUS "Configuring architecture: ${family}") message(STATUS "Configuring architecture: ${family}")

View File

@ -24,9 +24,11 @@
#include <algorithm> #include <algorithm>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/range/adaptor/reversed.hpp> #include <boost/range/adaptor/reversed.hpp>
#include <boost/uuid/detail/sha1.hpp>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <queue> #include <queue>
#include "constraints.impl.h" #include "constraints.impl.h"
#include "fpga_interchange.h" #include "fpga_interchange.h"
#include "log.h" #include "log.h"
@ -42,6 +44,11 @@
// Include tcl.h late because it messed with defines and let them leave the // Include tcl.h late because it messed with defines and let them leave the
// scope of the header. // scope of the header.
#include <tcl.h> #include <tcl.h>
//#define DEBUG_BINDING
//#define USE_LOOKAHEAD
//#define DEBUG_CELL_PIN_MAPPING
NEXTPNR_NAMESPACE_BEGIN NEXTPNR_NAMESPACE_BEGIN
struct SiteBelPair struct SiteBelPair
{ {
@ -83,6 +90,22 @@ void IdString::initialize_arch(const BaseCtx *ctx) {}
static const ChipInfoPOD *get_chip_info(const RelPtr<ChipInfoPOD> *ptr) { return ptr->get(); } static const ChipInfoPOD *get_chip_info(const RelPtr<ChipInfoPOD> *ptr) { return ptr->get(); }
static std::string sha1_hash(const char *data, size_t size)
{
boost::uuids::detail::sha1 hasher;
hasher.process_bytes(data, size);
// unsigned int[5]
boost::uuids::detail::sha1::digest_type digest;
hasher.get_digest(digest);
std::ostringstream buf;
for (int i = 0; i < 5; ++i)
buf << std::hex << std::setfill('0') << std::setw(8) << digest[i];
return buf.str();
}
Arch::Arch(ArchArgs args) : args(args) Arch::Arch(ArchArgs args) : args(args)
{ {
try { try {
@ -90,6 +113,8 @@ Arch::Arch(ArchArgs args) : args(args)
if (args.chipdb.empty() || !blob_file.is_open()) if (args.chipdb.empty() || !blob_file.is_open())
log_error("Unable to read chipdb %s\n", args.chipdb.c_str()); log_error("Unable to read chipdb %s\n", args.chipdb.c_str());
const char *blob = reinterpret_cast<const char *>(blob_file.data()); const char *blob = reinterpret_cast<const char *>(blob_file.data());
chipdb_hash = sha1_hash(blob, blob_file.size());
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(blob)); chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(blob));
} catch (...) { } catch (...) {
log_error("Unable to read chipdb %s\n", args.chipdb.c_str()); log_error("Unable to read chipdb %s\n", args.chipdb.c_str());
@ -249,7 +274,13 @@ Arch::Arch(ArchArgs args) : args(args)
default_tags.resize(max_tag_count); default_tags.resize(max_tag_count);
} }
void Arch::init() { dedicated_interconnect.init(getCtx()); } void Arch::init()
{
#ifdef USE_LOOKAHEAD
lookahead.init(getCtx(), getCtx());
#endif
dedicated_interconnect.init(getCtx());
}
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
@ -894,18 +925,11 @@ DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; };
delay_t Arch::estimateDelay(WireId src, WireId dst) const delay_t Arch::estimateDelay(WireId src, WireId dst) const
{ {
// FIXME: Implement something to push the A* router in the right direction. #ifdef USE_LOOKAHEAD
int src_x, src_y; return lookahead.estimateDelay(getCtx(), src, dst);
get_tile_x_y(src.tile, &src_x, &src_y); #else
return 0;
int dst_x, dst_y; #endif
get_tile_x_y(dst.tile, &dst_x, &dst_y);
delay_t base = 30 * std::min(std::abs(dst_x - src_x), 18) + 10 * std::max(std::abs(dst_x - src_x) - 18, 0) +
60 * std::min(std::abs(dst_y - src_y), 6) + 20 * std::max(std::abs(dst_y - src_y) - 6, 0) + 300;
base = (base * 3) / 2;
return base;
} }
delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
@ -1757,6 +1781,8 @@ bool Arch::checkPipAvailForNet(PipId pip, NetInfo *net) const
bool Arch::checkPipAvail(PipId pip) const { return checkPipAvailForNet(pip, nullptr); } bool Arch::checkPipAvail(PipId pip) const { return checkPipAvailForNet(pip, nullptr); }
std::string Arch::get_chipdb_hash() const { return chipdb_hash; }
// Instance constraint templates. // Instance constraint templates.
template void Arch::ArchConstraints::bindBel(Arch::ArchConstraints::TagState *, const Arch::ConstraintRange); template void Arch::ArchConstraints::bindBel(Arch::ArchConstraints::TagState *, const Arch::ConstraintRange);
template void Arch::ArchConstraints::unbindBel(Arch::ArchConstraints::TagState *, const Arch::ConstraintRange); template void Arch::ArchConstraints::unbindBel(Arch::ArchConstraints::TagState *, const Arch::ConstraintRange);

View File

@ -36,6 +36,7 @@
#include "arch_iterators.h" #include "arch_iterators.h"
#include "chipdb.h" #include "chipdb.h"
#include "dedicated_interconnect.h" #include "dedicated_interconnect.h"
#include "lookahead.h"
#include "site_router.h" #include "site_router.h"
#include "site_routing_cache.h" #include "site_routing_cache.h"
@ -45,6 +46,8 @@ struct ArchArgs
{ {
std::string chipdb; std::string chipdb;
std::string package; std::string package;
bool rebuild_lookahead;
bool dont_write_lookahead;
}; };
struct ArchRanges struct ArchRanges
@ -1038,8 +1041,13 @@ struct Arch : ArchAPI<ArchRanges>
std::regex verilog_hex_constant; std::regex verilog_hex_constant;
void read_lut_equation(DynamicBitarray<> *equation, const Property &equation_parameter) const; void read_lut_equation(DynamicBitarray<> *equation, const Property &equation_parameter) const;
bool route_vcc_to_unused_lut_pins(); bool route_vcc_to_unused_lut_pins();
Lookahead lookahead;
mutable RouteNodeStorage node_storage; mutable RouteNodeStorage node_storage;
mutable SiteRoutingCache site_routing_cache; mutable SiteRoutingCache site_routing_cache;
std::string chipdb_hash;
std::string get_chipdb_hash() const;
}; };
NEXTPNR_NAMESPACE_END NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,400 @@
/*
* 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 "cost_map.h"
#include "context.h"
#include "log.h"
NEXTPNR_NAMESPACE_BEGIN
///@brief Factor to adjust the penalty calculation for deltas outside the segment bounding box:
// factor < 1.0: penalty has less impact on the final returned delay
// factor > 1.0: penalty has more impact on the final returned delay
static constexpr float PENALTY_FACTOR = 1.f;
///@brief Minimum penalty cost that is added when penalizing a delta outside the segment bounding box.
static constexpr delay_t PENALTY_MIN = 1;
// also known as the L1 norm
static int manhattan_distance(const std::pair<int32_t, int32_t> &a, const std::pair<int32_t, int32_t> &b)
{
return std::abs(b.first - a.first) + std::abs(b.second - a.second);
}
static delay_t penalize(const delay_t &entry, int distance, delay_t penalty)
{
penalty = std::max(penalty, PENALTY_MIN);
return entry + distance * penalty * PENALTY_FACTOR;
}
delay_t CostMap::get_delay(const Context *ctx, WireId src_wire, WireId dst_wire) const
{
TypeWirePair type_pair;
type_pair.src = TypeWireId(ctx, src_wire);
type_pair.dst = TypeWireId(ctx, dst_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);
int dst_tile;
if (dst_wire.tile == -1) {
dst_tile = ctx->chip_info->nodes[dst_wire.index].tile_wires[0].tile;
} else {
dst_tile = dst_wire.tile;
}
int32_t dst_x, dst_y;
ctx->get_tile_x_y(dst_tile, &dst_x, &dst_y);
auto iter = cost_map_.find(type_pair);
if (iter == cost_map_.end()) {
auto &src_type = ctx->chip_info->tile_types[type_pair.src.type];
IdString src_tile_type(src_type.name);
IdString src_wire_name(src_type.wire_data[type_pair.src.index].name);
auto &dst_type = ctx->chip_info->tile_types[type_pair.dst.type];
IdString dst_tile_type(dst_type.name);
IdString dst_wire_name(dst_type.wire_data[type_pair.dst.index].name);
#if 0
log_warning("Delay matrix is missing %s/%s -> %s/%s\n",
src_tile_type.c_str(ctx),
src_wire_name.c_str(ctx),
dst_tile_type.c_str(ctx),
dst_wire_name.c_str(ctx));
#endif
return std::numeric_limits<delay_t>::max();
}
const auto &delay_matrix = iter->second;
int32_t off_x = delay_matrix.offset.first + (dst_x - src_x);
int32_t off_y = delay_matrix.offset.second + (dst_y - src_y);
int32_t x_dim = delay_matrix.data.shape()[0];
int32_t y_dim = delay_matrix.data.shape()[1];
NPNR_ASSERT(x_dim > 0);
NPNR_ASSERT(y_dim > 0);
// Bound closest_x/y to [0, dim)
int32_t closest_x = std::min(std::max(off_x, 0), x_dim - 1);
int32_t closest_y = std::min(std::max(off_y, 0), y_dim - 1);
// Get the cost entry from the cost map at the deltas values
auto cost = delay_matrix.data[closest_x][closest_y];
NPNR_ASSERT(cost >= 0);
// Get the base penalty corresponding to the current segment.
auto penalty = delay_matrix.penalty;
// Get the distance between the closest point in the bounding box and the original coordinates.
// Note that if the original coordinates are within the bounding box, the distance will be equal to zero.
auto distance = manhattan_distance(std::make_pair(off_x, off_y), std::make_pair(closest_x, closest_y));
// Return the penalized cost (no penalty is added if the coordinates are within the bounding box).
return penalize(cost, distance, penalty);
}
void CostMap::set_cost_map(const Context *ctx, const TypeWirePair &wire_pair,
const HashTables::HashMap<std::pair<int32_t, int32_t>, delay_t> &delays)
{
CostMapEntry delay_matrix;
auto &offset = delay_matrix.offset;
offset.first = 0;
offset.second = 0;
int32_t max_x_offset = 0;
int32_t max_y_offset = 0;
for (const auto &delay_pair : delays) {
auto &dx_dy = delay_pair.first;
offset.first = std::max(-dx_dy.first, offset.first);
offset.second = std::max(-dx_dy.second, offset.second);
max_x_offset = std::max(dx_dy.first, max_x_offset);
max_y_offset = std::max(dx_dy.second, max_y_offset);
}
int32_t x_dim = offset.first + max_x_offset + 1;
int32_t y_dim = offset.second + max_y_offset + 1;
delay_matrix.data.resize(boost::extents[x_dim][y_dim]);
// Fill matrix with sentinel of -1 to know where the holes in the matrix
// are.
std::fill_n(delay_matrix.data.data(), delay_matrix.data.num_elements(), -1);
for (const auto &delay_pair : delays) {
auto &dx_dy = delay_pair.first;
int32_t off_x = dx_dy.first + offset.first;
int32_t off_y = dx_dy.second + offset.second;
NPNR_ASSERT(off_x >= 0);
NPNR_ASSERT(off_x < x_dim);
NPNR_ASSERT(off_y >= 0);
NPNR_ASSERT(off_y < y_dim);
delay_matrix.data[off_x][off_y] = delay_pair.second;
}
delay_matrix.penalty = get_penalty(delay_matrix.data);
fill_holes(ctx, wire_pair, delay_matrix.data, delay_matrix.penalty);
{
cost_map_mutex_.lock();
auto result = cost_map_.emplace(wire_pair, delay_matrix);
NPNR_ASSERT(result.second);
cost_map_mutex_.unlock();
}
}
static void assign_min_entry(delay_t *dst, const delay_t &src)
{
if (src >= 0) {
if (*dst < 0) {
*dst = src;
} else if (src < *dst) {
*dst = src;
}
}
}
std::pair<delay_t, int> CostMap::get_nearby_cost_entry(const boost::multi_array<delay_t, 2> &matrix, int cx, int cy,
const ArcBounds &bounds)
{
#ifdef DEBUG_FILL
log_info("Filling %d, %d within (%d, %d, %d, %d)\n", cx, cy, bounds.x0, bounds.y0, bounds.x1, bounds.y1);
#endif
// spiral around (cx, cy) looking for a nearby entry
bool in_bounds = bounds.contains(cx, cy);
if (!in_bounds) {
#ifdef DEBUG_FILL
log_info("Already out of bounds, return!\n");
#endif
return std::make_pair(-1, 0);
}
int n = 0;
delay_t fill(matrix[cx][cy]);
while (in_bounds && (fill < 0)) {
n++;
#ifdef DEBUG_FILL
log_info("At n = %d\n", n);
#endif
in_bounds = false;
delay_t min_entry = -1;
for (int ox = -n; ox <= n; ox++) {
int x = cx + ox;
int oy = n - abs(ox);
int yp = cy + oy;
int yn = cy - oy;
#ifdef DEBUG_FILL
log_info("Testing %d, %d\n", x, yp);
#endif
if (bounds.contains(x, yp)) {
assign_min_entry(&min_entry, matrix[x][yp]);
in_bounds = true;
#ifdef DEBUG_FILL
log_info("matrix[%d, %d] = %d, min_entry = %d\n", x, yp, matrix[x][yp], min_entry);
#endif
}
#ifdef DEBUG_FILL
log_info("Testing %d, %d\n", x, yn);
#endif
if (bounds.contains(x, yn)) {
assign_min_entry(&min_entry, matrix[x][yn]);
in_bounds = true;
#ifdef DEBUG_FILL
log_info("matrix[%d, %d] = %d, min_entry = %d\n", x, yn, matrix[x][yn], min_entry);
#endif
}
}
if (fill < 0 && min_entry >= 0) {
fill = min_entry;
}
}
return std::make_pair(fill, n);
}
void CostMap::fill_holes(const Context *ctx, const TypeWirePair &type_pair, boost::multi_array<delay_t, 2> &matrix,
delay_t delay_penalty)
{
// find missing cost entries and fill them in by copying a nearby cost entry
std::vector<std::tuple<unsigned, unsigned, delay_t>> missing;
bool couldnt_fill = false;
auto shifted_bounds = ArcBounds(0, 0, matrix.shape()[0] - 1, matrix.shape()[1] - 1);
int max_fill = 0;
for (unsigned ix = 0; ix < matrix.shape()[0]; ix++) {
for (unsigned iy = 0; iy < matrix.shape()[1]; iy++) {
delay_t &cost_entry = matrix[ix][iy];
if (cost_entry < 0) {
// maximum search radius
delay_t filler;
int distance;
std::tie(filler, distance) = get_nearby_cost_entry(matrix, ix, iy, shifted_bounds);
if (filler >= 0) {
missing.push_back(std::make_tuple(ix, iy, penalize(filler, distance, delay_penalty)));
max_fill = std::max(max_fill, distance);
} else {
couldnt_fill = true;
}
}
}
if (couldnt_fill) {
// give up trying to fill an empty matrix
break;
}
}
if (!couldnt_fill && max_fill > 0) {
if (ctx->verbose) {
auto &src_type_data = ctx->chip_info->tile_types[type_pair.src.type];
IdString src_type(src_type_data.name);
IdString src_wire(src_type_data.wire_data[type_pair.src.index].name);
auto &dst_type_data = ctx->chip_info->tile_types[type_pair.dst.type];
IdString dst_type(dst_type_data.name);
IdString dst_wire(dst_type_data.wire_data[type_pair.dst.index].name);
#ifdef DEBUG_FILL
log_info("At %s/%s -> %s/%s: max_fill = %d, delay_penalty = %d\n", src_type.c_str(ctx), src_wire.c_str(ctx),
dst_type.c_str(ctx), dst_wire.c_str(ctx), max_fill, delay_penalty);
#endif
}
}
// write back the missing entries
for (auto &xy_entry : missing) {
matrix[std::get<0>(xy_entry)][std::get<1>(xy_entry)] = std::get<2>(xy_entry);
}
if (couldnt_fill) {
auto &src_type_data = ctx->chip_info->tile_types[type_pair.src.type];
IdString src_type(src_type_data.name);
IdString src_wire(src_type_data.wire_data[type_pair.src.index].name);
auto &dst_type_data = ctx->chip_info->tile_types[type_pair.dst.type];
IdString dst_type(dst_type_data.name);
IdString dst_wire(dst_type_data.wire_data[type_pair.dst.index].name);
log_warning("Couldn't fill holes in the cost matrix %s/%s -> %s/%s %d x %d bounding box\n", src_type.c_str(ctx),
src_wire.c_str(ctx), dst_type.c_str(ctx), dst_wire.c_str(ctx), shifted_bounds.x1,
shifted_bounds.y1);
for (unsigned y = 0; y < matrix.shape()[1]; y++) {
for (unsigned x = 0; x < matrix.shape()[0]; x++) {
NPNR_ASSERT(matrix[x][y] >= 0);
}
}
}
}
delay_t CostMap::get_penalty(const boost::multi_array<delay_t, 2> &matrix) const
{
delay_t min_delay = std::numeric_limits<delay_t>::max();
delay_t max_delay = std::numeric_limits<delay_t>::min();
std::pair<int32_t, int32_t> min_location(0, 0), max_location(0, 0);
for (unsigned ix = 0; ix < matrix.shape()[0]; ix++) {
for (unsigned iy = 0; iy < matrix.shape()[1]; iy++) {
const delay_t &cost_entry = matrix[ix][iy];
if (cost_entry >= 0) {
if (cost_entry < min_delay) {
min_delay = cost_entry;
min_location = std::make_pair(ix, iy);
}
if (cost_entry > max_delay) {
max_delay = cost_entry;
max_location = std::make_pair(ix, iy);
}
}
}
}
delay_t delay_penalty =
(max_delay - min_delay) / static_cast<float>(std::max(1, manhattan_distance(max_location, min_location)));
return delay_penalty;
}
void CostMap::from_reader(lookahead_storage::CostMap::Reader reader)
{
for (auto cost_entry : reader.getCostMap()) {
TypeWirePair key(cost_entry.getKey());
auto result = cost_map_.emplace(key, CostMapEntry());
NPNR_ASSERT(result.second);
CostMapEntry &entry = result.first->second;
auto data = cost_entry.getData();
auto in_iter = data.begin();
entry.data.resize(boost::extents[cost_entry.getXDim()][cost_entry.getYDim()]);
if (entry.data.num_elements() != data.size()) {
log_error("entry.data.num_elements() %zu != data.size() %u", entry.data.num_elements(), data.size());
}
delay_t *out = entry.data.origin();
for (; in_iter != data.end(); ++in_iter, ++out) {
*out = *in_iter;
}
entry.penalty = cost_entry.getPenalty();
entry.offset.first = cost_entry.getXOffset();
entry.offset.second = cost_entry.getYOffset();
}
}
void CostMap::to_builder(lookahead_storage::CostMap::Builder builder) const
{
auto cost_map = builder.initCostMap(cost_map_.size());
auto entry_iter = cost_map.begin();
auto in = cost_map_.begin();
for (; entry_iter != cost_map.end(); ++entry_iter, ++in) {
NPNR_ASSERT(in != cost_map_.end());
in->first.to_builder(entry_iter->getKey());
const CostMapEntry &entry = in->second;
auto data = entry_iter->initData(entry.data.num_elements());
const delay_t *data_in = entry.data.origin();
for (size_t i = 0; i < entry.data.num_elements(); ++i) {
data.set(i, data_in[i]);
}
entry_iter->setXDim(entry.data.shape()[0]);
entry_iter->setYDim(entry.data.shape()[1]);
entry_iter->setXOffset(entry.offset.first);
entry_iter->setYOffset(entry.offset.second);
entry_iter->setPenalty(entry.penalty);
}
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,68 @@
/*
* 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.
*
*/
#ifndef COST_MAP_H
#define COST_MAP_H
#include <boost/multi_array.hpp>
#include <mutex>
#include "hash_table.h"
#include "lookahead.capnp.h"
#include "nextpnr_namespaces.h"
#include "nextpnr_types.h"
#include "type_wire.h"
NEXTPNR_NAMESPACE_BEGIN
struct Context;
class CostMap
{
public:
delay_t get_delay(const Context *ctx, WireId src, WireId dst) const;
void set_cost_map(const Context *ctx, const TypeWirePair &wire_pair,
const HashTables::HashMap<std::pair<int32_t, int32_t>, delay_t> &delays);
void from_reader(lookahead_storage::CostMap::Reader reader);
void to_builder(lookahead_storage::CostMap::Builder builder) const;
private:
struct CostMapEntry
{
boost::multi_array<delay_t, 2> data;
std::pair<int32_t, int32_t> offset;
delay_t penalty;
};
std::mutex cost_map_mutex_;
HashTables::HashMap<TypeWirePair, CostMapEntry> cost_map_;
void fill_holes(const Context *ctx, const TypeWirePair &wire_pair, boost::multi_array<delay_t, 2> &matrix,
delay_t delay_penality);
std::pair<delay_t, int> get_nearby_cost_entry(const boost::multi_array<delay_t, 2> &matrix, int cx, int cy,
const ArcBounds &bounds);
delay_t get_penalty(const boost::multi_array<delay_t, 2> &matrix) const;
};
NEXTPNR_NAMESPACE_END
#endif /* COST_MAP_H */

View File

@ -24,14 +24,31 @@ add_custom_target(all-${family}-archcheck-tests)
add_subdirectory(${family}/examples/devices) add_subdirectory(${family}/examples/devices)
add_subdirectory(${family}/examples/tests) add_subdirectory(${family}/examples/tests)
set(PROTOS lookahead.capnp)
set(CAPNP_SRCS)
set(CAPNP_HDRS)
find_package(CapnProto REQUIRED)
foreach (proto ${PROTOS})
capnp_generate_cpp(CAPNP_SRC CAPNP_HDR fpga_interchange/${proto})
list(APPEND CAPNP_HDRS ${CAPNP_HDR})
list(APPEND CAPNP_SRCS ${CAPNP_SRC})
endforeach()
add_library(extra_capnp STATIC ${CAPNP_SRCS})
target_link_libraries(extra_capnp PRIVATE CapnProto::capnp)
target_include_directories(extra_capnp INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/fpga_interchange)
foreach (target ${family_targets}) foreach (target ${family_targets})
target_include_directories(${target} PRIVATE ${TCL_INCLUDE_PATH}) target_include_directories(${target} PRIVATE ${TCL_INCLUDE_PATH})
target_link_libraries(${target} PRIVATE ${TCL_LIBRARY}) target_link_libraries(${target} PRIVATE ${TCL_LIBRARY})
target_link_libraries(${target} PRIVATE fpga_interchange_capnp) target_link_libraries(${target} PRIVATE fpga_interchange_capnp)
target_link_libraries(${target} PRIVATE extra_capnp)
target_link_libraries(${target} PRIVATE z) target_link_libraries(${target} PRIVATE z)
endforeach() endforeach()
if(BUILD_GUI) if(BUILD_GUI)
target_link_libraries(gui_${family} fpga_interchange_capnp) target_link_libraries(gui_${family} fpga_interchange_capnp)
target_link_libraries(gui_${family} extra_capnp)
target_link_libraries(gui_${family} z) target_link_libraries(gui_${family} z)
endif() endif()

View File

@ -0,0 +1,61 @@
@0x97c69817483d9dea;
using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("lookahead_storage");
using DelayType = Int32;
struct TypeWireId {
type @0: Int32;
index @1: Int32;
}
struct TypeWirePair {
src @0 : TypeWireId;
dst @1 : TypeWireId;
}
struct InputSiteWireCost {
routeTo @0 : TypeWireId;
cost @1 : DelayType;
}
struct InputSiteWireCostMap {
key @0 : TypeWireId;
value @1 : List(InputSiteWireCost);
}
struct OutputSiteWireCostMap {
key @0 : TypeWireId;
cheapestRouteFrom @1 : TypeWireId;
cost @2 : DelayType;
}
struct SiteToSiteCostMap {
key @0 : TypeWirePair;
cost @1 : DelayType;
}
struct CostMapEntry {
key @0 : TypeWirePair;
data @1 : List(DelayType);
xDim @2 : UInt32;
yDim @3 : UInt32;
xOffset @4 : UInt32;
yOffset @5 : UInt32;
penalty @6 : DelayType;
}
struct CostMap {
costMap @0 : List(CostMapEntry);
}
struct Lookahead {
chipdbHash @0 : Text;
inputSiteWires @1 : List(InputSiteWireCostMap);
outputSiteWires @2 : List(OutputSiteWireCostMap);
siteToSiteCost @3 : List(SiteToSiteCostMap);
costMap @4 : CostMap;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,99 @@
/*
* 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.
*
*/
#ifndef LOOKAHEAD_H
#define LOOKAHEAD_H
#include <algorithm>
#include <vector>
#include "cost_map.h"
#include "deterministic_rng.h"
#include "hash_table.h"
#include "lookahead.capnp.h"
#include "nextpnr_namespaces.h"
#include "type_wire.h"
NEXTPNR_NAMESPACE_BEGIN
// Lookahead is a routing graph generic lookahead builder and evaluator.
//
// The lookahead data model is structured into 3 parts:
// - Output site wires to routing network cost
// - Routing network point to point cost
// - Routing network cost to input site wires
//
// If the lookahead is invoked from a routing wire to a routing wire, only
// the point to point cost is used.
//
// If the lookahead is invoked from an output site wire to a routing wire,
// the point to point cost is computed using the cheapest output routing wire
// from the current site wire and then returned cost is the sum of the output
// cost plus the point to point routing network cost.
//
// If the lookahead is invoked from a routing wire to an input site wire,
// then the cost is the point to point routing cost to the cheapest input
// routing wire plus the input routing cost.
//
// If the lookahead is invoked from an output site wire to an input site wire,
// then cost is the sum of each of the 3 parts.
struct Lookahead
{
void init(const Context *, DeterministicRNG *rng);
void build_lookahead(const Context *, DeterministicRNG *rng);
bool read_lookahead(const std::string &chipdb_hash, const std::string &file);
void write_lookahead(const std::string &chipdb_hash, const std::string &file) const;
bool from_reader(const std::string &chipdb_hash, lookahead_storage::Lookahead::Reader reader);
void to_builder(const std::string &chipdb_hash, lookahead_storage::Lookahead::Builder builder) const;
delay_t estimateDelay(const Context *, WireId src, WireId dst) const;
struct InputSiteWireCost
{
// This wire is the cheapest non-site wire that leads to this site
// wire.
TypeWireId route_to;
// This is the cost from the cheapest_route_to wire to the site wire in
// question.
delay_t cost;
};
struct OutputSiteWireCost
{
// This wire is the cheapest non-site wire that is reachable from
// this site wire.
TypeWireId cheapest_route_from;
// This is the cost from the site wire in question to
// cheapest_route_from wire.
delay_t cost;
};
HashTables::HashMap<TypeWireId, std::vector<InputSiteWireCost>> input_site_wires;
HashTables::HashMap<TypeWireId, OutputSiteWireCost> output_site_wires;
HashTables::HashMap<TypeWirePair, delay_t> site_to_site_cost;
CostMap cost_map;
};
NEXTPNR_NAMESPACE_END
#endif /* LOOKAHEAD_H */

View File

@ -55,6 +55,8 @@ po::options_description FpgaInterchangeCommandHandler::getArchOptions()
specific.add_options()("netlist", po::value<std::string>(), "FPGA interchange logical netlist to read"); specific.add_options()("netlist", po::value<std::string>(), "FPGA interchange logical netlist to read");
specific.add_options()("phys", po::value<std::string>(), "FPGA interchange Physical netlist to write"); specific.add_options()("phys", po::value<std::string>(), "FPGA interchange Physical netlist to write");
specific.add_options()("package", po::value<std::string>(), "Package to use"); specific.add_options()("package", po::value<std::string>(), "Package to use");
specific.add_options()("rebuild-lookahead", "Ignore lookahead cache and rebuild");
specific.add_options()("dont-write-lookahead", "Don't write the lookahead file");
return specific; return specific;
} }
@ -72,6 +74,9 @@ std::unique_ptr<Context> FpgaInterchangeCommandHandler::createContext(std::unord
auto start = std::chrono::high_resolution_clock::now(); auto start = std::chrono::high_resolution_clock::now();
ArchArgs chipArgs; ArchArgs chipArgs;
chipArgs.rebuild_lookahead = vm.count("rebuild_lookahead") != 0;
chipArgs.dont_write_lookahead = vm.count("dont_write_lookahead") != 0;
if (!vm.count("chipdb")) { if (!vm.count("chipdb")) {
log_error("chip database binary must be provided\n"); log_error("chip database binary must be provided\n");
} }

174
fpga_interchange/sampler.cc Normal file
View File

@ -0,0 +1,174 @@
/*
* 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 "sampler.h"
#include <algorithm>
#include <cmath>
#include <stdexcept>
NEXTPNR_NAMESPACE_BEGIN
static size_t partition_x(std::vector<size_t>::iterator begin, std::vector<size_t>::iterator end,
const std::vector<std::pair<int32_t, int32_t>> &samples)
{
if (std::distance(begin, end) == 0) {
return 0;
}
// Find the median x value.
std::vector<int32_t> xs;
xs.reserve(std::distance(begin, end));
for (auto iter = begin; iter != end; ++iter) {
xs.push_back(samples[*iter].first);
}
std::sort(xs.begin(), xs.end());
xs.erase(std::unique(xs.begin(), xs.end()), xs.end());
// Partion on the median x value (e.g. 50% of samples on one side and
// 50% of samples on the other side).
int32_t x_div = xs[(xs.size() - 1) / 2];
auto split = std::partition(begin, end,
[x_div, &samples](size_t index) -> bool { return samples[index].first <= x_div; });
return std::distance(begin, split);
}
/* Don't both splitting when the partition has less than kMinSplit. */
static constexpr ptrdiff_t kMinSplit = 20;
static size_t partition_y(std::vector<size_t>::iterator begin, std::vector<size_t>::iterator end,
const std::vector<std::pair<int32_t, int32_t>> &samples)
{
if (std::distance(begin, end) == 0) {
return 0;
}
std::vector<int32_t> ys;
ys.reserve(std::distance(begin, end));
for (auto iter = begin; iter != end; ++iter) {
ys.push_back(samples[*iter].second);
}
std::sort(ys.begin(), ys.end());
ys.erase(std::unique(ys.begin(), ys.end()), ys.end());
int32_t y_div = ys[(ys.size() - 1) / 2];
auto split = std::partition(begin, end,
[y_div, &samples](size_t index) -> bool { return samples[index].second <= y_div; });
return std::distance(begin, split);
}
static void add_split(std::vector<size_t> *splits, size_t new_split)
{
if (splits->back() < new_split) {
splits->push_back(new_split);
} else if (splits->back() != new_split) {
throw std::runtime_error("Split is not consectutive!");
}
}
void Sampler::divide_samples(size_t target_sample_count, const std::vector<std::pair<int32_t, int32_t>> &samples)
{
// Initialize indicies lookup and make 1 split with entire sample range.
indicies.resize(samples.size());
for (size_t i = 0; i < samples.size(); ++i) {
indicies[i] = i;
}
splits.reserve(2);
splits.push_back(0);
splits.push_back(samples.size());
size_t divisions = std::ceil(std::sqrt(target_sample_count) / 2.);
if (divisions == 0) {
throw std::runtime_error("Math failure, unreachable!");
}
if (divisions > samples.size()) {
// Handle cases where there are few samples.
return;
}
// Recursively split samples first 50% / 50% in x direction, and then
// 50% / 50% in y direction. Repeat until the bucket is smaller than
// kMinSplit or the samples have been divided `divisions` times.
std::vector<size_t> new_splits;
for (size_t division_count = 0; division_count < divisions; ++division_count) {
new_splits.clear();
new_splits.push_back(0);
for (size_t i = 0; i < splits.size() - 1; ++i) {
size_t split_begin = splits.at(i);
size_t split_end = splits.at(i + 1);
if (split_end > indicies.size()) {
throw std::runtime_error("split_end is not valid!");
}
if (split_begin >= split_end) {
throw std::runtime_error("Invalid split from earlier pass!");
}
std::vector<size_t>::iterator begin = indicies.begin() + split_begin;
std::vector<size_t>::iterator end = indicies.begin() + split_end;
if (std::distance(begin, end) < kMinSplit) {
add_split(&new_splits, split_begin);
continue;
}
// Try to split samples 50/50 in x direction.
size_t split = partition_x(begin, end, samples);
// Try to split samples 50/50 in y direction after the x split.
size_t split_y1 = partition_y(begin, begin + split, samples);
size_t split_y2 = partition_y(begin + split, end, samples);
// Because the y2 split starts at split, add it here.
split_y2 += split;
add_split(&new_splits, split_begin);
add_split(&new_splits, split_begin + split_y1);
add_split(&new_splits, split_begin + split);
add_split(&new_splits, split_begin + split_y2);
}
add_split(&new_splits, samples.size());
if (new_splits.front() != 0) {
throw std::runtime_error("Split must start at 0");
}
if (new_splits.back() != samples.size()) {
throw std::runtime_error("Split must end at last element");
}
for (size_t i = 0; i < new_splits.size() - 1; ++i) {
if (new_splits[i] >= new_splits[i + 1]) {
throw std::runtime_error("Split indicies must be increasing");
}
}
std::swap(splits, new_splits);
}
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,69 @@
/*
* 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.
*
*/
#ifndef SAMPLER_H_
#define SAMPLER_H_
#include <cstdint>
#include <functional>
#include <stdexcept>
#include <vector>
#include "nextpnr_namespaces.h"
NEXTPNR_NAMESPACE_BEGIN
// Given a set of coordinates, generates random samples that are geometric
// distributed.
struct Sampler
{
void divide_samples(size_t target_sample_count, const std::vector<std::pair<int32_t, int32_t>> &samples);
size_t number_of_regions() const { return splits.size() - 1; }
size_t get_sample_from_region(size_t region, std::function<int32_t()> rng) const
{
if (region >= (splits.size() - 1)) {
throw std::runtime_error("region out of range");
}
size_t split_begin = splits[region];
size_t split_end = splits[region + 1];
if (split_begin == split_end) {
throw std::runtime_error("Splits should never be empty!");
}
// Pick a random element from that region.
return indicies[split_begin + (rng() % (split_end - split_begin))];
}
size_t get_sample(std::function<int32_t()> rng) const
{
size_t region = rng() % number_of_regions();
return get_sample_from_region(region, rng);
}
std::vector<size_t> indicies;
std::vector<size_t> splits;
};
NEXTPNR_NAMESPACE_END
#endif /* SAMPLER_H_ */

View File

@ -0,0 +1,83 @@
/*
* 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 "type_wire.h"
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
TypeWireId::TypeWireId(const Context *ctx, WireId wire_inst)
{
NPNR_ASSERT(wire_inst != WireId());
if (wire_inst.tile == -1) {
auto &tile_wire = ctx->chip_info->nodes[wire_inst.index].tile_wires[0];
type = ctx->chip_info->tiles[tile_wire.tile].type;
index = tile_wire.index;
} else {
type = ctx->chip_info->tiles[wire_inst.tile].type;
index = wire_inst.index;
}
}
TypeWireSet::TypeWireSet(const Context *ctx, WireId wire)
{
if (wire.tile == -1) {
const auto &node_data = ctx->chip_info->nodes[wire.index];
wire_types_.reserve(node_data.tile_wires.size());
for (const auto &tile_wire : node_data.tile_wires) {
wire_types_.emplace_back();
wire_types_.back().type = ctx->chip_info->tiles[tile_wire.tile].type;
wire_types_.back().index = tile_wire.index;
}
} else {
TypeWireId wire_type(ctx, wire);
wire_types_.push_back(wire_type);
}
std::sort(wire_types_.begin(), wire_types_.end());
hash_ = 0;
boost::hash_combine(hash_, std::hash<size_t>()(wire_types_.size()));
for (const auto &wire : wire_types_) {
boost::hash_combine(hash_, std::hash<NEXTPNR_NAMESPACE_PREFIX TypeWireId>()(wire));
}
}
TypeWireId::TypeWireId(lookahead_storage::TypeWireId::Reader reader) : type(reader.getType()), index(reader.getIndex())
{
}
void TypeWireId::to_builder(lookahead_storage::TypeWireId::Builder builder) const
{
builder.setType(type);
builder.setIndex(index);
}
TypeWirePair::TypeWirePair(lookahead_storage::TypeWirePair::Reader reader) : src(reader.getSrc()), dst(reader.getDst())
{
}
void TypeWirePair::to_builder(lookahead_storage::TypeWirePair::Builder builder) const
{
src.to_builder(builder.getSrc());
dst.to_builder(builder.getDst());
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,111 @@
/*
* 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.
*
*/
#ifndef TYPE_WIRE_H
#define TYPE_WIRE_H
#include <algorithm>
#include <vector>
#include "nextpnr_namespaces.h"
#include "nextpnr_types.h"
#include "lookahead.capnp.h"
NEXTPNR_NAMESPACE_BEGIN
struct Context;
struct TypeWireId
{
TypeWireId() : type(-1), index(-1) {}
TypeWireId(const Context *ctx, WireId wire_inst);
explicit TypeWireId(lookahead_storage::TypeWireId::Reader reader);
void to_builder(lookahead_storage::TypeWireId::Builder builder) const;
bool operator==(const TypeWireId &other) const { return type == other.type && index == other.index; }
bool operator!=(const TypeWireId &other) const { return type != other.type || index != other.index; }
bool operator<(const TypeWireId &other) const
{
return type < other.type || (type == other.type && index < other.index);
}
int32_t type;
int32_t index;
};
struct TypeWirePair
{
TypeWireId src;
TypeWireId dst;
TypeWirePair() = default;
explicit TypeWirePair(lookahead_storage::TypeWirePair::Reader reader);
void to_builder(lookahead_storage::TypeWirePair::Builder builder) const;
bool operator==(const TypeWirePair &other) const { return src == other.src && dst == other.dst; }
bool operator!=(const TypeWirePair &other) const { return src != other.src || dst != other.dst; }
};
struct TypeWireSet
{
public:
TypeWireSet(const Context *ctx, WireId wire);
std::size_t hash() const { return hash_; }
bool operator==(const TypeWireSet &other) const { return wire_types_ == other.wire_types_; }
bool operator!=(const TypeWireSet &other) const { return wire_types_ != other.wire_types_; }
private:
std::size_t hash_;
std::vector<TypeWireId> wire_types_;
};
NEXTPNR_NAMESPACE_END
template <> struct std::hash<NEXTPNR_NAMESPACE_PREFIX TypeWireId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX TypeWireId &wire) const noexcept
{
std::size_t seed = 0;
boost::hash_combine(seed, std::hash<int>()(wire.type));
boost::hash_combine(seed, std::hash<int>()(wire.index));
return seed;
}
};
template <> struct std::hash<NEXTPNR_NAMESPACE_PREFIX TypeWirePair>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX TypeWirePair &pair) const noexcept
{
std::size_t seed = 0;
boost::hash_combine(seed, std::hash<NEXTPNR_NAMESPACE_PREFIX TypeWireId>()(pair.src));
boost::hash_combine(seed, std::hash<NEXTPNR_NAMESPACE_PREFIX TypeWireId>()(pair.dst));
return seed;
}
};
template <> struct std::hash<NEXTPNR_NAMESPACE_PREFIX TypeWireSet>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX TypeWireSet &set) const noexcept { return set.hash(); }
};
#endif /* TYPE_WIRE_H */