Skip to content

Commit 96c4fc9

Browse files
[AP] Generalized Argument Parsing and Added Target Density
The `appack_max_dist_th` cli option has a useful regex based interface which can be used in other parts of VPR, especially in AP. Generalized the interface so it can be used outside of the context of max distance thresholding. Used this general interface to pass in a target density per physical tile type. This spoofs the capacity into being smaller than it appears which tells the partial legalizer to not fill the tiles beyond the capacity. Setting the target density to interesting values revealed a bug in the partial legalizer regarding numerical precision when computing the prefix sum. Changed the underlying data type within the prefix sum class used into fixed-point which resolved the issue.
1 parent 5160a12 commit 96c4fc9

15 files changed

+414
-131
lines changed

doc/src/vpr/command_line_usage.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,6 +1281,33 @@ Analytical Placement is generally split into three stages:
12811281

12821282
**Default:** ``0.5``
12831283

1284+
.. option:: --ap_partial_legalizer_target_density { auto | <regex>:<float>,<float> }
1285+
1286+
Sets the target density of different physical tiles on the FPGA device
1287+
for the partial legalizer in the AP flow. The partial legalizer will
1288+
try to fill tiles up to (but not beyond) this target density. This
1289+
is used as a guide, the legalizer may not follow this if it must fill
1290+
the tile more.
1291+
1292+
When this option is set ot auto, VPR will select good values for the
1293+
target density of tiles.
1294+
1295+
reasonable values are between 0.0 and 1.0, with negative values not being allowed.
1296+
1297+
This option is similar to appack_max_dist_th, where a regex string
1298+
is used to set the target density of different physical tiles.
1299+
1300+
For example:
1301+
1302+
.. code-block:: none
1303+
1304+
--ap_partial_legalizer_target_density .*:0.9 "clb|memory:0.8"
1305+
1306+
Would set the target density of all physical tiles to be 0.9, except for the clb and
1307+
memory tiles, which will be set to a target density of 0.8.
1308+
1309+
**Default:** ``auto``
1310+
12841311
.. option:: --appack_max_dist_th { auto | <regex>:<float>,<float> }
12851312

12861313
Sets the maximum candidate distance thresholds for the logical block types

vpr/src/analytical_place/analytical_placement_flow.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ static PartialPlacement run_global_placer(const t_ap_opts& ap_opts,
156156
place_delay_model,
157157
ap_opts.ap_timing_tradeoff,
158158
ap_opts.generate_mass_report,
159+
ap_opts.ap_partial_legalizer_target_density,
159160
ap_opts.num_threads,
160161
ap_opts.log_verbosity);
161162
return global_placer->place();
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* @file
3+
* @author Alex Singer
4+
* @date June 2025
5+
* @brief Implementation of utility functions used for parsing AP arguments.
6+
*/
7+
#include "ap_argparse_utils.h"
8+
#include <regex>
9+
#include <string>
10+
#include <unordered_map>
11+
#include <vector>
12+
#include "vtr_assert.h"
13+
#include "vtr_log.h"
14+
#include "vpr_error.h"
15+
16+
/**
17+
* @brief Helper method to convert a string into a float with error checking.
18+
*/
19+
static float str_to_float_or_error(const std::string& str);
20+
21+
/**
22+
* @brief Parse the given key, value string argument. The string is expected to
23+
* be of the form:
24+
* "<key_regex>:<val1>,<val2>,<val3>"
25+
*
26+
* This method returns a tuple containing the regex string and a vector of the
27+
* values. The vector will be of the length expected_num_vals_per_key.
28+
*/
29+
static std::tuple<std::string, std::vector<float>>
30+
parse_key_val_arg(const std::string& arg, unsigned expected_num_vals_per_key);
31+
32+
std::unordered_map<std::string, std::vector<float>>
33+
key_to_float_argument_parser(const std::vector<std::string>& arg_vals,
34+
const std::vector<std::string>& valid_keys,
35+
unsigned expected_num_vals_per_key) {
36+
37+
// Create the key to float map which will be returned from this method.
38+
std::unordered_map<std::string, std::vector<float>> key_to_float_map;
39+
40+
// Go through each of the arguments to parse.
41+
for (const std::string& arg_val : arg_vals) {
42+
// Parse this argument.
43+
// Key is the regex string, vals is the vector of values.
44+
auto [key, vals] = parse_key_val_arg(arg_val, expected_num_vals_per_key);
45+
46+
// Create a regex object to be used to match for valid keys.
47+
std::regex key_regex(key);
48+
49+
// Go through each valid key and find which ones match the regex.
50+
bool found_match = false;
51+
for (const std::string& valid_key : valid_keys) {
52+
bool is_match = std::regex_match(valid_key, key_regex);
53+
if (!is_match)
54+
continue;
55+
56+
// If this key matches the regex, set the map to the given values.
57+
key_to_float_map[valid_key] = vals;
58+
found_match = true;
59+
}
60+
61+
// If no match is found for this key regex, raise a warning to the user.
62+
// They may have made a mistake and may want to be warned about it.
63+
if (!found_match) {
64+
VTR_LOG_WARN("Unable to find a valid key that matches regex pattern: %s\n",
65+
key.c_str());
66+
}
67+
}
68+
69+
// Return the map.
70+
return key_to_float_map;
71+
}
72+
73+
static std::tuple<std::string, std::vector<float>>
74+
parse_key_val_arg(const std::string& arg, unsigned expected_num_vals_per_key) {
75+
// Verify the format of the string. It must have one and only one colon.
76+
unsigned colon_count = 0;
77+
for (char c : arg) {
78+
if (c == ':')
79+
colon_count++;
80+
}
81+
if (colon_count != 1) {
82+
VTR_LOG_ERROR("Invalid argument string: %s\n",
83+
arg.c_str());
84+
VPR_FATAL_ERROR(VPR_ERROR_PACK,
85+
"Error when parsing argument string");
86+
}
87+
88+
// Split the string along the colon.
89+
auto del_pos = arg.find(':');
90+
std::string key_regex_str = arg.substr(0, del_pos);
91+
std::string val_list_str = arg.substr(del_pos + 1, std::string::npos);
92+
93+
// Verify that there are a correct number of commas given the expected number
94+
// of values.
95+
unsigned comma_count = 0;
96+
for (char c : val_list_str) {
97+
if (c == ',')
98+
comma_count++;
99+
}
100+
if (comma_count != expected_num_vals_per_key - 1) {
101+
VTR_LOG_ERROR("Invalid argument string (too many commas): %s\n",
102+
arg.c_str());
103+
VPR_FATAL_ERROR(VPR_ERROR_PACK,
104+
"Error when parsing argument string");
105+
}
106+
107+
// Collect the comma seperated values into a vector.
108+
std::vector<float> vals;
109+
vals.reserve(expected_num_vals_per_key);
110+
111+
// As we are reading each comma-seperated value, keep track of the current
112+
// part of the string we are reading. We read from left to right.
113+
std::string acc_val_list_str = val_list_str;
114+
115+
// For each expected value up to the last one, parse the current value before
116+
// the comma.
117+
VTR_ASSERT(expected_num_vals_per_key > 0);
118+
for (unsigned i = 0; i < expected_num_vals_per_key - 1; i++) {
119+
// Split the string before and after the comma.
120+
auto comma_pos = acc_val_list_str.find(",");
121+
VTR_ASSERT(comma_pos != std::string::npos);
122+
std::string current_val_str = val_list_str.substr(0, comma_pos);
123+
// Send the string after the comma to the next iteration.
124+
acc_val_list_str = val_list_str.substr(comma_pos + 1, std::string::npos);
125+
126+
// Cast the string before the comma into a float and store it.
127+
float current_val = str_to_float_or_error(current_val_str);
128+
vals.push_back(current_val);
129+
}
130+
131+
// Parse the last value in the list. This one should not have a comma in it.
132+
VTR_ASSERT(acc_val_list_str.find(",") == std::string::npos);
133+
float last_val = str_to_float_or_error(acc_val_list_str);
134+
vals.push_back(last_val);
135+
136+
// Return the results as a tuple.
137+
return std::make_tuple(key_regex_str, vals);
138+
}
139+
140+
static float str_to_float_or_error(const std::string& str) {
141+
float val = -1;
142+
try {
143+
val = std::stof(str);
144+
} catch (const std::invalid_argument& e) {
145+
VTR_LOG_ERROR("Error while parsing float arg value: %s\n"
146+
"Failed with invalid argument: %s\n",
147+
str.c_str(),
148+
e.what());
149+
} catch (const std::out_of_range& e) {
150+
VTR_LOG_ERROR("Error while parsing float arg value: %s\n"
151+
"Failed with out of range: %s\n",
152+
str.c_str(),
153+
e.what());
154+
}
155+
return val;
156+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#pragma once
2+
/**
3+
* @file
4+
* @author Alex Singer
5+
* @date June 2025
6+
* @brief Delcarations of utility functions used to parse AP options.
7+
*/
8+
9+
#include <string>
10+
#include <unordered_map>
11+
#include <vector>
12+
13+
/**
14+
* @brief Parser method for parsing a list of arguments of the form:
15+
* "<key_regex>:<val1>,<val2>,<val3>,..."
16+
*
17+
* This method will will return a map containing the value for each key matched.
18+
* The map will not contain an entry for a key that was not set by the arguments.
19+
*
20+
* Example usage:
21+
* // Create a list of valid keys.
22+
* std::vector<std::string> valid_keys = {"foo", "bar"}
23+
*
24+
* // User passed regex args. Sets all values to {0.5, 0.5, 0.5} THEN sets
25+
* // "foo" specifically to {0.1, 0.2, 0.3}.
26+
* // NOTE: Arguments are read left to right (first to last).
27+
* std::vector<std::string> arg_vals = {".*:0.5,0.5,0.5", "foo:0.1,0.2,0.3"}
28+
*
29+
* auto key_to_val_map = key_to_float_argument_parser(arg_vals,
30+
* valid_keys,
31+
* 3);
32+
* // Map will contain {0.1, 0.2, 0.3} for "foo" and {0.5, 0.5, 0.5} for "bar"
33+
*
34+
* @param arg_vals
35+
* The list of arguments to parse.
36+
* @param valid_keys
37+
* A list of valid keys that the argument regex patterns can match for.
38+
* @param expected_num_vals_per_key
39+
* The expected number of floating point values per key.
40+
*/
41+
std::unordered_map<std::string, std::vector<float>>
42+
key_to_float_argument_parser(const std::vector<std::string>& arg_vals,
43+
const std::vector<std::string>& valid_keys,
44+
unsigned expected_num_vals_per_key = 1);

vpr/src/analytical_place/flat_placement_density_manager.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include "flat_placement_density_manager.h"
99
#include <tuple>
10+
#include <unordered_map>
11+
#include "ap_argparse_utils.h"
1012
#include "ap_netlist.h"
1113
#include "ap_netlist_fwd.h"
1214
#include "atom_netlist.h"
@@ -18,6 +20,8 @@
1820
#include "prepack.h"
1921
#include "primitive_dim_manager.h"
2022
#include "primitive_vector.h"
23+
#include "vpr_error.h"
24+
#include "vpr_utils.h"
2125
#include "vtr_assert.h"
2226
#include "vtr_geometry.h"
2327
#include "vtr_vector.h"
@@ -45,13 +49,68 @@ static PrimitiveVector calc_bin_underfill(const PrimitiveVector& bin_utilization
4549
return underfill;
4650
}
4751

52+
/**
53+
* @brief Get the physical type target densities given the user arguments.
54+
*
55+
* This will automatically select good target densisities, but will allow the
56+
* user to override these values from the command line.
57+
*
58+
* @param target_density_arg_strs
59+
* The command-line arguments provided by the user.
60+
* @param physical_tile_types
61+
* A vector of all physical tile types in the architecture.
62+
*/
63+
static std::vector<float> get_phy_ty_target_densities(const std::vector<std::string>& target_density_arg_strs,
64+
const std::vector<t_physical_tile_type>& physical_tile_types) {
65+
// Get the target densisty of each physical block type.
66+
// TODO: Create auto feature to automatically select target densities based
67+
// on properties of the architecture. Need to sweep to find reasonable
68+
// values.
69+
std::vector<float> phy_ty_target_density(physical_tile_types.size(), 1.0f);
70+
71+
// Set to auto if no user args are provided.
72+
if (target_density_arg_strs.size() == 0)
73+
return phy_ty_target_density;
74+
if (target_density_arg_strs.size() == 1 && target_density_arg_strs[0] == "auto")
75+
return phy_ty_target_density;
76+
77+
// Parse the user args. The physical type names are expected to be used as keys.
78+
std::vector<std::string> phy_ty_names;
79+
phy_ty_names.reserve(physical_tile_types.size());
80+
std::unordered_map<std::string, int> phy_ty_name_to_index;
81+
for (const t_physical_tile_type& phy_ty : physical_tile_types) {
82+
phy_ty_names.push_back(phy_ty.name);
83+
phy_ty_name_to_index[phy_ty.name] = phy_ty.index;
84+
}
85+
auto phy_ty_name_to_tar_density = key_to_float_argument_parser(target_density_arg_strs,
86+
phy_ty_names,
87+
1);
88+
89+
// Update the target densities based on the user args.
90+
for (const auto& phy_ty_name_to_density_pair : phy_ty_name_to_tar_density) {
91+
const std::string& phy_ty_name = phy_ty_name_to_density_pair.first;
92+
VTR_ASSERT(phy_ty_name_to_density_pair.second.size() == 1);
93+
float target_density = phy_ty_name_to_density_pair.second[0];
94+
if (target_density < 0.0f) {
95+
VPR_FATAL_ERROR(VPR_ERROR_AP,
96+
"Cannot have negative target density");
97+
}
98+
99+
int phy_ty_index = phy_ty_name_to_index[phy_ty_name];
100+
phy_ty_target_density[phy_ty_index] = target_density;
101+
}
102+
103+
return phy_ty_target_density;
104+
}
105+
48106
FlatPlacementDensityManager::FlatPlacementDensityManager(const APNetlist& ap_netlist,
49107
const Prepacker& prepacker,
50108
const AtomNetlist& atom_netlist,
51109
const DeviceGrid& device_grid,
52110
const std::vector<t_logical_block_type>& logical_block_types,
53111
const std::vector<t_physical_tile_type>& physical_tile_types,
54112
const LogicalModels& models,
113+
const std::vector<std::string>& target_density_arg_strs,
55114
int log_verbosity)
56115
: ap_netlist_(ap_netlist)
57116
, bins_(ap_netlist)
@@ -62,6 +121,15 @@ FlatPlacementDensityManager::FlatPlacementDensityManager(const APNetlist& ap_net
62121
std::tie(num_layers, width, height) = device_grid.dim_sizes();
63122
bin_spatial_lookup_.resize({num_layers, width, height});
64123

124+
// Get the target densisty of each physical block type.
125+
std::vector<float> phy_ty_target_densities = get_phy_ty_target_densities(target_density_arg_strs,
126+
physical_tile_types);
127+
VTR_LOG("Partial legalizer is using target densities:");
128+
for (const t_physical_tile_type& phy_ty : physical_tile_types) {
129+
VTR_LOG(" %s:%.1f", phy_ty.name.c_str(), phy_ty_target_densities[phy_ty.index]);
130+
}
131+
VTR_LOG("\n");
132+
65133
// Create a bin for each tile. This will create one bin for each root tile
66134
// location.
67135
vtr::vector_map<FlatPlacementBinId, size_t> bin_phy_tile_type_idx;
@@ -96,6 +164,12 @@ FlatPlacementDensityManager::FlatPlacementDensityManager(const APNetlist& ap_net
96164
// Store the index of the physical tile type into a map to be
97165
// used to compute the capacity.
98166
bin_phy_tile_type_idx.insert(new_bin_id, tile_type->index);
167+
168+
// Set the target density for this bin based on the physical
169+
// tile type..
170+
float target_density = phy_ty_target_densities[tile_type->index];
171+
bin_target_density_.push_back(target_density);
172+
VTR_ASSERT(bin_target_density_[new_bin_id] = target_density);
99173
}
100174
}
101175
}

0 commit comments

Comments
 (0)