2018-07-23 20:03:23 +08:00
|
|
|
/*
|
|
|
|
* nextpnr -- Next Generation Place and Route
|
|
|
|
*
|
|
|
|
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
|
|
|
|
*
|
|
|
|
* 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 "log.h"
|
2018-07-24 21:45:49 +08:00
|
|
|
#include "nextpnr.h"
|
2018-07-23 20:03:23 +08:00
|
|
|
|
|
|
|
#if 0
|
|
|
|
#define dbg(...) log(__VA_ARGS__)
|
|
|
|
#else
|
|
|
|
#define dbg(...)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
USING_NEXTPNR_NAMESPACE
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
void archcheck_names(const Context *ctx)
|
|
|
|
{
|
|
|
|
log_info("Checking entity names.\n");
|
|
|
|
|
|
|
|
log_info("Checking bel names..\n");
|
|
|
|
for (BelId bel : ctx->getBels()) {
|
2021-01-29 20:12:12 +08:00
|
|
|
IdStringList name = ctx->getBelName(bel);
|
2018-07-23 20:03:23 +08:00
|
|
|
BelId bel2 = ctx->getBelByName(name);
|
2021-01-28 22:38:20 +08:00
|
|
|
if (bel != bel2) {
|
2021-01-29 20:12:12 +08:00
|
|
|
log_error("bel != bel2, name = %s\n", ctx->nameOfBel(bel));
|
2021-01-28 10:14:09 +08:00
|
|
|
}
|
2018-07-23 20:03:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
log_info("Checking wire names..\n");
|
|
|
|
for (WireId wire : ctx->getWires()) {
|
2021-01-29 21:30:56 +08:00
|
|
|
IdStringList name = ctx->getWireName(wire);
|
2018-07-23 20:03:23 +08:00
|
|
|
WireId wire2 = ctx->getWireByName(name);
|
2021-01-28 22:38:20 +08:00
|
|
|
if (wire != wire2) {
|
2021-01-29 20:58:41 +08:00
|
|
|
log_error("wire != wire2, name = %s\n", ctx->nameOfWire(wire));
|
2021-01-28 10:14:09 +08:00
|
|
|
}
|
2018-07-23 20:03:23 +08:00
|
|
|
}
|
2021-01-29 08:48:22 +08:00
|
|
|
|
2021-01-30 06:55:10 +08:00
|
|
|
log_info("Checking bucket names..\n");
|
2021-02-02 06:28:32 +08:00
|
|
|
for (BelBucketId bucket : ctx->getBelBuckets()) {
|
2021-01-30 06:55:10 +08:00
|
|
|
IdString name = ctx->getBelBucketName(bucket);
|
|
|
|
BelBucketId bucket2 = ctx->getBelBucketByName(name);
|
|
|
|
if (bucket != bucket2) {
|
|
|
|
log_error("bucket != bucket2, name = %s\n", name.c_str(ctx));
|
2021-01-29 08:48:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-29 18:20:37 +08:00
|
|
|
#ifndef ARCH_ECP5
|
2018-07-23 20:03:23 +08:00
|
|
|
log_info("Checking pip names..\n");
|
|
|
|
for (PipId pip : ctx->getPips()) {
|
2021-01-29 21:30:56 +08:00
|
|
|
IdStringList name = ctx->getPipName(pip);
|
2018-07-23 20:03:23 +08:00
|
|
|
PipId pip2 = ctx->getPipByName(name);
|
2021-01-28 22:38:20 +08:00
|
|
|
if (pip != pip2) {
|
2021-01-29 20:58:41 +08:00
|
|
|
log_error("pip != pip2, name = %s\n", ctx->nameOfPip(pip));
|
2021-01-28 10:14:09 +08:00
|
|
|
}
|
2018-07-23 20:03:23 +08:00
|
|
|
}
|
2020-06-29 18:20:37 +08:00
|
|
|
#endif
|
2018-07-23 20:03:23 +08:00
|
|
|
log_break();
|
|
|
|
}
|
|
|
|
|
|
|
|
void archcheck_locs(const Context *ctx)
|
|
|
|
{
|
|
|
|
log_info("Checking location data.\n");
|
|
|
|
|
|
|
|
log_info("Checking all bels..\n");
|
|
|
|
for (BelId bel : ctx->getBels()) {
|
|
|
|
log_assert(bel != BelId());
|
|
|
|
dbg("> %s\n", ctx->getBelName(bel).c_str(ctx));
|
|
|
|
|
|
|
|
Loc loc = ctx->getBelLocation(bel);
|
|
|
|
dbg(" ... %d %d %d\n", loc.x, loc.y, loc.z);
|
|
|
|
|
|
|
|
log_assert(0 <= loc.x);
|
|
|
|
log_assert(0 <= loc.y);
|
|
|
|
log_assert(0 <= loc.z);
|
|
|
|
log_assert(loc.x < ctx->getGridDimX());
|
|
|
|
log_assert(loc.y < ctx->getGridDimY());
|
2018-08-08 16:27:08 +08:00
|
|
|
log_assert(loc.z < ctx->getTileBelDimZ(loc.x, loc.y));
|
2018-07-23 20:03:23 +08:00
|
|
|
|
|
|
|
BelId bel2 = ctx->getBelByLocation(loc);
|
|
|
|
dbg(" ... %s\n", ctx->getBelName(bel2).c_str(ctx));
|
|
|
|
log_assert(bel == bel2);
|
|
|
|
}
|
|
|
|
|
|
|
|
log_info("Checking all locations..\n");
|
|
|
|
for (int x = 0; x < ctx->getGridDimX(); x++)
|
2018-07-24 21:45:49 +08:00
|
|
|
for (int y = 0; y < ctx->getGridDimY(); y++) {
|
2018-07-23 20:03:23 +08:00
|
|
|
dbg("> %d %d\n", x, y);
|
|
|
|
std::unordered_set<int> usedz;
|
|
|
|
|
2018-08-08 16:27:08 +08:00
|
|
|
for (int z = 0; z < ctx->getTileBelDimZ(x, y); z++) {
|
2018-07-23 20:03:23 +08:00
|
|
|
BelId bel = ctx->getBelByLocation(Loc(x, y, z));
|
|
|
|
if (bel == BelId())
|
|
|
|
continue;
|
|
|
|
Loc loc = ctx->getBelLocation(bel);
|
2021-01-29 20:58:41 +08:00
|
|
|
dbg(" + %d %s\n", z, ctx->nameOfBel(bel));
|
2018-07-23 20:03:23 +08:00
|
|
|
log_assert(x == loc.x);
|
|
|
|
log_assert(y == loc.y);
|
|
|
|
log_assert(z == loc.z);
|
|
|
|
usedz.insert(z);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (BelId bel : ctx->getBelsByTile(x, y)) {
|
|
|
|
Loc loc = ctx->getBelLocation(bel);
|
2021-01-29 20:58:41 +08:00
|
|
|
dbg(" - %d %s\n", loc.z, ctx->nameOfBel(bel));
|
2018-07-23 20:03:23 +08:00
|
|
|
log_assert(x == loc.x);
|
|
|
|
log_assert(y == loc.y);
|
|
|
|
log_assert(usedz.count(loc.z));
|
|
|
|
usedz.erase(loc.z);
|
|
|
|
}
|
|
|
|
|
|
|
|
log_assert(usedz.empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
log_break();
|
|
|
|
}
|
|
|
|
|
2021-02-04 05:45:47 +08:00
|
|
|
// Implements a LRU cache for pip to wire via getPipsDownhill/getPipsUphill.
|
|
|
|
//
|
|
|
|
// This allows a fast way to check getPipsDownhill/getPipsUphill from getPips,
|
|
|
|
// without balloning memory usage.
|
2021-02-05 06:47:45 +08:00
|
|
|
struct LruWireCacheMap
|
|
|
|
{
|
|
|
|
LruWireCacheMap(const Context *ctx, size_t cache_size) : ctx(ctx), cache_size(cache_size)
|
|
|
|
{
|
2021-02-04 05:45:47 +08:00
|
|
|
cache_hits = 0;
|
|
|
|
cache_misses = 0;
|
|
|
|
cache_evictions = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const Context *ctx;
|
|
|
|
size_t cache_size;
|
|
|
|
|
|
|
|
// Cache stats for checking on cache behavior.
|
|
|
|
size_t cache_hits;
|
|
|
|
size_t cache_misses;
|
|
|
|
size_t cache_evictions;
|
|
|
|
|
|
|
|
// Most recent accessed wires are added to the back of the list, front of
|
|
|
|
// list is oldest wire in cache.
|
|
|
|
std::list<WireId> last_access_list;
|
|
|
|
// Quick wire -> list element lookup.
|
|
|
|
std::unordered_map<WireId, std::list<WireId>::iterator> last_access_map;
|
|
|
|
|
|
|
|
std::unordered_map<PipId, WireId> pips_downhill;
|
|
|
|
std::unordered_map<PipId, WireId> pips_uphill;
|
|
|
|
|
2021-02-05 06:47:45 +08:00
|
|
|
void removeWireFromCache(WireId wire_to_remove)
|
|
|
|
{
|
2021-02-04 05:45:47 +08:00
|
|
|
for (PipId pip : ctx->getPipsDownhill(wire_to_remove)) {
|
|
|
|
log_assert(pips_downhill.erase(pip) == 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (PipId pip : ctx->getPipsUphill(wire_to_remove)) {
|
|
|
|
log_assert(pips_uphill.erase(pip) == 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-05 06:47:45 +08:00
|
|
|
void addWireToCache(WireId wire)
|
|
|
|
{
|
2021-02-04 05:45:47 +08:00
|
|
|
for (PipId pip : ctx->getPipsDownhill(wire)) {
|
|
|
|
auto result = pips_downhill.emplace(pip, wire);
|
|
|
|
log_assert(result.second);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (PipId pip : ctx->getPipsUphill(wire)) {
|
|
|
|
auto result = pips_uphill.emplace(pip, wire);
|
|
|
|
log_assert(result.second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-05 06:47:45 +08:00
|
|
|
void populateCache(WireId wire)
|
|
|
|
{
|
2021-02-04 05:45:47 +08:00
|
|
|
// Put this wire at the end of last_access_list.
|
|
|
|
auto iter = last_access_list.emplace(last_access_list.end(), wire);
|
|
|
|
last_access_map.emplace(wire, iter);
|
|
|
|
|
2021-02-05 06:47:45 +08:00
|
|
|
if (last_access_list.size() > cache_size) {
|
2021-02-04 05:45:47 +08:00
|
|
|
// Cache is full, remove front of last_access_list.
|
|
|
|
cache_evictions += 1;
|
|
|
|
WireId wire_to_remove = last_access_list.front();
|
|
|
|
last_access_list.pop_front();
|
|
|
|
log_assert(last_access_map.erase(wire_to_remove) == 1);
|
|
|
|
|
|
|
|
removeWireFromCache(wire_to_remove);
|
|
|
|
}
|
|
|
|
|
|
|
|
addWireToCache(wire);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine if wire is in the cache. If wire is not in the cache,
|
|
|
|
// adds the wire to the cache, and potentially evicts the oldest wire if
|
|
|
|
// cache is now full.
|
2021-02-05 06:47:45 +08:00
|
|
|
void checkCache(WireId wire)
|
|
|
|
{
|
2021-02-04 05:45:47 +08:00
|
|
|
auto iter = last_access_map.find(wire);
|
2021-02-05 06:47:45 +08:00
|
|
|
if (iter == last_access_map.end()) {
|
2021-02-04 05:45:47 +08:00
|
|
|
cache_misses += 1;
|
|
|
|
populateCache(wire);
|
|
|
|
} else {
|
|
|
|
// Record that this wire has been accessed.
|
|
|
|
cache_hits += 1;
|
|
|
|
last_access_list.splice(last_access_list.end(), last_access_list, iter->second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true if pip is uphill of wire (e.g. pip in getPipsUphill(wire)).
|
2021-02-05 06:47:45 +08:00
|
|
|
bool isPipUphill(PipId pip, WireId wire)
|
|
|
|
{
|
2021-02-04 05:45:47 +08:00
|
|
|
checkCache(wire);
|
|
|
|
return pips_uphill.at(pip) == wire;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true if pip is downhill of wire (e.g. pip in getPipsDownhill(wire)).
|
2021-02-05 06:47:45 +08:00
|
|
|
bool isPipDownhill(PipId pip, WireId wire)
|
|
|
|
{
|
2021-02-04 05:45:47 +08:00
|
|
|
checkCache(wire);
|
|
|
|
return pips_downhill.at(pip) == wire;
|
|
|
|
}
|
|
|
|
|
2021-02-05 06:47:45 +08:00
|
|
|
void cache_info() const
|
|
|
|
{
|
2021-02-04 05:45:47 +08:00
|
|
|
log_info("Cache hits: %zu\n", cache_hits);
|
|
|
|
log_info("Cache misses: %zu\n", cache_misses);
|
|
|
|
log_info("Cache evictions: %zu\n", cache_evictions);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-07-23 20:03:23 +08:00
|
|
|
void archcheck_conn(const Context *ctx)
|
|
|
|
{
|
|
|
|
log_info("Checking connectivity data.\n");
|
|
|
|
|
2021-01-28 10:14:09 +08:00
|
|
|
log_info("Checking all wires...\n");
|
|
|
|
|
2021-01-28 22:38:20 +08:00
|
|
|
for (WireId wire : ctx->getWires()) {
|
|
|
|
for (BelPin belpin : ctx->getWireBelPins(wire)) {
|
2021-01-28 10:14:09 +08:00
|
|
|
WireId wire2 = ctx->getBelPinWire(belpin.bel, belpin.pin);
|
|
|
|
log_assert(wire == wire2);
|
|
|
|
}
|
|
|
|
|
2021-01-28 22:38:20 +08:00
|
|
|
for (PipId pip : ctx->getPipsDownhill(wire)) {
|
2021-01-28 10:14:09 +08:00
|
|
|
WireId wire2 = ctx->getPipSrcWire(pip);
|
|
|
|
log_assert(wire == wire2);
|
|
|
|
}
|
|
|
|
|
2021-01-28 22:38:20 +08:00
|
|
|
for (PipId pip : ctx->getPipsUphill(wire)) {
|
2021-01-28 10:14:09 +08:00
|
|
|
WireId wire2 = ctx->getPipDstWire(pip);
|
|
|
|
log_assert(wire == wire2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log_info("Checking all BELs...\n");
|
|
|
|
for (BelId bel : ctx->getBels()) {
|
2021-01-28 22:38:20 +08:00
|
|
|
for (IdString pin : ctx->getBelPins(bel)) {
|
2021-01-28 10:14:09 +08:00
|
|
|
WireId wire = ctx->getBelPinWire(bel, pin);
|
|
|
|
|
2021-01-28 22:38:20 +08:00
|
|
|
if (wire == WireId()) {
|
2021-01-28 10:14:09 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool found_belpin = false;
|
2021-01-28 22:38:20 +08:00
|
|
|
for (BelPin belpin : ctx->getWireBelPins(wire)) {
|
|
|
|
if (belpin.bel == bel && belpin.pin == pin) {
|
2021-01-28 10:14:09 +08:00
|
|
|
found_belpin = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log_assert(found_belpin);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-04 05:45:47 +08:00
|
|
|
// This cache is used to meet two goals:
|
|
|
|
// - Avoid linear scan by invoking getPipsDownhill/getPipsUphill directly.
|
|
|
|
// - Avoid having pip -> wire maps for the entire part.
|
|
|
|
//
|
|
|
|
// The overhead of maintaining the cache is small relatively to the memory
|
|
|
|
// gains by avoiding the full pip -> wire map, and still preserves a fast
|
|
|
|
// pip -> wire, assuming that pips are returned from getPips with some
|
|
|
|
// chip locality.
|
2021-02-05 06:47:45 +08:00
|
|
|
LruWireCacheMap pip_cache(ctx, /*cache_size=*/64 * 1024);
|
2021-01-28 10:14:09 +08:00
|
|
|
log_info("Checking all PIPs...\n");
|
|
|
|
for (PipId pip : ctx->getPips()) {
|
|
|
|
WireId src_wire = ctx->getPipSrcWire(pip);
|
2021-01-28 22:38:20 +08:00
|
|
|
if (src_wire != WireId()) {
|
2021-02-04 05:45:47 +08:00
|
|
|
log_assert(pip_cache.isPipDownhill(pip, src_wire));
|
2021-01-28 10:14:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
WireId dst_wire = ctx->getPipDstWire(pip);
|
2021-01-28 22:38:20 +08:00
|
|
|
if (dst_wire != WireId()) {
|
2021-02-04 05:45:47 +08:00
|
|
|
log_assert(pip_cache.isPipUphill(pip, dst_wire));
|
2021-01-28 10:14:09 +08:00
|
|
|
}
|
2018-07-23 20:03:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-30 06:55:10 +08:00
|
|
|
void archcheck_buckets(const Context *ctx)
|
2021-01-29 08:48:22 +08:00
|
|
|
{
|
2021-01-30 06:55:10 +08:00
|
|
|
log_info("Checking bucket data.\n");
|
2021-01-29 08:48:22 +08:00
|
|
|
|
2021-01-30 06:55:10 +08:00
|
|
|
// BEL buckets should be subsets of BELs that form an exact cover.
|
|
|
|
// In particular that means cell types in a bucket should only be
|
|
|
|
// placable in that bucket.
|
2021-02-02 06:28:32 +08:00
|
|
|
for (BelBucketId bucket : ctx->getBelBuckets()) {
|
2021-01-29 08:48:22 +08:00
|
|
|
|
2021-01-30 06:55:10 +08:00
|
|
|
// Find out which cell types are in this bucket.
|
|
|
|
std::unordered_set<IdString> cell_types_in_bucket;
|
2021-02-02 06:28:32 +08:00
|
|
|
for (IdString cell_type : ctx->getCellTypes()) {
|
|
|
|
if (ctx->getBelBucketForCellType(cell_type) == bucket) {
|
2021-01-30 06:55:10 +08:00
|
|
|
cell_types_in_bucket.insert(cell_type);
|
2021-01-29 08:48:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-30 06:55:10 +08:00
|
|
|
// Make sure that all cell types in this bucket have at least one
|
2021-01-29 08:48:22 +08:00
|
|
|
// BelId they can be placed at.
|
|
|
|
std::unordered_set<IdString> cell_types_unused;
|
|
|
|
|
2021-01-30 06:55:10 +08:00
|
|
|
std::unordered_set<BelId> bels_in_bucket;
|
2021-02-02 06:28:32 +08:00
|
|
|
for (BelId bel : ctx->getBelsInBucket(bucket)) {
|
2021-01-30 06:55:10 +08:00
|
|
|
BelBucketId bucket2 = ctx->getBelBucketForBel(bel);
|
|
|
|
log_assert(bucket == bucket2);
|
2021-01-29 08:48:22 +08:00
|
|
|
|
2021-01-30 06:55:10 +08:00
|
|
|
bels_in_bucket.insert(bel);
|
2021-01-29 08:48:22 +08:00
|
|
|
|
2021-01-30 06:55:10 +08:00
|
|
|
// Check to see if a cell type not in this bucket can be
|
|
|
|
// placed at a BEL in this bucket.
|
2021-02-02 06:28:32 +08:00
|
|
|
for (IdString cell_type : ctx->getCellTypes()) {
|
|
|
|
if (ctx->getBelBucketForCellType(cell_type) == bucket) {
|
|
|
|
if (ctx->isValidBelForCellType(cell_type, bel)) {
|
2021-01-29 08:48:22 +08:00
|
|
|
cell_types_unused.erase(cell_type);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log_assert(!ctx->isValidBelForCellType(cell_type, bel));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-30 06:55:10 +08:00
|
|
|
// Verify that any BEL not in this bucket reports a different
|
|
|
|
// bucket.
|
2021-02-02 06:28:32 +08:00
|
|
|
for (BelId bel : ctx->getBels()) {
|
|
|
|
if (ctx->getBelBucketForBel(bel) != bucket) {
|
2021-01-30 06:55:10 +08:00
|
|
|
log_assert(bels_in_bucket.count(bel) == 0);
|
2021-01-29 08:48:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log_assert(cell_types_unused.empty());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-23 20:03:23 +08:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
NEXTPNR_NAMESPACE_BEGIN
|
|
|
|
|
|
|
|
void Context::archcheck() const
|
|
|
|
{
|
|
|
|
log_info("Running architecture database integrity check.\n");
|
|
|
|
log_break();
|
|
|
|
|
|
|
|
archcheck_names(this);
|
|
|
|
archcheck_locs(this);
|
|
|
|
archcheck_conn(this);
|
2021-01-30 06:55:10 +08:00
|
|
|
archcheck_buckets(this);
|
2018-07-23 20:03:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
NEXTPNR_NAMESPACE_END
|