Skip to content

[AP] Generalized Argument Parsing and Added Target Density #3148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions doc/src/vpr/command_line_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,40 @@ Analytical Placement is generally split into three stages:

**Default:** ``0.5``

.. option:: --ap_partial_legalizer_target_density { auto | <regex>:<float>,<float> }

Sets the target density of different physical tiles on the FPGA device
for the partial legalizer in the AP flow. The partial legalizer will
try to fill tiles up to (but not beyond) this target density. This
is used as a guide, the legalizer may not follow this if it must fill
the tile more.

The partial legalizer uses an abstraction called "mass" to describe the resources
used by a set of primitives in the netlist and the capacity of resources in a
given tile. For primitives like LUTs, FFs, and DSPs this mass can be thought of
as the number of pins used (but not exactly). For memories, this mass can be
thought of as the number of bits stored. This target density parameter lowers
the mass capacity of tiles.

When this option is set ot auto, VPR will select good values for the
target density of tiles.

reasonable values are between 0.0 and 1.0, with negative values not being allowed.

This option is similar to appack_max_dist_th, where a regex string
is used to set the target density of different physical tiles.

For example:

.. code-block:: none

--ap_partial_legalizer_target_density .*:0.9 "clb|memory:0.8"

Would set the target density of all physical tiles to be 0.9, except for the clb and
memory tiles, which will be set to a target density of 0.8.

**Default:** ``auto``

.. option:: --appack_max_dist_th { auto | <regex>:<float>,<float> }

Sets the maximum candidate distance thresholds for the logical block types
Expand Down
1 change: 1 addition & 0 deletions vpr/src/analytical_place/analytical_placement_flow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ static PartialPlacement run_global_placer(const t_ap_opts& ap_opts,
place_delay_model,
ap_opts.ap_timing_tradeoff,
ap_opts.generate_mass_report,
ap_opts.ap_partial_legalizer_target_density,
ap_opts.num_threads,
ap_opts.log_verbosity);
return global_placer->place();
Expand Down
156 changes: 156 additions & 0 deletions vpr/src/analytical_place/ap_argparse_utils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/**
* @file
* @author Alex Singer
* @date June 2025
* @brief Implementation of utility functions used for parsing AP arguments.
*/
#include "ap_argparse_utils.h"
#include <regex>
#include <string>
#include <unordered_map>
#include <vector>
#include "vtr_assert.h"
#include "vtr_log.h"
#include "vpr_error.h"

/**
* @brief Helper method to convert a string into a float with error checking.
*/
static float str_to_float_or_error(const std::string& str);

/**
* @brief Parse the given key, value string argument. The string is expected to
* be of the form:
* "<key_regex>:<val1>,<val2>,<val3>"
*
* This method returns a tuple containing the regex string and a vector of the
* values. The vector will be of the length expected_num_vals_per_key.
*/
static std::tuple<std::string, std::vector<float>>
parse_key_val_arg(const std::string& arg, unsigned expected_num_vals_per_key);

std::unordered_map<std::string, std::vector<float>>
key_to_float_argument_parser(const std::vector<std::string>& arg_vals,
const std::vector<std::string>& valid_keys,
unsigned expected_num_vals_per_key) {

// Create the key to float map which will be returned from this method.
std::unordered_map<std::string, std::vector<float>> key_to_float_map;

// Go through each of the arguments to parse.
for (const std::string& arg_val : arg_vals) {
// Parse this argument.
// Key is the regex string, vals is the vector of values.
auto [key, vals] = parse_key_val_arg(arg_val, expected_num_vals_per_key);

// Create a regex object to be used to match for valid keys.
std::regex key_regex(key);

// Go through each valid key and find which ones match the regex.
bool found_match = false;
for (const std::string& valid_key : valid_keys) {
bool is_match = std::regex_match(valid_key, key_regex);
if (!is_match)
continue;

// If this key matches the regex, set the map to the given values.
key_to_float_map[valid_key] = vals;
found_match = true;
}

// If no match is found for this key regex, raise a warning to the user.
// They may have made a mistake and may want to be warned about it.
if (!found_match) {
VTR_LOG_WARN("Unable to find a valid key that matches regex pattern: %s\n",
key.c_str());
}
}

// Return the map.
return key_to_float_map;
}

static std::tuple<std::string, std::vector<float>>
parse_key_val_arg(const std::string& arg, unsigned expected_num_vals_per_key) {
// Verify the format of the string. It must have one and only one colon.
unsigned colon_count = 0;
for (char c : arg) {
if (c == ':')
colon_count++;
}
if (colon_count != 1) {
VTR_LOG_ERROR("Invalid argument string: %s\n",
arg.c_str());
VPR_FATAL_ERROR(VPR_ERROR_PACK,
"Error when parsing argument string");
}

// Split the string along the colon.
auto del_pos = arg.find(':');
std::string key_regex_str = arg.substr(0, del_pos);
std::string val_list_str = arg.substr(del_pos + 1, std::string::npos);

// Verify that there are a correct number of commas given the expected number
// of values.
unsigned comma_count = 0;
for (char c : val_list_str) {
if (c == ',')
comma_count++;
}
if (comma_count != expected_num_vals_per_key - 1) {
VTR_LOG_ERROR("Invalid argument string (too many commas): %s\n",
arg.c_str());
VPR_FATAL_ERROR(VPR_ERROR_PACK,
"Error when parsing argument string");
}

// Collect the comma seperated values into a vector.
std::vector<float> vals;
vals.reserve(expected_num_vals_per_key);

// As we are reading each comma-seperated value, keep track of the current
// part of the string we are reading. We read from left to right.
std::string acc_val_list_str = val_list_str;

// For each expected value up to the last one, parse the current value before
// the comma.
VTR_ASSERT(expected_num_vals_per_key > 0);
for (unsigned i = 0; i < expected_num_vals_per_key - 1; i++) {
// Split the string before and after the comma.
auto comma_pos = acc_val_list_str.find(",");
VTR_ASSERT(comma_pos != std::string::npos);
std::string current_val_str = val_list_str.substr(0, comma_pos);
// Send the string after the comma to the next iteration.
acc_val_list_str = val_list_str.substr(comma_pos + 1, std::string::npos);

// Cast the string before the comma into a float and store it.
float current_val = str_to_float_or_error(current_val_str);
vals.push_back(current_val);
}

// Parse the last value in the list. This one should not have a comma in it.
VTR_ASSERT(acc_val_list_str.find(",") == std::string::npos);
float last_val = str_to_float_or_error(acc_val_list_str);
vals.push_back(last_val);

// Return the results as a tuple.
return std::make_tuple(key_regex_str, vals);
}

static float str_to_float_or_error(const std::string& str) {
float val = -1;
try {
val = std::stof(str);
} catch (const std::invalid_argument& e) {
VTR_LOG_ERROR("Error while parsing float arg value: %s\n"
"Failed with invalid argument: %s\n",
str.c_str(),
e.what());
} catch (const std::out_of_range& e) {
VTR_LOG_ERROR("Error while parsing float arg value: %s\n"
"Failed with out of range: %s\n",
str.c_str(),
e.what());
}
return val;
}
44 changes: 44 additions & 0 deletions vpr/src/analytical_place/ap_argparse_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#pragma once
/**
* @file
* @author Alex Singer
* @date June 2025
* @brief Delcarations of utility functions used to parse AP options.
*/

#include <string>
#include <unordered_map>
#include <vector>

/**
* @brief Parser method for parsing a list of arguments of the form:
* "<key_regex>:<val1>,<val2>,<val3>,..."
*
* This method will will return a map containing the value for each key matched.
* The map will not contain an entry for a key that was not set by the arguments.
*
* Example usage:
* // Create a list of valid keys.
* std::vector<std::string> valid_keys = {"foo", "bar"}
*
* // User passed regex args. Sets all values to {0.5, 0.5, 0.5} THEN sets
* // "foo" specifically to {0.1, 0.2, 0.3}.
* // NOTE: Arguments are read left to right (first to last).
* std::vector<std::string> arg_vals = {".*:0.5,0.5,0.5", "foo:0.1,0.2,0.3"}
*
* auto key_to_val_map = key_to_float_argument_parser(arg_vals,
* valid_keys,
* 3);
* // Map will contain {0.1, 0.2, 0.3} for "foo" and {0.5, 0.5, 0.5} for "bar"
*
* @param arg_vals
* The list of arguments to parse.
* @param valid_keys
* A list of valid keys that the argument regex patterns can match for.
* @param expected_num_vals_per_key
* The expected number of floating point values per key.
*/
std::unordered_map<std::string, std::vector<float>>
key_to_float_argument_parser(const std::vector<std::string>& arg_vals,
const std::vector<std::string>& valid_keys,
unsigned expected_num_vals_per_key = 1);
74 changes: 74 additions & 0 deletions vpr/src/analytical_place/flat_placement_density_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include "flat_placement_density_manager.h"
#include <tuple>
#include <unordered_map>
#include "ap_argparse_utils.h"
#include "ap_netlist.h"
#include "ap_netlist_fwd.h"
#include "atom_netlist.h"
Expand All @@ -18,6 +20,8 @@
#include "prepack.h"
#include "primitive_dim_manager.h"
#include "primitive_vector.h"
#include "vpr_error.h"
#include "vpr_utils.h"
#include "vtr_assert.h"
#include "vtr_geometry.h"
#include "vtr_vector.h"
Expand Down Expand Up @@ -45,13 +49,68 @@ static PrimitiveVector calc_bin_underfill(const PrimitiveVector& bin_utilization
return underfill;
}

/**
* @brief Get the physical type target densities given the user arguments.
*
* This will automatically select good target densisities, but will allow the
* user to override these values from the command line.
*
* @param target_density_arg_strs
* The command-line arguments provided by the user.
* @param physical_tile_types
* A vector of all physical tile types in the architecture.
*/
static std::vector<float> get_physical_type_target_densities(const std::vector<std::string>& target_density_arg_strs,
const std::vector<t_physical_tile_type>& physical_tile_types) {
// Get the target densisty of each physical block type.
// TODO: Create auto feature to automatically select target densities based
// on properties of the architecture. Need to sweep to find reasonable
// values.
std::vector<float> phy_ty_target_density(physical_tile_types.size(), 1.0f);

// Set to auto if no user args are provided.
if (target_density_arg_strs.size() == 0)
return phy_ty_target_density;
if (target_density_arg_strs.size() == 1 && target_density_arg_strs[0] == "auto")
return phy_ty_target_density;

// Parse the user args. The physical type names are expected to be used as keys.
std::vector<std::string> phy_ty_names;
phy_ty_names.reserve(physical_tile_types.size());
std::unordered_map<std::string, int> phy_ty_name_to_index;
for (const t_physical_tile_type& phy_ty : physical_tile_types) {
phy_ty_names.push_back(phy_ty.name);
phy_ty_name_to_index[phy_ty.name] = phy_ty.index;
}
auto phy_ty_name_to_tar_density = key_to_float_argument_parser(target_density_arg_strs,
phy_ty_names,
1);

// Update the target densities based on the user args.
for (const auto& phy_ty_name_to_density_pair : phy_ty_name_to_tar_density) {
const std::string& phy_ty_name = phy_ty_name_to_density_pair.first;
VTR_ASSERT(phy_ty_name_to_density_pair.second.size() == 1);
float target_density = phy_ty_name_to_density_pair.second[0];
if (target_density < 0.0f) {
VPR_FATAL_ERROR(VPR_ERROR_AP,
"Cannot have negative target density");
}

int phy_ty_index = phy_ty_name_to_index[phy_ty_name];
phy_ty_target_density[phy_ty_index] = target_density;
}

return phy_ty_target_density;
}

FlatPlacementDensityManager::FlatPlacementDensityManager(const APNetlist& ap_netlist,
const Prepacker& prepacker,
const AtomNetlist& atom_netlist,
const DeviceGrid& device_grid,
const std::vector<t_logical_block_type>& logical_block_types,
const std::vector<t_physical_tile_type>& physical_tile_types,
const LogicalModels& models,
const std::vector<std::string>& target_density_arg_strs,
int log_verbosity)
: ap_netlist_(ap_netlist)
, bins_(ap_netlist)
Expand All @@ -62,6 +121,15 @@ FlatPlacementDensityManager::FlatPlacementDensityManager(const APNetlist& ap_net
std::tie(num_layers, width, height) = device_grid.dim_sizes();
bin_spatial_lookup_.resize({num_layers, width, height});

// Get the target densisty of each physical block type.
std::vector<float> phy_ty_target_densities = get_physical_type_target_densities(target_density_arg_strs,
physical_tile_types);
VTR_LOG("Partial legalizer is using target densities:");
for (const t_physical_tile_type& phy_ty : physical_tile_types) {
VTR_LOG(" %s:%.1f", phy_ty.name.c_str(), phy_ty_target_densities[phy_ty.index]);
}
VTR_LOG("\n");

// Create a bin for each tile. This will create one bin for each root tile
// location.
vtr::vector_map<FlatPlacementBinId, size_t> bin_phy_tile_type_idx;
Expand Down Expand Up @@ -96,6 +164,12 @@ FlatPlacementDensityManager::FlatPlacementDensityManager(const APNetlist& ap_net
// Store the index of the physical tile type into a map to be
// used to compute the capacity.
bin_phy_tile_type_idx.insert(new_bin_id, tile_type->index);

// Set the target density for this bin based on the physical
// tile type..
float target_density = phy_ty_target_densities[tile_type->index];
bin_target_density_.push_back(target_density);
VTR_ASSERT(bin_target_density_[new_bin_id] = target_density);
}
}
}
Expand Down
Loading