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:
Keith Rothman 2021-03-22 17:46:00 -07:00
parent 9ef412c2cc
commit 8d1eb0a195
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()
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}")

View File

@ -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);

View File

@ -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

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/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()

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()("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
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 */