Skip to content

Commit e097368

Browse files
Merge pull request verilog-to-routing#3148 from verilog-to-routing/feature-ap-cli
[AP] Generalized Argument Parsing and Added Target Density
2 parents bd390e3 + 1ba2d53 commit e097368

17 files changed

+428
-138
lines changed

doc/src/vpr/command_line_usage.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,6 +1281,40 @@ 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+
The partial legalizer uses an abstraction called "mass" to describe the resources
1293+
used by a set of primitives in the netlist and the capacity of resources in a
1294+
given tile. For primitives like LUTs, FFs, and DSPs this mass can be thought of
1295+
as the number of pins used (but not exactly). For memories, this mass can be
1296+
thought of as the number of bits stored. This target density parameter lowers
1297+
the mass capacity of tiles.
1298+
1299+
When this option is set ot auto, VPR will select good values for the
1300+
target density of tiles.
1301+
1302+
reasonable values are between 0.0 and 1.0, with negative values not being allowed.
1303+
1304+
This option is similar to appack_max_dist_th, where a regex string
1305+
is used to set the target density of different physical tiles.
1306+
1307+
For example:
1308+
1309+
.. code-block:: none
1310+
1311+
--ap_partial_legalizer_target_density .*:0.9 "clb|memory:0.8"
1312+
1313+
Would set the target density of all physical tiles to be 0.9, except for the clb and
1314+
memory tiles, which will be set to a target density of 0.8.
1315+
1316+
**Default:** ``auto``
1317+
12841318
.. option:: --appack_max_dist_th { auto | <regex>:<float>,<float> }
12851319

12861320
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_physical_type_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_physical_type_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)