mirror of https://github.com/YosysHQ/nextpnr.git
gatemate: Import old hypergraph code
Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
parent
2b560ad0cc
commit
8ec393fd47
|
|
@ -19,6 +19,7 @@ set(SOURCES
|
|||
pack_mult.cc
|
||||
pack_serdes.cc
|
||||
pack.h
|
||||
partition.cc
|
||||
pll.cc
|
||||
route_clock.cc
|
||||
route_mult.cc
|
||||
|
|
|
|||
|
|
@ -0,0 +1,754 @@
|
|||
/*
|
||||
* nextpnr -- Next Generation Place and Route
|
||||
*
|
||||
* Copyright (C) 2024 The Project Peppercorn 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 <boost/algorithm/string.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
#include "gatemate.h"
|
||||
|
||||
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
|
||||
#include "himbaechel_constids.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
namespace {
|
||||
|
||||
struct HypergraphEdge
|
||||
{
|
||||
std::vector<int> nodes;
|
||||
int weight = 1;
|
||||
};
|
||||
|
||||
struct HypergraphNode
|
||||
{
|
||||
std::vector<int> edges;
|
||||
int partition = -1;
|
||||
float area = 1;
|
||||
bool fixed = false;
|
||||
};
|
||||
|
||||
struct Hypergraph
|
||||
{
|
||||
std::vector<HypergraphEdge> edges;
|
||||
std::vector<HypergraphNode> nodes;
|
||||
void dump(std::ostream &out) const;
|
||||
void read(std::istream &in);
|
||||
};
|
||||
|
||||
struct PartitionConstraint
|
||||
{
|
||||
double min_nodes, max_nodes;
|
||||
};
|
||||
|
||||
struct gain_store
|
||||
{
|
||||
gain_store(int num_nodes) { node2element.resize(num_nodes); }
|
||||
|
||||
struct gain_element
|
||||
{
|
||||
int node;
|
||||
int fwd_ptr = -1;
|
||||
int bwd_ptr = -1;
|
||||
};
|
||||
struct gain_pointer
|
||||
{
|
||||
int gain;
|
||||
int elem_ptr = -1;
|
||||
};
|
||||
struct gain_bucket
|
||||
{
|
||||
int head_ptr = -1;
|
||||
int tail_ptr = -1;
|
||||
};
|
||||
std::map<int, gain_bucket> buckets;
|
||||
|
||||
void remove_elem(int gain, int elem_ptr)
|
||||
{
|
||||
auto &bucket = buckets.at(gain);
|
||||
auto &elem = elements.at(elem_ptr);
|
||||
if (elem.fwd_ptr == -1) {
|
||||
bucket.tail_ptr = elem.bwd_ptr;
|
||||
} else {
|
||||
elements.at(elem.fwd_ptr).bwd_ptr = elem.bwd_ptr;
|
||||
}
|
||||
if (elem.bwd_ptr == -1) {
|
||||
bucket.head_ptr = elem.fwd_ptr;
|
||||
} else {
|
||||
elements.at(elem.bwd_ptr).fwd_ptr = elem.fwd_ptr;
|
||||
}
|
||||
if (bucket.tail_ptr == -1) {
|
||||
NPNR_ASSERT(bucket.head_ptr == -1);
|
||||
buckets.erase(gain);
|
||||
}
|
||||
elem.fwd_ptr = next_free_elem;
|
||||
elem.bwd_ptr = -1;
|
||||
next_free_elem = elem_ptr;
|
||||
|
||||
node2element.at(elem.node).elem_ptr = -1;
|
||||
elem.node = -1;
|
||||
};
|
||||
|
||||
void add_elem(int gain, int node, bool at_start = true)
|
||||
{
|
||||
int elem_ptr = next_free_elem;
|
||||
if (elem_ptr == int(elements.size())) {
|
||||
elements.emplace_back();
|
||||
++next_free_elem;
|
||||
} else {
|
||||
next_free_elem = elements.at(elem_ptr).fwd_ptr;
|
||||
}
|
||||
auto &elem = elements.at(elem_ptr);
|
||||
auto &bucket = buckets[gain];
|
||||
elem.node = node;
|
||||
if (at_start) {
|
||||
elem.fwd_ptr = bucket.head_ptr;
|
||||
elem.bwd_ptr = -1;
|
||||
if (bucket.head_ptr != -1)
|
||||
elements.at(bucket.head_ptr).bwd_ptr = elem_ptr;
|
||||
if (bucket.tail_ptr == -1)
|
||||
bucket.tail_ptr = elem_ptr;
|
||||
bucket.head_ptr = elem_ptr;
|
||||
} else {
|
||||
elem.bwd_ptr = bucket.tail_ptr;
|
||||
elem.fwd_ptr = -1;
|
||||
if (bucket.tail_ptr != -1)
|
||||
elements.at(bucket.tail_ptr).fwd_ptr = elem_ptr;
|
||||
if (bucket.head_ptr == -1)
|
||||
bucket.head_ptr = elem_ptr;
|
||||
bucket.tail_ptr = elem_ptr;
|
||||
}
|
||||
node2element.at(node).gain = gain;
|
||||
node2element.at(node).elem_ptr = elem_ptr;
|
||||
};
|
||||
|
||||
std::pair<int, int> pop_node(bool lifo = true)
|
||||
{
|
||||
// Pop the node with the highest gain
|
||||
NPNR_ASSERT(!buckets.empty());
|
||||
auto highest = buckets.rbegin();
|
||||
int elem_ptr = lifo ? highest->second.head_ptr : highest->second.tail_ptr;
|
||||
int node = elements.at(elem_ptr).node;
|
||||
int gain = highest->first;
|
||||
remove_elem(gain, elem_ptr);
|
||||
return {node, gain};
|
||||
}
|
||||
|
||||
bool has_moves() { return !buckets.empty(); }
|
||||
|
||||
int node_gain(int node) const
|
||||
{
|
||||
auto &c = node2element.at(node);
|
||||
NPNR_ASSERT(c.elem_ptr != -1);
|
||||
return c.gain;
|
||||
}
|
||||
|
||||
void update_node(int node, int delta)
|
||||
{
|
||||
auto &c = node2element.at(node);
|
||||
int new_gain = c.gain + delta;
|
||||
NPNR_ASSERT(c.elem_ptr != -1);
|
||||
remove_elem(c.gain, c.elem_ptr);
|
||||
add_elem(new_gain, node);
|
||||
}
|
||||
|
||||
// The linked list storage (so we don't have to malloc all the time)
|
||||
std::vector<gain_element> elements;
|
||||
int next_free_elem = 0;
|
||||
// node to element map (indexed by node index)
|
||||
std::vector<gain_pointer> node2element;
|
||||
};
|
||||
|
||||
struct FMPartitioner
|
||||
{
|
||||
|
||||
FMPartitioner(Context *ctx, Hypergraph &g, const std::vector<PartitionConstraint> &partitions)
|
||||
: ctx(ctx), g(g), partitions(partitions), gains(g.nodes.size())
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
Context *ctx;
|
||||
Hypergraph &g;
|
||||
std::vector<PartitionConstraint> partitions;
|
||||
gain_store gains;
|
||||
std::vector<bool> locked;
|
||||
std::vector<double> part_area;
|
||||
|
||||
void init()
|
||||
{
|
||||
locked.resize(g.nodes.size(), false);
|
||||
part_area.resize(partitions.size(), 0);
|
||||
gain_store empty(g.nodes.size());
|
||||
std::swap(gains, empty);
|
||||
for (int i = 0; i < int(g.nodes.size()); i++) {
|
||||
auto &c = g.nodes.at(i);
|
||||
if (c.fixed) {
|
||||
locked.at(i) = true;
|
||||
NPNR_ASSERT(c.partition != -1);
|
||||
part_area.at(c.partition) += c.area;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline bool above_eq_target()
|
||||
{
|
||||
for (int i = 0; i < int(partitions.size()); i++)
|
||||
if (part_area.at(i) < partitions.at(i).min_nodes)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline double total_area_slack(bool max_area)
|
||||
{
|
||||
double slack = 0;
|
||||
for (int i = 0; i < int(partitions.size()); i++) {
|
||||
slack += part_area_slack(i, max_area);
|
||||
}
|
||||
return slack;
|
||||
}
|
||||
|
||||
inline double part_area_slack(int part, bool max_area)
|
||||
{
|
||||
if (max_area)
|
||||
return std::max<double>(0, partitions.at(part).max_nodes - part_area.at(part));
|
||||
else
|
||||
return std::max<double>(0, partitions.at(part).min_nodes - part_area.at(part));
|
||||
}
|
||||
|
||||
void random_part()
|
||||
{
|
||||
/*
|
||||
Perform a random initial partitioning
|
||||
Probabilities are weighted based on relative occupancy of paritions relative to occupancy constraints
|
||||
*/
|
||||
std::fill(part_area.begin(), part_area.end(), 0);
|
||||
for (int i = 0; i < int(g.nodes.size()); i++) {
|
||||
auto &c = g.nodes.at(i);
|
||||
if (c.fixed)
|
||||
part_area.at(c.partition) += c.area;
|
||||
}
|
||||
std::vector<std::pair<int, float>> sorted_by_area;
|
||||
|
||||
for (int i = 0; i < int(g.nodes.size()); i++) {
|
||||
auto &c = g.nodes.at(i);
|
||||
if (c.fixed)
|
||||
continue;
|
||||
sorted_by_area.emplace_back(i, c.area);
|
||||
}
|
||||
std::sort(sorted_by_area.begin(), sorted_by_area.end(),
|
||||
[&](std::pair<int, int> a, std::pair<int, int> b) { return a.second > b.second; });
|
||||
for (auto item : sorted_by_area) {
|
||||
auto &c = g.nodes.at(item.first);
|
||||
double r = ctx->rng() / double(0x3fffffff);
|
||||
double p = 0;
|
||||
c.partition = int(partitions.size()) - 1;
|
||||
|
||||
bool use_max_area = above_eq_target();
|
||||
double total_slack = total_area_slack(use_max_area);
|
||||
|
||||
for (int i = 0; i < int(partitions.size()) - 1; i++) {
|
||||
p += double(part_area_slack(i, use_max_area)) / std::max<double>(1, total_slack);
|
||||
if (r <= p) {
|
||||
c.partition = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
part_area.at(c.partition) += c.area;
|
||||
}
|
||||
}
|
||||
|
||||
int compute_cost()
|
||||
{
|
||||
int cost = 0;
|
||||
std::vector<bool> seen_parts(partitions.size(), false);
|
||||
for (int i = 0; i < int(g.edges.size()); i++) {
|
||||
std::fill(seen_parts.begin(), seen_parts.end(), false);
|
||||
auto &e = g.edges.at(i);
|
||||
for (int n : e.nodes)
|
||||
seen_parts.at(g.nodes.at(n).partition) = true;
|
||||
int num_parts = 0;
|
||||
for (bool b : seen_parts)
|
||||
if (b)
|
||||
++num_parts;
|
||||
if (num_parts == 0)
|
||||
continue;
|
||||
cost += e.weight * (num_parts - 1);
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
|
||||
double total_area()
|
||||
{
|
||||
double area = 0;
|
||||
for (int i = 0; i < int(g.nodes.size()); i++) {
|
||||
area += g.nodes.at(i).area;
|
||||
}
|
||||
return area;
|
||||
}
|
||||
|
||||
void coarsen(Hypergraph &coarsened, dict<int, std::vector<int>> &new2orig)
|
||||
{
|
||||
// This is a very basic algorithm for coarsening that probably doesn't give very good results
|
||||
// Need to find a better one that
|
||||
dict<int, int> orig2new;
|
||||
|
||||
double area_sum = 0;
|
||||
int area_count = 0;
|
||||
for (int i = 0; i < int(g.nodes.size()); i++) {
|
||||
auto &n = g.nodes.at(i);
|
||||
if (n.fixed || n.edges.empty())
|
||||
continue;
|
||||
++area_count;
|
||||
area_sum += n.area;
|
||||
}
|
||||
|
||||
double average_area = area_sum / area_count;
|
||||
|
||||
// Merge nodes
|
||||
std::vector<int> node_indices(g.nodes.size());
|
||||
for (int i = 0; i < int(g.nodes.size()); i++)
|
||||
node_indices.at(i) = i;
|
||||
ctx->sorted_shuffle(node_indices);
|
||||
for (int i : node_indices) {
|
||||
auto &n = g.nodes.at(i);
|
||||
if (n.fixed) {
|
||||
// Locked nodes are never merged
|
||||
coarsened.nodes.emplace_back();
|
||||
auto &n2 = coarsened.nodes.back();
|
||||
n2.fixed = n.fixed;
|
||||
n2.partition = n.partition;
|
||||
n2.area = n.area;
|
||||
new2orig[int(coarsened.nodes.size()) - 1].push_back(i);
|
||||
orig2new[i] = int(coarsened.nodes.size()) - 1;
|
||||
continue;
|
||||
}
|
||||
const int max_count = 2;
|
||||
const int area_ratio = 3;
|
||||
if (!n.edges.empty()) {
|
||||
dict<int, float> neighbours;
|
||||
for (int thresh = 20; thresh < 200; thresh *= 1.2) {
|
||||
for (int merge_edge : n.edges) {
|
||||
auto &e = g.edges.at(merge_edge);
|
||||
if (int(e.nodes.size()) > thresh)
|
||||
continue;
|
||||
for (int neighbour : e.nodes) {
|
||||
if (neighbour == i)
|
||||
continue;
|
||||
auto &merge_node_data = g.nodes.at(neighbour);
|
||||
if (merge_node_data.fixed)
|
||||
continue;
|
||||
if (orig2new.count(neighbour)) {
|
||||
if (orig2new.count(i))
|
||||
continue; // don't merge two clusters
|
||||
// Already a cluster
|
||||
int n2_idx = orig2new.at(neighbour);
|
||||
if ((coarsened.nodes.at(n2_idx).area + n.area) > (area_ratio * average_area))
|
||||
continue;
|
||||
if (int(new2orig.at(n2_idx).size()) >= (max_count - 1))
|
||||
continue;
|
||||
// Alias to the first node in the cluster
|
||||
neighbours[new2orig.at(n2_idx).front()] += (1.0f / sqrt(e.nodes.size()));
|
||||
} else if (orig2new.count(i)) {
|
||||
int n2_idx = orig2new.at(i);
|
||||
if ((coarsened.nodes.at(n2_idx).area + merge_node_data.area) >
|
||||
(area_ratio * average_area))
|
||||
continue;
|
||||
if (int(new2orig.at(n2_idx).size()) >= (max_count - 1))
|
||||
continue;
|
||||
neighbours[neighbour] += (1.0f / sqrt(e.nodes.size()));
|
||||
} else {
|
||||
neighbours[neighbour] += (1.0f / sqrt(e.nodes.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!neighbours.empty())
|
||||
break;
|
||||
}
|
||||
if (int(neighbours.size()) > 0) {
|
||||
auto best_neighbour =
|
||||
std::max_element(neighbours.begin(), neighbours.end(),
|
||||
[&](const std::pair<int, int> &a, const std::pair<int, int> &b) {
|
||||
return (a.second < b.second);
|
||||
});
|
||||
int merge_node = best_neighbour->first;
|
||||
auto &merge_node_data = g.nodes.at(merge_node);
|
||||
if (orig2new.count(i)) {
|
||||
int n2_idx = orig2new.at(i);
|
||||
coarsened.nodes.at(n2_idx).area += merge_node_data.area;
|
||||
new2orig[n2_idx].push_back(merge_node);
|
||||
orig2new[merge_node] = n2_idx;
|
||||
goto merged;
|
||||
} else if (orig2new.count(merge_node)) {
|
||||
// Already a cluster
|
||||
int n2_idx = orig2new.at(merge_node);
|
||||
coarsened.nodes.at(n2_idx).area += n.area;
|
||||
new2orig[n2_idx].push_back(i);
|
||||
orig2new[i] = n2_idx;
|
||||
goto merged;
|
||||
} else {
|
||||
// Create a cluster
|
||||
coarsened.nodes.emplace_back();
|
||||
auto &n2 = coarsened.nodes.back();
|
||||
n2.fixed = false;
|
||||
n2.partition = -1;
|
||||
n2.area = n.area + merge_node_data.area;
|
||||
|
||||
new2orig[int(coarsened.nodes.size()) - 1].push_back(i);
|
||||
new2orig[int(coarsened.nodes.size()) - 1].push_back(merge_node);
|
||||
orig2new[i] = int(coarsened.nodes.size()) - 1;
|
||||
orig2new[merge_node] = int(coarsened.nodes.size()) - 1;
|
||||
goto merged;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (0) {
|
||||
merged:
|
||||
continue;
|
||||
}
|
||||
// Didn't find anything to merge with
|
||||
if (!orig2new.count(i)) {
|
||||
coarsened.nodes.emplace_back();
|
||||
auto &n2 = coarsened.nodes.back();
|
||||
n2.fixed = n.fixed;
|
||||
n2.partition = n.partition;
|
||||
n2.area = n.area;
|
||||
new2orig[int(coarsened.nodes.size()) - 1].push_back(i);
|
||||
orig2new[i] = int(coarsened.nodes.size()) - 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Reconstruct edges
|
||||
std::unordered_set<int> seen_nodes;
|
||||
for (int i = 0; i < int(g.edges.size()); i++) {
|
||||
auto &e = g.edges.at(i);
|
||||
if (e.nodes.size() <= 1)
|
||||
continue;
|
||||
// Don't create a new edge if it now only connects to one node
|
||||
if (std::all_of(e.nodes.begin(), e.nodes.end(), [&](int n) { return n == e.nodes.at(0); }))
|
||||
continue;
|
||||
|
||||
int e2_idx = int(coarsened.edges.size());
|
||||
coarsened.edges.emplace_back();
|
||||
auto &e2 = coarsened.edges.back();
|
||||
e2.weight = e.weight;
|
||||
|
||||
seen_nodes.clear();
|
||||
for (auto n : e.nodes) {
|
||||
int n2 = orig2new.at(n);
|
||||
if (seen_nodes.count(n2))
|
||||
continue;
|
||||
seen_nodes.insert(n2);
|
||||
coarsened.nodes.at(n2).edges.push_back(e2_idx);
|
||||
e2.nodes.push_back(n2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void assert_area()
|
||||
{
|
||||
std::fill(part_area.begin(), part_area.end(), 0);
|
||||
for (int i = 0; i < int(g.nodes.size()); i++) {
|
||||
auto &c = g.nodes.at(i);
|
||||
part_area.at(c.partition) += c.area;
|
||||
}
|
||||
for (int i = 0; i < int(partitions.size()); i++)
|
||||
NPNR_ASSERT((part_area.at(i) >= partitions.at(i).min_nodes) &&
|
||||
(part_area.at(i) <= partitions.at(i).max_nodes));
|
||||
}
|
||||
|
||||
void uncoarsen(const Hypergraph &coarsened, const dict<int, std::vector<int>> &new2orig)
|
||||
{
|
||||
for (const auto &item : new2orig) {
|
||||
const auto &new_node = coarsened.nodes.at(item.first);
|
||||
for (int old_node_idx : item.second) {
|
||||
auto &old_node = g.nodes.at(old_node_idx);
|
||||
if (old_node.fixed)
|
||||
continue;
|
||||
old_node.partition = new_node.partition;
|
||||
}
|
||||
}
|
||||
std::fill(part_area.begin(), part_area.end(), 0);
|
||||
for (int i = 0; i < int(g.nodes.size()); i++) {
|
||||
auto &c = g.nodes.at(i);
|
||||
part_area.at(c.partition) += c.area;
|
||||
}
|
||||
}
|
||||
|
||||
void gain_update(int node, int src_part, int dst_part)
|
||||
{
|
||||
// Figure 13 - Pseudo-code for a faster gain update that takes advantage of special cases.
|
||||
auto &n = g.nodes.at(node);
|
||||
for (int e_idx : n.edges) {
|
||||
auto &e = g.edges.at(e_idx);
|
||||
if (e.nodes.size() == 2) {
|
||||
for (int n2_idx : e.nodes) {
|
||||
if (n2_idx == node)
|
||||
continue;
|
||||
if (locked.at(n2_idx))
|
||||
break;
|
||||
auto &n2 = g.nodes.at(n2_idx);
|
||||
if (n2.partition == src_part)
|
||||
gains.update_node(n2_idx, 2 * e.weight);
|
||||
else
|
||||
gains.update_node(n2_idx, -2 * e.weight);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (e.nodes.size() == 1)
|
||||
continue;
|
||||
int src_tally = 0;
|
||||
int dst_tally = 0;
|
||||
for (int n2_idx : e.nodes) {
|
||||
auto &n2 = g.nodes.at(n2_idx);
|
||||
if (n2.partition == src_part)
|
||||
++src_tally;
|
||||
if (n2.partition == dst_part)
|
||||
++dst_tally;
|
||||
}
|
||||
if (dst_tally == 0) {
|
||||
// This move is the first node on the edge to enter the dst partition
|
||||
for (int n2_idx : e.nodes) {
|
||||
if (n2_idx == node || locked.at(n2_idx))
|
||||
continue;
|
||||
gains.update_node(n2_idx, e.weight);
|
||||
}
|
||||
} else if (src_tally == 1) {
|
||||
// This move is the last node on the edge to leave the src partition
|
||||
for (int n2_idx : e.nodes) {
|
||||
if (n2_idx == node || locked.at(n2_idx))
|
||||
continue;
|
||||
gains.update_node(n2_idx, -e.weight);
|
||||
}
|
||||
} else {
|
||||
// None of the special cases apply
|
||||
for (int n2_idx : e.nodes) {
|
||||
if (n2_idx == node || locked.at(n2_idx))
|
||||
continue;
|
||||
auto &n2 = g.nodes.at(n2_idx);
|
||||
if (n2.partition == src_part && src_tally == 2) {
|
||||
// This other node is the last one left in the src partition
|
||||
// other than the one being moved
|
||||
gains.update_node(n2_idx, e.weight);
|
||||
}
|
||||
if (n2.partition == dst_part && dst_tally == 1) {
|
||||
// This other node is the only other one in the dst partition
|
||||
// other than the one being moved
|
||||
gains.update_node(n2_idx, -e.weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup_initial_gains()
|
||||
{
|
||||
// Setup the starting gains
|
||||
for (int i = 0; i < int(g.nodes.size()); i++) {
|
||||
auto &n = g.nodes.at(i);
|
||||
if (n.fixed)
|
||||
continue;
|
||||
int src_part = n.partition;
|
||||
int dst_part = 1 - n.partition;
|
||||
int gain = 0;
|
||||
for (int e_idx : n.edges) {
|
||||
auto &e = g.edges.at(e_idx);
|
||||
if (e.nodes.size() == 2) {
|
||||
// Special-casing for two-element nodes
|
||||
for (int n2_idx : e.nodes) {
|
||||
if (n2_idx == i)
|
||||
continue;
|
||||
if (locked.at(n2_idx))
|
||||
break;
|
||||
auto &n2 = g.nodes.at(n2_idx);
|
||||
if (n2.partition == src_part)
|
||||
gain -= e.weight; // now introducing a split
|
||||
else
|
||||
gain += e.weight; // now removing a split
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.nodes.size() == 1)
|
||||
continue;
|
||||
|
||||
int src_tally = 0;
|
||||
int dst_tally = 0;
|
||||
for (int n2_idx : e.nodes) {
|
||||
auto &n2 = g.nodes.at(n2_idx);
|
||||
if (n2.partition == src_part)
|
||||
++src_tally;
|
||||
if (n2.partition == dst_part)
|
||||
++dst_tally;
|
||||
}
|
||||
|
||||
if (src_tally == 1) {
|
||||
gain += e.weight; // now removing a split
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dst_tally == 0) {
|
||||
gain -= e.weight; // now introducing a split
|
||||
continue;
|
||||
}
|
||||
}
|
||||
gains.add_elem(gain, i);
|
||||
}
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
std::vector<std::pair<int, int>> moves_made;
|
||||
std::vector<std::pair<int, int>> reinsert;
|
||||
|
||||
int score = 0;
|
||||
int best_score = 0;
|
||||
int best_score_idx = -1;
|
||||
|
||||
setup_initial_gains();
|
||||
|
||||
int start_cost = compute_cost();
|
||||
|
||||
while (true) {
|
||||
int move_node = -1;
|
||||
int move_gain = 0;
|
||||
reinsert.clear();
|
||||
// Find a legal move
|
||||
while (gains.has_moves()) {
|
||||
auto move = gains.pop_node();
|
||||
int n_idx = move.first;
|
||||
auto &n = g.nodes.at(n_idx);
|
||||
int src_part = n.partition;
|
||||
int dst_part = 1 - n.partition;
|
||||
if (/*(part_area.at(src_part) >= partitions.at(src_part).min_nodes) && */ (
|
||||
(part_area.at(src_part) - n.area) < partitions.at(src_part).min_nodes))
|
||||
goto fail;
|
||||
if (/*(part_area.at(dst_part) <= partitions.at(dst_part).max_nodes) && */ (
|
||||
(part_area.at(dst_part) + n.area) > partitions.at(dst_part).max_nodes))
|
||||
goto fail;
|
||||
move_node = n_idx;
|
||||
move_gain = move.second;
|
||||
break;
|
||||
fail:
|
||||
reinsert.push_back(move);
|
||||
}
|
||||
if (move_node == -1)
|
||||
break;
|
||||
// Re-add the illegal moves we popped
|
||||
for (auto re : reinsert)
|
||||
gains.add_elem(re.second, re.first);
|
||||
|
||||
auto &n = g.nodes.at(move_node);
|
||||
int src_part = n.partition;
|
||||
int dst_part = 1 - n.partition;
|
||||
|
||||
// Update gains
|
||||
gain_update(move_node, src_part, dst_part);
|
||||
|
||||
// Update areas
|
||||
part_area.at(src_part) -= n.area;
|
||||
part_area.at(dst_part) += n.area;
|
||||
n.partition = dst_part;
|
||||
|
||||
// Update score
|
||||
score += move_gain;
|
||||
if ((best_score_idx == -1) || (score > best_score)) {
|
||||
best_score_idx = int(moves_made.size());
|
||||
best_score = score;
|
||||
}
|
||||
|
||||
// Add move to list
|
||||
moves_made.emplace_back(move_node, src_part);
|
||||
locked.at(move_node) = true;
|
||||
}
|
||||
|
||||
// Revert moves after the best score
|
||||
for (int i = best_score_idx + 1; i < int(moves_made.size()); i++) {
|
||||
auto &mm = moves_made.at(i);
|
||||
auto &n = g.nodes.at(mm.first);
|
||||
part_area.at(n.partition) -= n.area;
|
||||
part_area.at(mm.second) += n.area;
|
||||
n.partition = mm.second;
|
||||
}
|
||||
|
||||
if (ctx->verbose)
|
||||
log_info(" start: %d end: %d incr_gain: %d moves_made: %d\n", start_cost, compute_cost(), best_score,
|
||||
int(moves_made.size()));
|
||||
}
|
||||
};
|
||||
|
||||
int partition_recursive(Context *ctx, Hypergraph &g, const std::vector<PartitionConstraint> &partitions, int level)
|
||||
{
|
||||
int non_fixed_nodes = 0;
|
||||
for (auto &n : g.nodes)
|
||||
if (!n.fixed && !n.edges.empty() > 0)
|
||||
++non_fixed_nodes;
|
||||
FMPartitioner fm(ctx, g, partitions);
|
||||
fm.init();
|
||||
if (ctx->verbose)
|
||||
log_info("enter level=%d, N=%d, A=%f\n", level, non_fixed_nodes, fm.total_area());
|
||||
if (non_fixed_nodes <= 200) {
|
||||
// Final level in the hierarchy
|
||||
// Initial random partioning as our seed
|
||||
fm.random_part();
|
||||
} else {
|
||||
// Coarse the hypergraph, partition the coarsened graph and use that result as our seed
|
||||
Hypergraph coarsened;
|
||||
dict<int, std::vector<int>> new2orig;
|
||||
fm.coarsen(coarsened, new2orig);
|
||||
partition_recursive(ctx, coarsened, partitions, level + 1);
|
||||
fm.uncoarsen(coarsened, new2orig);
|
||||
}
|
||||
if (ctx->verbose) {
|
||||
log_info("re-enter level=%d, N=%d, cost=%d", level, non_fixed_nodes, fm.compute_cost());
|
||||
for (int i = 0; i < int(partitions.size()); i++) {
|
||||
log(", A%d=%f", i, fm.part_area.at(i));
|
||||
}
|
||||
log("\n");
|
||||
}
|
||||
|
||||
fm.assert_area();
|
||||
// The FM optimisation phase
|
||||
fm.run();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
FMPartitioner fm(ctx, g, partitions);
|
||||
fm.init();
|
||||
fm.assert_area();
|
||||
fm.run();
|
||||
}
|
||||
fm.assert_area();
|
||||
|
||||
// Status print
|
||||
|
||||
if (ctx->verbose) {
|
||||
log_info("exit level=%d, N=%d, cost=%d", level, non_fixed_nodes, fm.compute_cost());
|
||||
for (int i = 0; i < int(partitions.size()); i++) {
|
||||
log(", A%d=%f", i, fm.part_area.at(i));
|
||||
}
|
||||
log("\n");
|
||||
}
|
||||
|
||||
fm.assert_area();
|
||||
return fm.compute_cost();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
Loading…
Reference in New Issue