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>
This commit is contained in:
parent
9ef412c2cc
commit
8d1eb0a195
2
3rdparty/fpga-interchange-schema
vendored
2
3rdparty/fpga-interchange-schema
vendored
@ -1 +1 @@
|
||||
Subproject commit cb6d16847dfca7f104205c83bbdc056303ac82a0
|
||||
Subproject commit 8ec5b1739d7b91b84df3e92ccc70c7a7ee644089
|
@ -43,6 +43,10 @@ if(WASI)
|
||||
)
|
||||
else()
|
||||
set(USE_THREADS ON)
|
||||
find_package(TBB QUIET)
|
||||
if (TBB_FOUND)
|
||||
add_definitions(-DNEXTPNR_USE_TBB)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT USE_THREADS)
|
||||
@ -243,6 +247,9 @@ endif()
|
||||
if(PROFILER)
|
||||
list(APPEND EXTRA_LIB_DEPS profiler)
|
||||
endif()
|
||||
if(TBB_FOUND)
|
||||
list(APPEND EXTRA_LIB_DEPS tbb)
|
||||
endif()
|
||||
|
||||
foreach (family ${ARCH})
|
||||
message(STATUS "Configuring architecture: ${family}")
|
||||
|
@ -24,9 +24,11 @@
|
||||
#include <algorithm>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
#include <boost/uuid/detail/sha1.hpp>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <queue>
|
||||
|
||||
#include "constraints.impl.h"
|
||||
#include "fpga_interchange.h"
|
||||
#include "log.h"
|
||||
@ -42,6 +44,11 @@
|
||||
// Include tcl.h late because it messed with defines and let them leave the
|
||||
// scope of the header.
|
||||
#include <tcl.h>
|
||||
|
||||
//#define DEBUG_BINDING
|
||||
//#define USE_LOOKAHEAD
|
||||
//#define DEBUG_CELL_PIN_MAPPING
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
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 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)
|
||||
{
|
||||
try {
|
||||
@ -90,6 +113,8 @@ Arch::Arch(ArchArgs args) : args(args)
|
||||
if (args.chipdb.empty() || !blob_file.is_open())
|
||||
log_error("Unable to read chipdb %s\n", args.chipdb.c_str());
|
||||
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));
|
||||
} catch (...) {
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// FIXME: Implement something to push the A* router in the right direction.
|
||||
int src_x, src_y;
|
||||
get_tile_x_y(src.tile, &src_x, &src_y);
|
||||
|
||||
int dst_x, dst_y;
|
||||
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;
|
||||
#ifdef USE_LOOKAHEAD
|
||||
return lookahead.estimateDelay(getCtx(), src, dst);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
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); }
|
||||
|
||||
std::string Arch::get_chipdb_hash() const { return chipdb_hash; }
|
||||
|
||||
// Instance constraint templates.
|
||||
template void Arch::ArchConstraints::bindBel(Arch::ArchConstraints::TagState *, const Arch::ConstraintRange);
|
||||
template void Arch::ArchConstraints::unbindBel(Arch::ArchConstraints::TagState *, const Arch::ConstraintRange);
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "arch_iterators.h"
|
||||
#include "chipdb.h"
|
||||
#include "dedicated_interconnect.h"
|
||||
#include "lookahead.h"
|
||||
#include "site_router.h"
|
||||
#include "site_routing_cache.h"
|
||||
|
||||
@ -45,6 +46,8 @@ struct ArchArgs
|
||||
{
|
||||
std::string chipdb;
|
||||
std::string package;
|
||||
bool rebuild_lookahead;
|
||||
bool dont_write_lookahead;
|
||||
};
|
||||
|
||||
struct ArchRanges
|
||||
@ -1038,8 +1041,13 @@ struct Arch : ArchAPI<ArchRanges>
|
||||
std::regex verilog_hex_constant;
|
||||
void read_lut_equation(DynamicBitarray<> *equation, const Property &equation_parameter) const;
|
||||
bool route_vcc_to_unused_lut_pins();
|
||||
|
||||
Lookahead lookahead;
|
||||
mutable RouteNodeStorage node_storage;
|
||||
mutable SiteRoutingCache site_routing_cache;
|
||||
|
||||
std::string chipdb_hash;
|
||||
std::string get_chipdb_hash() const;
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
400
fpga_interchange/cost_map.cc
Normal file
400
fpga_interchange/cost_map.cc
Normal 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
|
68
fpga_interchange/cost_map.h
Normal file
68
fpga_interchange/cost_map.h
Normal 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 */
|
@ -24,14 +24,31 @@ add_custom_target(all-${family}-archcheck-tests)
|
||||
add_subdirectory(${family}/examples/devices)
|
||||
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})
|
||||
target_include_directories(${target} PRIVATE ${TCL_INCLUDE_PATH})
|
||||
target_link_libraries(${target} PRIVATE ${TCL_LIBRARY})
|
||||
target_link_libraries(${target} PRIVATE fpga_interchange_capnp)
|
||||
target_link_libraries(${target} PRIVATE extra_capnp)
|
||||
target_link_libraries(${target} PRIVATE z)
|
||||
endforeach()
|
||||
|
||||
if(BUILD_GUI)
|
||||
target_link_libraries(gui_${family} fpga_interchange_capnp)
|
||||
target_link_libraries(gui_${family} extra_capnp)
|
||||
target_link_libraries(gui_${family} z)
|
||||
endif()
|
||||
|
61
fpga_interchange/lookahead.capnp
Normal file
61
fpga_interchange/lookahead.capnp
Normal 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;
|
||||
}
|
||||
|
1548
fpga_interchange/lookahead.cc
Normal file
1548
fpga_interchange/lookahead.cc
Normal file
File diff suppressed because it is too large
Load Diff
99
fpga_interchange/lookahead.h
Normal file
99
fpga_interchange/lookahead.h
Normal 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 */
|
@ -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()("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()("rebuild-lookahead", "Ignore lookahead cache and rebuild");
|
||||
specific.add_options()("dont-write-lookahead", "Don't write the lookahead file");
|
||||
|
||||
return specific;
|
||||
}
|
||||
@ -72,6 +74,9 @@ std::unique_ptr<Context> FpgaInterchangeCommandHandler::createContext(std::unord
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
ArchArgs chipArgs;
|
||||
chipArgs.rebuild_lookahead = vm.count("rebuild_lookahead") != 0;
|
||||
chipArgs.dont_write_lookahead = vm.count("dont_write_lookahead") != 0;
|
||||
|
||||
if (!vm.count("chipdb")) {
|
||||
log_error("chip database binary must be provided\n");
|
||||
}
|
||||
|
174
fpga_interchange/sampler.cc
Normal file
174
fpga_interchange/sampler.cc
Normal 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
|
69
fpga_interchange/sampler.h
Normal file
69
fpga_interchange/sampler.h
Normal 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_ */
|
83
fpga_interchange/type_wire.cc
Normal file
83
fpga_interchange/type_wire.cc
Normal 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
|
111
fpga_interchange/type_wire.h
Normal file
111
fpga_interchange/type_wire.h
Normal 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 */
|
Loading…
Reference in New Issue
Block a user