Skip to content

Commit

Permalink
phetdp: new placer
Browse files Browse the repository at this point in the history
  • Loading branch information
Ravenslofty committed Sep 30, 2023
1 parent e8602fb commit 04d9925
Show file tree
Hide file tree
Showing 3 changed files with 378 additions and 1 deletion.
344 changes: 344 additions & 0 deletions common/place/placer_phetdp.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,344 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2023 Hannah Ravensloft <[email protected]>
*
* 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 "placer_phetdp.h"
#include <algorithm>
#include <chrono>
#include <vector>
#include "hashlib.h"
#include "nextpnr.h"

NEXTPNR_NAMESPACE_BEGIN

struct GridSpace {
GridSpace(int x, int y) : x(x), y(y) {}
GridSpace(Loc loc) : x(loc.x), y(loc.y) {}
int x, y;
};

struct BinSpace {
BinSpace(int x, int y) : x(x), y(y) {
NPNR_ASSERT(x >= 0 && x < 12);
NPNR_ASSERT(y >= 0 && y < 12);
}
BinSpace(Context *ctx, GridSpace grid) {
x = grid.x * 12 / ctx->getGridDimX();
y = grid.y * 12 / ctx->getGridDimY();
}
int x, y;
};

class GlobalBin {
public:
GlobalBin(size_t capacity = 1250) : capacity{capacity}, conns{}, nets{} {}

// The amount of available space in this bin.
int whitespace() const {
return int(capacity) - int(nets.size());
}

// The number of edges
// Confusingly, this term is e_uv in Formula (2), but also `c_x <- (n_i ∩ n_j)` in Formula (3).
int edge_count(const NetInfo *candidate) const {
auto edges = 0;
auto result = conns.find(candidate->name);
if (result != conns.end())
edges += result->second;
for (const auto port : candidate->users) {
result = conns.find(port.cell->name);
if (result != conns.end())
edges += result->second;
}
return edges;
}

// Add a net to this bin.
void insert_net(NetInfo* net) {
nets.push_back(net);
build_connectivity_for_net(net);
}

// Formula (2), which scores a net for this bin based on its connectivity or free space.
// `(1 + edge_count(net))` is used to work around `edge_count(net) == 0` leading to whitespace
// being ignored.
// TODO: this is more or less `gamma(net) * whitespace()`.
int connectivity(const NetInfo *net) const {
return (1 + edge_count(net)) * whitespace() / (1 + net->users.entries());
}

// Formula (3), which scores how connected this net is to the other nets in this bin.
int gamma(const NetInfo* net) const {
return edge_count(net) / (1 + net->users.entries());
}

// Sort nets by their gamma score.
void sort() {
std::sort(nets.begin(), nets.end(), [&](const NetInfo* a, const NetInfo* b) {
return gamma(a) > gamma(b);
});
}

// Pop the lowest-gamma net from this bin.
NetInfo* pop_least_connected(int *net_gamma) {
if (nets.empty())
return nullptr;
auto net = nets.back();
*net_gamma = gamma(net);
nets.pop_back();
auto cell_name = net->driver.cell->name;
auto result1 = conns.find(cell_name);
if (result1 != conns.end())
result1->second--;
for (const auto port : net->users) {
auto result2 = conns.find(port.cell->name);
if (result2 != conns.end())
result2->second--;
}
return net;
}

private:
// Incrementally update conn when a new net is added.
void build_connectivity_for_net(const NetInfo *net) {
auto cell_name = net->driver.cell->name;
auto result1 = conns.insert({cell_name, 1});
if (!result1.second)
result1.first->second++;
for (const auto port : net->users) {
auto result2 = conns.insert({port.cell->name, 1});
if (!result2.second)
result2.first->second++;
}
}

size_t capacity;
dict<IdString, int> conns;
std::vector<NetInfo*> nets;
};


class GlobalBins {
public:
GlobalBins(Context *ctx) : ctx{ctx}, bins{12, std::vector<GlobalBin>(12)} {}

// Return the net with the highest connectivity score.
// TODO: can I turn this into a std::max_element call?
BinSpace highest_connectivity(NetInfo *const net) const {
auto best_score = -2147483648;
auto best_x = 0;
auto best_y = 0;
for (int x = 0; x < 12; x++) {
for (int y = 0; y < 12; y++) {
auto score = bins.at(x).at(y).connectivity(net);
if (score > best_score) {
best_score = score;
best_x = x;
best_y = y;
}
}
}
return BinSpace{best_x, best_y};
}

// Insert a net into a bin.
void insert_net(BinSpace bin, NetInfo* net) {
bins.at(bin.x).at(bin.y).insert_net(net);
}

// Reduce congestion by spreading cells with low connectivity into neighbouring cells.
void spread_whitespace() {
for (int x = 0; x < 12; x++) {
for (int y = 0; y < 12; y++) {
spread_bin(x, y);
}
}
}

// Display a heatmap of the whitespace in the bins.
void print_occupancy() const {
printf("\n");
for (int y = 11; y >= 0; y--) {
for (int x = 0; x < 12; x++) {
printf("%4d,", bins.at(x).at(y).whitespace());
}
printf("\n");
}
}

private:

// Spread a bin's least-connected cells to its neighbours to reduce peak congestion.
bool spread_bin(int x, int y) {
bool updated_design = false;
bool did_something = true;
while (did_something) {
bins.at(x).at(y).sort();
did_something = false;
int gamma = 0;
auto net = bins.at(x).at(y).pop_least_connected(&gamma);
auto best_x = 0;
auto best_y = 0;
auto best_score = 100000;
for (int x_offset = -1; x_offset <= +1; x_offset++) {
for (int y_offset = -1; y_offset <= +1; y_offset++) {
int new_x = x + x_offset;
int new_y = y + y_offset;
bool x_ok = new_x >= 0 && new_x < 12;
bool y_ok = new_y >= 0 && new_y < 12;
if (!x_ok || !y_ok || (new_x == x && new_y == y))
continue;
int score = (1250 - bins.at(new_x).at(new_y).whitespace()) + (1 - (std::abs(x_offset) + std::abs(y_offset)));
if (score < best_score) {
best_score = score;
best_x = new_x;
best_y = new_y;
}
}
}
if (best_score < (1251 - bins.at(x).at(y).whitespace())) {
bins.at(best_x).at(best_y).insert_net(net);
did_something = true;
updated_design = true;
} else {
bins.at(x).at(y).insert_net(net);
}
}
return updated_design;
}

Context *ctx;
std::vector<std::vector<GlobalBin>> bins;
};

class Phetdp {
public:
Phetdp(Context* ctx) : ctx(ctx), g{ctx} {}

void place() {
using std::chrono::high_resolution_clock;
using std::chrono::duration;
log_info("=== PHetDP START ===\n");
auto start_time = high_resolution_clock::now();
// Step 1: initial placement of fixed/constrained cells in global bins
initial_place_constraints();
auto post_initial_constraints = high_resolution_clock::now();
// Step 2: initial placement of unconstrained cells in global bins
initial_place_rest();
auto post_initial_rest = high_resolution_clock::now();
// Step 3: spreading of whitespace to reduce congestion
initial_spread_whitespace();
auto post_spread_whitespace = high_resolution_clock::now();
log_info("=== PHetDP FINISH ===\n");
log_info("initial placement:\n");
log_info(" initial_place_constraints(): %.02fs\n", duration<double>(post_initial_constraints - start_time).count());
log_info(" initial_place_rest(): %.02fs\n", duration<double>(post_initial_rest - post_initial_constraints).count());
log_info(" initial_spread_whitespace(): %.02fs\n", duration<double>(post_spread_whitespace - post_initial_rest).count());
NPNR_ASSERT_FALSE_STR("not yet implemented");
}

void initial_place_constraints() {
size_t placed_cells = 0;
for (auto &net_entry : ctx->nets) {
NetInfo *net = net_entry.second.get();
CellInfo *cell = net->driver.cell;
if (!cell)
continue; // maybe?
if (cell->isPseudo())
continue;
auto loc = cell->attrs.find(ctx->id("BEL"));
if (loc != cell->attrs.end()) {
std::string loc_name = loc->second.as_string();
BelId bel = ctx->getBelByNameStr(loc_name);
if (bel == BelId()) {
log_error("No Bel named \'%s\' located for "
"this chip (processing BEL attribute on \'%s\')\n",
loc_name.c_str(), cell->name.c_str(ctx));
}

if (!ctx->isValidBelForCellType(cell->type, bel)) {
IdString bel_type = ctx->getBelType(bel);
log_error("Bel \'%s\' of type \'%s\' does not match cell "
"\'%s\' of type \'%s\'\n",
loc_name.c_str(), bel_type.c_str(ctx), cell->name.c_str(ctx), cell->type.c_str(ctx));
}
auto bound_cell = ctx->getBoundBelCell(bel);
if (bound_cell) {
if (cell != bound_cell) {
log_error("Cell \'%s\' cannot be bound to bel \'%s\' since it is already bound to cell \'%s\'\n",
cell->name.c_str(ctx), loc_name.c_str(), bound_cell->name.c_str(ctx));
}
continue;
}

ctx->bindBel(bel, cell, STRENGTH_USER);
auto bel_loc = BinSpace{ctx, GridSpace{ctx->getBelLocation(bel)}};
g.insert_net(bel_loc, net);

if (!ctx->isBelLocationValid(bel, /* explain_invalid */ true)) {
IdString bel_type = ctx->getBelType(bel);
log_error("Bel \'%s\' of type \'%s\' is not valid for cell "
"\'%s\' of type \'%s\'\n",
loc_name.c_str(), bel_type.c_str(ctx), cell->name.c_str(ctx), cell->type.c_str(ctx));
}
placed_cells++;
}
}
log_info("Placed %d cells based on constraints.\n", int(placed_cells));
log_info("after fixed initial placement:\n");
g.print_occupancy();
}

void initial_place_rest() {
size_t placed_cells = 0;
for (auto &net_entry : ctx->nets) {
NetInfo *net = net_entry.second.get();
CellInfo *cell = net->driver.cell;
if (!cell)
continue; // maybe?
if (cell->isPseudo())
continue;

// Fixed constraints are handled in initial_place_constraints().
auto loc = cell->attrs.find(ctx->id("BEL"));
if (loc != cell->attrs.end())
continue;

g.insert_net(g.highest_connectivity(net), net);
placed_cells++;
}
log_info("Binned %d cells.\n", int(placed_cells));
log_info("after connectivity-based initial placement:\n");
g.print_occupancy();
}

void initial_spread_whitespace() {
g.spread_whitespace();
log_info("after whitespace spreading:\n");
g.print_occupancy();
}
private:
Context* ctx;

GlobalBins g;
};

void placer_phetdp(Context* ctx) {
Phetdp{ctx}.place();
}

NEXTPNR_NAMESPACE_END
30 changes: 30 additions & 0 deletions common/place/placer_phetdp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2023 Hannah Ravensloft <[email protected]>
*
* 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 PLACER_PHETDP_H
#define PLACER_PHETDP_H
#include "log.h"
#include "nextpnr.h"

NEXTPNR_NAMESPACE_BEGIN

extern void placer_phetdp(Context *ctx);

NEXTPNR_NAMESPACE_END

#endif
Loading

0 comments on commit 04d9925

Please sign in to comment.