From c096e2f9c4d3e668fd2dcb46fd2cf949bc57300d Mon Sep 17 00:00:00 2001 From: AlexandreSinger Date: Sun, 13 Oct 2024 13:28:30 -0400 Subject: [PATCH] [Place] Independent Placement Verification Created a method that would independently verify the placement in the VPR flow. If a placement passes this verification, it is assumed that it can be used in routing without issue. By design, this method does not use any global variables (everything needs to be passed in) and recomputes everything that it does not assume. This allows it to be independent of the placement flow being used, so this method can be used in the AP flow as well. --- vpr/src/analytical_place/full_legalizer.cpp | 13 +- vpr/src/base/vpr_api.cpp | 3 + vpr/src/place/place.cpp | 146 +---------- vpr/src/place/place_constraints.cpp | 15 -- vpr/src/place/place_constraints.h | 9 - vpr/src/place/verify_placement.cpp | 254 ++++++++++++++++++++ vpr/src/place/verify_placement.h | 81 +++++++ 7 files changed, 362 insertions(+), 159 deletions(-) create mode 100644 vpr/src/place/verify_placement.cpp create mode 100644 vpr/src/place/verify_placement.h diff --git a/vpr/src/analytical_place/full_legalizer.cpp b/vpr/src/analytical_place/full_legalizer.cpp index 1383a73b99b..97a6b518c3a 100644 --- a/vpr/src/analytical_place/full_legalizer.cpp +++ b/vpr/src/analytical_place/full_legalizer.cpp @@ -7,7 +7,6 @@ #include "full_legalizer.h" -#include #include #include #include @@ -27,6 +26,7 @@ #include "physical_types.h" #include "place_constraints.h" #include "place_macro.h" +#include "verify_placement.h" #include "vpr_api.h" #include "vpr_context.h" #include "vpr_error.h" @@ -396,5 +396,16 @@ void FullLegalizer::legalize(const PartialPlacement& p_placement) { // Place the clusters based on where the atoms want to be placed. place_clusters(clb_nlist, p_placement); + + // Verify that the placement created by the full legalizer is valid. + unsigned num_placement_errors = verify_placement(g_vpr_ctx); + if (num_placement_errors == 0) { + VTR_LOG("Completed placement consistency check successfully.\n"); + } else { + VPR_ERROR(VPR_ERROR_AP, + "Completed placement consistency check, %u errors found.\n" + "Aborting program.\n", + num_placement_errors); + } } diff --git a/vpr/src/base/vpr_api.cpp b/vpr/src/base/vpr_api.cpp index e824f20f712..60490eecdc2 100644 --- a/vpr/src/base/vpr_api.cpp +++ b/vpr/src/base/vpr_api.cpp @@ -793,6 +793,9 @@ bool vpr_place_flow(const Netlist<>& net_list, t_vpr_setup& vpr_setup, const t_a vpr_load_placement(vpr_setup, arch); } + // FIXME: This synchronization is not consistent with the rest of + // placement. This requires it to happen after the placement is + // verified. See issue #2801 sync_grid_to_blocks(); post_place_sync(); } diff --git a/vpr/src/place/place.cpp b/vpr/src/place/place.cpp index e41fbcbd2ba..9a3884abc20 100644 --- a/vpr/src/place/place.cpp +++ b/vpr/src/place/place.cpp @@ -8,6 +8,9 @@ #include #include "NetPinTimingInvalidator.h" +#include "clustered_netlist.h" +#include "device_grid.h" +#include "verify_placement.h" #include "vtr_assert.h" #include "vtr_log.h" #include "vtr_util.h" @@ -228,11 +231,6 @@ static int check_placement_costs(const t_placer_costs& costs, PlacerState& placer_state, NetCostHandler& net_cost_handler); - -static int check_placement_consistency(const BlkLocRegistry& blk_loc_registry); -static int check_block_placement_consistency(const BlkLocRegistry& blk_loc_registry); -static int check_macro_placement_consistency(const BlkLocRegistry& blk_loc_registry); - static float starting_t(const t_annealing_state* state, t_placer_costs* costs, t_annealing_sched annealing_sched, @@ -1943,12 +1941,19 @@ static void check_place(const t_placer_costs& costs, * every block, blocks are in legal spots, etc. Also recomputes * * the final placement cost from scratch and makes sure it is * * within roundoff of what we think the cost is. */ + const ClusteredNetlist& clb_nlist = g_vpr_ctx.clustering().clb_nlist; + const DeviceGrid& device_grid = g_vpr_ctx.device().grid; + const auto& cluster_constraints = g_vpr_ctx.floorplanning().cluster_constraints; int error = 0; - error += check_placement_consistency(placer_state.blk_loc_registry()); + // Verify the placement invariants independent to the placement flow. + error += verify_placement(placer_state.blk_loc_registry(), + clb_nlist, + device_grid, + cluster_constraints); + error += check_placement_costs(costs, delay_model, criticalities, place_algorithm, placer_state, net_cost_handler); - error += check_placement_floorplanning(placer_state.block_locs()); if (noc_opts.noc) { // check the NoC costs during placement if the user is using the NoC supported flow @@ -2000,133 +2005,6 @@ static int check_placement_costs(const t_placer_costs& costs, return error; } -static int check_placement_consistency(const BlkLocRegistry& blk_loc_registry) { - return check_block_placement_consistency(blk_loc_registry) + check_macro_placement_consistency(blk_loc_registry); -} - -static int check_block_placement_consistency(const BlkLocRegistry& blk_loc_registry) { - auto& cluster_ctx = g_vpr_ctx.clustering(); - auto& device_ctx = g_vpr_ctx.device(); - const auto& block_locs = blk_loc_registry.block_locs(); - const auto& grid_blocks = blk_loc_registry.grid_blocks(); - - int error = 0; - - vtr::vector bdone(cluster_ctx.clb_nlist.blocks().size(), 0); - - /* Step through device grid and placement. Check it against blocks */ - for (int layer_num = 0; layer_num < (int)device_ctx.grid.get_num_layers(); layer_num++) { - for (int i = 0; i < (int)device_ctx.grid.width(); i++) { - for (int j = 0; j < (int)device_ctx.grid.height(); j++) { - const t_physical_tile_loc tile_loc(i, j, layer_num); - const auto& type = device_ctx.grid.get_physical_type(tile_loc); - if (grid_blocks.get_usage(tile_loc) > type->capacity) { - VTR_LOG_ERROR( - "%d blocks were placed at grid location (%d,%d,%d), but location capacity is %d.\n", - grid_blocks.get_usage(tile_loc), i, j, layer_num, type->capacity); - error++; - } - int usage_check = 0; - for (int k = 0; k < type->capacity; k++) { - ClusterBlockId bnum = grid_blocks.block_at_location({i, j, k, layer_num}); - if (bnum == ClusterBlockId::INVALID()) { - continue; - } - - auto logical_block = cluster_ctx.clb_nlist.block_type(bnum); - auto physical_tile = type; - t_pl_loc block_loc = block_locs[bnum].loc; - - if (physical_tile_type(block_loc) != physical_tile) { - VTR_LOG_ERROR( - "Block %zu type (%s) does not match grid location (%zu,%zu, %d) type (%s).\n", - size_t(bnum), logical_block->name.c_str(), i, j, layer_num, physical_tile->name.c_str()); - error++; - } - - auto& loc = block_locs[bnum].loc; - if (loc.x != i || loc.y != j || loc.layer != layer_num - || !is_sub_tile_compatible(physical_tile, logical_block, - loc.sub_tile)) { - VTR_LOG_ERROR( - "Block %zu's location is (%d,%d,%d,%d) but found in grid at (%d,%d,%d,%d).\n", - size_t(bnum), - loc.x, - loc.y, - loc.sub_tile, - loc.layer, - i, - j, - k, - layer_num); - error++; - } - ++usage_check; - bdone[bnum]++; - } - if (usage_check != grid_blocks.get_usage(tile_loc)) { - VTR_LOG_ERROR( - "%d block(s) were placed at location (%d,%d,%d), but location contains %d block(s).\n", - grid_blocks.get_usage(tile_loc), - tile_loc.x, - tile_loc.y, - tile_loc.layer_num, - usage_check); - error++; - } - } - } - } - - /* Check that every block exists in the device_ctx.grid and cluster_ctx.blocks arrays somewhere. */ - for (ClusterBlockId blk_id : cluster_ctx.clb_nlist.blocks()) - if (bdone[blk_id] != 1) { - VTR_LOG_ERROR("Block %zu listed %d times in device context grid.\n", - size_t(blk_id), bdone[blk_id]); - error++; - } - - return error; -} - -int check_macro_placement_consistency(const BlkLocRegistry& blk_loc_registry) { - const auto& pl_macros = blk_loc_registry.place_macros().macros(); - const auto& block_locs = blk_loc_registry.block_locs(); - const auto& grid_blocks = blk_loc_registry.grid_blocks(); - - int error = 0; - - /* Check the pl_macro placement are legal - blocks are in the proper relative position. */ - for (size_t imacro = 0; imacro < pl_macros.size(); imacro++) { - auto head_iblk = pl_macros[imacro].members[0].blk_index; - - for (size_t imember = 0; imember < pl_macros[imacro].members.size(); imember++) { - auto member_iblk = pl_macros[imacro].members[imember].blk_index; - - // Compute the supposed member's x,y,z location - t_pl_loc member_pos = block_locs[head_iblk].loc + pl_macros[imacro].members[imember].offset; - - // Check the blk_loc_registry.block_locs data structure first - if (block_locs[member_iblk].loc != member_pos) { - VTR_LOG_ERROR( - "Block %zu in pl_macro #%zu is not placed in the proper orientation.\n", - size_t(member_iblk), imacro); - error++; - } - - // Then check the blk_loc_registry.grid data structure - if (grid_blocks.block_at_location(member_pos) != member_iblk) { - VTR_LOG_ERROR( - "Block %zu in pl_macro #%zu is not placed in the proper orientation.\n", - size_t(member_iblk), imacro); - error++; - } - } // Finish going through all the members - } // Finish going through all the macros - - return error; -} - #ifdef VERBOSE void print_clb_placement(const char* fname) { /* Prints out the clb placements to a file. */ diff --git a/vpr/src/place/place_constraints.cpp b/vpr/src/place/place_constraints.cpp index f53efc0f4ef..2fee09e9d7b 100644 --- a/vpr/src/place/place_constraints.cpp +++ b/vpr/src/place/place_constraints.cpp @@ -13,21 +13,6 @@ #include "place_util.h" #include "vpr_context.h" -int check_placement_floorplanning(const vtr::vector_map& block_locs) { - int error = 0; - auto& cluster_ctx = g_vpr_ctx.clustering(); - - for (ClusterBlockId blk_id : cluster_ctx.clb_nlist.blocks()) { - t_pl_loc loc = block_locs[blk_id].loc; - if (!cluster_floorplanning_legal(blk_id, loc)) { - error++; - VTR_LOG_ERROR("Block %zu is not in correct floorplanning region.\n", size_t(blk_id)); - } - } - - return error; -} - bool is_cluster_constrained(ClusterBlockId blk_id) { auto& floorplanning_ctx = g_vpr_ctx.floorplanning(); const PartitionRegion& pr = floorplanning_ctx.cluster_constraints[blk_id]; diff --git a/vpr/src/place/place_constraints.h b/vpr/src/place/place_constraints.h index 78497dd20f8..9e045178e77 100644 --- a/vpr/src/place/place_constraints.h +++ b/vpr/src/place/place_constraints.h @@ -15,15 +15,6 @@ #include "place_macro.h" #include "grid_tile_lookup.h" -/** - * @brief Check that placement of each block is within the floorplan constraint region - * of that block (if the block has any constraints). - * - * @param block_locs Contains the location where each clustered block is placed. - * @return int The number of errors (inconsistencies in adherence to floorplanning constraints). - */ -int check_placement_floorplanning(const vtr::vector_map& block_locs); - /** * @brief Check if the block has floorplanning constraints. * diff --git a/vpr/src/place/verify_placement.cpp b/vpr/src/place/verify_placement.cpp new file mode 100644 index 00000000000..7fca36d6d19 --- /dev/null +++ b/vpr/src/place/verify_placement.cpp @@ -0,0 +1,254 @@ +/** + * @file + * @author Alex Singer + * @date October 2024 + * @brief Definitions of the independent placement verifier. + * + * Most of this code originally came from place.cpp and place_constraints.cpp + * + * By design, these methods should not use any global variables and anything + * that it does not assume should be re-computed from scratch. This ensures that + * the placement is actually valid. + */ + +#include "verify_placement.h" +#include +#include "blk_loc_registry.h" +#include "clustered_netlist.h" +#include "device_grid.h" +#include "partition_region.h" +#include "physical_types.h" +#include "place_macro.h" +#include "vpr_context.h" +#include "vpr_types.h" +#include "vpr_utils.h" +#include "vtr_log.h" +#include "vtr_vector.h" + +/** + * @brief Check the the block placement and grid blocks are consistent with each + * other and are both valid. + * + * This method checks: + * - The grid blocks matches the block locations. + * - Blocks are not in invalid grid locations. + * - All clusters are placed. + * + * @param blk_loc_registry A registry containing the block locations and + * the blocks at each grid location. + * @param clb_nlist The clustered netlist being verified. + * @param device_grid The device grid being verified over. + * + * @return The number of errors in the block placement. + */ +static unsigned check_block_placement_consistency(const BlkLocRegistry& blk_loc_registry, + const ClusteredNetlist& clb_nlist, + const DeviceGrid& device_grid) { + const auto& block_locs = blk_loc_registry.block_locs(); + const auto& grid_blocks = blk_loc_registry.grid_blocks(); + + unsigned num_errors = 0; + + vtr::vector bdone(clb_nlist.blocks().size(), 0); + + /* Step through device grid and placement. Check it against blocks */ + for (int layer_num = 0; layer_num < (int)device_grid.get_num_layers(); layer_num++) { + for (int i = 0; i < (int)device_grid.width(); i++) { + for (int j = 0; j < (int)device_grid.height(); j++) { + const t_physical_tile_loc tile_loc(i, j, layer_num); + const auto& type = device_grid.get_physical_type(tile_loc); + if (grid_blocks.get_usage(tile_loc) > type->capacity) { + VTR_LOG_ERROR( + "%d blocks were placed at grid location (%d,%d,%d), but location capacity is %d.\n", + grid_blocks.get_usage(tile_loc), i, j, layer_num, type->capacity); + num_errors++; + } + + int usage_check = 0; + for (int k = 0; k < type->capacity; k++) { + ClusterBlockId bnum = grid_blocks.block_at_location({i, j, k, layer_num}); + if (bnum == ClusterBlockId::INVALID()) { + continue; + } + + auto logical_block = clb_nlist.block_type(bnum); + auto physical_tile = type; + t_pl_loc block_loc = block_locs[bnum].loc; + + if (physical_tile_type(block_loc) != physical_tile) { + VTR_LOG_ERROR( + "Block %zu type (%s) does not match grid location (%zu,%zu, %d) type (%s).\n", + size_t(bnum), logical_block->name.c_str(), i, j, layer_num, physical_tile->name.c_str()); + num_errors++; + } + + auto& loc = block_locs[bnum].loc; + if (loc.x != i || loc.y != j || loc.layer != layer_num + || !is_sub_tile_compatible(physical_tile, logical_block, + loc.sub_tile)) { + VTR_LOG_ERROR( + "Block %zu's location is (%d,%d,%d,%d) but found in grid at (%d,%d,%d,%d).\n", + size_t(bnum), + loc.x, + loc.y, + loc.sub_tile, + loc.layer, + i, + j, + k, + layer_num); + num_errors++; + } + ++usage_check; + bdone[bnum]++; + } + if (usage_check != grid_blocks.get_usage(tile_loc)) { + VTR_LOG_ERROR( + "%d block(s) were placed at location (%d,%d,%d), but location contains %d block(s).\n", + grid_blocks.get_usage(tile_loc), + tile_loc.x, + tile_loc.y, + tile_loc.layer_num, + usage_check); + num_errors++; + } + } + } + } + + /* Check that every block exists in the device_ctx.grid and cluster_ctx.blocks arrays somewhere. */ + for (ClusterBlockId blk_id : clb_nlist.blocks()) + if (bdone[blk_id] != 1) { + VTR_LOG_ERROR("Block %zu listed %d times in device context grid.\n", + size_t(blk_id), bdone[blk_id]); + num_errors++; + } + + return num_errors; +} + +/** + * @brief Check that the macro placement is consistent + * + * This checks that the pl_macro placement is legal by making sure that blocks + * are in the proper relative positions. + * + * It is assumed in this method that the macros were initialized properly. + * FIXME: This needs to be verified somewhere in the flow. + * + * @param blk_loc_registry A registry containing the block locations and + * the blocks at each grid location. + * + * @return The number of errors in the macro placement. + */ +static unsigned check_macro_placement_consistency(const BlkLocRegistry& blk_loc_registry) { + const PlaceMacros& pl_macros = blk_loc_registry.place_macros(); + const auto& block_locs = blk_loc_registry.block_locs(); + const auto& grid_blocks = blk_loc_registry.grid_blocks(); + + unsigned num_errors = 0; + + /* Check the pl_macro placement are legal - blocks are in the proper relative position. */ + for (size_t imacro = 0; imacro < pl_macros.macros().size(); imacro++) { + auto head_iblk = pl_macros[imacro].members[0].blk_index; + + for (size_t imember = 0; imember < pl_macros[imacro].members.size(); imember++) { + auto member_iblk = pl_macros[imacro].members[imember].blk_index; + + // Compute the supposed member's x,y,z location + t_pl_loc member_pos = block_locs[head_iblk].loc + pl_macros[imacro].members[imember].offset; + + // Check the blk_loc_registry.block_locs data structure first + if (block_locs[member_iblk].loc != member_pos) { + VTR_LOG_ERROR( + "Block %zu in pl_macro #%zu is not placed in the proper orientation.\n", + size_t(member_iblk), imacro); + num_errors++; + } + + // Then check the blk_loc_registry.grid data structure + if (grid_blocks.block_at_location(member_pos) != member_iblk) { + VTR_LOG_ERROR( + "Block %zu in pl_macro #%zu is not placed in the proper orientation.\n", + size_t(member_iblk), imacro); + num_errors++; + } + } // Finish going through all the members + } // Finish going through all the macros + + return num_errors; +} + +/** + * @brief Check that the placement is following the floorplanning constraints. + * + * This checks that all constrained blocks are placed within their floorplanning + * constraints. + * + * @param blk_loc_registry A registry containing the block locations and + * the blocks at each grid location. + * @param cluster_constraints The constraints on the placement of each + * cluster. + * @param clb_nlist The clustered netlist being verified. + * + * @return The number of errors in the placement regarding floorplanning. + */ +static unsigned check_placement_floorplanning(const BlkLocRegistry& blk_loc_registry, + const vtr::vector& cluster_constraints, + const ClusteredNetlist& clb_nlist) { + const auto& block_locs = blk_loc_registry.block_locs(); + + unsigned num_errors = 0; + for (ClusterBlockId blk_id : clb_nlist.blocks()) { + const PartitionRegion& blk_pr = cluster_constraints[blk_id]; + // If the cluster is not constrained, no need to check. + if (blk_pr.empty()) + continue; + // Check if the block is placed in its constrained partition region. + const t_pl_loc& blk_loc = block_locs[blk_id].loc; + if (!blk_pr.is_loc_in_part_reg(blk_loc)) { + VTR_LOG_ERROR( + "Block %zu is not in correct floorplanning region.\n", + size_t(blk_id)); + num_errors++; + } + } + return num_errors; +} + +unsigned verify_placement(const BlkLocRegistry& blk_loc_registry, + const ClusteredNetlist& clb_nlist, + const DeviceGrid& device_grid, + const vtr::vector& cluster_constraints) { + // TODO: Verify the assumptions with an assert_debug. + // - Once a verify_clustering method is written, it would make sense to + // call it here in debug mode. + unsigned num_errors = 0; + + // Check that the block placement is valid. + num_errors += check_block_placement_consistency(blk_loc_registry, + clb_nlist, + device_grid); + + // Check that the pl_macros are observed. + // FIXME: Should we be checking the macro consistency at all? Does the + // router use the pl_macros? If not this should be removed from this + // method and only used when the macro placement is actually used. + num_errors += check_macro_placement_consistency(blk_loc_registry); + + // Check that the floorplanning is observed. + num_errors += check_placement_floorplanning(blk_loc_registry, + cluster_constraints, + clb_nlist); + + return num_errors; +} + +unsigned verify_placement(const VprContext& ctx) { + // Verify the placement within the given context. + return verify_placement(ctx.placement().blk_loc_registry(), + ctx.clustering().clb_nlist, + ctx.device().grid, + ctx.floorplanning().cluster_constraints); +} + diff --git a/vpr/src/place/verify_placement.h b/vpr/src/place/verify_placement.h new file mode 100644 index 00000000000..1bee823ea5e --- /dev/null +++ b/vpr/src/place/verify_placement.h @@ -0,0 +1,81 @@ +/** + * @file + * @author Alex Singer + * @date October 2024 + * @brief Independent verify methods to check invariants that ensure that the + * given placement is valid and can be used with the rest of the VPR + * flow. + * + * This methods were orginally part of methods found in the place.cpp file and + * the place_constraints.cpp files. Moved into here to put it all in one place. + * Since these methods should be completely independent of the place flow, it + * makes sense that they are in their own file. + */ + +#pragma once + +#include "vtr_vector.h" + +// Forward declarations +class BlkLocRegistry; +class ClusterBlockId; +class ClusteredNetlist; +class DeviceGrid; +class PartitionRegion; +class VprContext; + +/** + * @brief Verify the placement of the clustered blocks on the device. + * + * This is verifier is independent to how the placement was performed and check + * invariants that all placements must adhere to in order to be used in the + * rest of the VPR flow. The assumption is that if a placement passes this + * verifier it can be passed into routing without issue. + * + * By design, this verifier uses no global variables and tries to recompute + * anything that it does not assume. + * + * Invariants (are checked by this function): + * - All clusters are placed. + * - All clusters are placed in a tile, sub-tile that it can exist in. + * - The cluster locations are consistent with the clusters in each block. + * - The placement macros are consistent such that all blocks are in the + * proper relative positions. + * - All clusters are placed in legal positions according to the floorplanning + * constraints. + * + * Assumptions (are not checked by this function): + * - The clustered netlist is valid. + * - The device grid is valid. + * - The cluster constraints are valid. + * - The macros are valid. + * + * @param blk_loc_registry A registry containing the current placement of + * the clusters. + * @param clb_nlist The clustered netlist being verified. + * @param device_grid The device grid being verified over. + * @param cluster_constraints The constrained regions that each cluster is + * constrained to. + * + * @return The number of errors found in the placement. Will also print error + * log messages for each error found. + */ +unsigned verify_placement(const BlkLocRegistry& blk_loc_registry, + const ClusteredNetlist& clb_nlist, + const DeviceGrid& device_grid, + const vtr::vector& cluster_constraints); + +/** + * @brief Verifies the placement as it appears in the global context. + * + * This performs the verification in the method above, but performs it on the + * global VPR context itself. This verifies that the actual placement being + * used through the rest of the flow is valid. + * + * NOTE: The above method is in-case the user wishes to verify a temporary + * placement before updating the actual global VPR context. + * + * @param ctx The global VPR context variable found in g_vpr_ctx. + */ +unsigned verify_placement(const VprContext& ctx); +