Skip to content

Commit 240822c

Browse files
[AP][GlobalPlacement] Improved Partial Legalizer Legality
Updated the partial legalizer to now take into account block types when spreading blocks. This will create windows around overfilled bins that is aware of which block types are overfilled and how large the window needs to be to accomodate them. It also takes these block types into account when spreading to only allow blocks to spread into sub-windows that they can exist in. This improves quality but was detremental to performance, so some performance improvements were needed. To improve the performance of the partial legalizer, I split the problem into groups of models which must be spread together. This allows us to create tighter windows and can make some parts of the legalizer more efficient. Create a model grouper class which forms the model pack patterns into a graph and find disconnected sub-graphs to form the model groups. Also improved the window generation by pre-clustering the overfilled bins before creating the windows. This sped up the window generation code since less windows overlap.
1 parent 0af49af commit 240822c

16 files changed

+1409
-238
lines changed

vpr/src/analytical_place/analytical_solver.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ static inline void update_linear_system_with_anchors(Eigen::SparseMatrix<double>
267267
vtr::vector<APRowId, APBlockId> row_id_to_blk_id,
268268
unsigned iteration) {
269269
// Anchor weights grow exponentially with iteration.
270-
double coeff_pseudo_anchor = 0.01 * std::exp((double)iteration / 5);
270+
double coeff_pseudo_anchor = 0.001 * std::exp((double)iteration / 5);
271271
for (size_t row_id_idx = 0; row_id_idx < num_moveable_blocks; row_id_idx++) {
272272
APRowId row_id = APRowId(row_id_idx);
273273
APBlockId blk_id = row_id_to_blk_id[row_id];

vpr/src/analytical_place/flat_placement_bins.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ class FlatPlacementBins {
111111
inline const vtr::Rect<double>& bin_region(FlatPlacementBinId bin_id) const {
112112
VTR_ASSERT(bin_id.is_valid());
113113
return bin_region_[bin_id];
114-
;
115114
}
116115

117116
/**

vpr/src/analytical_place/flat_placement_density_manager.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ FlatPlacementDensityManager::FlatPlacementDensityManager(const APNetlist& ap_net
8080
auto tile_type = device_grid.get_physical_type(tile_loc);
8181
int tw = tile_type->width;
8282
int th = tile_type->height;
83+
VTR_ASSERT_SAFE(tw != 0 && th != 0);
8384
vtr::Rect<double> new_bin_region(vtr::Point<double>(x, y),
8485
vtr::Point<double>(x + tw,
8586
y + th));
@@ -215,9 +216,9 @@ void FlatPlacementDensityManager::empty_bins() {
215216
// Reset all of the bins and their utilizations.
216217
for (FlatPlacementBinId bin_id : bins_.bins()) {
217218
bins_.remove_all_blocks_from_bin(bin_id);
218-
bin_utilization_[bin_id] = PrimitiveVector();
219-
bin_overfill_[bin_id] = calc_bin_overfill(bin_utilization_[bin_id], bin_capacity_[bin_id]);
220-
bin_underfill_[bin_id] = calc_bin_underfill(bin_utilization_[bin_id], bin_capacity_[bin_id]);
219+
bin_utilization_[bin_id].clear();
220+
bin_overfill_[bin_id].clear();
221+
bin_underfill_[bin_id] = bin_capacity_[bin_id];
221222
}
222223
// Once all the bins are reset, all bins should be empty; therefore no bins
223224
// are overfilled.

vpr/src/analytical_place/flat_placement_mass_calculator.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ static void print_capacities(const std::vector<PrimitiveVector>& logical_block_t
234234
VTR_LOG("\n");
235235
}
236236
VTR_LOG("\n");
237+
// TODO: Print the masses of each model.
237238
}
238239

239240
FlatPlacementMassCalculator::FlatPlacementMassCalculator(const APNetlist& ap_netlist,

vpr/src/analytical_place/global_placer.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@
1313
#include "analytical_solver.h"
1414
#include "ap_flow_enums.h"
1515
#include "ap_netlist.h"
16+
#include "ap_netlist_fwd.h"
1617
#include "atom_netlist.h"
1718
#include "device_grid.h"
19+
#include "flat_placement_bins.h"
1820
#include "flat_placement_density_manager.h"
21+
#include "globals.h"
1922
#include "partial_legalizer.h"
2023
#include "partial_placement.h"
2124
#include "physical_types.h"
25+
#include "primitive_vector.h"
2226
#include "vpr_error.h"
2327
#include "vtr_log.h"
2428
#include "vtr_time.h"
@@ -90,9 +94,74 @@ SimPLGlobalPlacer::SimPLGlobalPlacer(e_partial_legalizer partial_legalizer_type,
9094
partial_legalizer_ = make_partial_legalizer(partial_legalizer_type,
9195
ap_netlist_,
9296
density_manager_,
97+
prepacker,
9398
log_verbosity_);
9499
}
95100

101+
/**
102+
* @brief Helper method to print the statistics on the given partial placement.
103+
*/
104+
static void print_placement_stats(const PartialPlacement& p_placement,
105+
const APNetlist& ap_netlist,
106+
FlatPlacementDensityManager& density_manager) {
107+
// Print the placement HPWL
108+
VTR_LOG("\tPlacement HPWL: %f\n", p_placement.get_hpwl(ap_netlist));
109+
110+
// Print density information. Need to reset the density manager to ensure
111+
// the data is valid.
112+
density_manager.empty_bins();
113+
density_manager.import_placement_into_bins(p_placement);
114+
115+
// Print the number of overfilled bins.
116+
size_t num_overfilled_bins = density_manager.get_overfilled_bins().size();
117+
VTR_LOG("\tNumber of overfilled bins: %zu\n", num_overfilled_bins);
118+
119+
// Print the average overfill
120+
float total_overfill = 0.0f;
121+
for (FlatPlacementBinId bin_id : density_manager.get_overfilled_bins()) {
122+
total_overfill += density_manager.get_bin_overfill(bin_id).manhattan_norm();
123+
}
124+
float avg_overfill = 0.0f;
125+
if (num_overfilled_bins != 0)
126+
avg_overfill = total_overfill / static_cast<float>(num_overfilled_bins);
127+
VTR_LOG("\tAverage overfill magnitude: %f\n", avg_overfill);
128+
129+
// Print the number of overfilled tiles per type.
130+
const auto& physical_tile_types = g_vpr_ctx.device().physical_tile_types;
131+
const auto& device_grid = g_vpr_ctx.device().grid;
132+
std::vector<unsigned> overfilled_tiles_by_type(physical_tile_types.size(), 0);
133+
for (FlatPlacementBinId bin_id : density_manager.get_overfilled_bins()) {
134+
const auto& bin_region = density_manager.flat_placement_bins().bin_region(bin_id);
135+
auto tile_loc = t_physical_tile_loc((int)bin_region.xmin(),
136+
(int)bin_region.ymin(),
137+
0);
138+
auto tile_type = device_grid.get_physical_type(tile_loc);
139+
overfilled_tiles_by_type[tile_type->index]++;
140+
}
141+
VTR_LOG("\tOverfilled bins by tile type:\n");
142+
for (size_t type_idx = 0; type_idx < physical_tile_types.size(); type_idx++) {
143+
VTR_LOG("\t\t%10s: %zu\n",
144+
physical_tile_types[type_idx].name.c_str(),
145+
overfilled_tiles_by_type[type_idx]);
146+
}
147+
148+
// Count the number of blocks that were placed in the wrong place.
149+
unsigned num_misplaced_blocks = 0;
150+
for (FlatPlacementBinId bin_id : density_manager.get_overfilled_bins()) {
151+
for (APBlockId ap_blk_id : density_manager.flat_placement_bins().bin_contained_blocks(bin_id)) {
152+
// Get the blk mass and project it onto the capacity of its bin.
153+
PrimitiveVector blk_mass = density_manager.mass_calculator().get_block_mass(ap_blk_id);
154+
PrimitiveVector projected_mass = blk_mass;
155+
projected_mass.project(density_manager.get_bin_capacity(bin_id));
156+
// If the projected mass does not match its match, this implies that
157+
// there this block does not belong in this bin.
158+
if (projected_mass != blk_mass)
159+
num_misplaced_blocks++;
160+
}
161+
}
162+
VTR_LOG("\tNumber of blocks in the wrong tile: %zu\n", num_misplaced_blocks);
163+
}
164+
96165
/**
97166
* @brief Helper method to print the header of the per-iteration status updates
98167
* of the global placer.
@@ -177,6 +246,13 @@ PartialPlacement SimPLGlobalPlacer::place() {
177246
if (hpwl_relative_gap < target_hpwl_relative_gap_)
178247
break;
179248
}
249+
250+
// Print some statistics on the final placement.
251+
VTR_LOG("Placement after Global Placement:\n");
252+
print_placement_stats(p_placement,
253+
ap_netlist_,
254+
*density_manager_);
255+
180256
// Return the placement from the final iteration.
181257
// TODO: investigate saving the best solution found so far. It should be
182258
// cheap to save a copy of the PartialPlacement object.

vpr/src/analytical_place/global_placer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class SimPLGlobalPlacer : public GlobalPlacer {
116116
/// lower-bound placements. The placer will stop if the difference
117117
/// between the two bounds, normalized to the upper-bound, is smaller
118118
/// than this number.
119-
static constexpr double target_hpwl_relative_gap_ = 0.10;
119+
static constexpr double target_hpwl_relative_gap_ = 0.05;
120120

121121
/// @brief The solver which generates the lower-bound placement.
122122
std::unique_ptr<AnalyticalSolver> solver_;
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**
2+
* @file
3+
* @author Alex Singer
4+
* @date March 2025
5+
* @brief Implementation of a model grouper class which groups models together
6+
* which must be legalized together in a flat placement.
7+
*/
8+
9+
#include "model_grouper.h"
10+
#include <queue>
11+
#include <unordered_map>
12+
#include <unordered_set>
13+
#include <vector>
14+
#include "cad_types.h"
15+
#include "logic_types.h"
16+
#include "prepack.h"
17+
#include "vtr_assert.h"
18+
#include "vtr_log.h"
19+
20+
/**
21+
* @brief Recursive helper function which gets the models in the given pattern
22+
* block.
23+
*
24+
* @param pattern_block
25+
* The pattern block to get the models of.
26+
* @param models
27+
* A set of the models found so far.
28+
* @param block_visited
29+
* A vector of flags for each pattern block to signify which blocks have
30+
* been visited.
31+
*/
32+
static void get_pattern_models(t_pack_pattern_block* pattern_block,
33+
std::unordered_set<int>& models,
34+
std::vector<bool>& block_visited) {
35+
// If the pattern block is invalid or this block has been visited, return.
36+
if (pattern_block == nullptr || block_visited[pattern_block->block_id]) {
37+
return;
38+
}
39+
40+
// Mark this block as visited and insert its model into the models vector.
41+
block_visited[pattern_block->block_id] = true;
42+
models.insert(pattern_block->pb_type->model->index);
43+
44+
// Go through this block's connections and get their pattern models.
45+
t_pack_pattern_connections* connection = pattern_block->connections;
46+
while (connection != nullptr) {
47+
get_pattern_models(connection->from_block, models, block_visited);
48+
get_pattern_models(connection->to_block, models, block_visited);
49+
connection = connection->next;
50+
}
51+
}
52+
53+
ModelGrouper::ModelGrouper(const Prepacker& prepacker,
54+
t_model* user_models,
55+
t_model* library_models,
56+
int log_verbosity) {
57+
/**
58+
* Group the models together based on their pack patterns. If model A and
59+
* model B form a pattern, and model B and model C form a pattern, then
60+
* models A, B, and C are in a group together.
61+
*
62+
* An efficient way to find this is to represent this problem as a graph,
63+
* where each node is a model and each edge is a relationship where a model
64+
* is in a pack pattern with another model. We can then perform BFS to find
65+
* the connected sub-graphs which will be the groups.
66+
*/
67+
68+
// Get the number of models
69+
// TODO: Clean up the models vectors in VTR.
70+
std::unordered_map<int, char*> model_name;
71+
unsigned num_models = 0;
72+
t_model* model = library_models;
73+
while (model != nullptr) {
74+
model_name[model->index] = model->name;
75+
num_models++;
76+
model = model->next;
77+
}
78+
model = user_models;
79+
while (model != nullptr) {
80+
model_name[model->index] = model->name;
81+
num_models++;
82+
model = model->next;
83+
}
84+
85+
// Create an adjacency list for the edges. An edge is formed where two
86+
// models share a pack pattern together.
87+
std::vector<std::unordered_set<int>> adj_list(num_models);
88+
for (const t_pack_patterns& pack_pattern : prepacker.get_all_pack_patterns()) {
89+
// Get the models within this pattern.
90+
std::unordered_set<int> models_in_pattern;
91+
std::vector<bool> block_visited(pack_pattern.num_blocks, false);
92+
get_pattern_models(pack_pattern.root_block, models_in_pattern, block_visited);
93+
VTR_ASSERT_SAFE(!models_in_pattern.empty());
94+
95+
// Debug print the models within the pattern.
96+
if (log_verbosity >= 20) {
97+
VTR_LOG("Pattern: %s\n\t", pack_pattern.name);
98+
for (int model_idx : models_in_pattern) {
99+
VTR_LOG("%s ", model_name[model_idx]);
100+
}
101+
VTR_LOG("\n");
102+
}
103+
104+
// Connect each of the models to the first model in the pattern. Since
105+
// we only care if there exist a path from each model to another, we do
106+
// not need to connect the models in a clique.
107+
int first_model_idx = *models_in_pattern.begin();
108+
for (int model_idx : models_in_pattern) {
109+
adj_list[model_idx].insert(first_model_idx);
110+
adj_list[first_model_idx].insert(model_idx);
111+
}
112+
}
113+
114+
// Perform BFS to group the models.
115+
VTR_LOGV(log_verbosity >= 20,
116+
"Finding model groups...\n");
117+
std::queue<int> node_queue;
118+
model_group_id_.resize(num_models, ModelGroupId::INVALID());
119+
for (int model_idx = 0; model_idx < (int)num_models; model_idx++) {
120+
// If this model is already in a group, skip it.
121+
if (model_group_id_[model_idx].is_valid()) {
122+
VTR_LOGV(log_verbosity >= 20,
123+
"\t(%d -> %d)\n", model_idx, model_group_id_[model_idx]);
124+
continue;
125+
}
126+
127+
ModelGroupId group_id = ModelGroupId(group_ids_.size());
128+
// Put the model in this group and push to the queue.
129+
model_group_id_[model_idx] = group_id;
130+
node_queue.push(model_idx);
131+
132+
while (!node_queue.empty()) {
133+
// Pop a node from the queue, and explore its neighbors.
134+
int node_model_idx = node_queue.front();
135+
node_queue.pop();
136+
for (int neighbor_model_idx : adj_list[node_model_idx]) {
137+
// If this neighbor is already in this group, skip it.
138+
if (model_group_id_[neighbor_model_idx].is_valid()) {
139+
VTR_ASSERT_SAFE(model_group_id_[neighbor_model_idx] == group_id);
140+
continue;
141+
}
142+
// Put the neighbor in this group and push it to the queue.
143+
model_group_id_[neighbor_model_idx] = group_id;
144+
node_queue.push(neighbor_model_idx);
145+
}
146+
}
147+
148+
VTR_LOGV(log_verbosity >= 20,
149+
"\t(%d -> %d)\n", model_idx, model_group_id_[model_idx]);
150+
group_ids_.push_back(group_id);
151+
}
152+
153+
// Create a lookup between each group and the models it contains.
154+
groups_.resize(groups().size());
155+
for (int model_idx = 0; model_idx < (int)num_models; model_idx++) {
156+
groups_[model_group_id_[model_idx]].push_back(model_idx);
157+
}
158+
159+
// Debug printing for each group.
160+
if (log_verbosity >= 20) {
161+
for (ModelGroupId group_id : groups()) {
162+
const std::vector<int>& group = groups_[group_id];
163+
VTR_LOG("Group %zu:\n", group_id);
164+
VTR_LOG("\tSize = %zu\n", group.size());
165+
VTR_LOG("\tContained models:\n");
166+
for (int model_idx : group) {
167+
VTR_LOG("\t\t%s\n", model_name[model_idx]);
168+
}
169+
}
170+
}
171+
}

0 commit comments

Comments
 (0)