Use a LRU cache for pip to wire map.
This avoids storing the entire pip to wire map in memory with a moderate runtime increase and a dramatic memory decrease (2 GB to 200 MB for A50T). Signed-off-by: Keith Rothman <537074+litghost@users.noreply.github.com>
This commit is contained in:
parent
155e0b9c42
commit
235a8e07e3
@ -131,14 +131,114 @@ void archcheck_locs(const Context *ctx)
|
|||||||
log_break();
|
log_break();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
struct LruWireCacheMap {
|
||||||
|
LruWireCacheMap(const Context *ctx, size_t cache_size) : ctx(ctx), cache_size(cache_size) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
void removeWireFromCache(WireId wire_to_remove) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addWireToCache(WireId wire) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void populateCache(WireId wire) {
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
if(last_access_list.size() > cache_size) {
|
||||||
|
// 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.
|
||||||
|
void checkCache(WireId wire) {
|
||||||
|
auto iter = last_access_map.find(wire);
|
||||||
|
if(iter == last_access_map.end()) {
|
||||||
|
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)).
|
||||||
|
bool isPipUphill(PipId pip, WireId wire) {
|
||||||
|
checkCache(wire);
|
||||||
|
return pips_uphill.at(pip) == wire;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if pip is downhill of wire (e.g. pip in getPipsDownhill(wire)).
|
||||||
|
bool isPipDownhill(PipId pip, WireId wire) {
|
||||||
|
checkCache(wire);
|
||||||
|
return pips_downhill.at(pip) == wire;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cache_info() const {
|
||||||
|
log_info("Cache hits: %zu\n", cache_hits);
|
||||||
|
log_info("Cache misses: %zu\n", cache_misses);
|
||||||
|
log_info("Cache evictions: %zu\n", cache_evictions);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
void archcheck_conn(const Context *ctx)
|
void archcheck_conn(const Context *ctx)
|
||||||
{
|
{
|
||||||
log_info("Checking connectivity data.\n");
|
log_info("Checking connectivity data.\n");
|
||||||
|
|
||||||
log_info("Checking all wires...\n");
|
log_info("Checking all wires...\n");
|
||||||
|
|
||||||
std::unordered_map<PipId, WireId> pips_downhill;
|
|
||||||
std::unordered_map<PipId, WireId> pips_uphill;
|
|
||||||
for (WireId wire : ctx->getWires()) {
|
for (WireId wire : ctx->getWires()) {
|
||||||
for (BelPin belpin : ctx->getWireBelPins(wire)) {
|
for (BelPin belpin : ctx->getWireBelPins(wire)) {
|
||||||
WireId wire2 = ctx->getBelPinWire(belpin.bel, belpin.pin);
|
WireId wire2 = ctx->getBelPinWire(belpin.bel, belpin.pin);
|
||||||
@ -148,17 +248,11 @@ void archcheck_conn(const Context *ctx)
|
|||||||
for (PipId pip : ctx->getPipsDownhill(wire)) {
|
for (PipId pip : ctx->getPipsDownhill(wire)) {
|
||||||
WireId wire2 = ctx->getPipSrcWire(pip);
|
WireId wire2 = ctx->getPipSrcWire(pip);
|
||||||
log_assert(wire == wire2);
|
log_assert(wire == wire2);
|
||||||
|
|
||||||
auto result = pips_downhill.emplace(pip, wire);
|
|
||||||
log_assert(result.second);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (PipId pip : ctx->getPipsUphill(wire)) {
|
for (PipId pip : ctx->getPipsUphill(wire)) {
|
||||||
WireId wire2 = ctx->getPipDstWire(pip);
|
WireId wire2 = ctx->getPipDstWire(pip);
|
||||||
log_assert(wire == wire2);
|
log_assert(wire == wire2);
|
||||||
|
|
||||||
auto result = pips_uphill.emplace(pip, wire);
|
|
||||||
log_assert(result.second);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,16 +277,25 @@ void archcheck_conn(const Context *ctx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
LruWireCacheMap pip_cache(ctx, /*cache_size=*/64*1024);
|
||||||
log_info("Checking all PIPs...\n");
|
log_info("Checking all PIPs...\n");
|
||||||
for (PipId pip : ctx->getPips()) {
|
for (PipId pip : ctx->getPips()) {
|
||||||
WireId src_wire = ctx->getPipSrcWire(pip);
|
WireId src_wire = ctx->getPipSrcWire(pip);
|
||||||
if (src_wire != WireId()) {
|
if (src_wire != WireId()) {
|
||||||
log_assert(pips_downhill.at(pip) == src_wire);
|
log_assert(pip_cache.isPipDownhill(pip, src_wire));
|
||||||
}
|
}
|
||||||
|
|
||||||
WireId dst_wire = ctx->getPipDstWire(pip);
|
WireId dst_wire = ctx->getPipDstWire(pip);
|
||||||
if (dst_wire != WireId()) {
|
if (dst_wire != WireId()) {
|
||||||
log_assert(pips_uphill.at(pip) == dst_wire);
|
log_assert(pip_cache.isPipUphill(pip, dst_wire));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user