From 93cddd57dff015cb7464d2a3a38447745a47f954 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Tue, 10 Jul 2018 22:38:04 -0700 Subject: [PATCH 001/116] Start porting initial placer from VPR to npnr --- common/place_vpr.cc | 619 ++++++++++++++++++++++++++++++++++++++++++++ common/place_vpr.h | 30 +++ ice40/main.cc | 3 +- 3 files changed, 651 insertions(+), 1 deletion(-) create mode 100644 common/place_vpr.cc create mode 100644 common/place_vpr.h diff --git a/common/place_vpr.cc b/common/place_vpr.cc new file mode 100644 index 0000000000..479fc706db --- /dev/null +++ b/common/place_vpr.cc @@ -0,0 +1,619 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * Copyright (C) 2018 David Shah + * + * Simulated annealing implementation based on arachne-pnr + * Copyright (C) 2015-2018 Cotton Seed + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "place_vpr.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "place_common.h" +#include "place_legaliser.h" +#include "timing.h" +#include "util.h" +NEXTPNR_NAMESPACE_BEGIN + +class VPRPlacer +{ + private: + std::vector> free_locations; + + public: + VPRPlacer(Context *ctx) : ctx(ctx) + { + int num_bel_types = 0; + for (auto bel : ctx->getBels()) { + int x, y; + bool gb; + ctx->estimatePosition(bel, x, y, gb); + BelType type = ctx->getBelType(bel); + int type_idx; + if (bel_types.find(type) == bel_types.end()) { + type_idx = num_bel_types++; + bel_types[type] = type_idx; + } else { + type_idx = bel_types.at(type); + } + if (int(fast_bels.size()) < type_idx + 1) { + fast_bels.resize(type_idx + 1); + free_locations.resize(type_idx + 1); + } + if (int(fast_bels.at(type_idx).size()) < (x + 1)) + fast_bels.at(type_idx).resize(x + 1); + if (int(fast_bels.at(type_idx).at(x).size()) < (y + 1)) + fast_bels.at(type_idx).at(x).resize(y + 1); + max_x = std::max(max_x, x); + max_y = std::max(max_y, y); + fast_bels.at(type_idx).at(x).at(y).push_back(bel); + free_locations[type_idx].push_back(bel); + } + diameter = std::max(max_x, max_y) + 1; + } + + bool place() + { + log_break(); + + size_t placed_cells = 0; + // Initial constraints placer + for (auto &cell_entry : ctx->cells) { + CellInfo *cell = cell_entry.second.get(); + auto loc = cell->attrs.find(ctx->id("BEL")); + if (loc != cell->attrs.end()) { + std::string loc_name = loc->second; + BelId bel = ctx->getBelByName(ctx->id(loc_name)); + if (bel == BelId()) { + log_error("No Bel named \'%s\' located for " + "this chip (processing BEL attribute on \'%s\')\n", + loc_name.c_str(), cell->name.c_str(ctx)); + } + + BelType bel_type = ctx->getBelType(bel); + if (bel_type != ctx->belTypeFromId(cell->type)) { + log_error("Bel \'%s\' of type \'%s\' does not match cell " + "\'%s\' of type \'%s\'", + loc_name.c_str(), ctx->belTypeToId(bel_type).c_str(ctx), cell->name.c_str(ctx), + cell->type.c_str(ctx)); + } + + ctx->bindBel(bel, cell->name, STRENGTH_USER); + locked_bels.insert(bel); + placed_cells++; + } + } + int constr_placed_cells = placed_cells; + log_info("Placed %d cells based on constraints.\n", int(placed_cells)); + + // Sort to-place cells for deterministic initial placement + std::vector autoplaced; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->bel == BelId()) { + autoplaced.push_back(cell.second.get()); + } + } + std::sort(autoplaced.begin(), autoplaced.end(), [](CellInfo *a, CellInfo *b) { return a->name < b->name; }); + ctx->shuffle(autoplaced); + + // Place cells randomly initially + log_info("Creating initial placement for remaining %d cells.\n", int(autoplaced.size())); + + vpr_initial_placement(autoplaced); +#if 0 + for (auto cell : autoplaced) { + place_initial(cell); + placed_cells++; + if ((placed_cells - constr_placed_cells) % 500 == 0) + log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), + int(autoplaced.size())); + } + if ((placed_cells - constr_placed_cells) % 500 != 0) + log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), + int(autoplaced.size())); +#endif + + log_info("Running simulated annealing placer.\n"); + + // Calculate wirelength after initial placement + curr_wirelength = 0; + curr_tns = 0; + for (auto &net : ctx->nets) { + wirelen_t wl = get_net_wirelength(ctx, net.second.get(), curr_tns); + wirelengths[net.first] = wl; + curr_wirelength += wl; + } + + int n_no_progress = 0; + double avg_wirelength = curr_wirelength; + temp = 10000; + + // Main simulated annealing loop + for (int iter = 1;; iter++) { + n_move = n_accept = 0; + improved = false; + + if (iter % 5 == 0 || iter == 1) + log_info(" at iteration #%d: temp = %f, wire length = " + "%.0f, est tns = %.02fns\n", + iter, temp, double(curr_wirelength), curr_tns); + + for (int m = 0; m < 15; ++m) { + // Loop through all automatically placed cells + for (auto cell : autoplaced) { + // Find another random Bel for this cell + BelId try_bel = random_bel_for_cell(cell); + // If valid, try and swap to a new position and see if + // the new position is valid/worthwhile + if (try_bel != BelId() && try_bel != cell->bel) + try_swap_position(cell, try_bel); + } + } + // Heuristic to improve placement on the 8k + if (improved) + n_no_progress = 0; + else + n_no_progress++; + + if (temp <= 1e-3 && n_no_progress >= 5) { + if (iter % 5 != 0) + log_info(" at iteration #%d: temp = %f, wire length = %f\n", iter, temp, double(curr_wirelength)); + break; + } + + double Raccept = double(n_accept) / double(n_move); + + int M = std::max(max_x, max_y) + 1; + + double upper = 0.6, lower = 0.4; + + if (curr_wirelength < 0.95 * avg_wirelength) { + avg_wirelength = 0.8 * avg_wirelength + 0.2 * curr_wirelength; + } else { + if (Raccept >= 0.8) { + temp *= 0.7; + } else if (Raccept > upper) { + if (diameter < M) + diameter++; + else + temp *= 0.9; + } else if (Raccept > lower) { + temp *= 0.95; + } else { + // Raccept < 0.3 + if (diameter > 1) + diameter--; + else + temp *= 0.8; + } + } + // Once cooled below legalise threshold, run legalisation and start requiring + // legal moves only + if (temp < legalise_temp && !require_legal) { + legalise_design(ctx); + require_legal = true; + autoplaced.clear(); + for (auto cell : sorted(ctx->cells)) { + if (cell.second->belStrength < STRENGTH_STRONG) + autoplaced.push_back(cell.second); + } + temp = post_legalise_temp; + diameter *= post_legalise_dia_scale; + ctx->shuffle(autoplaced); + assign_budget(ctx); + } + + // Recalculate total wirelength entirely to avoid rounding errors + // accumulating over time + curr_wirelength = 0; + curr_tns = 0; + for (auto &net : ctx->nets) { + wirelen_t wl = get_net_wirelength(ctx, net.second.get(), curr_tns); + wirelengths[net.first] = wl; + curr_wirelength += wl; + } + } + // Final post-pacement validitiy check + for (auto bel : ctx->getBels()) { + IdString cell = ctx->getBoundBelCell(bel); + if (!ctx->isBelLocationValid(bel)) { + std::string cell_text = "no cell"; + if (cell != IdString()) + cell_text = std::string("cell '") + cell.str(ctx) + "'"; + if (ctx->force) { + log_warning("post-placement validity check failed for Bel '%s' " + "(%s)\n", + ctx->getBelName(bel).c_str(ctx), cell_text.c_str()); + } else { + log_error("post-placement validity check failed for Bel '%s' " + "(%s)\n", + ctx->getBelName(bel).c_str(ctx), cell_text.c_str()); + } + } + } + return true; + } + + private: +#if 0 + // Initial random placement + void place_initial(CellInfo *cell) + { + bool all_placed = false; + int iters = 25; + while (!all_placed) { + BelId best_bel = BelId(); + uint64_t best_score = std::numeric_limits::max(), + best_ripup_score = std::numeric_limits::max(); + CellInfo *ripup_target = nullptr; + BelId ripup_bel = BelId(); + if (cell->bel != BelId()) { + ctx->unbindBel(cell->bel); + } + BelType targetType = ctx->belTypeFromId(cell->type); + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) == targetType && (ctx->isValidBelForCell(cell, bel) || !require_legal)) { + if (ctx->checkBelAvail(bel)) { + uint64_t score = ctx->rng64(); + if (score <= best_score) { + best_score = score; + best_bel = bel; + } + } else { + uint64_t score = ctx->rng64(); + if (score <= best_ripup_score) { + best_ripup_score = score; + ripup_target = ctx->cells.at(ctx->getBoundBelCell(bel)).get(); + ripup_bel = bel; + } + } + } + } + if (best_bel == BelId()) { + if (iters == 0 || ripup_bel == BelId()) + log_error("failed to place cell '%s' of type '%s'\n", cell->name.c_str(ctx), cell->type.c_str(ctx)); + --iters; + ctx->unbindBel(ripup_target->bel); + best_bel = ripup_bel; + } else { + all_placed = true; + } + ctx->bindBel(best_bel, cell->name, STRENGTH_WEAK); + + // Back annotate location + cell->attrs[ctx->id("BEL")] = ctx->getBelName(cell->bel).str(ctx); + cell = ripup_target; + } + } +#endif + + void vpr_initial_placement(const std::vector& autoplaced) { + + /* Randomly places the blocks to create an initial placement. We rely on + * the legal_pos array already being loaded. That legal_pos[itype] is an + * array that gives every legal value of (x,y,z) that can accomodate a block. + * The number of such locations is given by num_legal_pos[itype]. + */ +// int itype, x, y, z, ipos; +// int *free_locations; /* [0..device_ctx.num_block_types-1]. +// * Stores how many locations there are for this type that *might* still be free. +// * That is, this stores the number of entries in legal_pos[itype] that are worth considering +// * as you look for a free location. +// */ +// auto& device_ctx = g_vpr_ctx.device(); +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& place_ctx = g_vpr_ctx.mutable_placement(); +// + + // free_locations is populated in constructor + +// free_locations = (int *) vtr::malloc(device_ctx.num_block_types * sizeof(int)); +// for (itype = 0; itype < device_ctx.num_block_types; itype++) { +// free_locations[itype] = num_legal_pos[itype]; +// } +// +// /* We'll use the grid to record where everything goes. Initialize to the grid has no +// * blocks placed anywhere. +// */ +// for (size_t i = 0; i < device_ctx.grid.width(); i++) { +// for (size_t j = 0; j < device_ctx.grid.height(); j++) { +// place_ctx.grid_blocks[i][j].usage = 0; +// itype = device_ctx.grid[i][j].type->index; +// for (int k = 0; k < device_ctx.block_types[itype].capacity; k++) { +// if (place_ctx.grid_blocks[i][j].blocks[k] != INVALID_BLOCK_ID) { +// place_ctx.grid_blocks[i][j].blocks[k] = EMPTY_BLOCK_ID; +// } +// } +// } +// } +// +// /* Similarly, mark all blocks as not being placed yet. */ +// for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { +// place_ctx.block_locs[blk_id].x = OPEN; +// place_ctx.block_locs[blk_id].y = OPEN; +// place_ctx.block_locs[blk_id].z = OPEN; +// } +// +// initial_placement_pl_macros(MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY, free_locations); +// +// // All the macros are placed, update the legal_pos[][] array +// for (itype = 0; itype < device_ctx.num_block_types; itype++) { +// VTR_ASSERT(free_locations[itype] >= 0); +// for (ipos = 0; ipos < free_locations[itype]; ipos++) { +// x = legal_pos[itype][ipos].x; +// y = legal_pos[itype][ipos].y; +// z = legal_pos[itype][ipos].z; +// +// // Check if that location is occupied. If it is, remove from legal_pos +// if (place_ctx.grid_blocks[x][y].blocks[z] != EMPTY_BLOCK_ID && place_ctx.grid_blocks[x][y].blocks[z] != INVALID_BLOCK_ID) { +// legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; +// free_locations[itype]--; +// +// // After the move, I need to check this particular entry again +// ipos--; +// continue; +// } +// } +// } // Finish updating the legal_pos[][] and free_locations[] array + + vpr_initial_placement_blocks(autoplaced); + +// if (pad_loc_type == USER) { +// read_user_pad_loc(pad_loc_file); +// } +// +// /* Restore legal_pos */ +// load_legal_placements(); +// +// #ifdef VERBOSE +// vtr::printf_info("At end of initial_placement.\n"); +// if (getEchoEnabled() && isEchoFileEnabled(E_ECHO_INITIAL_CLB_PLACEMENT)) { +// print_clb_placement(getEchoFileName(E_ECHO_INITIAL_CLB_PLACEMENT)); +// } +// #endif +// free(free_locations); + } + + /* Place blocks that are NOT a part of any macro. + * We'll randomly place each block in the clustered netlist, one by one. */ + void vpr_initial_placement_blocks(const std::vector& autoplaced) { +// int itype, ipos, x, y, z; +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& place_ctx = g_vpr_ctx.mutable_placement(); +// auto& device_ctx = g_vpr_ctx.device(); + + size_t itype; + BelId bel; + + // Shuffle all free locations once here, rather than picking a block at random + for (auto& free_locations_type : free_locations) { + ctx->shuffle(free_locations_type); + } + + for (auto cell : autoplaced) { +// if (place_ctx.block_locs[blk_id].x != -1) { // -1 is a sentinel for an empty block +// // block placed. +// continue; +// } + +// /* Don't do IOs if the user specifies IOs; we'll read those locations later. */ +// if (!(is_io_type(cluster_ctx.clb_nlist.block_type(blk_id)) && pad_loc_type == USER)) { +// +// /* Randomly select a free location of the appropriate type for blk_id. +// * We have a linearized list of all the free locations that can +// * accomodate a block of that type in free_locations[itype]. +// * Choose one randomly and put blk_id there. Then we don't want to pick +// * that location again, so remove it from the free_locations array. +// */ +// itype = cluster_ctx.clb_nlist.block_type(blk_id)->index; +// if (free_locations[itype] <= 0) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "Initial placement failed.\n" +// "Could not place block %s (#%zu); no free locations of type %s (#%d).\n", +// cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id), device_ctx.block_types[itype].name, itype); +// } + + vpr_initial_placement_location(cell, itype, bel); + + NPNR_ASSERT(ctx->checkBelAvail(bel)); + ctx->bindBel(bel, cell->name, STRENGTH_WEAK); + +// //Mark IOs as fixed if specifying a (fixed) random placement +// if(is_io_type(cluster_ctx.clb_nlist.block_type(blk_idctx->belTypeFromId(cell->type))) && pad_loc_type == RANDOM) { +// place_ctx.block_locs[blk_id].is_fixed = true; +// } + +// /* Ensure randomizer doesn't pick this location again, since it's occupied. Could shift all the +// * legal positions in legal_pos to remove the entry (choice) we just used, but faster to +// * just move the last entry in legal_pos to the spot we just used and decrement the +// * count of free_locations. */ +// legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; /* overwrite used block position */ +// free_locations[itype]--; + + free_locations[itype].pop_back(); + +// } + } + } + + void vpr_initial_placement_location(CellInfo *cell, size_t& itype, BelId& bel) { +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// + auto type = ctx->belTypeFromId(cell->type); + itype = bel_types.at(type); + + bel = free_locations[itype].back(); + } + + // Attempt a SA position swap, return true on success or false on failure + bool try_swap_position(CellInfo *cell, BelId newBel) + { + static std::unordered_set update; + static std::vector> new_lengths; + new_lengths.clear(); + update.clear(); + BelId oldBel = cell->bel; + IdString other = ctx->getBoundBelCell(newBel); + CellInfo *other_cell = nullptr; + if (other != IdString()) { + other_cell = ctx->cells[other].get(); + if (other_cell->belStrength > STRENGTH_WEAK) + return false; + } + wirelen_t new_wirelength = 0, delta; + ctx->unbindBel(oldBel); + if (other != IdString()) { + ctx->unbindBel(newBel); + } + + for (const auto &port : cell->ports) + if (port.second.net != nullptr) + update.insert(port.second.net); + + if (other != IdString()) { + for (const auto &port : other_cell->ports) + if (port.second.net != nullptr) + update.insert(port.second.net); + } + + ctx->bindBel(newBel, cell->name, STRENGTH_WEAK); + + if (other != IdString()) { + ctx->bindBel(oldBel, other_cell->name, STRENGTH_WEAK); + } + if (require_legal) { + if (!ctx->isBelLocationValid(newBel) || ((other != IdString() && !ctx->isBelLocationValid(oldBel)))) { + ctx->unbindBel(newBel); + if (other != IdString()) + ctx->unbindBel(oldBel); + goto swap_fail; + } + } + + new_wirelength = curr_wirelength; + + // Recalculate wirelengths for all nets touched by the peturbation + for (auto net : update) { + new_wirelength -= wirelengths.at(net->name); + float temp_tns = 0; + wirelen_t net_new_wl = get_net_wirelength(ctx, net, temp_tns); + new_wirelength += net_new_wl; + new_lengths.push_back(std::make_pair(net->name, net_new_wl)); + } + delta = new_wirelength - curr_wirelength; + n_move++; + // SA acceptance criterea + if (delta < 0 || (temp > 1e-6 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) { + n_accept++; + if (delta < 2) + improved = true; + } else { + if (other != IdString()) + ctx->unbindBel(oldBel); + ctx->unbindBel(newBel); + goto swap_fail; + } + curr_wirelength = new_wirelength; + for (auto new_wl : new_lengths) + wirelengths.at(new_wl.first) = new_wl.second; + + return true; + swap_fail: + ctx->bindBel(oldBel, cell->name, STRENGTH_WEAK); + if (other != IdString()) { + ctx->bindBel(newBel, other, STRENGTH_WEAK); + } + return false; + } + + // Find a random Bel of the correct type for a cell, within the specified + // diameter + BelId random_bel_for_cell(CellInfo *cell) + { + BelType targetType = ctx->belTypeFromId(cell->type); + int x, y; + bool gb; + ctx->estimatePosition(cell->bel, x, y, gb); + while (true) { + int nx = ctx->rng(2 * diameter + 1) + std::max(x - diameter, 0); + int ny = ctx->rng(2 * diameter + 1) + std::max(y - diameter, 0); + int beltype_idx = bel_types.at(targetType); + if (nx >= int(fast_bels.at(beltype_idx).size())) + continue; + if (ny >= int(fast_bels.at(beltype_idx).at(nx).size())) + continue; + const auto &fb = fast_bels.at(beltype_idx).at(nx).at(ny); + if (fb.size() == 0) + continue; + BelId bel = fb.at(ctx->rng(int(fb.size()))); + if (locked_bels.find(bel) != locked_bels.end()) + continue; + return bel; + } + } + + Context *ctx; + std::unordered_map wirelengths; + wirelen_t curr_wirelength = std::numeric_limits::max(); + float curr_tns = 0; + float temp = 1000; + bool improved = false; + int n_move, n_accept; + int diameter = 35, max_x = 1, max_y = 1; + std::unordered_map bel_types; + std::vector>>> fast_bels; + std::unordered_set locked_bels; + bool require_legal = false; + const float legalise_temp = 1; + const float post_legalise_temp = 20; + const float post_legalise_dia_scale = 2; +}; + +bool place_design_vpr(Context *ctx) +{ + try { + VPRPlacer placer(ctx); + placer.place(); + log_info("Checksum: 0x%08x\n", ctx->checksum()); +#ifndef NDEBUG + ctx->check(); +#endif + return true; + } catch (log_execution_error_exception) { +#ifndef NDEBUG + ctx->check(); +#endif + return false; + } +} + +NEXTPNR_NAMESPACE_END diff --git a/common/place_vpr.h b/common/place_vpr.h new file mode 100644 index 0000000000..6b7c6fb9ad --- /dev/null +++ b/common/place_vpr.h @@ -0,0 +1,30 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ +#ifndef PLACE_VPR_H +#define PLACE_VPR_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +extern bool place_design_vpr(Context *ctx); + +NEXTPNR_NAMESPACE_END + +#endif // PLACE_VPR_H diff --git a/ice40/main.cc b/ice40/main.cc index 87a32ded1a..b26a5744f5 100644 --- a/ice40/main.cc +++ b/ice40/main.cc @@ -43,6 +43,7 @@ #include "pcf.h" #include "place_legaliser.h" #include "place_sa.h" +#include "place_vpr.h" #include "route.h" #include "timing.h" #include "version.h" @@ -369,7 +370,7 @@ int main(int argc, char *argv[]) if (vm.count("no-tmdriv")) ctx.timing_driven = false; if (!vm.count("pack-only")) { - if (!place_design_sa(&ctx) && !ctx.force) + if (!place_design_vpr(&ctx) && !ctx.force) log_error("Placing design failed.\n"); ctx.check(); if (!route_design(&ctx) && !ctx.force) From a0a5174dfb6bad534c5e381a9695063d5e07f2a9 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Tue, 10 Jul 2018 22:52:54 -0700 Subject: [PATCH 002/116] Match original npnr messaging during initial placement --- common/place_vpr.cc | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index 479fc706db..d2ecf28e51 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -125,10 +125,20 @@ class VPRPlacer std::sort(autoplaced.begin(), autoplaced.end(), [](CellInfo *a, CellInfo *b) { return a->name < b->name; }); ctx->shuffle(autoplaced); + // Remove locked_bels from free_locations + // TODO Make this more efficient + for (auto& i : free_locations) + for (auto j = i.begin(); j != i.end(); ) { + if (locked_bels.count(*j)) + j = i.erase(j); + else + ++j; + } + // Place cells randomly initially log_info("Creating initial placement for remaining %d cells.\n", int(autoplaced.size())); - vpr_initial_placement(autoplaced); + vpr_initial_placement(autoplaced, placed_cells, constr_placed_cells); #if 0 for (auto cell : autoplaced) { place_initial(cell); @@ -316,7 +326,7 @@ class VPRPlacer } #endif - void vpr_initial_placement(const std::vector& autoplaced) { + void vpr_initial_placement(const std::vector& autoplaced, size_t& placed_cells, int constr_placed_cells) { /* Randomly places the blocks to create an initial placement. We rely on * the legal_pos array already being loaded. That legal_pos[itype] is an @@ -385,7 +395,7 @@ class VPRPlacer // } // } // Finish updating the legal_pos[][] and free_locations[] array - vpr_initial_placement_blocks(autoplaced); + vpr_initial_placement_blocks(autoplaced, placed_cells, constr_placed_cells); // if (pad_loc_type == USER) { // read_user_pad_loc(pad_loc_file); @@ -405,7 +415,7 @@ class VPRPlacer /* Place blocks that are NOT a part of any macro. * We'll randomly place each block in the clustered netlist, one by one. */ - void vpr_initial_placement_blocks(const std::vector& autoplaced) { + void vpr_initial_placement_blocks(const std::vector& autoplaced, size_t &placed_cells, const int constr_placed_cells) { // int itype, ipos, x, y, z; // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); @@ -462,7 +472,15 @@ class VPRPlacer free_locations[itype].pop_back(); // } + + ++placed_cells; + if ((placed_cells - constr_placed_cells) % 500 == 0) + log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), + int(autoplaced.size())); } + if ((placed_cells - constr_placed_cells) % 500 != 0) + log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), + int(autoplaced.size())); } void vpr_initial_placement_location(CellInfo *cell, size_t& itype, BelId& bel) { From 6020eef70090638679a7802676d54ad5efa2de9a Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Tue, 10 Jul 2018 23:06:56 -0700 Subject: [PATCH 003/116] Emulate VPR functionality by locking IOs after initial placement --- common/place_vpr.cc | 5 +++++ generic/arch.cc | 1 + generic/arch.h | 2 ++ ice40/arch.cc | 5 +++++ ice40/arch.h | 2 ++ 5 files changed, 15 insertions(+) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index d2ecf28e51..56120ce6e8 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -462,6 +462,11 @@ class VPRPlacer // place_ctx.block_locs[blk_id].is_fixed = true; // } + if (ctx->isIO(cell)) { + ctx->unbindBel(bel); + ctx->bindBel(bel, cell->name, STRENGTH_LOCKED); + } + // /* Ensure randomizer doesn't pick this location again, since it's occupied. Could shift all the // * legal positions in legal_pos to remove the entry (choice) we just used, but faster to // * just move the last entry in legal_pos to the spot we just used and decrement the diff --git a/generic/arch.cc b/generic/arch.cc index 8f89760469..5a1f5053d3 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -328,6 +328,7 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort IdString Arch::getPortClock(const CellInfo *cell, IdString port) const { return IdString(); } bool Arch::isClockPort(const CellInfo *cell, IdString port) const { return false; } +bool Arch::isIO(const CellInfo *cell) const { return false; } bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; } bool Arch::isBelLocationValid(BelId bel) const { return true; } diff --git a/generic/arch.h b/generic/arch.h index e739cfabb0..5c48aebb8e 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -169,6 +169,8 @@ struct Arch : BaseCtx bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const; IdString getPortClock(const CellInfo *cell, IdString port) const; bool isClockPort(const CellInfo *cell, IdString port) const; + // Return true if cell is a IO + bool isIO(const CellInfo* cell) const; bool isValidBelForCell(CellInfo *cell, BelId bel) const; bool isBelLocationValid(BelId bel) const; diff --git a/ice40/arch.cc b/ice40/arch.cc index 72f9c1f366..866fd12d89 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -538,4 +538,9 @@ bool Arch::isGlobalNet(const NetInfo *net) const return net->driver.cell != nullptr && net->driver.port == id_glb_buf_out; } +bool Arch::isIO(const CellInfo* cell) const +{ + return cell->type == id("SB_IO"); +} + NEXTPNR_NAMESPACE_END diff --git a/ice40/arch.h b/ice40/arch.h index 43aa0829a2..cfe083f4ae 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -665,6 +665,8 @@ struct Arch : BaseCtx bool isClockPort(const CellInfo *cell, IdString port) const; // Return true if a port is a net bool isGlobalNet(const NetInfo *net) const; + // Return true if cell is a IO + bool isIO(const CellInfo* cell) const; // ------------------------------------------------- From 6deafe57b07ce6a34af031884565fb62e89d1194 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Tue, 10 Jul 2018 23:11:03 -0700 Subject: [PATCH 004/116] Add TODO --- common/place_vpr.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index 56120ce6e8..1b31b890f7 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -463,6 +463,7 @@ class VPRPlacer // } if (ctx->isIO(cell)) { + // TODO: Add method to change bind strength without unbinding and re-binding ctx->unbindBel(bel); ctx->bindBel(bel, cell->name, STRENGTH_LOCKED); } From 65c0e502c2dff6fb3535ff1cc39187384d7dda48 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Tue, 10 Jul 2018 23:16:47 -0700 Subject: [PATCH 005/116] Cleanup --- common/place_vpr.cc | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index 1b31b890f7..331eb251c5 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -342,37 +342,9 @@ class VPRPlacer // auto& device_ctx = g_vpr_ctx.device(); // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); -// // free_locations is populated in constructor -// free_locations = (int *) vtr::malloc(device_ctx.num_block_types * sizeof(int)); -// for (itype = 0; itype < device_ctx.num_block_types; itype++) { -// free_locations[itype] = num_legal_pos[itype]; -// } -// -// /* We'll use the grid to record where everything goes. Initialize to the grid has no -// * blocks placed anywhere. -// */ -// for (size_t i = 0; i < device_ctx.grid.width(); i++) { -// for (size_t j = 0; j < device_ctx.grid.height(); j++) { -// place_ctx.grid_blocks[i][j].usage = 0; -// itype = device_ctx.grid[i][j].type->index; -// for (int k = 0; k < device_ctx.block_types[itype].capacity; k++) { -// if (place_ctx.grid_blocks[i][j].blocks[k] != INVALID_BLOCK_ID) { -// place_ctx.grid_blocks[i][j].blocks[k] = EMPTY_BLOCK_ID; -// } -// } -// } -// } -// -// /* Similarly, mark all blocks as not being placed yet. */ -// for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { -// place_ctx.block_locs[blk_id].x = OPEN; -// place_ctx.block_locs[blk_id].y = OPEN; -// place_ctx.block_locs[blk_id].z = OPEN; -// } -// // initial_placement_pl_macros(MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY, free_locations); // // // All the macros are placed, update the legal_pos[][] array From 56c33d2572d471552f5a12d61bc24141f0465e56 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Tue, 10 Jul 2018 23:31:43 -0700 Subject: [PATCH 006/116] Remove IOs from autoplaced, plus cleanup spacing --- common/place_vpr.cc | 30 +++++++++++++++++------------- ice40/chipdb.py | 0 2 files changed, 17 insertions(+), 13 deletions(-) mode change 100644 => 100755 ice40/chipdb.py diff --git a/common/place_vpr.cc b/common/place_vpr.cc index 331eb251c5..b80d9b7046 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -326,7 +326,7 @@ class VPRPlacer } #endif - void vpr_initial_placement(const std::vector& autoplaced, size_t& placed_cells, int constr_placed_cells) { + void vpr_initial_placement(std::vector autoplaced, size_t& placed_cells, int constr_placed_cells) { /* Randomly places the blocks to create an initial placement. We rely on * the legal_pos array already being loaded. That legal_pos[itype] is an @@ -387,21 +387,21 @@ class VPRPlacer /* Place blocks that are NOT a part of any macro. * We'll randomly place each block in the clustered netlist, one by one. */ - void vpr_initial_placement_blocks(const std::vector& autoplaced, size_t &placed_cells, const int constr_placed_cells) { -// int itype, ipos, x, y, z; + void vpr_initial_placement_blocks(std::vector autoplaced, size_t &placed_cells, const int constr_placed_cells) { +// int itype, ipos, x, y, z; // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); // auto& device_ctx = g_vpr_ctx.device(); + + size_t itype; + BelId bel; - size_t itype; - BelId bel; - - // Shuffle all free locations once here, rather than picking a block at random - for (auto& free_locations_type : free_locations) { - ctx->shuffle(free_locations_type); - } - - for (auto cell : autoplaced) { + // Shuffle all free locations once here, rather than picking a block at random + for (auto& free_locations_type : free_locations) { + ctx->shuffle(free_locations_type); + } + + for (auto& cell : autoplaced) { // if (place_ctx.block_locs[blk_id].x != -1) { // -1 is a sentinel for an empty block // // block placed. // continue; @@ -422,7 +422,7 @@ class VPRPlacer // "Initial placement failed.\n" // "Could not place block %s (#%zu); no free locations of type %s (#%d).\n", // cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id), device_ctx.block_types[itype].name, itype); -// } +// } vpr_initial_placement_location(cell, itype, bel); @@ -438,6 +438,7 @@ class VPRPlacer // TODO: Add method to change bind strength without unbinding and re-binding ctx->unbindBel(bel); ctx->bindBel(bel, cell->name, STRENGTH_LOCKED); + cell = nullptr; } // /* Ensure randomizer doesn't pick this location again, since it's occupied. Could shift all the @@ -459,6 +460,9 @@ class VPRPlacer if ((placed_cells - constr_placed_cells) % 500 != 0) log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), int(autoplaced.size())); + + // Linear complexity removal of all IOs cells that were set to nullptr + autoplaced.erase(std::remove(autoplaced.begin(), autoplaced.end(), nullptr), autoplaced.end()); } void vpr_initial_placement_location(CellInfo *cell, size_t& itype, BelId& bel) { diff --git a/ice40/chipdb.py b/ice40/chipdb.py old mode 100644 new mode 100755 From 73c628da000f95648cb0055f99a6af80f77a338d Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Wed, 11 Jul 2018 02:16:12 -0700 Subject: [PATCH 007/116] Started cramming the VPR placer in; at least enough to compute the starting temp --- common/place_vpr.cc | 909 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 873 insertions(+), 36 deletions(-) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index b80d9b7046..7b608a368a 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -43,10 +43,33 @@ #include "util.h" NEXTPNR_NAMESPACE_BEGIN +static double get_std_dev(int n, double sum_x_squared, double av_x) { + + /* Returns the standard deviation of data set x. There are n sample points, * + * sum_x_squared is the summation over n of x^2 and av_x is the average x. * + * All operations are done in double precision, since round off error can be * + * a problem in the initial temp. std_dev calculation for big circuits. */ + + double std_dev; + + if (n <= 1) + std_dev = 0.; + else + std_dev = (sum_x_squared - n * av_x * av_x) / (double) (n - 1); + + if (std_dev > 0.) /* Very small variances sometimes round negative */ + std_dev = sqrt(std_dev); + else + std_dev = 0.; + + return (std_dev); +} + class VPRPlacer { private: std::vector> free_locations; + std::vector::const_iterator> free_locations_back; public: VPRPlacer(Context *ctx) : ctx(ctx) @@ -115,7 +138,6 @@ class VPRPlacer log_info("Placed %d cells based on constraints.\n", int(placed_cells)); // Sort to-place cells for deterministic initial placement - std::vector autoplaced; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->bel == BelId()) { @@ -138,19 +160,7 @@ class VPRPlacer // Place cells randomly initially log_info("Creating initial placement for remaining %d cells.\n", int(autoplaced.size())); - vpr_initial_placement(autoplaced, placed_cells, constr_placed_cells); -#if 0 - for (auto cell : autoplaced) { - place_initial(cell); - placed_cells++; - if ((placed_cells - constr_placed_cells) % 500 == 0) - log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), - int(autoplaced.size())); - } - if ((placed_cells - constr_placed_cells) % 500 != 0) - log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), - int(autoplaced.size())); -#endif + vpr_initial_placement(placed_cells, constr_placed_cells); log_info("Running simulated annealing placer.\n"); @@ -163,9 +173,33 @@ class VPRPlacer curr_wirelength += wl; } + num_swap_rejected = 0; + num_swap_accepted = 0; +// num_swap_aborted = 0; + + move_lim = int(inner_num * pow(autoplaced.size(), 1.3333)); + + /* Sometimes I want to run the router with a random placement. Avoid * + * using 0 moves to stop division by 0 and 0 length vector problems, * + * by setting move_lim to 1 (which is still too small to do any * + * significant optimization). */ + if (move_lim <= 0) + move_lim = 1; + + rlim = float(std::max(max_x, max_y)); + + //first_rlim = rlim; /*used in timing-driven placement for exponent computation */ + //final_rlim = 1; + //inverse_delta_rlim = 1 / (first_rlim - final_rlim); + + t = vpr_starting_t(move_lim /*, rlim, + placer_opts.place_algorithm, placer_opts.timing_tradeoff, + inverse_prev_bb_cost, inverse_prev_timing_cost, &delay_cost*/); + +#if 1 int n_no_progress = 0; double avg_wirelength = curr_wirelength; - temp = 10000; +// t = 10000; // Main simulated annealing loop for (int iter = 1;; iter++) { @@ -175,7 +209,7 @@ class VPRPlacer if (iter % 5 == 0 || iter == 1) log_info(" at iteration #%d: temp = %f, wire length = " "%.0f, est tns = %.02fns\n", - iter, temp, double(curr_wirelength), curr_tns); + iter, t, double(curr_wirelength), curr_tns); for (int m = 0; m < 15; ++m) { // Loop through all automatically placed cells @@ -194,9 +228,9 @@ class VPRPlacer else n_no_progress++; - if (temp <= 1e-3 && n_no_progress >= 5) { + if (t <= 1e-3 && n_no_progress >= 5) { if (iter % 5 != 0) - log_info(" at iteration #%d: temp = %f, wire length = %f\n", iter, temp, double(curr_wirelength)); + log_info(" at iteration #%d: temp = %f, wire length = %f\n", iter, t, double(curr_wirelength)); break; } @@ -210,25 +244,25 @@ class VPRPlacer avg_wirelength = 0.8 * avg_wirelength + 0.2 * curr_wirelength; } else { if (Raccept >= 0.8) { - temp *= 0.7; + t *= 0.7; } else if (Raccept > upper) { if (diameter < M) diameter++; else - temp *= 0.9; + t *= 0.9; } else if (Raccept > lower) { - temp *= 0.95; + t *= 0.95; } else { // Raccept < 0.3 if (diameter > 1) diameter--; else - temp *= 0.8; + t *= 0.8; } } // Once cooled below legalise threshold, run legalisation and start requiring // legal moves only - if (temp < legalise_temp && !require_legal) { + if (t < legalise_temp && !require_legal) { legalise_design(ctx); require_legal = true; autoplaced.clear(); @@ -236,7 +270,7 @@ class VPRPlacer if (cell.second->belStrength < STRENGTH_STRONG) autoplaced.push_back(cell.second); } - temp = post_legalise_temp; + t = post_legalise_temp; diameter *= post_legalise_dia_scale; ctx->shuffle(autoplaced); assign_budget(ctx); @@ -252,6 +286,164 @@ class VPRPlacer curr_wirelength += wl; } } +#else + tot_iter = 0; + //moves_since_cost_recompute = 0; + + /* Outer loop of the simmulated annealing begins */ + while (!vpr_exit_crit(temp, wirelength)) { + +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// cost = 1; +// } + +// outer_loop_recompute_criticalities(placer_opts, num_connections, +// crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, +// &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, +// netlist_pin_lookup, +//#ifdef ENABLE_CLASSIC_VPR_STA +// slacks, +// timing_inf, +//#endif +// *timing_info); + + placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, + move_lim, crit_exponent, inner_recompute_limit, &stats, + &cost, &bb_cost, &timing_cost, &delay_cost, +#ifdef ENABLE_CLASSIC_VPR_STA + slacks, + timing_inf, +#endif + netlist_pin_lookup, + *timing_info); + +// /* Lines below prevent too much round-off error from accumulating * +// * in the cost over many iterations. This round-off can lead to * +// * error checks failing because the cost is different from what * +// * you get when you recompute from scratch. */ +// +// moves_since_cost_recompute += move_lim; +// if (moves_since_cost_recompute > MAX_MOVES_BEFORE_RECOMPUTE) { +// new_bb_cost = recompute_bb_cost(); +// if (fabs(new_bb_cost - bb_cost) > bb_cost * ERROR_TOL) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "in try_place: new_bb_cost = %g, old bb_cost = %g\n", +// new_bb_cost, bb_cost); +// } +// bb_cost = new_bb_cost; +// +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// comp_td_costs(&new_timing_cost, &new_delay_cost); +// if (fabs(new_timing_cost - timing_cost) > timing_cost * ERROR_TOL) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "in try_place: new_timing_cost = %g, old timing_cost = %g, ERROR_TOL = %g\n", +// new_timing_cost, timing_cost, ERROR_TOL); +// } +// if (fabs(new_delay_cost - delay_cost) > delay_cost * ERROR_TOL) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "in try_place: new_delay_cost = %g, old delay_cost = %g, ERROR_TOL = %g\n", +// new_delay_cost, delay_cost, ERROR_TOL); +// } +// timing_cost = new_timing_cost; +// } +// +// if (placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { +// cost = new_bb_cost; +// } +// moves_since_cost_recompute = 0; +// } +// + tot_iter += move_lim; + success_rat = ((float) stats.success_sum) / move_lim; +// if (stats.success_sum == 0) { +// stats.av_cost = cost; +// stats.av_bb_cost = bb_cost; +// stats.av_timing_cost = timing_cost; +// stats.av_delay_cost = delay_cost; +// } else { +// stats.av_cost /= stats.success_sum; +// stats.av_bb_cost /= stats.success_sum; +// stats.av_timing_cost /= stats.success_sum; +// stats.av_delay_cost /= stats.success_sum; +// } +// std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); + +// oldt = t; /* for finding and printing alpha. */ + vpr_update_t(); + +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// critical_path = timing_info->least_slack_critical_path(); +// sTNS = timing_info->setup_total_negative_slack(); +// sWNS = timing_info->setup_worst_negative_slack(); +// } +// +// vtr::printf_info("%7.3f " +// "%7.4f %10.4f %-10.5g %-10.5g " +// "%-10.5g %7.3f % 10.3g % 8.3f " +// "%7.4f %7.4f %7.4f %6.3f" +// "%9d %6.3f\n", +// oldt, +// stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, +// place_delay_value, 1e9*critical_path.delay(), 1e9*sTNS, 1e9*sWNS, +// success_rat, std_dev, rlim, crit_exponent, +// tot_iter, t / oldt); +// +//#ifdef ENABLE_CLASSIC_VPR_STA +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); +// if(cpd_diff_ns > ERROR_TOL) { +// print_classic_cpds(); +// print_tatum_cpds(timing_info->critical_paths()); +// +// vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); +// } +// } +//#endif + + sprintf(msg, "Cost: %g BB Cost %g TD Cost %g Temperature: %g", + cost, bb_cost, timing_cost, t); +// update_screen(ScreenUpdatePriority::MINOR, msg, PLACEMENT, timing_info); + update_rlim(&rlim, success_rat, device_ctx.grid); + +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// crit_exponent = (1 - (rlim - final_rlim) * inverse_delta_rlim) +// * (placer_opts.td_place_exp_last - placer_opts.td_place_exp_first) +// + placer_opts.td_place_exp_first; +// } +//#ifdef VERBOSE +// if (getEchoEnabled()) { +// print_clb_placement("first_iteration_clb_placement.echo"); +// } +//#endif + } + /* Outer loop of the simmulated annealing ends */ + + + outer_loop_recompute_criticalities(placer_opts, num_connections, + crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, + &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, + netlist_pin_lookup, +#ifdef ENABLE_CLASSIC_VPR_STA + slacks, + timing_inf, +#endif + *timing_info); + + t = 0; /* freeze out */ + + /* Run inner loop again with temperature = 0 so as to accept only swaps + * which reduce the cost of the placement */ + placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, + move_lim, crit_exponent, inner_recompute_limit, &stats, + &cost, &bb_cost, &timing_cost, &delay_cost, +#ifdef ENABLE_CLASSIC_VPR_STA + slacks, + timing_inf, +#endif + netlist_pin_lookup, + *timing_info); +#endif + // Final post-pacement validitiy check for (auto bel : ctx->getBels()) { IdString cell = ctx->getBoundBelCell(bel); @@ -326,7 +518,7 @@ class VPRPlacer } #endif - void vpr_initial_placement(std::vector autoplaced, size_t& placed_cells, int constr_placed_cells) { + void vpr_initial_placement(size_t& placed_cells, int constr_placed_cells) { /* Randomly places the blocks to create an initial placement. We rely on * the legal_pos array already being loaded. That legal_pos[itype] is an @@ -367,7 +559,7 @@ class VPRPlacer // } // } // Finish updating the legal_pos[][] and free_locations[] array - vpr_initial_placement_blocks(autoplaced, placed_cells, constr_placed_cells); + vpr_initial_placement_blocks(placed_cells, constr_placed_cells); // if (pad_loc_type == USER) { // read_user_pad_loc(pad_loc_file); @@ -387,7 +579,7 @@ class VPRPlacer /* Place blocks that are NOT a part of any macro. * We'll randomly place each block in the clustered netlist, one by one. */ - void vpr_initial_placement_blocks(std::vector autoplaced, size_t &placed_cells, const int constr_placed_cells) { + void vpr_initial_placement_blocks(size_t &placed_cells, int& constr_placed_cells) { // int itype, ipos, x, y, z; // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); @@ -397,10 +589,12 @@ class VPRPlacer BelId bel; // Shuffle all free locations once here, rather than picking a block at random + free_locations_back.reserve(free_locations.size()); for (auto& free_locations_type : free_locations) { ctx->shuffle(free_locations_type); + free_locations_back.push_back(free_locations_type.cbegin()); } - + for (auto& cell : autoplaced) { // if (place_ctx.block_locs[blk_id].x != -1) { // -1 is a sentinel for an empty block // // block placed. @@ -448,7 +642,7 @@ class VPRPlacer // legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; /* overwrite used block position */ // free_locations[itype]--; - free_locations[itype].pop_back(); + ++free_locations_back[itype]; // } @@ -457,23 +651,654 @@ class VPRPlacer log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), int(autoplaced.size())); } - if ((placed_cells - constr_placed_cells) % 500 != 0) - log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), - int(autoplaced.size())); // Linear complexity removal of all IOs cells that were set to nullptr autoplaced.erase(std::remove(autoplaced.begin(), autoplaced.end(), nullptr), autoplaced.end()); - } + //if ((placed_cells - constr_placed_cells) % 500 != 0) + log_info(" initial placement finished with %d unconstrained cells\n", int(autoplaced.size())); + + } void vpr_initial_placement_location(CellInfo *cell, size_t& itype, BelId& bel) { // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto type = ctx->belTypeFromId(cell->type); itype = bel_types.at(type); - bel = free_locations[itype].back(); + bel = *free_locations_back[itype]; + } + + float vpr_starting_t(int max_moves) { + + /* Finds the starting temperature (hot condition). */ + + int i, num_accepted, move_lim; + double std_dev, av, sum_of_squares; /* Double important to avoid round off */ + +// if (annealing_sched.type == USER_SCHED) +// return (annealing_sched.init_t); +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); + + move_lim = std::min(max_moves, (int) autoplaced.size()); + + num_accepted = 0; + av = 0.; + sum_of_squares = 0.; + + /* Try one move per block. Set t high so essentially all accepted. */ + + for (i = 0; i < move_lim; i++) { + auto swap_result = vpr_try_swap(std::numeric_limits::max()/*, cost_ptr, bb_cost_ptr, timing_cost_ptr, rlim, + place_algorithm, timing_tradeoff, + inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost_ptr*/); + + if (swap_result/* == ACCEPTED*/) { + num_accepted++; + av += cost; + sum_of_squares += cost * cost; + num_swap_accepted++; + //} else if (swap_result == ABORTED) { + // num_swap_aborted++; + } else { + num_swap_rejected++; + } + } + + if (num_accepted != 0) + av /= num_accepted; + else + av = 0.; + + std_dev = get_std_dev(num_accepted, sum_of_squares, av); + + if (num_accepted != move_lim) { + log_warning("Starting t: %d of %d configurations accepted.\n", num_accepted, move_lim); + } + +// #ifdef VERBOSE + log_info("std_dev: %g, average cost: %g, starting temp: %g\n", std_dev, av, 20. * std_dev); +// #endif + + /* Set the initial temperature to 20 times the standard of deviation */ + /* so that the initial temperature adjusts according to the circuit */ + return (20. * std_dev); + } + + bool vpr_try_swap(float t /*, float *cost, float *bb_cost, float *timing_cost, + float rlim, + enum e_place_algorithm place_algorithm, float timing_tradeoff, + float inverse_prev_bb_cost, float inverse_prev_timing_cost, + float *delay_cost*/) { + +// /* Picks some block and moves it to another spot. If this spot is * +// * occupied, switch the blocks. Assess the change in cost function. * +// * rlim is the range limiter. * +// * Returns whether the swap is accepted, rejected or aborted. * +// * Passes back the new value of the cost functions. */ +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& place_ctx = g_vpr_ctx.mutable_placement(); +// +// num_ts_called ++; + + /* I'm using negative values of temp_net_cost as a flag, so DO NOT * + * use cost functions that can go negative. */ + + /*float*/ delta_c = 0; /* Change in cost due to this swap. */ + /*float*/ bb_delta_c = 0; +// float timing_delta_c = 0; +// float delay_delta_c = 0.0; + + /* Pick a random block to be swapped with another random block. */ + auto cell_from = vpr_pick_from_block(); + if (!cell_from) { + return false/*ABORTED*/; //No movable block found + } + +// int x_from = place_ctx.block_locs[b_from].x; +// int y_from = place_ctx.block_locs[b_from].y; +// int z_from = place_ctx.block_locs[b_from].z; +// +// int x_to = OPEN; +// int y_to = OPEN; +// int z_to = OPEN; + + BelId bel_to; + +// auto cluster_from_type = cluster_ctx.clb_nlist.block_type(b_from); +// auto grid_from_type = g_vpr_ctx.device().grid[x_from][y_from].type; +// VTR_ASSERT(cluster_from_type == grid_from_type); + + if (!vpr_find_to(cell_from, bel_to /*cluster_ctx.clb_nlist.block_type(b_from), rlim, x_from, y_from, &x_to, &y_to, &z_to*/)) + return false/*REJECTED*/; + + #if 0 + auto& grid = g_vpr_ctx.device().grid; + int b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; + vtr::printf_info( "swap [%d][%d][%d] %s \"%s\" <=> [%d][%d][%d] %s \"%s\"\n", + x_from, y_from, z_from, grid[x_from][y_from].type->name, (b_from != -1 ? cluster_ctx.blocks[b_from].name : ""), + x_to, y_to, z_to, grid[x_to][y_to].type->name, (b_to != -1 ? cluster_ctx.blocks[b_to].name : "")); + #endif + +// /* Make the switch in order to make computing the new bounding * +// * box simpler. If the cost increase is too high, switch them * +// * back. (place_ctx.block_locs data structures switched, clbs not switched * +// * until success of move is determined.) * +// * Also check that whether those are the only 2 blocks * +// * to be moved - check for carry chains and other placement * +// * macros. */ +// +// /* Check whether the from_block is part of a macro first. * +// * If it is, the whole macro has to be moved. Calculate the * +// * x, y, z offsets of the swap to maintain relative placements * +// * of the blocks. Abort the swap if the to_block is part of a * +// * macro (not supported yet). */ +// +// bool abort_swap = find_affected_blocks(b_from, x_to, y_to, z_to); +// +// if (abort_swap == false) { + + BelId bel_from = cell_from->bel; + IdString other = ctx->getBoundBelCell(bel_to); + CellInfo *cell_to = nullptr; + if (other != IdString()) { + cell_to = ctx->cells[other].get(); + if (cell_to->belStrength > STRENGTH_WEAK) + return false; + } + ctx->unbindBel(bel_from); + if (other != IdString()) { + ctx->unbindBel(bel_to); + } + + for (const auto &port : cell_from->ports) + if (port.second.net != nullptr) + affected_nets.insert(port.second.net); + + if (other != IdString()) { + for (const auto &port : cell_to->ports) + if (port.second.net != nullptr) + affected_nets.insert(port.second.net); + } + + // Find all the nets affected by this swap and update thier bounding box + /*int num_nets_affected =*/ vpr_find_affected_nets_and_update_costs(cell_from, cell_to, bel_from, bel_to, /*place_algorithm,*/ bb_delta_c /*, timing_delta_c, delay_delta_c*/); + + +// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// /*in this case we redefine delta_c as a combination of timing and bb. * +// *additionally, we normalize all values, therefore delta_c is in * +// *relation to 1*/ +// +// delta_c = (1 - timing_tradeoff) * bb_delta_c * inverse_prev_bb_cost +// + timing_tradeoff * timing_delta_c * inverse_prev_timing_cost; +// } else + { + delta_c = bb_delta_c; + } + + /* 1 -> move accepted, 0 -> rejected. */ + auto keep_switch = vpr_assess_swap(t); + + if (keep_switch) { + cost += delta_c; + bb_cost += bb_delta_c; + +// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// /*update the point_to_point_timing_cost and point_to_point_delay_cost +// * values from the temporary values */ +// *timing_cost = *timing_cost + timing_delta_c; +// *delay_cost = *delay_cost + delay_delta_c; +// +// update_td_cost(); +// } + +// /* update net cost functions and reset flags. */ +// for (int inet_affected = 0; inet_affected < num_nets_affected; inet_affected++) { +// ClusterNetId net_id = ts_nets_to_update[inet_affected]; +// +// bb_coords[net_id] = ts_bb_coord_new[net_id]; +// if (cluster_ctx.clb_nlist.net_sinks(net_id).size() >= SMALL_NET) +// bb_num_on_edges[net_id] = ts_bb_edge_new[net_id]; +// +// net_cost[net_id] = temp_net_cost[net_id]; +// +// /* negative temp_net_cost value is acting as a flag. */ +// temp_net_cost[net_id] = -1; +// bb_updated_before[net_id] = NOT_UPDATED_YET; +// } +// +// /* Update clb data structures since we kept the move. */ +// /* Swap physical location */ +// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { +// +// x_to = blocks_affected.moved_blocks[iblk].xnew; +// y_to = blocks_affected.moved_blocks[iblk].ynew; +// z_to = blocks_affected.moved_blocks[iblk].znew; +// +// x_from = blocks_affected.moved_blocks[iblk].xold; +// y_from = blocks_affected.moved_blocks[iblk].yold; +// z_from = blocks_affected.moved_blocks[iblk].zold; +// +// b_from = blocks_affected.moved_blocks[iblk].block_num; +// +// place_ctx.grid_blocks[x_to][y_to].blocks[z_to] = b_from; +// +// if (blocks_affected.moved_blocks[iblk].swapped_to_was_empty) { +// place_ctx.grid_blocks[x_to][y_to].usage++; +// } +// if (blocks_affected.moved_blocks[iblk].swapped_from_is_empty) { +// place_ctx.grid_blocks[x_from][y_from].usage--; +// place_ctx.grid_blocks[x_from][y_from].blocks[z_from] = EMPTY_BLOCK_ID; +// } +// +// } // Finish updating clb for all blocks + + for (auto new_wl : new_lengths) + wirelengths.at(new_wl.first) = new_wl.second; + + + } else { /* Move was rejected. */ + +// /* Reset the net cost function flags first. */ +// for (int inet_affected = 0; inet_affected < num_nets_affected; inet_affected++) { +// ClusterNetId net_id = ts_nets_to_update[inet_affected]; +// temp_net_cost[net_id] = -1; +// bb_updated_before[net_id] = NOT_UPDATED_YET; +// } +// +// /* Restore the place_ctx.block_locs data structures to their state before the move. */ +// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { +// b_from = blocks_affected.moved_blocks[iblk].block_num; +// +// place_ctx.block_locs[b_from].x = blocks_affected.moved_blocks[iblk].xold; +// place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; +// place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; +// } + + if (other != IdString()) + ctx->unbindBel(bel_from); + ctx->unbindBel(bel_to); + ctx->bindBel(bel_from, cell_from->name, STRENGTH_WEAK); + if (other != IdString()) + ctx->bindBel(bel_to, other, STRENGTH_WEAK); + return false; + } + +// /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ +// blocks_affected.num_moved_blocks = 0; +// +// #if 0 +// //Check that each accepted swap yields a valid placement +// check_place(*bb_cost, *timing_cost, place_algorithm, *delay_cost); +// #endif + + return (keep_switch); +// } else { +// +// /* Restore the place_ctx.block_locs data structures to their state before the move. */ +// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { +// b_from = blocks_affected.moved_blocks[iblk].block_num; +// +// place_ctx.block_locs[b_from].x = blocks_affected.moved_blocks[iblk].xold; +// place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; +// place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; +// } +// +// /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ +// blocks_affected.num_moved_blocks = 0; +// +// return ABORTED; +// } + } + + //Pick a random block to be swapped with another random block. + //If none is found return ClusterBlockId::INVALID() + CellInfo* vpr_pick_from_block() { +// /* Some blocks may be fixed, and should never be moved from their * +// * initial positions. If we randomly selected such a block try * +// * another random block. * +// * * +// * We need to track the blocks we have tried to avoid an infinite * +// * loop if all blocks are fixed. */ +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& place_ctx = g_vpr_ctx.mutable_placement(); +// +// std::unordered_set tried_from_blocks; +// +// //So long as untried blocks remain +// while (tried_from_blocks.size() < cluster_ctx.clb_nlist.blocks().size()) { +// +// //Pick a block at random +// ClusterBlockId b_from = ClusterBlockId(vtr::irand((int) cluster_ctx.clb_nlist.blocks().size() - 1)); +// +// //Record it as tried +// tried_from_blocks.insert(b_from); +// +// if (place_ctx.block_locs[b_from].is_fixed) { +// continue; //Fixed location, try again +// } +// +// //Found a movable block +// return b_from; +// } +// +// //No movable blocks found +// return ClusterBlockId::INVALID(); + + // Assume that autoplaced only contains movable blocks + return autoplaced.at(ctx->rng(int(autoplaced.size()))); + } + + bool vpr_find_to(CellInfo* cell, BelId& bel) { + + /* Returns the point to which I want to swap, properly range limited. + * rlim must always be between 1 and device_ctx.grid.width() - 2 (inclusive) for this routine + * to work. Note -2 for no perim channels + */ + + int min_x, max_x, min_y, max_y; + int num_tries; + int active_area; + bool is_legal; +// int itype; +// +// auto& grid = g_vpr_ctx.device().grid; +// auto& place_ctx = g_vpr_ctx.placement(); +// +// auto grid_type = grid[x_from][y_from].type; +// VTR_ASSERT(type == grid_type); + + int rlx = std::min(this->max_x, rlim); + int rly = std::min(this->max_y, rlim); /* Added rly for aspect_ratio != 1 case. */ + active_area = 4 * rlx * rly; + + int x_from, y_from; + bool gb; + ctx->estimatePosition(cell->bel, x_from, y_from, gb); + + min_x = std::max(0, x_from - rlx); + max_x = std::min(this->max_x, x_from + rlx); + min_y = std::max(0, y_from - rly); + max_y = std::min(this->max_y, y_from + rly); + + if (rlx < 1 || rlx > int(this->max_x)) { + log_error("in find_to: rlx = %d out of range\n", rlx); + } + if (rly < 1 || rly > int(this->max_y)) { + log_error("in find_to: rly = %d out of range\n", rly); + } + + num_tries = 0; +// itype = type->index; + auto type = ctx->belTypeFromId(cell->type); + auto itype = bel_types.at(type); + + int px_to, py_to; + + do { /* Until legal */ + is_legal = true; + + /* Limit the number of tries when searching for an alternative position */ + if(num_tries >= 2 * std::min(active_area /*/ (type->width * type->height)*/, free_locations[itype].size()) + 10) { + /* Tried randomly searching for a suitable position */ + return false; + } else { + num_tries++; + } + + vpr_find_to_location(cell, bel); + ctx->estimatePosition(bel, px_to, py_to, gb); + + if((x_from == px_to) && (y_from == py_to)) { + is_legal = false; + } else if(px_to > max_x || px_to < min_x || py_to > max_y || py_to < min_y) { + is_legal = false; + } else if(type != ctx->getBelType(bel)) { + is_legal = false; + } else { +// /* Find z_to and test to validate that the "to" block is *not* fixed */ +// *pz_to = 0; +// if (grid[*px_to][*py_to].type->capacity > 1) { +// *pz_to = vtr::irand(grid[*px_to][*py_to].type->capacity - 1); +// } +// ClusterBlockId b_to = place_ctx.grid_blocks[*px_to][*py_to].blocks[*pz_to]; +// if ((b_to != EMPTY_BLOCK_ID) && (place_ctx.block_locs[b_to].is_fixed == true)) { +// is_legal = false; +// } + } + + NPNR_ASSERT(px_to >= 0 && px_to <= int(this->max_x)); + NPNR_ASSERT(py_to >= 0 && py_to <= int(this->max_y)); + } while (is_legal == false); + + if (px_to < 0 || px_to > int(this->max_x) || py_to < 0 || py_to > int(this->max_y)) { + log_error("in routine find_to: (x_to,y_to) = (%d,%d)\n", px_to, py_to); + } + + NPNR_ASSERT(type == ctx->getBelType(bel)); + return true; + } + + void vpr_find_to_location(CellInfo* cell, BelId& bel/*t_type_ptr type, float rlim, + int x_from, int y_from, + int *px_to, int *py_to, int *pz_to*/) { + +// auto& device_ctx = g_vpr_ctx.device(); +// auto& grid = device_ctx.grid; +// +// int itype = type->index; + auto type = ctx->belTypeFromId(cell->type); + auto itype = bel_types.at(type); + + int x_from, y_from; + bool gb; + ctx->estimatePosition(cell->bel, x_from, y_from, gb); + + int rlx = std::min(this->max_x, rlim); + int rly = std::min(this->max_y, rlim); /* Added rly for aspect_ratio != 1 case. */ + unsigned active_area = 4 * rlx * rly; + + int min_x = std::max(0, x_from - rlx); + int max_x = std::min(this->max_x, x_from + rlx); + int min_y = std::max(0, y_from - rly); + int max_y = std::min(this->max_y, y_from + rly); + + //*pz_to = 0; + if (int(max_x / 4) < rlx || int(max_y / 4) < rly || free_locations[itype].size() < active_area) { + int ipos = ctx->rng(free_locations[itype].size()); + bel = free_locations[itype][ipos]; +// *px_to = [itype][ipos].x; +// *py_to = [itype][ipos].y; +// *pz_to = [itype][ipos].z; + } else { + int x_rel = ctx->rng(std::max(0, max_x - min_x)+1); + int y_rel = ctx->rng(std::max(0, max_y - min_y)+1); +// *px_to = min_x + x_rel; +// *py_to = min_y + y_rel; +// *px_to = (*px_to) - grid[*px_to][*py_to].width_offset; /* align it */ +// *py_to = (*py_to) - grid[*px_to][*py_to].height_offset; /* align it */ + int px_to = min_x + x_rel; + int py_to = min_y + y_rel; + if (px_to >= int(fast_bels.at(itype).size())) { + bel = BelId(); + return; + } + if (py_to >= int(fast_bels.at(itype).at(px_to).size())) { + bel = BelId(); + return; + } + const auto &fb = fast_bels.at(itype).at(px_to).at(py_to); + if (fb.size() == 0) { + bel = BelId(); + return; + } + bel = fb.at(ctx->rng(int(fb.size()))); + // TODO: Remove locked_bels from fb + if (locked_bels.find(bel) != locked_bels.end()) { + bel = BelId(); + return; + } + } + } + + bool vpr_assess_swap(/*float delta_c,*/ float t) { + +// /* Returns: 1 -> move accepted, 0 -> rejected. */ +// + bool accept; + float prob_fac, fnum; + + if (delta_c <= 0) { + + /* Reduce variation in final solution due to round off */ + fnum = ctx->rng() / float(0x3fffffff); + + accept = true; + return (accept); + } + + if (t == 0.) + return false; + + fnum = ctx->rng() / float(0x3fffffff); + prob_fac = std::exp(-delta_c / t); + if (prob_fac > fnum) { + accept = true; + } + else { + accept = false; + } + return (accept); + } + + bool vpr_exit_crit(float t, float cost) { +// /* Return 1 when the exit criterion is met. */ +// +// if (annealing_sched.type == USER_SCHED) { +// if (t < annealing_sched.exit_t) { +// return (1); +// } else { +// return (0); +// } +// } +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); + + /* Automatic annealing schedule */ + float t_exit = 0.005 * cost / ctx->nets.size(); + + if (t < t_exit) { + return (1); + } else if (std::isnan(t_exit)) { + //May get nan if there are no nets + return (1); + } else { + return (0); + } + } + + /* Update the temperature according to the annealing schedule selected. */ + void vpr_update_t(float success_rat) { + + /* float fac; */ + +// if (annealing_sched.type == USER_SCHED) { +// *t = annealing_sched.alpha_t * (*t); +// } else + { /* AUTO_SCHED */ + if (success_rat > 0.96) { + t *= 0.5; + } else if (success_rat > 0.8) { + t *= 0.9; + } else if (success_rat > 0.15 || rlim > 1.) { + t *= 0.95; + } else { + t *= 0.8; + } + } + } + + void vpr_find_affected_nets_and_update_costs(CellInfo* cell_from, CellInfo* cell_to, BelId bel_from, BelId bel_to, /*e_place_algorithm place_algorithm,*/ float& bb_delta_c /*, float& timing_delta_c, float& delay_delta_c*/) { +// VTR_ASSERT_SAFE(bb_delta_c == 0.); +// VTR_ASSERT_SAFE(timing_delta_c == 0.); +// VTR_ASSERT_SAFE(delay_delta_c == 0.); +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// int num_affected_nets = 0; + affected_nets.clear(); + +// //Go through all the blocks moved +// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { +// ClusterBlockId blk = blocks_affected.moved_blocks[iblk].block_num; +// +// //Go through all the pins in the moved block +// for (ClusterPinId blk_pin : cluster_ctx.clb_nlist.block_pins(blk)) { +// ClusterNetId net_id = cluster_ctx.clb_nlist.pin_net(blk_pin); +// VTR_ASSERT_SAFE_MSG(net_id, "Only valid nets should be found in compressed netlist block pins"); +// +// if (cluster_ctx.clb_nlist.net_is_global(net_id)) +// continue; //Global nets are assumed to span the whole chip, and do not effect costs +// +// //Record effected nets +// record_affected_net(net_id, num_affected_nets); +// +// //Update the net bounding boxes +// // +// //Do not update the net cost here since it should only be updated +// //once per net, not once per pin. +// update_net_bb(net_id, iblk, blk, blk_pin); +// +// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// //Determine the change in timing costs if required +// update_td_delta_costs(net_id, blk_pin, timing_delta_c, delay_delta_c); +// } +// } +// } + + for (const auto &port : cell_from->ports) + if (port.second.net != nullptr) + affected_nets.insert(port.second.net); + + if (cell_to) { + for (const auto &port : cell_to->ports) + if (port.second.net != nullptr) + affected_nets.insert(port.second.net); + } + + ctx->bindBel(bel_to, cell_from->name, STRENGTH_WEAK); + if (cell_to) { + ctx->bindBel(bel_from, cell_to->name, STRENGTH_WEAK); + } + + +// /* Now update the bounding box costs (since the net bounding boxes are up-to-date). +// * The cost is only updated once per net. +// */ +// for (int inet_affected = 0; inet_affected < num_affected_nets; inet_affected++) { +// ClusterNetId net_id = ts_nets_to_update[inet_affected]; +// +// temp_net_cost[net_id] = get_net_cost(net_id, &ts_bb_coord_new[net_id]); +// bb_delta_c += temp_net_cost[net_id] - net_cost[net_id]; +// } + + auto new_wirelength = curr_wirelength; + + // Recalculate wirelengths for all nets touched by the peturbation + for (auto net : affected_nets) { + new_wirelength -= wirelengths.at(net->name); + float temp_tns = 0; + wirelen_t net_new_wl = get_net_wirelength(ctx, net, temp_tns); + new_wirelength += net_new_wl; + new_lengths.push_back(std::make_pair(net->name, net_new_wl)); + } + bb_delta_c = new_wirelength - curr_wirelength; + +// return num_affected_nets; } + // Attempt a SA position swap, return true on success or false on failure bool try_swap_position(CellInfo *cell, BelId newBel) { @@ -532,7 +1357,7 @@ class VPRPlacer delta = new_wirelength - curr_wirelength; n_move++; // SA acceptance criterea - if (delta < 0 || (temp > 1e-6 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) { + if (delta < 0 || (t > 1e-6 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / t))) { n_accept++; if (delta < 2) improved = true; @@ -585,7 +1410,7 @@ class VPRPlacer std::unordered_map wirelengths; wirelen_t curr_wirelength = std::numeric_limits::max(); float curr_tns = 0; - float temp = 1000; + float t = 1000; bool improved = false; int n_move, n_accept; int diameter = 35, max_x = 1, max_y = 1; @@ -596,6 +1421,18 @@ class VPRPlacer const float legalise_temp = 1; const float post_legalise_temp = 20; const float post_legalise_dia_scale = 2; + std::vector autoplaced; + + const float inner_num = 1.0; + int move_lim, tot_iter; + float rlim; + float cost, bb_cost; + float delta_c, bb_delta_c; + float success_sum; + float success_rat; + std::unordered_set affected_nets; + std::vector> new_lengths; + int num_swap_accepted, num_swap_rejected; }; bool place_design_vpr(Context *ctx) From ea14552605b28caa808e55e4c793d3fa00ea32e5 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Wed, 11 Jul 2018 02:49:54 -0700 Subject: [PATCH 008/116] Revert ice40/chipdb.py to master --- ice40/chipdb.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 ice40/chipdb.py diff --git a/ice40/chipdb.py b/ice40/chipdb.py old mode 100755 new mode 100644 From cada2ef1bece78f213738eece2327ea283718ae3 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Wed, 11 Jul 2018 02:50:24 -0700 Subject: [PATCH 009/116] vpr_find_to to now return legal block; also fix uninitialised cost value --- common/place_vpr.cc | 49 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index 7b608a368a..a1966d097d 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -176,6 +176,8 @@ class VPRPlacer num_swap_rejected = 0; num_swap_accepted = 0; // num_swap_aborted = 0; + + cost = curr_wirelength; move_lim = int(inner_num * pow(autoplaced.size(), 1.3333)); @@ -843,8 +845,8 @@ class VPRPlacer if (keep_switch) { cost += delta_c; - bb_cost += bb_delta_c; - +// bb_cost += bb_delta_c; +// // if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // /*update the point_to_point_timing_cost and point_to_point_delay_cost // * values from the temporary values */ @@ -991,7 +993,7 @@ class VPRPlacer return autoplaced.at(ctx->rng(int(autoplaced.size()))); } - bool vpr_find_to(CellInfo* cell, BelId& bel) { + bool vpr_find_to(CellInfo* cell_from, BelId& bel_to) { /* Returns the point to which I want to swap, properly range limited. * rlim must always be between 1 and device_ctx.grid.width() - 2 (inclusive) for this routine @@ -1016,7 +1018,7 @@ class VPRPlacer int x_from, y_from; bool gb; - ctx->estimatePosition(cell->bel, x_from, y_from, gb); + ctx->estimatePosition(cell_from->bel, x_from, y_from, gb); min_x = std::max(0, x_from - rlx); max_x = std::min(this->max_x, x_from + rlx); @@ -1032,7 +1034,7 @@ class VPRPlacer num_tries = 0; // itype = type->index; - auto type = ctx->belTypeFromId(cell->type); + auto type = ctx->belTypeFromId(cell_from->type); auto itype = bel_types.at(type); int px_to, py_to; @@ -1048,16 +1050,43 @@ class VPRPlacer num_tries++; } - vpr_find_to_location(cell, bel); - ctx->estimatePosition(bel, px_to, py_to, gb); + vpr_find_to_location(cell_from, bel_to); + ctx->estimatePosition(bel_to, px_to, py_to, gb); if((x_from == px_to) && (y_from == py_to)) { is_legal = false; } else if(px_to > max_x || px_to < min_x || py_to > max_y || py_to < min_y) { is_legal = false; - } else if(type != ctx->getBelType(bel)) { + } else if(type != ctx->getBelType(bel_to)) { is_legal = false; } else { + auto bel_from = cell_from->bel; + IdString other = ctx->getBoundBelCell(bel_to); + if (other != IdString()) { + auto cell_to = ctx->cells[other].get(); + if (cell_to->belStrength > STRENGTH_WEAK) + is_legal = false; + } + + if (is_legal) { + ctx->unbindBel(bel_from); + if (other != IdString()) + ctx->unbindBel(bel_to); + ctx->bindBel(bel_to, cell_from->name, STRENGTH_WEAK); + if (other != IdString()) + ctx->bindBel(bel_from, other, STRENGTH_WEAK); + if (!ctx->isBelLocationValid(bel_to) || ((other != IdString() && !ctx->isBelLocationValid(bel_from)))) { + is_legal = false; + } + ctx->unbindBel(bel_to); + if (other != IdString()) + ctx->unbindBel(bel_from); + ctx->bindBel(bel_from, cell_from->name, STRENGTH_WEAK); + if (other != IdString()) { + ctx->bindBel(bel_to, other, STRENGTH_WEAK); + } + } + // /* Find z_to and test to validate that the "to" block is *not* fixed */ // *pz_to = 0; // if (grid[*px_to][*py_to].type->capacity > 1) { @@ -1077,7 +1106,7 @@ class VPRPlacer log_error("in routine find_to: (x_to,y_to) = (%d,%d)\n", px_to, py_to); } - NPNR_ASSERT(type == ctx->getBelType(bel)); + NPNR_ASSERT(type == ctx->getBelType(bel_to)); return true; } @@ -1426,7 +1455,7 @@ class VPRPlacer const float inner_num = 1.0; int move_lim, tot_iter; float rlim; - float cost, bb_cost; + float cost /*, bb_cost*/; float delta_c, bb_delta_c; float success_sum; float success_rat; From 1368b2ad5193a08c621d4c77013d62c872ad3336 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Wed, 11 Jul 2018 23:20:51 -0700 Subject: [PATCH 010/116] Rename free_locations_back -> free_locations_front --- common/place_vpr.cc | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index a1966d097d..d6dad7d27e 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -69,7 +69,7 @@ class VPRPlacer { private: std::vector> free_locations; - std::vector::const_iterator> free_locations_back; + std::vector::iterator> free_locations_front; public: VPRPlacer(Context *ctx) : ctx(ctx) @@ -591,10 +591,10 @@ class VPRPlacer BelId bel; // Shuffle all free locations once here, rather than picking a block at random - free_locations_back.reserve(free_locations.size()); + free_locations_front.reserve(free_locations.size()); for (auto& free_locations_type : free_locations) { ctx->shuffle(free_locations_type); - free_locations_back.push_back(free_locations_type.cbegin()); + free_locations_front.push_back(free_locations_type.begin()); } for (auto& cell : autoplaced) { @@ -644,7 +644,7 @@ class VPRPlacer // legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; /* overwrite used block position */ // free_locations[itype]--; - ++free_locations_back[itype]; + ++free_locations_front[itype]; // } @@ -661,13 +661,25 @@ class VPRPlacer log_info(" initial placement finished with %d unconstrained cells\n", int(autoplaced.size())); } - void vpr_initial_placement_location(CellInfo *cell, size_t& itype, BelId& bel) { + + void vpr_initial_placement_location(CellInfo *cell_from, size_t& itype, BelId& bel_to) { // auto& cluster_ctx = g_vpr_ctx.clustering(); // - auto type = ctx->belTypeFromId(cell->type); + auto type = ctx->belTypeFromId(cell_from->type); itype = bel_types.at(type); - bel = *free_locations_back[itype]; + for (auto it = free_locations_front[itype]; it != free_locations[itype].end(); ++it) { + bel_to = *it; + ctx->bindBel(bel_to, cell_from->name, STRENGTH_WEAK); + if (!ctx->isBelLocationValid(bel_to)) { + ctx->unbindBel(bel_to); + continue; + } + ctx->unbindBel(bel_to); + std::iter_swap(it, free_locations_front[itype]); + return; + } + log_error(" initial placement failed; unable to find location for '%s'\n", cell_from->name.c_str(ctx)); } float vpr_starting_t(int max_moves) { From 4bce37846ffb46667c68776d1c8b02f9202d8a00 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Wed, 11 Jul 2018 23:22:08 -0700 Subject: [PATCH 011/116] Disable valid check during placement --- common/place_vpr.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index d6dad7d27e..78f1595905 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -670,6 +670,7 @@ class VPRPlacer for (auto it = free_locations_front[itype]; it != free_locations[itype].end(); ++it) { bel_to = *it; +#if 0 // TODO: Check that placement is indeed valid ctx->bindBel(bel_to, cell_from->name, STRENGTH_WEAK); if (!ctx->isBelLocationValid(bel_to)) { ctx->unbindBel(bel_to); @@ -677,6 +678,7 @@ class VPRPlacer } ctx->unbindBel(bel_to); std::iter_swap(it, free_locations_front[itype]); +#endif return; } log_error(" initial placement failed; unable to find location for '%s'\n", cell_from->name.c_str(ctx)); From bc03441b5fd3027272f867b010b1b9fdb880bea4 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Thu, 12 Jul 2018 01:03:15 -0700 Subject: [PATCH 012/116] Add place_vpr.inc which comes from VPR, and refactor place_vpr.cc to use it --- common/place_vpr.cc | 284 +--- common/place_vpr.inc | 3382 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 3436 insertions(+), 230 deletions(-) create mode 100644 common/place_vpr.inc diff --git a/common/place_vpr.cc b/common/place_vpr.cc index 78f1595905..5c9a4e2b3a 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -41,6 +41,12 @@ #include "place_legaliser.h" #include "timing.h" #include "util.h" + +namespace vpr { + using namespace NEXTPNR_NAMESPACE; + #include "place_vpr.inc" +} + NEXTPNR_NAMESPACE_BEGIN static double get_std_dev(int n, double sum_x_squared, double av_x) { @@ -107,6 +113,8 @@ class VPRPlacer { log_break(); + vpr::initial_placement(ctx, bel_types); + size_t placed_cells = 0; // Initial constraints placer for (auto &cell_entry : ctx->cells) { @@ -115,54 +123,55 @@ class VPRPlacer if (loc != cell->attrs.end()) { std::string loc_name = loc->second; BelId bel = ctx->getBelByName(ctx->id(loc_name)); - if (bel == BelId()) { - log_error("No Bel named \'%s\' located for " - "this chip (processing BEL attribute on \'%s\')\n", - loc_name.c_str(), cell->name.c_str(ctx)); - } - - BelType bel_type = ctx->getBelType(bel); - if (bel_type != ctx->belTypeFromId(cell->type)) { - log_error("Bel \'%s\' of type \'%s\' does not match cell " - "\'%s\' of type \'%s\'", - loc_name.c_str(), ctx->belTypeToId(bel_type).c_str(ctx), cell->name.c_str(ctx), - cell->type.c_str(ctx)); - } - - ctx->bindBel(bel, cell->name, STRENGTH_USER); + //if (bel == BelId()) { + // log_error("No Bel named \'%s\' located for " + // "this chip (processing BEL attribute on \'%s\')\n", + // loc_name.c_str(), cell->name.c_str(ctx)); + //} + + //BelType bel_type = ctx->getBelType(bel); + //if (bel_type != ctx->belTypeFromId(cell->type)) { + // log_error("Bel \'%s\' of type \'%s\' does not match cell " + // "\'%s\' of type \'%s\'", + // loc_name.c_str(), ctx->belTypeToId(bel_type).c_str(ctx), cell->name.c_str(ctx), + // cell->type.c_str(ctx)); + //} + + //ctx->bindBel(bel, cell->name, STRENGTH_USER); locked_bels.insert(bel); placed_cells++; } } - int constr_placed_cells = placed_cells; + //int constr_placed_cells = placed_cells; log_info("Placed %d cells based on constraints.\n", int(placed_cells)); // Sort to-place cells for deterministic initial placement + std::vector autoplaced; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); - if (ci->bel == BelId()) { +// if (ci->bel == BelId()) { + if (ci->belStrength == STRENGTH_WEAK) { autoplaced.push_back(cell.second.get()); } } std::sort(autoplaced.begin(), autoplaced.end(), [](CellInfo *a, CellInfo *b) { return a->name < b->name; }); ctx->shuffle(autoplaced); - // Remove locked_bels from free_locations - // TODO Make this more efficient - for (auto& i : free_locations) - for (auto j = i.begin(); j != i.end(); ) { - if (locked_bels.count(*j)) - j = i.erase(j); - else - ++j; - } - - // Place cells randomly initially - log_info("Creating initial placement for remaining %d cells.\n", int(autoplaced.size())); - - vpr_initial_placement(placed_cells, constr_placed_cells); +// // Place cells randomly initially +// log_info("Creating initial placement for remaining %d cells.\n", int(autoplaced.size())); +// +// for (auto cell : autoplaced) { +// place_initial(cell); +// placed_cells++; +// if ((placed_cells - constr_placed_cells) % 500 == 0) +// log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), +// int(autoplaced.size())); +// } +// if ((placed_cells - constr_placed_cells) % 500 != 0) +// log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), +// int(autoplaced.size())); - log_info("Running simulated annealing placer.\n"); + log_info("Running simulated annealing placer on %d cells.\n", int(autoplaced.size())); // Calculate wirelength after initial placement curr_wirelength = 0; @@ -173,35 +182,9 @@ class VPRPlacer curr_wirelength += wl; } - num_swap_rejected = 0; - num_swap_accepted = 0; -// num_swap_aborted = 0; - - cost = curr_wirelength; - - move_lim = int(inner_num * pow(autoplaced.size(), 1.3333)); - - /* Sometimes I want to run the router with a random placement. Avoid * - * using 0 moves to stop division by 0 and 0 length vector problems, * - * by setting move_lim to 1 (which is still too small to do any * - * significant optimization). */ - if (move_lim <= 0) - move_lim = 1; - - rlim = float(std::max(max_x, max_y)); - - //first_rlim = rlim; /*used in timing-driven placement for exponent computation */ - //final_rlim = 1; - //inverse_delta_rlim = 1 / (first_rlim - final_rlim); - - t = vpr_starting_t(move_lim /*, rlim, - placer_opts.place_algorithm, placer_opts.timing_tradeoff, - inverse_prev_bb_cost, inverse_prev_timing_cost, &delay_cost*/); - -#if 1 int n_no_progress = 0; double avg_wirelength = curr_wirelength; -// t = 10000; + temp = 10000; // Main simulated annealing loop for (int iter = 1;; iter++) { @@ -211,7 +194,7 @@ class VPRPlacer if (iter % 5 == 0 || iter == 1) log_info(" at iteration #%d: temp = %f, wire length = " "%.0f, est tns = %.02fns\n", - iter, t, double(curr_wirelength), curr_tns); + iter, temp, double(curr_wirelength), curr_tns); for (int m = 0; m < 15; ++m) { // Loop through all automatically placed cells @@ -230,9 +213,9 @@ class VPRPlacer else n_no_progress++; - if (t <= 1e-3 && n_no_progress >= 5) { + if (temp <= 1e-3 && n_no_progress >= 5) { if (iter % 5 != 0) - log_info(" at iteration #%d: temp = %f, wire length = %f\n", iter, t, double(curr_wirelength)); + log_info(" at iteration #%d: temp = %f, wire length = %f\n", iter, temp, double(curr_wirelength)); break; } @@ -246,25 +229,25 @@ class VPRPlacer avg_wirelength = 0.8 * avg_wirelength + 0.2 * curr_wirelength; } else { if (Raccept >= 0.8) { - t *= 0.7; + temp *= 0.7; } else if (Raccept > upper) { if (diameter < M) diameter++; else - t *= 0.9; + temp *= 0.9; } else if (Raccept > lower) { - t *= 0.95; + temp *= 0.95; } else { // Raccept < 0.3 if (diameter > 1) diameter--; else - t *= 0.8; + temp *= 0.8; } } // Once cooled below legalise threshold, run legalisation and start requiring // legal moves only - if (t < legalise_temp && !require_legal) { + if (temp < legalise_temp && !require_legal) { legalise_design(ctx); require_legal = true; autoplaced.clear(); @@ -272,7 +255,7 @@ class VPRPlacer if (cell.second->belStrength < STRENGTH_STRONG) autoplaced.push_back(cell.second); } - t = post_legalise_temp; + temp = post_legalise_temp; diameter *= post_legalise_dia_scale; ctx->shuffle(autoplaced); assign_budget(ctx); @@ -288,164 +271,6 @@ class VPRPlacer curr_wirelength += wl; } } -#else - tot_iter = 0; - //moves_since_cost_recompute = 0; - - /* Outer loop of the simmulated annealing begins */ - while (!vpr_exit_crit(temp, wirelength)) { - -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// cost = 1; -// } - -// outer_loop_recompute_criticalities(placer_opts, num_connections, -// crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, -// &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, -// netlist_pin_lookup, -//#ifdef ENABLE_CLASSIC_VPR_STA -// slacks, -// timing_inf, -//#endif -// *timing_info); - - placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, - move_lim, crit_exponent, inner_recompute_limit, &stats, - &cost, &bb_cost, &timing_cost, &delay_cost, -#ifdef ENABLE_CLASSIC_VPR_STA - slacks, - timing_inf, -#endif - netlist_pin_lookup, - *timing_info); - -// /* Lines below prevent too much round-off error from accumulating * -// * in the cost over many iterations. This round-off can lead to * -// * error checks failing because the cost is different from what * -// * you get when you recompute from scratch. */ -// -// moves_since_cost_recompute += move_lim; -// if (moves_since_cost_recompute > MAX_MOVES_BEFORE_RECOMPUTE) { -// new_bb_cost = recompute_bb_cost(); -// if (fabs(new_bb_cost - bb_cost) > bb_cost * ERROR_TOL) { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "in try_place: new_bb_cost = %g, old bb_cost = %g\n", -// new_bb_cost, bb_cost); -// } -// bb_cost = new_bb_cost; -// -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// comp_td_costs(&new_timing_cost, &new_delay_cost); -// if (fabs(new_timing_cost - timing_cost) > timing_cost * ERROR_TOL) { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "in try_place: new_timing_cost = %g, old timing_cost = %g, ERROR_TOL = %g\n", -// new_timing_cost, timing_cost, ERROR_TOL); -// } -// if (fabs(new_delay_cost - delay_cost) > delay_cost * ERROR_TOL) { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "in try_place: new_delay_cost = %g, old delay_cost = %g, ERROR_TOL = %g\n", -// new_delay_cost, delay_cost, ERROR_TOL); -// } -// timing_cost = new_timing_cost; -// } -// -// if (placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { -// cost = new_bb_cost; -// } -// moves_since_cost_recompute = 0; -// } -// - tot_iter += move_lim; - success_rat = ((float) stats.success_sum) / move_lim; -// if (stats.success_sum == 0) { -// stats.av_cost = cost; -// stats.av_bb_cost = bb_cost; -// stats.av_timing_cost = timing_cost; -// stats.av_delay_cost = delay_cost; -// } else { -// stats.av_cost /= stats.success_sum; -// stats.av_bb_cost /= stats.success_sum; -// stats.av_timing_cost /= stats.success_sum; -// stats.av_delay_cost /= stats.success_sum; -// } -// std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); - -// oldt = t; /* for finding and printing alpha. */ - vpr_update_t(); - -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// critical_path = timing_info->least_slack_critical_path(); -// sTNS = timing_info->setup_total_negative_slack(); -// sWNS = timing_info->setup_worst_negative_slack(); -// } -// -// vtr::printf_info("%7.3f " -// "%7.4f %10.4f %-10.5g %-10.5g " -// "%-10.5g %7.3f % 10.3g % 8.3f " -// "%7.4f %7.4f %7.4f %6.3f" -// "%9d %6.3f\n", -// oldt, -// stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, -// place_delay_value, 1e9*critical_path.delay(), 1e9*sTNS, 1e9*sWNS, -// success_rat, std_dev, rlim, crit_exponent, -// tot_iter, t / oldt); -// -//#ifdef ENABLE_CLASSIC_VPR_STA -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); -// if(cpd_diff_ns > ERROR_TOL) { -// print_classic_cpds(); -// print_tatum_cpds(timing_info->critical_paths()); -// -// vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); -// } -// } -//#endif - - sprintf(msg, "Cost: %g BB Cost %g TD Cost %g Temperature: %g", - cost, bb_cost, timing_cost, t); -// update_screen(ScreenUpdatePriority::MINOR, msg, PLACEMENT, timing_info); - update_rlim(&rlim, success_rat, device_ctx.grid); - -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// crit_exponent = (1 - (rlim - final_rlim) * inverse_delta_rlim) -// * (placer_opts.td_place_exp_last - placer_opts.td_place_exp_first) -// + placer_opts.td_place_exp_first; -// } -//#ifdef VERBOSE -// if (getEchoEnabled()) { -// print_clb_placement("first_iteration_clb_placement.echo"); -// } -//#endif - } - /* Outer loop of the simmulated annealing ends */ - - - outer_loop_recompute_criticalities(placer_opts, num_connections, - crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, - &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, - netlist_pin_lookup, -#ifdef ENABLE_CLASSIC_VPR_STA - slacks, - timing_inf, -#endif - *timing_info); - - t = 0; /* freeze out */ - - /* Run inner loop again with temperature = 0 so as to accept only swaps - * which reduce the cost of the placement */ - placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, - move_lim, crit_exponent, inner_recompute_limit, &stats, - &cost, &bb_cost, &timing_cost, &delay_cost, -#ifdef ENABLE_CLASSIC_VPR_STA - slacks, - timing_inf, -#endif - netlist_pin_lookup, - *timing_info); -#endif - // Final post-pacement validitiy check for (auto bel : ctx->getBels()) { IdString cell = ctx->getBoundBelCell(bel); @@ -670,7 +495,6 @@ class VPRPlacer for (auto it = free_locations_front[itype]; it != free_locations[itype].end(); ++it) { bel_to = *it; -#if 0 // TODO: Check that placement is indeed valid ctx->bindBel(bel_to, cell_from->name, STRENGTH_WEAK); if (!ctx->isBelLocationValid(bel_to)) { ctx->unbindBel(bel_to); @@ -678,7 +502,6 @@ class VPRPlacer } ctx->unbindBel(bel_to); std::iter_swap(it, free_locations_front[itype]); -#endif return; } log_error(" initial placement failed; unable to find location for '%s'\n", cell_from->name.c_str(ctx)); @@ -1400,7 +1223,7 @@ class VPRPlacer delta = new_wirelength - curr_wirelength; n_move++; // SA acceptance criterea - if (delta < 0 || (t > 1e-6 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / t))) { + if (delta < 0 || (temp > 1e-6 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) { n_accept++; if (delta < 2) improved = true; @@ -1453,7 +1276,7 @@ class VPRPlacer std::unordered_map wirelengths; wirelen_t curr_wirelength = std::numeric_limits::max(); float curr_tns = 0; - float t = 1000; + float temp = 1000; bool improved = false; int n_move, n_accept; int diameter = 35, max_x = 1, max_y = 1; @@ -1466,6 +1289,7 @@ class VPRPlacer const float post_legalise_dia_scale = 2; std::vector autoplaced; + float t = 1000; const float inner_num = 1.0; int move_lim, tot_iter; float rlim; diff --git a/common/place_vpr.inc b/common/place_vpr.inc new file mode 100644 index 0000000000..45bb20bd19 --- /dev/null +++ b/common/place_vpr.inc @@ -0,0 +1,3382 @@ +#if 0 +#include +#include +#include +#include +using namespace std; + +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_util.h" +#include "vtr_random.h" +#include "vtr_matrix.h" + +#include "vpr_types.h" +#include "vpr_error.h" +#include "vpr_utils.h" + +#include "globals.h" +#include "place.h" +#include "read_place.h" +#include "draw.h" +#include "place_and_route.h" +#include "net_delay.h" +#include "path_delay.h" +#include "timing_place_lookup.h" +#include "timing_place.h" +#include "read_xml_arch_file.h" +#include "echo_files.h" +#include "vpr_utils.h" +#include "place_macro.h" +#include "histogram.h" +#include "place_util.h" + +#include "PlacementDelayCalculator.h" +#include "timing_util.h" +#include "timing_info.h" +#include "tatum/echo_writer.hpp" + +/************** Types and defines local to place.c ***************************/ + +/* Cut off for incremental bounding box updates. * + * 4 is fastest -- I checked. */ +/* To turn off incremental bounding box updates, set this to a huge value */ +#define SMALL_NET 4 + +/* This defines the error tolerance for floating points variables used in * + * cost computation. 0.01 means that there is a 1% error tolerance. */ +#define ERROR_TOL .01 + +/* This defines the maximum number of swap attempts before invoking the * + * once-in-a-while placement legality check as well as floating point * + * variables round-offs check. */ +#define MAX_MOVES_BEFORE_RECOMPUTE 50000 + +/* The maximum number of tries when trying to place a carry chain at a * + * random location before trying exhaustive placement - find the fist * + * legal position and place it during initial placement. */ +#define MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY 4 + +/* Flags for the states of the bounding box. * + * Stored as char for memory efficiency. */ +#define NOT_UPDATED_YET 'N' +#define UPDATED_ONCE 'U' +#define GOT_FROM_SCRATCH 'S' + +/* For comp_cost. NORMAL means use the method that generates updateable * + * bounding boxes for speed. CHECK means compute all bounding boxes from * + * scratch using a very simple routine to allow checks of the other * + * costs. */ +enum e_cost_methods { + NORMAL, CHECK +}; + +/* This is for the placement swap routines. A swap attempt could be * + * rejected, accepted or aborted (due to the limitations placed on the * + * carry chain support at this point). */ +enum e_swap_result { + REJECTED, ACCEPTED, ABORTED +}; + +struct t_placer_statistics { + double av_cost, av_bb_cost, av_timing_cost, + sum_of_squares, av_delay_cost; + int success_sum; +}; + +#define MAX_INV_TIMING_COST 1.e9 +/* Stops inverse timing cost from going to infinity with very lax timing constraints, +which avoids multiplying by a gigantic inverse_prev_timing_cost when auto-normalizing. +The exact value of this cost has relatively little impact, but should not be +large enough to be on the order of timing costs for normal constraints. */ + +/********************** Variables local to place.c ***************************/ + +/* Cost of a net, and a temporary cost of a net used during move assessment. */ +static vtr::vector net_cost, temp_net_cost; + +static t_legal_pos **legal_pos = nullptr; /* [0..device_ctx.num_block_types-1][0..type_tsize - 1] */ +static int *num_legal_pos = nullptr; /* [0..num_legal_pos-1] */ + +/* [0...cluster_ctx.clb_nlist.nets().size()-1] * + * A flag array to indicate whether the specific bounding box has been updated * + * in this particular swap or not. If it has been updated before, the code * + * must use the updated data, instead of the out-of-date data passed into the * + * subroutine, particularly used in try_swap(). The value NOT_UPDATED_YET * + * indicates that the net has not been updated before, UPDATED_ONCE indicated * + * that the net has been updated once, if it is going to be updated again, the * + * values from the previous update must be used. GOT_FROM_SCRATCH is only * + * applicable for nets larger than SMALL_NETS and it indicates that the * + * particular bounding box cannot be updated incrementally before, hence the * + * bounding box is got from scratch, so the bounding box would definitely be * + * right, DO NOT update again. */ +static vtr::vector bb_updated_before; + +/* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1]. What is the value of the timing */ +/* driven portion of the cost function. These arrays will be set to */ +/* (criticality * delay) for each point to point connection. */ + +static vtr::vector point_to_point_timing_cost; +static vtr::vector temp_point_to_point_timing_cost; + +/* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1]. What is the value of the delay */ +/* for each connection in the circuit */ +static vtr::vector_map point_to_point_delay_cost; +static vtr::vector_map temp_point_to_point_delay_cost; + +/* [0..cluster_ctx.clb_nlist.blocks().size()-1][0..pins_per_clb-1]. Indicates which pin on the net */ +/* this block corresponds to, this is only required during timing-driven */ +/* placement. It is used to allow us to update individual connections on */ +/* each net */ +static vtr::vector> net_pin_indices; + +/* [0..cluster_ctx.clb_nlist.nets().size()-1]. Store the bounding box coordinates and the number of * + * blocks on each of a net's bounding box (to allow efficient updates), * + * respectively. */ + +static vtr::vector bb_coords, bb_num_on_edges; + +/* Store the information on the blocks to be moved in a swap during * + * placement, in the form of array of structs instead of struct with * + * arrays for cache effifiency * + */ +static t_pl_blocks_to_be_moved blocks_affected; + +/* The arrays below are used to precompute the inverse of the average * + * number of tracks per channel between [subhigh] and [sublow]. Access * + * them as chan?_place_cost_fac[subhigh][sublow]. They are used to * + * speed up the computation of the cost function that takes the length * + * of the net bounding box in each dimension, divided by the average * + * number of tracks in that direction; for other cost functions they * + * will never be used. * + */ +static float** chanx_place_cost_fac; //[0...device_ctx.grid.width()-2] +static float** chany_place_cost_fac; //[0...device_ctx.grid.height()-2] + +/* The following arrays are used by the try_swap function for speed. */ +/* [0...cluster_ctx.clb_nlist.nets().size()-1] */ +static vtr::vector ts_bb_coord_new, ts_bb_edge_new; +static std::vector ts_nets_to_update; + +/* The pl_macros array stores all the carry chains placement macros. * + * [0...num_pl_macros-1] */ +static t_pl_macro * pl_macros = nullptr; +static int num_pl_macros; + +/* These file-scoped variables keep track of the number of swaps * + * rejected, accepted or aborted. The total number of swap attempts * + * is the sum of the three number. */ +static int num_swap_rejected = 0; +static int num_swap_accepted = 0; +static int num_swap_aborted = 0; +static int num_ts_called = 0; + +/* Expected crossing counts for nets with different #'s of pins. From * + * ICCAD 94 pp. 690 - 695 (with linear interpolation applied by me). * + * Multiplied to bounding box of a net to better estimate wire length * + * for higher fanout nets. Each entry is the correction factor for the * + * fanout index-1 */ +static const float cross_count[50] = { /* [0..49] */1.0, 1.0, 1.0, 1.0828, 1.1536, 1.2206, 1.2823, 1.3385, 1.3991, 1.4493, 1.4974, + 1.5455, 1.5937, 1.6418, 1.6899, 1.7304, 1.7709, 1.8114, 1.8519, 1.8924, + 1.9288, 1.9652, 2.0015, 2.0379, 2.0743, 2.1061, 2.1379, 2.1698, 2.2016, + 2.2334, 2.2646, 2.2958, 2.3271, 2.3583, 2.3895, 2.4187, 2.4479, 2.4772, + 2.5064, 2.5356, 2.5610, 2.5864, 2.6117, 2.6371, 2.6625, 2.6887, 2.7148, + 2.7410, 2.7671, 2.7933 }; + +/********************* Static subroutines local to place.c *******************/ +#ifdef VERBOSE + static void print_clb_placement(const char *fname); +#endif + +static void alloc_and_load_placement_structs( + float place_cost_exp, t_placer_opts placer_opts, + t_direct_inf *directs, int num_directs); + +static void alloc_and_load_net_pin_indices(); + +static void alloc_and_load_try_swap_structs(); + +static void free_placement_structs(t_placer_opts placer_opts); + +static void alloc_and_load_for_fast_cost_update(float place_cost_exp); + +static void free_fast_cost_update(); + +static void alloc_legal_placements(); +static void load_legal_placements(); + +static void free_legal_placements(); + +static int check_macro_can_be_placed(int imacro, int itype, int x, int y, int z); +#endif + +static int try_place_macro(/*int itype, int ipos, int imacro*/ + Context *ctx, CellInfo* cell, const std::string& loc_name); + +static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_locations*/ + Context *ctx, std::vector> &free_locations); + +static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type pad_loc_type*/ + Context* ctx, + std::vector> &free_locations, + const std::unordered_map &bel_types); + +static void initial_placement_location(/*int * free_locations, ClusterBlockId blk_id, + int *pipos, int *px, int *py, int *pz*/ + Context *ctx, + const std::vector> &free_locations, + CellInfo *cell, + const std::unordered_map &bel_types, + BelId& bel); + +static void initial_placement(/*enum e_pad_loc_type pad_loc_type, + const char *pad_loc_file*/ + Context *ctx, + const std::unordered_map &bel_types); + +#if 0 +static float comp_bb_cost(e_cost_methods method); + +static int setup_blocks_affected(ClusterBlockId b_from, int x_to, int y_to, int z_to); + +static int find_affected_blocks(ClusterBlockId b_from, int x_to, int y_to, int z_to); + +static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, + float rlim, + enum e_place_algorithm place_algorithm, float timing_tradeoff, + float inverse_prev_bb_cost, float inverse_prev_timing_cost, + float *delay_cost); + +static ClusterBlockId pick_from_block(); + +static void check_place(float bb_cost, float timing_cost, + enum e_place_algorithm place_algorithm, + float delay_cost); + +static float starting_t(float *cost_ptr, float *bb_cost_ptr, + float *timing_cost_ptr, + t_annealing_sched annealing_sched, int max_moves, float rlim, + enum e_place_algorithm place_algorithm, float timing_tradeoff, + float inverse_prev_bb_cost, float inverse_prev_timing_cost, + float *delay_cost_ptr); + +static void update_t(float *t, float rlim, float success_rat, + t_annealing_sched annealing_sched); + +static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid); + +static int exit_crit(float t, float cost, + t_annealing_sched annealing_sched); + +static int count_connections(); + +static double get_std_dev(int n, double sum_x_squared, double av_x); + +static float recompute_bb_cost(); + +static float comp_td_point_to_point_delay(ClusterNetId net_id, int ipin); + +static void comp_td_point_to_point_delays(); + +static void update_td_cost(); + +static bool driven_by_moved_block(const ClusterNetId net); + +static void comp_td_costs(float *timing_cost, float *connection_delay_sum); + +static e_swap_result assess_swap(float delta_c, float t); + +static bool find_to(t_type_ptr type, float rlim, + int x_from, int y_from, + int *px_to, int *py_to, int *pz_to); +static void find_to_location(t_type_ptr type, float rlim, + int x_from, int y_from, + int *px_to, int *py_to, int *pz_to); + +static void get_non_updateable_bb(ClusterNetId net_id, t_bb *bb_coord_new); + +static void update_bb(ClusterNetId net_id, t_bb *bb_coord_new, + t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew); + +static int find_affected_nets_and_update_costs(e_place_algorithm place_algorithm, float& bb_delta_c, float& timing_delta_c, float& delay_delta_c); + +static void record_affected_net(const ClusterNetId net, int& num_affected_nets); + +static void update_net_bb(const ClusterNetId net, int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin); +static void update_td_delta_costs(const ClusterNetId net, const ClusterPinId pin, float& delta_timing_cost, float& delta_delay_cost); + +static float get_net_cost(ClusterNetId net_id, t_bb *bb_ptr); + +static void get_bb_from_scratch(ClusterNetId net_id, t_bb *coords, + t_bb *num_on_edges); + +static double get_net_wirelength_estimate(ClusterNetId net_id, t_bb *bbptr); + +static void free_try_swap_arrays(); + +static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, + int num_connections, float crit_exponent, float bb_cost, + float * place_delay_value, float * timing_cost, float * delay_cost, + int * outer_crit_iter_count, float * inverse_prev_timing_cost, + float * inverse_prev_bb_cost, + const ClusteredPinAtomPinsLookup& netlist_pin_lookup, +#ifdef ENABLE_CLASSIC_VPR_STA + t_slack* slacks, + t_timing_inf timing_inf, +#endif + SetupTimingInfo& timing_info); + +static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, + float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, + float crit_exponent, int inner_recompute_limit, + t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, + float * delay_cost, +#ifdef ENABLE_CLASSIC_VPR_STA + t_slack* slacks, + t_timing_inf timing_inf, +#endif + const ClusteredPinAtomPinsLookup& netlist_pin_lookup, + SetupTimingInfo& timing_info); + +/*****************************************************************************/ +void try_place(t_placer_opts placer_opts, + t_annealing_sched annealing_sched, + t_chan_width_dist chan_width_dist, t_router_opts router_opts, + t_det_routing_arch *det_routing_arch, t_segment_inf * segment_inf, +#ifdef ENABLE_CLASSIC_VPR_STA + t_timing_inf timing_inf, +#endif + t_direct_inf *directs, int num_directs) { + + /* Does almost all the work of placing a circuit. Width_fac gives the * + * width of the widest channel. Place_cost_exp says what exponent the * + * width should be taken to when calculating costs. This allows a * + * greater bias for anisotropic architectures. */ + + int tot_iter, move_lim, moves_since_cost_recompute, width_fac, num_connections, + outer_crit_iter_count, inner_recompute_limit; + unsigned int ipin; + float t, success_rat, rlim, cost, timing_cost, bb_cost, new_bb_cost, new_timing_cost, + delay_cost, new_delay_cost, place_delay_value, inverse_prev_bb_cost, inverse_prev_timing_cost, + oldt, crit_exponent, + first_rlim, final_rlim, inverse_delta_rlim; + tatum::TimingPathInfo critical_path; + float sTNS = NAN; + float sWNS = NAN; + + double std_dev; + char msg[vtr::bufsize]; + t_placer_statistics stats; +#ifdef ENABLE_CLASSIC_VPR_STA + t_slack * slacks = NULL; +#endif + + auto& device_ctx = g_vpr_ctx.device(); + auto& cluster_ctx = g_vpr_ctx.clustering(); + + std::shared_ptr timing_info; + std::shared_ptr placement_delay_calc; + + /* Allocated here because it goes into timing critical code where each memory allocation is expensive */ + IntraLbPbPinLookup pb_gpin_lookup(device_ctx.block_types, device_ctx.num_block_types); + + + /* init file scope variables */ + num_swap_rejected = 0; + num_swap_accepted = 0; + num_swap_aborted = 0; + num_ts_called = 0; + + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + || placer_opts.enable_timing_computations) { + /*do this before the initial placement to avoid messing up the initial placement */ + alloc_lookups_and_criticalities(chan_width_dist, router_opts, det_routing_arch, segment_inf, directs, num_directs); + +#ifdef ENABLE_CLASSIC_VPR_STA + slacks = alloc_and_load_timing_graph(timing_inf); +#endif + } + + width_fac = placer_opts.place_chan_width; + + init_chan(width_fac, chan_width_dist); + + alloc_and_load_placement_structs(placer_opts.place_cost_exp, placer_opts, + directs, num_directs); + + initial_placement(placer_opts.pad_loc_type, placer_opts.pad_loc_file.c_str()); + init_draw_coords((float) width_fac); + + //Enables fast look-up of atom pins connect to CLB pins + ClusteredPinAtomPinsLookup netlist_pin_lookup(cluster_ctx.clb_nlist, pb_gpin_lookup); + + /* Gets initial cost and loads bounding boxes. */ + + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE || placer_opts.enable_timing_computations) { + bb_cost = comp_bb_cost(NORMAL); + + crit_exponent = placer_opts.td_place_exp_first; /*this will be modified when rlim starts to change */ + + num_connections = count_connections(); + vtr::printf_info("\n"); + vtr::printf_info("There are %d point to point connections in this circuit.\n", num_connections); + vtr::printf_info("\n"); + + place_delay_value = 0; + + //Update the point-to-point delays from the initial placement + comp_td_point_to_point_delays(); + + /* + * Initialize timing analysis + */ + auto& atom_ctx = g_vpr_ctx.atom(); + placement_delay_calc = std::make_shared(atom_ctx.nlist, atom_ctx.lookup, point_to_point_delay_cost); + timing_info = make_setup_timing_info(placement_delay_calc); + + timing_info->update(); + timing_info->set_warn_unconstrained(false); //Don't warn again about unconstrained nodes again during placement + + //Initial slack estimates + load_criticalities(*timing_info, crit_exponent, netlist_pin_lookup); + + critical_path = timing_info->least_slack_critical_path(); + + //Write out the initial timing echo file + if(isEchoFileEnabled(E_ECHO_INITIAL_PLACEMENT_TIMING_GRAPH)) { + auto& timing_ctx = g_vpr_ctx.timing(); + + tatum::write_echo(getEchoFileName(E_ECHO_INITIAL_PLACEMENT_TIMING_GRAPH), + *timing_ctx.graph, *timing_ctx.constraints, *placement_delay_calc, timing_info->analyzer()); + } + +#ifdef ENABLE_CLASSIC_VPR_STA + load_timing_graph_net_delays(point_to_point_delay_cost); + do_timing_analysis(slacks, timing_inf, false, true); + + float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); + if(cpd_diff_ns > ERROR_TOL) { + print_classic_cpds(); + print_tatum_cpds(timing_info->critical_paths()); + + vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); + } +#endif + + /*now we can properly compute costs */ + comp_td_costs(&timing_cost, &delay_cost); /*also updates values in point_to_point_delay_cost */ + + if (getEchoEnabled()) { +#ifdef ENABLE_CLASSIC_VPR_STA + if(isEchoFileEnabled(E_ECHO_INITIAL_PLACEMENT_SLACK)) + print_slack(slacks->slack, false, getEchoFileName(E_ECHO_INITIAL_PLACEMENT_SLACK)); + if(isEchoFileEnabled(E_ECHO_INITIAL_PLACEMENT_CRITICALITY)) + print_criticality(slacks, getEchoFileName(E_ECHO_INITIAL_PLACEMENT_CRITICALITY)); +#endif + } + outer_crit_iter_count = 1; + + inverse_prev_timing_cost = 1 / timing_cost; + inverse_prev_bb_cost = 1 / bb_cost; + cost = 1; /*our new cost function uses normalized values of */ + /*bb_cost and timing_cost, the value of cost will be reset */ + /*to 1 at each temperature when *_TIMING_DRIVEN_PLACE is true */ + } else { /*BOUNDING_BOX_PLACE */ + cost = bb_cost = comp_bb_cost(NORMAL); + timing_cost = 0; + delay_cost = 0; + place_delay_value = 0; + outer_crit_iter_count = 0; + num_connections = 0; + crit_exponent = 0; + + inverse_prev_timing_cost = 0; /*inverses not used */ + inverse_prev_bb_cost = 0; + } + + //Sanity check that initial placement is legal + check_place(bb_cost, timing_cost, placer_opts.place_algorithm, delay_cost); + + //Initial pacement statistics + vtr::printf_info("Initial placement cost: %g bb_cost: %g td_cost: %g delay_cost: %g\n", + cost, bb_cost, timing_cost, delay_cost); + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + vtr::printf_info("Initial placement estimated Critical Path Delay (CPD): %g ns\n", + 1e9*critical_path.delay()); + vtr::printf_info("Initial placement estimated setup Total Negative Slack (sTNS): %g ns\n", + 1e9*timing_info->setup_total_negative_slack()); + vtr::printf_info("Initial placement estimated setup Worst Negative Slack (sWNS): %g ns\n", + 1e9*timing_info->setup_worst_negative_slack()); + vtr::printf_info("\n"); + + vtr::printf_info("Initial placement estimated setup slack histogram:\n"); + print_histogram(create_setup_slack_histogram(*timing_info->setup_analyzer())); + } + vtr::printf_info("\n"); + + //Table header + vtr::printf_info("%7s " + "%7s %10s %10s %10s " + "%10s %7s %10s %8s " + "%7s %7s %7s %6s " + "%9s %6s\n", + "-------", + "-------", "----------", "----------", "----------", + "----------", "-------", "----------", "--------", + "-------", "-------", "-------", "------", + "---------", "------"); + vtr::printf_info("%7s " + "%7s %10s %10s %10s " + "%10s %7s %10s %8s " + "%7s %7s %7s %6s " + "%9s %6s\n", + "T", + "Cost", "Av BB Cost", "Av TD Cost", "Av Tot Del", + "P to P Del", "CPD", "sTNS", "sWNS", + "Ac Rate", "Std Dev", "R limit", "Exp", + "Tot Moves", "Alpha"); + vtr::printf_info("%7s " + "%7s %10s %10s %10s " + "%10s %7s %10s %8s " + "%7s %7s %7s %6s " + "%9s %6s\n", + "-------", + "-------", "----------", "----------", "----------", + "----------", "-------", "----------", "--------", + "-------", "-------", "-------", "------", + "---------", "------"); + + sprintf(msg, "Initial Placement. Cost: %g BB Cost: %g TD Cost %g Delay Cost: %g \t Channel Factor: %d", + cost, bb_cost, timing_cost, delay_cost, width_fac); + + //Draw the initial placement + update_screen(ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info); + + move_lim = (int) (annealing_sched.inner_num * pow(cluster_ctx.clb_nlist.blocks().size(), 1.3333)); + + /* Sometimes I want to run the router with a random placement. Avoid * + * using 0 moves to stop division by 0 and 0 length vector problems, * + * by setting move_lim to 1 (which is still too small to do any * + * significant optimization). */ + if (move_lim <= 0) + move_lim = 1; + + if (placer_opts.inner_loop_recompute_divider != 0) { + inner_recompute_limit = (int) + (0.5 + (float) move_lim / (float) placer_opts.inner_loop_recompute_divider); + } else { + /*don't do an inner recompute */ + inner_recompute_limit = move_lim + 1; + } + + rlim = (float) max(device_ctx.grid.width() - 1, device_ctx.grid.height() - 1); + + first_rlim = rlim; /*used in timing-driven placement for exponent computation */ + final_rlim = 1; + inverse_delta_rlim = 1 / (first_rlim - final_rlim); + + t = starting_t(&cost, &bb_cost, &timing_cost, + annealing_sched, move_lim, rlim, + placer_opts.place_algorithm, placer_opts.timing_tradeoff, + inverse_prev_bb_cost, inverse_prev_timing_cost, &delay_cost); + + tot_iter = 0; + moves_since_cost_recompute = 0; + + + + + /* Outer loop of the simmulated annealing begins */ + while (exit_crit(t, cost, annealing_sched) == 0) { + + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + cost = 1; + } + + outer_loop_recompute_criticalities(placer_opts, num_connections, + crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, + &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, + netlist_pin_lookup, +#ifdef ENABLE_CLASSIC_VPR_STA + slacks, + timing_inf, +#endif + *timing_info); + + placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, + move_lim, crit_exponent, inner_recompute_limit, &stats, + &cost, &bb_cost, &timing_cost, &delay_cost, +#ifdef ENABLE_CLASSIC_VPR_STA + slacks, + timing_inf, +#endif + netlist_pin_lookup, + *timing_info); + + /* Lines below prevent too much round-off error from accumulating * + * in the cost over many iterations. This round-off can lead to * + * error checks failing because the cost is different from what * + * you get when you recompute from scratch. */ + + moves_since_cost_recompute += move_lim; + if (moves_since_cost_recompute > MAX_MOVES_BEFORE_RECOMPUTE) { + new_bb_cost = recompute_bb_cost(); + if (fabs(new_bb_cost - bb_cost) > bb_cost * ERROR_TOL) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "in try_place: new_bb_cost = %g, old bb_cost = %g\n", + new_bb_cost, bb_cost); + } + bb_cost = new_bb_cost; + + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + comp_td_costs(&new_timing_cost, &new_delay_cost); + if (fabs(new_timing_cost - timing_cost) > timing_cost * ERROR_TOL) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "in try_place: new_timing_cost = %g, old timing_cost = %g, ERROR_TOL = %g\n", + new_timing_cost, timing_cost, ERROR_TOL); + } + if (fabs(new_delay_cost - delay_cost) > delay_cost * ERROR_TOL) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "in try_place: new_delay_cost = %g, old delay_cost = %g, ERROR_TOL = %g\n", + new_delay_cost, delay_cost, ERROR_TOL); + } + timing_cost = new_timing_cost; + } + + if (placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { + cost = new_bb_cost; + } + moves_since_cost_recompute = 0; + } + + tot_iter += move_lim; + success_rat = ((float) stats.success_sum) / move_lim; + if (stats.success_sum == 0) { + stats.av_cost = cost; + stats.av_bb_cost = bb_cost; + stats.av_timing_cost = timing_cost; + stats.av_delay_cost = delay_cost; + } else { + stats.av_cost /= stats.success_sum; + stats.av_bb_cost /= stats.success_sum; + stats.av_timing_cost /= stats.success_sum; + stats.av_delay_cost /= stats.success_sum; + } + std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); + + oldt = t; /* for finding and printing alpha. */ + update_t(&t, rlim, success_rat, annealing_sched); + + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + critical_path = timing_info->least_slack_critical_path(); + sTNS = timing_info->setup_total_negative_slack(); + sWNS = timing_info->setup_worst_negative_slack(); + } + + vtr::printf_info("%7.3f " + "%7.4f %10.4f %-10.5g %-10.5g " + "%-10.5g %7.3f % 10.3g % 8.3f " + "%7.4f %7.4f %7.4f %6.3f" + "%9d %6.3f\n", + oldt, + stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, + place_delay_value, 1e9*critical_path.delay(), 1e9*sTNS, 1e9*sWNS, + success_rat, std_dev, rlim, crit_exponent, + tot_iter, t / oldt); + +#ifdef ENABLE_CLASSIC_VPR_STA + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); + if(cpd_diff_ns > ERROR_TOL) { + print_classic_cpds(); + print_tatum_cpds(timing_info->critical_paths()); + + vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); + } + } +#endif + + sprintf(msg, "Cost: %g BB Cost %g TD Cost %g Temperature: %g", + cost, bb_cost, timing_cost, t); + update_screen(ScreenUpdatePriority::MINOR, msg, PLACEMENT, timing_info); + update_rlim(&rlim, success_rat, device_ctx.grid); + + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + crit_exponent = (1 - (rlim - final_rlim) * inverse_delta_rlim) + * (placer_opts.td_place_exp_last - placer_opts.td_place_exp_first) + + placer_opts.td_place_exp_first; + } +#ifdef VERBOSE + if (getEchoEnabled()) { + print_clb_placement("first_iteration_clb_placement.echo"); + } +#endif + } + /* Outer loop of the simmulated annealing ends */ + + + outer_loop_recompute_criticalities(placer_opts, num_connections, + crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, + &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, + netlist_pin_lookup, +#ifdef ENABLE_CLASSIC_VPR_STA + slacks, + timing_inf, +#endif + *timing_info); + + t = 0; /* freeze out */ + + /* Run inner loop again with temperature = 0 so as to accept only swaps + * which reduce the cost of the placement */ + placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, + move_lim, crit_exponent, inner_recompute_limit, &stats, + &cost, &bb_cost, &timing_cost, &delay_cost, +#ifdef ENABLE_CLASSIC_VPR_STA + slacks, + timing_inf, +#endif + netlist_pin_lookup, + *timing_info); + + tot_iter += move_lim; + success_rat = ((float) stats.success_sum) / move_lim; + if (stats.success_sum == 0) { + stats.av_cost = cost; + stats.av_bb_cost = bb_cost; + stats.av_delay_cost = delay_cost; + stats.av_timing_cost = timing_cost; + } else { + stats.av_cost /= stats.success_sum; + stats.av_bb_cost /= stats.success_sum; + stats.av_delay_cost /= stats.success_sum; + stats.av_timing_cost /= stats.success_sum; + } + + std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); + + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + critical_path = timing_info->least_slack_critical_path(); + sTNS = timing_info->setup_total_negative_slack(); + sWNS = timing_info->setup_worst_negative_slack(); + } + + vtr::printf_info("%7.3f " + "%7.4f %10.4f %-10.5g %-10.5g " + "%-10.5g %7.3f % 10.3g % 8.3f " + "%7.4f %7.4f %7.4f %6.3f" + "%9d %6.3f\n", + t, + stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, + place_delay_value, 1e9*critical_path.delay(), 1e9*sTNS, 1e9*sWNS, + success_rat, std_dev, rlim, crit_exponent, + tot_iter, 0.); + + // TODO: + // 1. print a message about number of aborted moves. + // 2. add some subroutine hierarchy! Too big! + +#ifdef VERBOSE + if (getEchoEnabled() && isEchoFileEnabled(E_ECHO_END_CLB_PLACEMENT)) { + print_clb_placement(getEchoFileName(E_ECHO_END_CLB_PLACEMENT)); + } +#endif + + check_place(bb_cost, timing_cost, placer_opts.place_algorithm, delay_cost); + + //Some stats + vtr::printf_info("\n"); + vtr::printf_info("Swaps called: %d\n", num_ts_called); + + if (placer_opts.enable_timing_computations + && placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { + /*need this done since the timing data has not been kept up to date* + *in bounding_box mode */ + for (auto net_id : cluster_ctx.clb_nlist.nets()) { + for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) + set_timing_place_crit(net_id, ipin, 0); /*dummy crit values */ + } + comp_td_costs(&timing_cost, &delay_cost); /*computes point_to_point_delay_cost */ + } + + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + || placer_opts.enable_timing_computations) { + + //Final timing estimate + VTR_ASSERT(timing_info); + timing_info->update(); //Tatum + critical_path = timing_info->least_slack_critical_path(); + + if(isEchoFileEnabled(E_ECHO_FINAL_PLACEMENT_TIMING_GRAPH)) { + auto& timing_ctx = g_vpr_ctx.timing(); + + tatum::write_echo(getEchoFileName(E_ECHO_FINAL_PLACEMENT_TIMING_GRAPH), + *timing_ctx.graph, *timing_ctx.constraints, *placement_delay_calc, timing_info->analyzer()); + } + +#ifdef ENABLE_CLASSIC_VPR_STA + //Old VPR analyzer + load_timing_graph_net_delays(point_to_point_delay_cost); + do_timing_analysis(slacks, timing_inf, false, true); +#endif + + + /* Print critical path delay. */ + vtr::printf_info("\n"); + vtr::printf_info("Placement estimated critical path delay: %g ns", + 1e9*critical_path.delay(), get_critical_path_delay()); +#ifdef ENABLE_CLASSIC_VPR_STA + vtr::printf_info(" (classic VPR STA %g ns)", get_critical_path_delay()); +#endif + vtr::printf("\n"); + vtr::printf_info("Placement estimated setup Total Negative Slack (sTNS): %g ns\n", + 1e9*timing_info->setup_total_negative_slack()); + vtr::printf_info("Placement estimated setup Worst Negative Slack (sWNS): %g ns\n", + 1e9*timing_info->setup_worst_negative_slack()); + vtr::printf_info("\n"); + + vtr::printf_info("Placement estimated setup slack histogram:\n"); + print_histogram(create_setup_slack_histogram(*timing_info->setup_analyzer())); + vtr::printf_info("\n"); + +#ifdef ENABLE_CLASSIC_VPR_STA + float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); + if(cpd_diff_ns > ERROR_TOL) { + print_classic_cpds(); + print_tatum_cpds(timing_info->critical_paths()); + + vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); + } +#endif + } + + sprintf(msg, "Placement. Cost: %g bb_cost: %g td_cost: %g Channel Factor: %d", + cost, bb_cost, timing_cost, width_fac); + vtr::printf_info("Placement cost: %g, bb_cost: %g, td_cost: %g, delay_cost: %g\n", + cost, bb_cost, timing_cost, delay_cost); + update_screen(ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info); + + // Print out swap statistics + size_t total_swap_attempts = num_swap_rejected + num_swap_accepted + num_swap_aborted; + VTR_ASSERT(total_swap_attempts > 0); + + size_t num_swap_print_digits = ceil(log10(total_swap_attempts)); + float reject_rate = (float) num_swap_rejected / total_swap_attempts; + float accept_rate = (float) num_swap_accepted / total_swap_attempts; + float abort_rate = (float) num_swap_aborted / total_swap_attempts; + vtr::printf_info("Placement total # of swap attempts: %*d\n", num_swap_print_digits, total_swap_attempts); + vtr::printf_info("\tSwaps accepted: %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_accepted, 100*accept_rate); + vtr::printf_info("\tSwaps rejected: %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_rejected, 100*reject_rate); + vtr::printf_info("\tSwaps aborted : %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_aborted, 100*abort_rate); + + free_placement_structs(placer_opts); + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + || placer_opts.enable_timing_computations) { + +#ifdef ENABLE_CLASSIC_VPR_STA + free_timing_graph(slacks); +#endif + + free_lookups_and_criticalities(); + } + + free_try_swap_arrays(); +} + +/* Function to recompute the criticalities before the inner loop of the annealing */ +static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, + int num_connections, float crit_exponent, float bb_cost, + float * place_delay_value, float * timing_cost, float * delay_cost, + int * outer_crit_iter_count, float * inverse_prev_timing_cost, + float * inverse_prev_bb_cost, + const ClusteredPinAtomPinsLookup& netlist_pin_lookup, +#ifdef ENABLE_CLASSIC_VPR_STA + t_slack* slacks, + t_timing_inf timing_inf, +#endif + SetupTimingInfo& timing_info) { + + if (placer_opts.place_algorithm != PATH_TIMING_DRIVEN_PLACE) + return; + + /*at each temperature change we update these values to be used */ + /*for normalizing the tradeoff between timing and wirelength (bb) */ + if (*outer_crit_iter_count >= placer_opts.recompute_crit_iter + || placer_opts.inner_loop_recompute_divider != 0) { +#ifdef VERBOSE + vtr::printf_info("Outer loop recompute criticalities\n"); +#endif + num_connections = std::max(num_connections, 1); //Avoid division by zero + VTR_ASSERT(num_connections > 0); + + *place_delay_value = (*delay_cost) / num_connections; + + //Per-temperature timing update + timing_info.update(); + load_criticalities(timing_info, crit_exponent, netlist_pin_lookup); + +#ifdef ENABLE_CLASSIC_VPR_STA + load_timing_graph_net_delays(point_to_point_delay_cost); + do_timing_analysis(slacks, timing_inf, false, true); +#endif + + /*recompute costs from scratch, based on new criticalities */ + comp_td_costs(timing_cost, delay_cost); + *outer_crit_iter_count = 0; + } + (*outer_crit_iter_count)++; + + /*at each temperature change we update these values to be used */ + /*for normalizing the tradeoff between timing and wirelength (bb) */ +*inverse_prev_bb_cost = 1 / bb_cost; +/*Prevent inverse timing cost from going to infinity */ +*inverse_prev_timing_cost = min(1 / (*timing_cost), (float)MAX_INV_TIMING_COST); +} + +/* Function which contains the inner loop of the simulated annealing */ +static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, + float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, + float crit_exponent, int inner_recompute_limit, + t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, + float * delay_cost, +#ifdef ENABLE_CLASSIC_VPR_STA + t_slack* slacks, + t_timing_inf timing_inf, +#endif + const ClusteredPinAtomPinsLookup& netlist_pin_lookup, + SetupTimingInfo& timing_info) { + + int inner_crit_iter_count, inner_iter; + + stats->av_cost = 0.; + stats->av_bb_cost = 0.; + stats->av_delay_cost = 0.; + stats->av_timing_cost = 0.; + stats->sum_of_squares = 0.; + stats->success_sum = 0; + + inner_crit_iter_count = 1; + + /* Inner loop begins */ + for (inner_iter = 0; inner_iter < move_lim; inner_iter++) { + e_swap_result swap_result = try_swap(t, cost, bb_cost, timing_cost, rlim, + placer_opts.place_algorithm, placer_opts.timing_tradeoff, + inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost); + + if (swap_result == ACCEPTED) { + /* Move was accepted. Update statistics that are useful for the annealing schedule. */ + stats->success_sum++; + stats->av_cost += *cost; + stats->av_bb_cost += *bb_cost; + stats->av_timing_cost += *timing_cost; + stats->av_delay_cost += *delay_cost; + stats->sum_of_squares += (*cost) * (*cost); + num_swap_accepted++; + } + else if (swap_result == ABORTED) { + num_swap_aborted++; + } + else { // swap_result == REJECTED + num_swap_rejected++; + } + + + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + + /* Do we want to re-timing analyze the circuit to get updated slack and criticality values? + * We do this only once in a while, since it is expensive. + */ + if (inner_crit_iter_count >= inner_recompute_limit + && inner_iter != move_lim - 1) { /*on last iteration don't recompute */ + + inner_crit_iter_count = 0; +#ifdef VERBOSE + vtr::printf("Inner loop recompute criticalities\n"); +#endif + /* Using the delays in net_delay, do a timing analysis to update slacks and + * criticalities; then update the timing cost since it will change. + */ + //Inner loop timing update + timing_info.update(); + load_criticalities(timing_info, crit_exponent, netlist_pin_lookup); + +#ifdef ENABLE_CLASSIC_VPR_STA + load_timing_graph_net_delays(point_to_point_delay_cost); + do_timing_analysis(slacks, timing_inf, false, true); +#endif + + comp_td_costs(timing_cost, delay_cost); + } + inner_crit_iter_count++; + } +#ifdef VERBOSE + vtr::printf("t = %g cost = %g bb_cost = %g timing_cost = %g move = %d dmax = %g\n", + t, *cost, *bb_cost, *timing_cost, inner_iter, *delay_cost); + if (fabs((*bb_cost) - comp_bb_cost(CHECK)) > (*bb_cost) * ERROR_TOL) + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "fabs((*bb_cost) - comp_bb_cost(CHECK)) > (*bb_cost) * ERROR_TOL"); +#endif + } + /* Inner loop ends */ +} + +/*only count non-global connections */ +static int count_connections() { + + int count = 0; + + auto& cluster_ctx = g_vpr_ctx.clustering(); + for (auto net_id : cluster_ctx.clb_nlist.nets()) { + if (cluster_ctx.clb_nlist.net_is_global(net_id)) + continue; + + count += cluster_ctx.clb_nlist.net_sinks(net_id).size(); + } + + return (count); +} + +static double get_std_dev(int n, double sum_x_squared, double av_x) { + + /* Returns the standard deviation of data set x. There are n sample points, * + * sum_x_squared is the summation over n of x^2 and av_x is the average x. * + * All operations are done in double precision, since round off error can be * + * a problem in the initial temp. std_dev calculation for big circuits. */ + + double std_dev; + + if (n <= 1) + std_dev = 0.; + else + std_dev = (sum_x_squared - n * av_x * av_x) / (double) (n - 1); + + if (std_dev > 0.) /* Very small variances sometimes round negative */ + std_dev = sqrt(std_dev); + else + std_dev = 0.; + + return (std_dev); +} + +static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid) { + + /* Update the range limited to keep acceptance prob. near 0.44. Use * + * a floating point rlim to allow gradual transitions at low temps. */ + + float upper_lim; + + *rlim = (*rlim) * (1. - 0.44 + success_rat); + upper_lim = max(grid.width() - 1, grid.height() - 1); + *rlim = min(*rlim, upper_lim); + *rlim = max(*rlim, (float)1.); +} + +/* Update the temperature according to the annealing schedule selected. */ +static void update_t(float *t, float rlim, float success_rat, + t_annealing_sched annealing_sched) { + + /* float fac; */ + + if (annealing_sched.type == USER_SCHED) { + *t = annealing_sched.alpha_t * (*t); + } else { /* AUTO_SCHED */ + if (success_rat > 0.96) { + *t = (*t) * 0.5; + } else if (success_rat > 0.8) { + *t = (*t) * 0.9; + } else if (success_rat > 0.15 || rlim > 1.) { + *t = (*t) * 0.95; + } else { + *t = (*t) * 0.8; + } + } +} + +static int exit_crit(float t, float cost, + t_annealing_sched annealing_sched) { + + /* Return 1 when the exit criterion is met. */ + + if (annealing_sched.type == USER_SCHED) { + if (t < annealing_sched.exit_t) { + return (1); + } else { + return (0); + } + } + + auto& cluster_ctx = g_vpr_ctx.clustering(); + + /* Automatic annealing schedule */ + float t_exit = 0.005 * cost / cluster_ctx.clb_nlist.nets().size(); + + if (t < t_exit) { + return (1); + } else if (std::isnan(t_exit)) { + //May get nan if there are no nets + return (1); + } else { + return (0); + } +} + +static float starting_t(float *cost_ptr, float *bb_cost_ptr, + float *timing_cost_ptr, + t_annealing_sched annealing_sched, int max_moves, float rlim, + enum e_place_algorithm place_algorithm, float timing_tradeoff, + float inverse_prev_bb_cost, float inverse_prev_timing_cost, + float *delay_cost_ptr) { + + /* Finds the starting temperature (hot condition). */ + + int i, num_accepted, move_lim; + double std_dev, av, sum_of_squares; /* Double important to avoid round off */ + + if (annealing_sched.type == USER_SCHED) + return (annealing_sched.init_t); + + auto& cluster_ctx = g_vpr_ctx.clustering(); + + move_lim = min(max_moves, (int) cluster_ctx.clb_nlist.blocks().size()); + + num_accepted = 0; + av = 0.; + sum_of_squares = 0.; + + /* Try one move per block. Set t high so essentially all accepted. */ + + for (i = 0; i < move_lim; i++) { + e_swap_result swap_result = try_swap(HUGE_POSITIVE_FLOAT, cost_ptr, bb_cost_ptr, timing_cost_ptr, rlim, + place_algorithm, timing_tradeoff, + inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost_ptr); + + if (swap_result == ACCEPTED) { + num_accepted++; + av += *cost_ptr; + sum_of_squares += *cost_ptr * (*cost_ptr); + num_swap_accepted++; + } else if (swap_result == ABORTED) { + num_swap_aborted++; + } else { + num_swap_rejected++; + } + } + + if (num_accepted != 0) + av /= num_accepted; + else + av = 0.; + + std_dev = get_std_dev(num_accepted, sum_of_squares, av); + + if (num_accepted != move_lim) { + vtr::printf_warning(__FILE__, __LINE__, + "Starting t: %d of %d configurations accepted.\n", num_accepted, move_lim); + } + +#ifdef VERBOSE + vtr::printf_info("std_dev: %g, average cost: %g, starting temp: %g\n", std_dev, av, 20. * std_dev); +#endif + + /* Set the initial temperature to 20 times the standard of deviation */ + /* so that the initial temperature adjusts according to the circuit */ + return (20. * std_dev); +} + + +static int setup_blocks_affected(ClusterBlockId b_from, int x_to, int y_to, int z_to) { + + /* Find all the blocks affected when b_from is swapped with b_to. + * Returns abort_swap. */ + + int imoved_blk, imacro; + int x_from, y_from, z_from; + ClusterBlockId b_to; + int abort_swap = false; + + auto& place_ctx = g_vpr_ctx.mutable_placement(); + + x_from = place_ctx.block_locs[b_from].x; + y_from = place_ctx.block_locs[b_from].y; + z_from = place_ctx.block_locs[b_from].z; + + b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; + + // Check whether the to_location is empty + if (b_to == EMPTY_BLOCK_ID) { + + // Swap the block, dont swap the nets yet + place_ctx.block_locs[b_from].x = x_to; + place_ctx.block_locs[b_from].y = y_to; + place_ctx.block_locs[b_from].z = z_to; + + // Sets up the blocks moved + imoved_blk = blocks_affected.num_moved_blocks; + blocks_affected.moved_blocks[imoved_blk].block_num = b_from; + blocks_affected.moved_blocks[imoved_blk].xold = x_from; + blocks_affected.moved_blocks[imoved_blk].xnew = x_to; + blocks_affected.moved_blocks[imoved_blk].yold = y_from; + blocks_affected.moved_blocks[imoved_blk].ynew = y_to; + blocks_affected.moved_blocks[imoved_blk].zold = z_from; + blocks_affected.moved_blocks[imoved_blk].znew = z_to; + blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = true; + blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = true; + blocks_affected.num_moved_blocks ++; + + } else if (b_to != INVALID_BLOCK_ID) { + + // Does not allow a swap with a macro yet + get_imacro_from_iblk(&imacro, b_to, pl_macros, num_pl_macros); + if (imacro != -1) { + abort_swap = true; + return (abort_swap); + } + + // Swap the block, dont swap the nets yet + place_ctx.block_locs[b_to].x = x_from; + place_ctx.block_locs[b_to].y = y_from; + place_ctx.block_locs[b_to].z = z_from; + + place_ctx.block_locs[b_from].x = x_to; + place_ctx.block_locs[b_from].y = y_to; + place_ctx.block_locs[b_from].z = z_to; + + // Sets up the blocks moved + imoved_blk = blocks_affected.num_moved_blocks; + blocks_affected.moved_blocks[imoved_blk].block_num = b_from; + blocks_affected.moved_blocks[imoved_blk].xold = x_from; + blocks_affected.moved_blocks[imoved_blk].xnew = x_to; + blocks_affected.moved_blocks[imoved_blk].yold = y_from; + blocks_affected.moved_blocks[imoved_blk].ynew = y_to; + blocks_affected.moved_blocks[imoved_blk].zold = z_from; + blocks_affected.moved_blocks[imoved_blk].znew = z_to; + blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = false; + blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = false; + blocks_affected.num_moved_blocks ++; + + imoved_blk = blocks_affected.num_moved_blocks; + blocks_affected.moved_blocks[imoved_blk].block_num = b_to; + blocks_affected.moved_blocks[imoved_blk].xold = x_to; + blocks_affected.moved_blocks[imoved_blk].xnew = x_from; + blocks_affected.moved_blocks[imoved_blk].yold = y_to; + blocks_affected.moved_blocks[imoved_blk].ynew = y_from; + blocks_affected.moved_blocks[imoved_blk].zold = z_to; + blocks_affected.moved_blocks[imoved_blk].znew = z_from; + blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = false; + blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = false; + blocks_affected.num_moved_blocks ++; + + } // Finish swapping the blocks and setting up blocks_affected + + return (abort_swap); + +} + +static int find_affected_blocks(ClusterBlockId b_from, int x_to, int y_to, int z_to) { + + /* Finds and set ups the affected_blocks array. + * Returns abort_swap. */ + + int imacro, imember; + int x_swap_offset, y_swap_offset, z_swap_offset, x_from, y_from, z_from; + ClusterBlockId curr_b_from; + int curr_x_from, curr_y_from, curr_z_from, curr_x_to, curr_y_to, curr_z_to; + int abort_swap = false; + + auto& place_ctx = g_vpr_ctx.placement(); + auto& device_ctx = g_vpr_ctx.device(); + auto& cluster_ctx = g_vpr_ctx.clustering(); + + x_from = place_ctx.block_locs[b_from].x; + y_from = place_ctx.block_locs[b_from].y; + z_from = place_ctx.block_locs[b_from].z; + + get_imacro_from_iblk(&imacro, b_from, pl_macros, num_pl_macros); + if ( imacro != -1) { + // b_from is part of a macro, I need to swap the whole macro + + // Record down the relative position of the swap + x_swap_offset = x_to - x_from; + y_swap_offset = y_to - y_from; + z_swap_offset = z_to - z_from; + + for (imember = 0; imember < pl_macros[imacro].num_blocks && abort_swap == false; imember++) { + + // Gets the new from and to info for every block in the macro + // cannot use the old from and to info + curr_b_from = pl_macros[imacro].members[imember].blk_index; + + curr_x_from = place_ctx.block_locs[curr_b_from].x; + curr_y_from = place_ctx.block_locs[curr_b_from].y; + curr_z_from = place_ctx.block_locs[curr_b_from].z; + + curr_x_to = curr_x_from + x_swap_offset; + curr_y_to = curr_y_from + y_swap_offset; + curr_z_to = curr_z_from + z_swap_offset; + + //Make sure that the swap_to location is valid + //It must be: + // * chip, and + // * match the correct block type + // + //Note that we need to explicitly check that the types match, since the device floorplan is not + //(neccessarily) translationally invariant for an arbitrary macro + if ( curr_x_to < 1 || curr_x_to >= int(device_ctx.grid.width()) + || curr_y_to < 1 || curr_y_to >= int(device_ctx.grid.height()) + || curr_z_to < 0 + || device_ctx.grid[curr_x_to][curr_y_to].type != cluster_ctx.clb_nlist.block_type(curr_b_from)) { + abort_swap = true; + } else { + abort_swap = setup_blocks_affected(curr_b_from, curr_x_to, curr_y_to, curr_z_to); + } + } // Finish going through all the blocks in the macro + + } else { + // This is not a macro - I could use the from and to info from before + abort_swap = setup_blocks_affected(b_from, x_to, y_to, z_to); + + } // Finish handling cases for blocks in macro and otherwise + + return (abort_swap); + +} + +static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, + float rlim, + enum e_place_algorithm place_algorithm, float timing_tradeoff, + float inverse_prev_bb_cost, float inverse_prev_timing_cost, + float *delay_cost) { + + /* Picks some block and moves it to another spot. If this spot is * + * occupied, switch the blocks. Assess the change in cost function. * + * rlim is the range limiter. * + * Returns whether the swap is accepted, rejected or aborted. * + * Passes back the new value of the cost functions. */ + + auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& place_ctx = g_vpr_ctx.mutable_placement(); + + num_ts_called ++; + + /* I'm using negative values of temp_net_cost as a flag, so DO NOT * + * use cost functions that can go negative. */ + + float delta_c = 0; /* Change in cost due to this swap. */ + float bb_delta_c = 0; + float timing_delta_c = 0; + float delay_delta_c = 0.0; + + /* Pick a random block to be swapped with another random block. */ + ClusterBlockId b_from = pick_from_block(); + if (!b_from) { + return ABORTED; //No movable block found + } + + int x_from = place_ctx.block_locs[b_from].x; + int y_from = place_ctx.block_locs[b_from].y; + int z_from = place_ctx.block_locs[b_from].z; + + int x_to = OPEN; + int y_to = OPEN; + int z_to = OPEN; + + auto cluster_from_type = cluster_ctx.clb_nlist.block_type(b_from); + auto grid_from_type = g_vpr_ctx.device().grid[x_from][y_from].type; + VTR_ASSERT(cluster_from_type == grid_from_type); + + if (!find_to(cluster_ctx.clb_nlist.block_type(b_from), rlim, x_from, y_from, &x_to, &y_to, &z_to)) + return REJECTED; + +#if 0 + auto& grid = g_vpr_ctx.device().grid; + int b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; + vtr::printf_info( "swap [%d][%d][%d] %s \"%s\" <=> [%d][%d][%d] %s \"%s\"\n", + x_from, y_from, z_from, grid[x_from][y_from].type->name, (b_from != -1 ? cluster_ctx.blocks[b_from].name : ""), + x_to, y_to, z_to, grid[x_to][y_to].type->name, (b_to != -1 ? cluster_ctx.blocks[b_to].name : "")); +#endif + + /* Make the switch in order to make computing the new bounding * + * box simpler. If the cost increase is too high, switch them * + * back. (place_ctx.block_locs data structures switched, clbs not switched * + * until success of move is determined.) * + * Also check that whether those are the only 2 blocks * + * to be moved - check for carry chains and other placement * + * macros. */ + + /* Check whether the from_block is part of a macro first. * + * If it is, the whole macro has to be moved. Calculate the * + * x, y, z offsets of the swap to maintain relative placements * + * of the blocks. Abort the swap if the to_block is part of a * + * macro (not supported yet). */ + + bool abort_swap = find_affected_blocks(b_from, x_to, y_to, z_to); + + if (abort_swap == false) { + + // Find all the nets affected by this swap and update thier bounding box + int num_nets_affected = find_affected_nets_and_update_costs(place_algorithm, bb_delta_c, timing_delta_c, delay_delta_c); + + if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + /*in this case we redefine delta_c as a combination of timing and bb. * + *additionally, we normalize all values, therefore delta_c is in * + *relation to 1*/ + + delta_c = (1 - timing_tradeoff) * bb_delta_c * inverse_prev_bb_cost + + timing_tradeoff * timing_delta_c * inverse_prev_timing_cost; + } else { + delta_c = bb_delta_c; + } + + /* 1 -> move accepted, 0 -> rejected. */ + e_swap_result keep_switch = assess_swap(delta_c, t); + + if (keep_switch == ACCEPTED) { + *cost = *cost + delta_c; + *bb_cost = *bb_cost + bb_delta_c; + + if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + /*update the point_to_point_timing_cost and point_to_point_delay_cost + * values from the temporary values */ + *timing_cost = *timing_cost + timing_delta_c; + *delay_cost = *delay_cost + delay_delta_c; + + update_td_cost(); + } + + /* update net cost functions and reset flags. */ + for (int inet_affected = 0; inet_affected < num_nets_affected; inet_affected++) { + ClusterNetId net_id = ts_nets_to_update[inet_affected]; + + bb_coords[net_id] = ts_bb_coord_new[net_id]; + if (cluster_ctx.clb_nlist.net_sinks(net_id).size() >= SMALL_NET) + bb_num_on_edges[net_id] = ts_bb_edge_new[net_id]; + + net_cost[net_id] = temp_net_cost[net_id]; + + /* negative temp_net_cost value is acting as a flag. */ + temp_net_cost[net_id] = -1; + bb_updated_before[net_id] = NOT_UPDATED_YET; + } + + /* Update clb data structures since we kept the move. */ + /* Swap physical location */ + for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { + + x_to = blocks_affected.moved_blocks[iblk].xnew; + y_to = blocks_affected.moved_blocks[iblk].ynew; + z_to = blocks_affected.moved_blocks[iblk].znew; + + x_from = blocks_affected.moved_blocks[iblk].xold; + y_from = blocks_affected.moved_blocks[iblk].yold; + z_from = blocks_affected.moved_blocks[iblk].zold; + + b_from = blocks_affected.moved_blocks[iblk].block_num; + + place_ctx.grid_blocks[x_to][y_to].blocks[z_to] = b_from; + + if (blocks_affected.moved_blocks[iblk].swapped_to_was_empty) { + place_ctx.grid_blocks[x_to][y_to].usage++; + } + if (blocks_affected.moved_blocks[iblk].swapped_from_is_empty) { + place_ctx.grid_blocks[x_from][y_from].usage--; + place_ctx.grid_blocks[x_from][y_from].blocks[z_from] = EMPTY_BLOCK_ID; + } + + } // Finish updating clb for all blocks + + } else { /* Move was rejected. */ + + /* Reset the net cost function flags first. */ + for (int inet_affected = 0; inet_affected < num_nets_affected; inet_affected++) { + ClusterNetId net_id = ts_nets_to_update[inet_affected]; + temp_net_cost[net_id] = -1; + bb_updated_before[net_id] = NOT_UPDATED_YET; + } + + /* Restore the place_ctx.block_locs data structures to their state before the move. */ + for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { + b_from = blocks_affected.moved_blocks[iblk].block_num; + + place_ctx.block_locs[b_from].x = blocks_affected.moved_blocks[iblk].xold; + place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; + place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; + } + } + + /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ + blocks_affected.num_moved_blocks = 0; + +#if 0 + //Check that each accepted swap yields a valid placement + check_place(*bb_cost, *timing_cost, place_algorithm, *delay_cost); +#endif + + return (keep_switch); + } else { + + /* Restore the place_ctx.block_locs data structures to their state before the move. */ + for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { + b_from = blocks_affected.moved_blocks[iblk].block_num; + + place_ctx.block_locs[b_from].x = blocks_affected.moved_blocks[iblk].xold; + place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; + place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; + } + + /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ + blocks_affected.num_moved_blocks = 0; + + return ABORTED; + } +} + +//Pick a random block to be swapped with another random block. +//If none is found return ClusterBlockId::INVALID() +static ClusterBlockId pick_from_block() { + /* Some blocks may be fixed, and should never be moved from their * + * initial positions. If we randomly selected such a block try * + * another random block. * + * * + * We need to track the blocks we have tried to avoid an infinite * + * loop if all blocks are fixed. */ + auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& place_ctx = g_vpr_ctx.mutable_placement(); + + std::unordered_set tried_from_blocks; + + //So long as untried blocks remain + while (tried_from_blocks.size() < cluster_ctx.clb_nlist.blocks().size()) { + + //Pick a block at random + ClusterBlockId b_from = ClusterBlockId(vtr::irand((int) cluster_ctx.clb_nlist.blocks().size() - 1)); + + //Record it as tried + tried_from_blocks.insert(b_from); + + if (place_ctx.block_locs[b_from].is_fixed) { + continue; //Fixed location, try again + } + + //Found a movable block + return b_from; + } + + //No movable blocks found + return ClusterBlockId::INVALID(); +} + +//Puts all the nets changed by the current swap into nets_to_update, +//and updates their bounding box. +// +//Returns the number of affected nets. +static int find_affected_nets_and_update_costs(e_place_algorithm place_algorithm, float& bb_delta_c, float& timing_delta_c, float& delay_delta_c) { + VTR_ASSERT_SAFE(bb_delta_c == 0.); + VTR_ASSERT_SAFE(timing_delta_c == 0.); + VTR_ASSERT_SAFE(delay_delta_c == 0.); + auto& cluster_ctx = g_vpr_ctx.clustering(); + + int num_affected_nets = 0; + + //Go through all the blocks moved + for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { + ClusterBlockId blk = blocks_affected.moved_blocks[iblk].block_num; + + //Go through all the pins in the moved block + for (ClusterPinId blk_pin : cluster_ctx.clb_nlist.block_pins(blk)) { + ClusterNetId net_id = cluster_ctx.clb_nlist.pin_net(blk_pin); + VTR_ASSERT_SAFE_MSG(net_id, "Only valid nets should be found in compressed netlist block pins"); + + if (cluster_ctx.clb_nlist.net_is_global(net_id)) + continue; //Global nets are assumed to span the whole chip, and do not effect costs + + //Record effected nets + record_affected_net(net_id, num_affected_nets); + + //Update the net bounding boxes + // + //Do not update the net cost here since it should only be updated + //once per net, not once per pin. + update_net_bb(net_id, iblk, blk, blk_pin); + + if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + //Determine the change in timing costs if required + update_td_delta_costs(net_id, blk_pin, timing_delta_c, delay_delta_c); + } + } + } + + /* Now update the bounding box costs (since the net bounding boxes are up-to-date). + * The cost is only updated once per net. + */ + for (int inet_affected = 0; inet_affected < num_affected_nets; inet_affected++) { + ClusterNetId net_id = ts_nets_to_update[inet_affected]; + + temp_net_cost[net_id] = get_net_cost(net_id, &ts_bb_coord_new[net_id]); + bb_delta_c += temp_net_cost[net_id] - net_cost[net_id]; + } + + return num_affected_nets; +} + +static void record_affected_net(const ClusterNetId net, int& num_affected_nets) { + //Record effected nets + if (temp_net_cost[net] < 0.) { + //Net not marked yet. + ts_nets_to_update[num_affected_nets] = net; + num_affected_nets++; + + //Flag to say we've marked this net. + temp_net_cost[net] = 1.; + } +} + +static void update_net_bb(const ClusterNetId net, int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin) { + auto& cluster_ctx = g_vpr_ctx.clustering(); + + if (cluster_ctx.clb_nlist.net_sinks(net).size() < SMALL_NET) { + //For small nets brute-force bounding box update is faster + + if(bb_updated_before[net] == NOT_UPDATED_YET) { //Only once per-net + get_non_updateable_bb(net, &ts_bb_coord_new[net]); + } + } else { + //For large nets, update bounding box incrementally + int iblk_pin = cluster_ctx.clb_nlist.pin_physical_index(blk_pin); + + t_type_ptr blk_type = cluster_ctx.clb_nlist.block_type(blk); + int pin_width_offset = blk_type->pin_width_offset[iblk_pin]; + int pin_height_offset = blk_type->pin_height_offset[iblk_pin]; + + //Incremental bounding box update + update_bb(net, &ts_bb_coord_new[net], + &ts_bb_edge_new[net], + blocks_affected.moved_blocks[iblk].xold + pin_width_offset, + blocks_affected.moved_blocks[iblk].yold + pin_height_offset, + blocks_affected.moved_blocks[iblk].xnew + pin_width_offset, + blocks_affected.moved_blocks[iblk].ynew + pin_height_offset); + } + +} + +static void update_td_delta_costs(const ClusterNetId net, const ClusterPinId pin, float& delta_timing_cost, float& delta_delay_cost) { + auto& cluster_ctx = g_vpr_ctx.clustering(); + + if (cluster_ctx.clb_nlist.pin_type(pin) == PinType::DRIVER) { + //This pin is a net driver on a moved block. + //Re-compute all point to point connections for this net. + for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net).size(); ipin++) { + float temp_delay = comp_td_point_to_point_delay(net, ipin); + temp_point_to_point_delay_cost[net][ipin] = temp_delay; + + temp_point_to_point_timing_cost[net][ipin] = get_timing_place_crit(net, ipin) * temp_delay; + delta_timing_cost += temp_point_to_point_timing_cost[net][ipin] - point_to_point_timing_cost[net][ipin]; + delta_delay_cost += temp_point_to_point_delay_cost[net][ipin] - point_to_point_delay_cost[net][ipin]; + + } + } else { + //This pin is a net sink on a moved block + VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.pin_type(pin) == PinType::SINK); + + //If this net is being driven by a moved block, we do not + //need to compute the change in the timing cost (here) since it will + //be computed by the net's driver pin (since the driver block moved). + // + //Computing it here would double count the change, and mess up the + //delta_timing_cost value. + if (!driven_by_moved_block(net)) { + int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin); + + float temp_delay = comp_td_point_to_point_delay(net, net_pin); + temp_point_to_point_delay_cost[net][net_pin] = temp_delay; + + temp_point_to_point_timing_cost[net][net_pin] = get_timing_place_crit(net, net_pin) * temp_delay; + delta_timing_cost += temp_point_to_point_timing_cost[net][net_pin] - point_to_point_timing_cost[net][net_pin]; + delta_delay_cost += temp_point_to_point_delay_cost[net][net_pin] - point_to_point_delay_cost[net][net_pin]; + } + } +} + +static bool find_to(t_type_ptr type, float rlim, + int x_from, int y_from, + int *px_to, int *py_to, int *pz_to) { + + /* Returns the point to which I want to swap, properly range limited. + * rlim must always be between 1 and device_ctx.grid.width() - 2 (inclusive) for this routine + * to work. Note -2 for no perim channels + */ + + int min_x, max_x, min_y, max_y; + int num_tries; + int active_area; + bool is_legal; + int itype; + + auto& grid = g_vpr_ctx.device().grid; + auto& place_ctx = g_vpr_ctx.placement(); + + auto grid_type = grid[x_from][y_from].type; + VTR_ASSERT(type == grid_type); + + int rlx = min(grid.width() - 1, rlim); + int rly = min(grid.height() - 1, rlim); /* Added rly for aspect_ratio != 1 case. */ + active_area = 4 * rlx * rly; + + min_x = max(0, x_from - rlx); + max_x = min(grid.width() - 1, x_from + rlx); + min_y = max(0, y_from - rly); + max_y = min(grid.height() - 1, y_from + rly); + + if (rlx < 1 || rlx > int(grid.width() - 1)) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in find_to: rlx = %d out of range\n", rlx); + } + if (rly < 1 || rly > int(grid.height() - 1)) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in find_to: rly = %d out of range\n", rly); + } + + num_tries = 0; + itype = type->index; + + do { /* Until legal */ + is_legal = true; + + /* Limit the number of tries when searching for an alternative position */ + if(num_tries >= 2 * min(active_area / (type->width * type->height), num_legal_pos[itype]) + 10) { + /* Tried randomly searching for a suitable position */ + return false; + } else { + num_tries++; + } + + find_to_location(type, rlim, x_from, y_from, + px_to, py_to, pz_to); + + if((x_from == *px_to) && (y_from == *py_to)) { + is_legal = false; + } else if(*px_to > max_x || *px_to < min_x || *py_to > max_y || *py_to < min_y) { + is_legal = false; + } else if(grid[*px_to][*py_to].type != grid[x_from][y_from].type) { + is_legal = false; + } else { + /* Find z_to and test to validate that the "to" block is *not* fixed */ + *pz_to = 0; + if (grid[*px_to][*py_to].type->capacity > 1) { + *pz_to = vtr::irand(grid[*px_to][*py_to].type->capacity - 1); + } + ClusterBlockId b_to = place_ctx.grid_blocks[*px_to][*py_to].blocks[*pz_to]; + if ((b_to != EMPTY_BLOCK_ID) && (place_ctx.block_locs[b_to].is_fixed == true)) { + is_legal = false; + } + } + + VTR_ASSERT(*px_to >= 0 && *px_to < int(grid.width())); + VTR_ASSERT(*py_to >= 0 && *py_to < int(grid.height())); + } while (is_legal == false); + + if (*px_to < 0 || *px_to > int(grid.width() - 1) || *py_to < 0 || *py_to > int(grid.height() - 1)) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in routine find_to: (x_to,y_to) = (%d,%d)\n", *px_to, *py_to); + } + + VTR_ASSERT(type == grid[*px_to][*py_to].type); + return true; +} + +static void find_to_location(t_type_ptr type, float rlim, + int x_from, int y_from, + int *px_to, int *py_to, int *pz_to) { + + auto& device_ctx = g_vpr_ctx.device(); + auto& grid = device_ctx.grid; + + int itype = type->index; + + + int rlx = min(grid.width() - 1, rlim); + int rly = min(grid.height() - 1, rlim); /* Added rly for aspect_ratio != 1 case. */ + int active_area = 4 * rlx * rly; + + int min_x = max(0, x_from - rlx); + int max_x = min(grid.width() - 1, x_from + rlx); + int min_y = max(0, y_from - rly); + int max_y = min(grid.height() - 1, y_from + rly); + + *pz_to = 0; + if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || num_legal_pos[itype] < active_area) { + int ipos = vtr::irand(num_legal_pos[itype] - 1); + *px_to = legal_pos[itype][ipos].x; + *py_to = legal_pos[itype][ipos].y; + *pz_to = legal_pos[itype][ipos].z; + } else { + int x_rel = vtr::irand(max(0, max_x - min_x)); + int y_rel = vtr::irand(max(0, max_y - min_y)); + *px_to = min_x + x_rel; + *py_to = min_y + y_rel; + *px_to = (*px_to) - grid[*px_to][*py_to].width_offset; /* align it */ + *py_to = (*py_to) - grid[*px_to][*py_to].height_offset; /* align it */ + } +} + +static e_swap_result assess_swap(float delta_c, float t) { + + /* Returns: 1 -> move accepted, 0 -> rejected. */ + + e_swap_result accept; + float prob_fac, fnum; + + if (delta_c <= 0) { + + /* Reduce variation in final solution due to round off */ + fnum = vtr::frand(); + + accept = ACCEPTED; + return (accept); + } + + if (t == 0.) + return (REJECTED); + + fnum = vtr::frand(); + prob_fac = exp(-delta_c / t); + if (prob_fac > fnum) { + accept = ACCEPTED; + } + else { + accept = REJECTED; + } + return (accept); +} + +static float recompute_bb_cost() { + /* Recomputes the cost to eliminate roundoff that may have accrued. * + * This routine does as little work as possible to compute this new * + * cost. */ + + float cost = 0; + + auto& cluster_ctx = g_vpr_ctx.clustering(); + + for (auto net_id : cluster_ctx.clb_nlist.nets()) { /* for each net ... */ + if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ + /* Bounding boxes don't have to be recomputed; they're correct. */ + cost += net_cost[net_id]; + } + } + + return (cost); +} + +/*returns the delay of one point to point connection */ +static float comp_td_point_to_point_delay(ClusterNetId net_id, int ipin) { + auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& place_ctx = g_vpr_ctx.placement(); + + float delay_source_to_sink = 0.; + + if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { + //Only estimate delay for signals routed through the inter-block + //routing network. Global signals are assumed to have zero delay. + + ClusterBlockId source_block = cluster_ctx.clb_nlist.net_driver_block(net_id); + ClusterBlockId sink_block = cluster_ctx.clb_nlist.net_pin_block(net_id, ipin); + + VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.block_type(source_block) != nullptr); + VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.block_type(sink_block) != nullptr); + + int delta_x = abs(place_ctx.block_locs[sink_block].x - place_ctx.block_locs[source_block].x); + int delta_y = abs(place_ctx.block_locs[sink_block].y - place_ctx.block_locs[source_block].y); + + /* Note: This heuristic only considers delta_x and delta_y, a much better heuristic + * would be to to create a more comprehensive lookup table. + * + * In particular this aproach does not accurately capture the effect of fast + * carry-chain connections. + */ + delay_source_to_sink = get_delta_delay(delta_x, delta_y); + if (delay_source_to_sink < 0) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "in comp_td_point_to_point_delay: Bad delay_source_to_sink value delta(%d, %d) delay of %g\n" + "in comp_td_point_to_point_delay: Delay is less than 0\n", + delta_x, delta_y, delay_source_to_sink); + } + } + + + return (delay_source_to_sink); +} + +//Recompute all point to point delays, updating point_to_point_delay_cost +static void comp_td_point_to_point_delays() { + auto& cluster_ctx = g_vpr_ctx.clustering(); + + for (auto net_id : cluster_ctx.clb_nlist.nets()) { + for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ++ipin) { + point_to_point_delay_cost[net_id][ipin] = comp_td_point_to_point_delay(net_id, ipin); + } + } +} + +/* Update the point_to_point_timing_cost values from the temporary * +* values for all connections that have changed. */ +static void update_td_cost() { + auto& cluster_ctx = g_vpr_ctx.clustering(); + + /* Go through all the blocks moved. */ + for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { + ClusterBlockId bnum = blocks_affected.moved_blocks[iblk].block_num; + for (ClusterPinId pin_id : cluster_ctx.clb_nlist.block_pins(bnum)) { + ClusterNetId net_id = cluster_ctx.clb_nlist.pin_net(pin_id); + + if (cluster_ctx.clb_nlist.net_is_global(net_id)) + continue; + + if (cluster_ctx.clb_nlist.pin_type(pin_id) == PinType::DRIVER) { + //This net is being driven by a moved block, recompute + //all point to point connections on this net. + for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { + point_to_point_delay_cost[net_id][ipin] = temp_point_to_point_delay_cost[net_id][ipin]; + temp_point_to_point_delay_cost[net_id][ipin] = -1; + point_to_point_timing_cost[net_id][ipin] = temp_point_to_point_timing_cost[net_id][ipin]; + temp_point_to_point_timing_cost[net_id][ipin] = -1; + } + } else { + //This pin is a net sink on a moved block + VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.pin_type(pin_id) == PinType::SINK); + + /* The following "if" prevents the value from being updated twice. */ + if (!driven_by_moved_block(net_id)) { + int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin_id); + + point_to_point_delay_cost[net_id][net_pin] = temp_point_to_point_delay_cost[net_id][net_pin]; + temp_point_to_point_delay_cost[net_id][net_pin] = -1; + point_to_point_timing_cost[net_id][net_pin] = temp_point_to_point_timing_cost[net_id][net_pin]; + temp_point_to_point_timing_cost[net_id][net_pin] = -1; + } + } + } /* Finished going through all the pins in the moved block */ + } /* Finished going through all the blocks moved */ +} + +static bool driven_by_moved_block(const ClusterNetId net) { + auto& cluster_ctx = g_vpr_ctx.clustering(); + + ClusterBlockId net_driver_block = cluster_ctx.clb_nlist.net_driver_block(net); + for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { + if (net_driver_block == blocks_affected.moved_blocks[iblk].block_num) { + return true; + } + } + return false; +} + +static void comp_td_costs(float *timing_cost, float *connection_delay_sum) { + /* Computes the cost (from scratch) due to the delays and criticalities * + * on all point to point connections, we define the timing cost of * + * each connection as criticality*delay. */ + + unsigned ipin; + float loc_timing_cost, loc_connection_delay_sum, temp_delay_cost, + temp_timing_cost; + + auto& cluster_ctx = g_vpr_ctx.clustering(); + + loc_timing_cost = 0.; + loc_connection_delay_sum = 0.; + + for (auto net_id : cluster_ctx.clb_nlist.nets()) { /* For each net ... */ + + if (cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ + continue; + } + + for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { + temp_delay_cost = comp_td_point_to_point_delay(net_id, ipin); + temp_timing_cost = temp_delay_cost * get_timing_place_crit(net_id, ipin); + + loc_connection_delay_sum += temp_delay_cost; + point_to_point_delay_cost[net_id][ipin] = temp_delay_cost; + temp_point_to_point_delay_cost[net_id][ipin] = -1; /* Undefined */ + + point_to_point_timing_cost[net_id][ipin] = temp_timing_cost; + temp_point_to_point_timing_cost[net_id][ipin] = -1; /* Undefined */ + loc_timing_cost += temp_timing_cost; + } + } + + /* Make sure timing cost does not go above MIN_TIMING_COST. */ + *timing_cost = loc_timing_cost; + + *connection_delay_sum = loc_connection_delay_sum; +} + + +/* Finds the cost from scratch. Done only when the placement * +* has been radically changed (i.e. after initial placement). * +* Otherwise find the cost change incrementally. If method * +* check is NORMAL, we find bounding boxes that are updateable * +* for the larger nets. If method is CHECK, all bounding boxes * +* are found via the non_updateable_bb routine, to provide a * +* cost which can be used to check the correctness of the * +* other routine. */ +static float comp_bb_cost(e_cost_methods method) { + float cost = 0; + double expected_wirelength = 0.0; + auto& cluster_ctx = g_vpr_ctx.clustering(); + + for (auto net_id : cluster_ctx.clb_nlist.nets()) { /* for each net ... */ + if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ + /* Small nets don't use incremental updating on their bounding boxes, * + * so they can use a fast bounding box calculator. */ + if (cluster_ctx.clb_nlist.net_sinks(net_id).size() >= SMALL_NET && method == NORMAL) { + get_bb_from_scratch(net_id, &bb_coords[net_id], + &bb_num_on_edges[net_id]); + } + else { + get_non_updateable_bb(net_id, &bb_coords[net_id]); + } + + net_cost[net_id] = get_net_cost(net_id, &bb_coords[net_id]); + cost += net_cost[net_id]; + if (method == CHECK) + expected_wirelength += get_net_wirelength_estimate(net_id, &bb_coords[net_id]); + } + } + + if (method == CHECK) { + vtr::printf_info("\n"); + vtr::printf_info("BB estimate of min-dist (placement) wire length: %.0f\n", expected_wirelength); + } + return cost; +} + + +/* Frees the major structures needed by the placer (and not needed * +* elsewhere). */ +static void free_placement_structs(t_placer_opts placer_opts) { + int imacro; + + auto& cluster_ctx = g_vpr_ctx.clustering(); + + free_legal_placements(); + free_fast_cost_update(); + + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + || placer_opts.enable_timing_computations) { + + for (auto net_id : cluster_ctx.clb_nlist.nets()) { + /*add one to the address since it is indexed from 1 not 0 */ + point_to_point_timing_cost[net_id]++; + free(point_to_point_timing_cost[net_id]); + + temp_point_to_point_timing_cost[net_id]++; + free(temp_point_to_point_timing_cost[net_id]); + + point_to_point_delay_cost[net_id]++; + free(point_to_point_delay_cost[net_id]); + + temp_point_to_point_delay_cost[net_id]++; + free(temp_point_to_point_delay_cost[net_id]); + } + + point_to_point_timing_cost.clear(); + point_to_point_delay_cost.clear(); + temp_point_to_point_timing_cost.clear(); + temp_point_to_point_delay_cost.clear(); + + net_pin_indices.clear(); + } + + free_placement_macros_structs(); + + for (imacro = 0; imacro < num_pl_macros; imacro++) + free(pl_macros[imacro].members); + free(pl_macros); + + /* Defensive coding. */ + pl_macros = nullptr; + + /* Frees up all the data structure used in vpr_utils. */ + free_port_pin_from_blk_pin(); + free_blk_pin_from_port_pin(); + +} + +/* Allocates the major structures needed only by the placer, primarily for * +* computing costs quickly and such. */ +static void alloc_and_load_placement_structs( + float place_cost_exp, t_placer_opts placer_opts, + t_direct_inf *directs, int num_directs) { + + int max_pins_per_clb, i; + unsigned int ipin; + + auto& device_ctx = g_vpr_ctx.device(); + auto& cluster_ctx = g_vpr_ctx.clustering(); + + size_t num_nets = cluster_ctx.clb_nlist.nets().size(); + + init_placement_context(); + + alloc_legal_placements(); + load_legal_placements(); + + max_pins_per_clb = 0; + for (i = 0; i < device_ctx.num_block_types; i++) { + max_pins_per_clb = max(max_pins_per_clb, device_ctx.block_types[i].num_pins); + } + + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + || placer_opts.enable_timing_computations) { + /* Allocate structures associated with timing driven placement */ + /* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1] */ + point_to_point_delay_cost.resize(num_nets); + temp_point_to_point_delay_cost.resize(num_nets); + + point_to_point_timing_cost.resize(num_nets); + temp_point_to_point_timing_cost.resize(num_nets); + + for (auto net_id : cluster_ctx.clb_nlist.nets()) { + size_t num_sinks = cluster_ctx.clb_nlist.net_sinks(net_id).size(); + /* In the following, subract one so index starts at * + * 1 instead of 0 */ + point_to_point_delay_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); + point_to_point_delay_cost[net_id]--; + + temp_point_to_point_delay_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); + temp_point_to_point_delay_cost[net_id]--; + + point_to_point_timing_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); + point_to_point_timing_cost[net_id]--; + + temp_point_to_point_timing_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); + temp_point_to_point_timing_cost[net_id]--; + } + for (auto net_id : cluster_ctx.clb_nlist.nets()) { + for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { + point_to_point_delay_cost[net_id][ipin] = 0; + temp_point_to_point_delay_cost[net_id][ipin] = 0; + } + } + } + + net_cost.resize(num_nets, -1.); + temp_net_cost.resize(num_nets, -1.); + bb_coords.resize(num_nets, t_bb()); + bb_num_on_edges.resize(num_nets, t_bb()); + + /* Used to store costs for moves not yet made and to indicate when a net's * + * cost has been recomputed. temp_net_cost[inet] < 0 means net's cost hasn't * + * been recomputed. */ + bb_updated_before.resize(num_nets, NOT_UPDATED_YET); + + alloc_and_load_for_fast_cost_update(place_cost_exp); + + alloc_and_load_net_pin_indices(); + + alloc_and_load_try_swap_structs(); + + num_pl_macros = alloc_and_load_placement_macros(directs, num_directs, &pl_macros); +} + +/* Allocates and loads net_pin_indices array, this array allows us to quickly * +* find what pin on the net a block pin corresponds to. Returns the pointer * +* to the 2D net_pin_indices array. */ +static void alloc_and_load_net_pin_indices() { + unsigned int netpin; + int itype, max_pins_per_clb = 0; + + auto& device_ctx = g_vpr_ctx.device(); + auto& cluster_ctx = g_vpr_ctx.clustering(); + + /* Compute required size. */ + for (itype = 0; itype < device_ctx.num_block_types; itype++) + max_pins_per_clb = max(max_pins_per_clb, device_ctx.block_types[itype].num_pins); + + /* Allocate for maximum size. */ + net_pin_indices.resize(cluster_ctx.clb_nlist.blocks().size()); + + for (auto blk_id : cluster_ctx.clb_nlist.blocks()) + net_pin_indices[blk_id].resize(max_pins_per_clb); + + /* Load the values */ + for (auto net_id : cluster_ctx.clb_nlist.nets()) { + if (cluster_ctx.clb_nlist.net_is_global(net_id)) + continue; + netpin = 0; + for (auto pin_id : cluster_ctx.clb_nlist.net_pins(net_id)) { + int pin_index = cluster_ctx.clb_nlist.pin_physical_index(pin_id); + ClusterBlockId block_id = cluster_ctx.clb_nlist.pin_block(pin_id); + net_pin_indices[block_id][pin_index] = netpin; + netpin++; + } + } +} + +static void alloc_and_load_try_swap_structs() { + /* Allocate the local bb_coordinate storage, etc. only once. */ + /* Allocate with size cluster_ctx.clb_nlist.nets().size() for any number of nets affected. */ + auto& cluster_ctx = g_vpr_ctx.clustering(); + + size_t num_nets = cluster_ctx.clb_nlist.nets().size(); + + ts_bb_coord_new.resize(num_nets, t_bb()); + ts_bb_edge_new.resize(num_nets, t_bb()); + ts_nets_to_update.resize(num_nets, ClusterNetId::INVALID()); + + /* Allocate with size cluster_ctx.clb_nlist.blocks().size() for any number of moved blocks. */ + blocks_affected.moved_blocks = (t_pl_moved_block*) vtr::calloc((int) cluster_ctx.clb_nlist.blocks().size(), sizeof(t_pl_moved_block)); + blocks_affected.num_moved_blocks = 0; + +} + +/* This routine finds the bounding box of each net from scratch (i.e. * +* from only the block location information). It updates both the * +* coordinate and number of pins on each edge information. It * +* should only be called when the bounding box information is not valid. */ +static void get_bb_from_scratch(ClusterNetId net_id, t_bb *coords, + t_bb *num_on_edges) { + int pnum, x, y, xmin, xmax, ymin, ymax; + int xmin_edge, xmax_edge, ymin_edge, ymax_edge; + + auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& place_ctx = g_vpr_ctx.placement(); + auto& device_ctx = g_vpr_ctx.device(); + auto& grid = device_ctx.grid; + + ClusterBlockId bnum = cluster_ctx.clb_nlist.net_driver_block(net_id); + pnum = cluster_ctx.clb_nlist.net_pin_physical_index(net_id, 0); + x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; + y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; + + x = max(min(x, grid.width() - 2), 1); + y = max(min(y, grid.height() - 2), 1); + + xmin = x; + ymin = y; + xmax = x; + ymax = y; + xmin_edge = 1; + ymin_edge = 1; + xmax_edge = 1; + ymax_edge = 1; + + for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) { + bnum = cluster_ctx.clb_nlist.pin_block(pin_id); + pnum = cluster_ctx.clb_nlist.pin_physical_index(pin_id); + x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; + y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; + + /* Code below counts IO blocks as being within the 1..grid.width()-2, 1..grid.height()-2 clb array. * + * This is because channels do not go out of the 0..grid.width()-2, 0..grid.height()-2 range, and * + * I always take all channels impinging on the bounding box to be within * + * that bounding box. Hence, this "movement" of IO blocks does not affect * + * the which channels are included within the bounding box, and it * + * simplifies the code a lot. */ + + x = max(min(x, grid.width() - 2), 1); //-2 for no perim channels + y = max(min(y, grid.height() - 2), 1); //-2 for no perim channels + + if (x == xmin) { + xmin_edge++; + } + if (x == xmax) { /* Recall that xmin could equal xmax -- don't use else */ + xmax_edge++; + } + else if (x < xmin) { + xmin = x; + xmin_edge = 1; + } + else if (x > xmax) { + xmax = x; + xmax_edge = 1; + } + + if (y == ymin) { + ymin_edge++; + } + if (y == ymax) { + ymax_edge++; + } + else if (y < ymin) { + ymin = y; + ymin_edge = 1; + } + else if (y > ymax) { + ymax = y; + ymax_edge = 1; + } + } + + /* Copy the coordinates and number on edges information into the proper * + * structures. */ + coords->xmin = xmin; + coords->xmax = xmax; + coords->ymin = ymin; + coords->ymax = ymax; + + num_on_edges->xmin = xmin_edge; + num_on_edges->xmax = xmax_edge; + num_on_edges->ymin = ymin_edge; + num_on_edges->ymax = ymax_edge; +} + +static double get_net_wirelength_estimate(ClusterNetId net_id, t_bb *bbptr) { + + /* WMF: Finds the estimate of wirelength due to one net by looking at * + * its coordinate bounding box. */ + + double ncost, crossing; + auto& cluster_ctx = g_vpr_ctx.clustering(); + + /* Get the expected "crossing count" of a net, based on its number * + * of pins. Extrapolate for very large nets. */ + + if (((cluster_ctx.clb_nlist.net_pins(net_id).size()) > 50) + && ((cluster_ctx.clb_nlist.net_pins(net_id).size()) < 85)) { + crossing = 2.7933 + 0.02616 * ((cluster_ctx.clb_nlist.net_pins(net_id).size()) - 50); + } else if ((cluster_ctx.clb_nlist.net_pins(net_id).size()) >= 85) { + crossing = 2.7933 + 0.011 * (cluster_ctx.clb_nlist.net_pins(net_id).size()) + - 0.0000018 * (cluster_ctx.clb_nlist.net_pins(net_id).size()) + * (cluster_ctx.clb_nlist.net_pins(net_id).size()); + } else { + crossing = cross_count[cluster_ctx.clb_nlist.net_pins(net_id).size() - 1]; + } + + /* Could insert a check for xmin == xmax. In that case, assume * + * connection will be made with no bends and hence no x-cost. * + * Same thing for y-cost. */ + + /* Cost = wire length along channel * cross_count / average * + * channel capacity. Do this for x, then y direction and add. */ + + ncost = (bbptr->xmax - bbptr->xmin + 1) * crossing; + + ncost += (bbptr->ymax - bbptr->ymin + 1) * crossing; + + return (ncost); +} + +static float get_net_cost(ClusterNetId net_id, t_bb *bbptr) { + + /* Finds the cost due to one net by looking at its coordinate bounding * + * box. */ + + float ncost, crossing; + auto& cluster_ctx = g_vpr_ctx.clustering(); + + /* Get the expected "crossing count" of a net, based on its number * + * of pins. Extrapolate for very large nets. */ + + if ((cluster_ctx.clb_nlist.net_pins(net_id).size()) > 50) { + crossing = 2.7933 + 0.02616 * ((cluster_ctx.clb_nlist.net_pins(net_id).size()) - 50); + /* crossing = 3.0; Old value */ + } else { + crossing = cross_count[(cluster_ctx.clb_nlist.net_pins(net_id).size()) - 1]; + } + + /* Could insert a check for xmin == xmax. In that case, assume * + * connection will be made with no bends and hence no x-cost. * + * Same thing for y-cost. */ + + /* Cost = wire length along channel * cross_count / average * + * channel capacity. Do this for x, then y direction and add. */ + + ncost = (bbptr->xmax - bbptr->xmin + 1) * crossing + * chanx_place_cost_fac[bbptr->ymax][bbptr->ymin - 1]; + + ncost += (bbptr->ymax - bbptr->ymin + 1) * crossing + * chany_place_cost_fac[bbptr->xmax][bbptr->xmin - 1]; + + return (ncost); +} + +/* Finds the bounding box of a net and stores its coordinates in the * +* bb_coord_new data structure. This routine should only be called * +* for small nets, since it does not determine enough information for * +* the bounding box to be updated incrementally later. * +* Currently assumes channels on both sides of the CLBs forming the * +* edges of the bounding box can be used. Essentially, I am assuming * +* the pins always lie on the outside of the bounding box. */ +static void get_non_updateable_bb(ClusterNetId net_id, t_bb *bb_coord_new) { + //TODO: account for multiple physical pin instances per logical pin + + int xmax, ymax, xmin, ymin, x, y; + int pnum; + + auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& place_ctx = g_vpr_ctx.placement(); + auto& device_ctx = g_vpr_ctx.device(); + + ClusterBlockId bnum = cluster_ctx.clb_nlist.net_driver_block(net_id); + pnum = cluster_ctx.clb_nlist.net_pin_physical_index(net_id, 0); + x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; + y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; + + xmin = x; + ymin = y; + xmax = x; + ymax = y; + + for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) { + bnum = cluster_ctx.clb_nlist.pin_block(pin_id); + pnum = cluster_ctx.clb_nlist.pin_physical_index(pin_id); + x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; + y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; + + if (x < xmin) { + xmin = x; + } else if (x > xmax) { + xmax = x; + } + + if (y < ymin) { + ymin = y; + } else if (y > ymax) { + ymax = y; + } + } + + /* Now I've found the coordinates of the bounding box. There are no * + * channels beyond device_ctx.grid.width()-2 and * + * device_ctx.grid.height() - 2, so I want to clip to that. As well,* + * since I'll always include the channel immediately below and the * + * channel immediately to the left of the bounding box, I want to * + * clip to 1 in both directions as well (since minimum channel index * + * is 0). See route_common.cpp for a channel diagram. */ + + bb_coord_new->xmin = max(min(xmin, device_ctx.grid.width() - 2), 1); //-2 for no perim channels + bb_coord_new->ymin = max(min(ymin, device_ctx.grid.height() - 2), 1); //-2 for no perim channels + bb_coord_new->xmax = max(min(xmax, device_ctx.grid.width() - 2), 1); //-2 for no perim channels + bb_coord_new->ymax = max(min(ymax, device_ctx.grid.height() - 2), 1); //-2 for no perim channels +} + +static void update_bb(ClusterNetId net_id, t_bb *bb_coord_new, + t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew) { + + /* Updates the bounding box of a net by storing its coordinates in * + * the bb_coord_new data structure and the number of blocks on each * + * edge in the bb_edge_new data structure. This routine should only * + * be called for large nets, since it has some overhead relative to * + * just doing a brute force bounding box calculation. The bounding * + * box coordinate and edge information for inet must be valid before * + * this routine is called. * + * Currently assumes channels on both sides of the CLBs forming the * + * edges of the bounding box can be used. Essentially, I am assuming * + * the pins always lie on the outside of the bounding box. * + * The x and y coordinates are the pin's x and y coordinates. */ + /* IO blocks are considered to be one cell in for simplicity. */ + //TODO: account for multiple physical pin instances per logical pin + + t_bb *curr_bb_edge, *curr_bb_coord; + + auto& device_ctx = g_vpr_ctx.device(); + + xnew = max(min(xnew, device_ctx.grid.width() - 2), 1); //-2 for no perim channels + ynew = max(min(ynew, device_ctx.grid.height() - 2), 1); //-2 for no perim channels + xold = max(min(xold, device_ctx.grid.width() - 2), 1); //-2 for no perim channels + yold = max(min(yold, device_ctx.grid.height() - 2), 1); //-2 for no perim channels + + /* Check if the net had been updated before. */ + if (bb_updated_before[net_id] == GOT_FROM_SCRATCH) { + /* The net had been updated from scratch, DO NOT update again! */ + return; + } else if (bb_updated_before[net_id] == NOT_UPDATED_YET) { + /* The net had NOT been updated before, could use the old values */ + curr_bb_coord = &bb_coords[net_id]; + curr_bb_edge = &bb_num_on_edges[net_id]; + bb_updated_before[net_id] = UPDATED_ONCE; + } else { + /* The net had been updated before, must use the new values */ + curr_bb_coord = bb_coord_new; + curr_bb_edge = bb_edge_new; + } + + /* Check if I can update the bounding box incrementally. */ + + if (xnew < xold) { /* Move to left. */ + + /* Update the xmax fields for coordinates and number of edges first. */ + + if (xold == curr_bb_coord->xmax) { /* Old position at xmax. */ + if (curr_bb_edge->xmax == 1) { + get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); + bb_updated_before[net_id] = GOT_FROM_SCRATCH; + return; + } else { + bb_edge_new->xmax = curr_bb_edge->xmax - 1; + bb_coord_new->xmax = curr_bb_coord->xmax; + } + } else { /* Move to left, old postion was not at xmax. */ + bb_coord_new->xmax = curr_bb_coord->xmax; + bb_edge_new->xmax = curr_bb_edge->xmax; + } + + /* Now do the xmin fields for coordinates and number of edges. */ + + if (xnew < curr_bb_coord->xmin) { /* Moved past xmin */ + bb_coord_new->xmin = xnew; + bb_edge_new->xmin = 1; + } else if (xnew == curr_bb_coord->xmin) { /* Moved to xmin */ + bb_coord_new->xmin = xnew; + bb_edge_new->xmin = curr_bb_edge->xmin + 1; + } else { /* Xmin unchanged. */ + bb_coord_new->xmin = curr_bb_coord->xmin; + bb_edge_new->xmin = curr_bb_edge->xmin; + } + /* End of move to left case. */ + + } else if (xnew > xold) { /* Move to right. */ + + /* Update the xmin fields for coordinates and number of edges first. */ + + if (xold == curr_bb_coord->xmin) { /* Old position at xmin. */ + if (curr_bb_edge->xmin == 1) { + get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); + bb_updated_before[net_id] = GOT_FROM_SCRATCH; + return; + } else { + bb_edge_new->xmin = curr_bb_edge->xmin - 1; + bb_coord_new->xmin = curr_bb_coord->xmin; + } + } else { /* Move to right, old position was not at xmin. */ + bb_coord_new->xmin = curr_bb_coord->xmin; + bb_edge_new->xmin = curr_bb_edge->xmin; + } + + /* Now do the xmax fields for coordinates and number of edges. */ + + if (xnew > curr_bb_coord->xmax) { /* Moved past xmax. */ + bb_coord_new->xmax = xnew; + bb_edge_new->xmax = 1; + } else if (xnew == curr_bb_coord->xmax) { /* Moved to xmax */ + bb_coord_new->xmax = xnew; + bb_edge_new->xmax = curr_bb_edge->xmax + 1; + } else { /* Xmax unchanged. */ + bb_coord_new->xmax = curr_bb_coord->xmax; + bb_edge_new->xmax = curr_bb_edge->xmax; + } + /* End of move to right case. */ + + } else { /* xnew == xold -- no x motion. */ + bb_coord_new->xmin = curr_bb_coord->xmin; + bb_coord_new->xmax = curr_bb_coord->xmax; + bb_edge_new->xmin = curr_bb_edge->xmin; + bb_edge_new->xmax = curr_bb_edge->xmax; + } + + /* Now account for the y-direction motion. */ + + if (ynew < yold) { /* Move down. */ + + /* Update the ymax fields for coordinates and number of edges first. */ + + if (yold == curr_bb_coord->ymax) { /* Old position at ymax. */ + if (curr_bb_edge->ymax == 1) { + get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); + bb_updated_before[net_id] = GOT_FROM_SCRATCH; + return; + } else { + bb_edge_new->ymax = curr_bb_edge->ymax - 1; + bb_coord_new->ymax = curr_bb_coord->ymax; + } + } else { /* Move down, old postion was not at ymax. */ + bb_coord_new->ymax = curr_bb_coord->ymax; + bb_edge_new->ymax = curr_bb_edge->ymax; + } + + /* Now do the ymin fields for coordinates and number of edges. */ + + if (ynew < curr_bb_coord->ymin) { /* Moved past ymin */ + bb_coord_new->ymin = ynew; + bb_edge_new->ymin = 1; + } else if (ynew == curr_bb_coord->ymin) { /* Moved to ymin */ + bb_coord_new->ymin = ynew; + bb_edge_new->ymin = curr_bb_edge->ymin + 1; + } else { /* ymin unchanged. */ + bb_coord_new->ymin = curr_bb_coord->ymin; + bb_edge_new->ymin = curr_bb_edge->ymin; + } + /* End of move down case. */ + + } else if (ynew > yold) { /* Moved up. */ + + /* Update the ymin fields for coordinates and number of edges first. */ + + if (yold == curr_bb_coord->ymin) { /* Old position at ymin. */ + if (curr_bb_edge->ymin == 1) { + get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); + bb_updated_before[net_id] = GOT_FROM_SCRATCH; + return; + } else { + bb_edge_new->ymin = curr_bb_edge->ymin - 1; + bb_coord_new->ymin = curr_bb_coord->ymin; + } + } else { /* Moved up, old position was not at ymin. */ + bb_coord_new->ymin = curr_bb_coord->ymin; + bb_edge_new->ymin = curr_bb_edge->ymin; + } + + /* Now do the ymax fields for coordinates and number of edges. */ + + if (ynew > curr_bb_coord->ymax) { /* Moved past ymax. */ + bb_coord_new->ymax = ynew; + bb_edge_new->ymax = 1; + } else if (ynew == curr_bb_coord->ymax) { /* Moved to ymax */ + bb_coord_new->ymax = ynew; + bb_edge_new->ymax = curr_bb_edge->ymax + 1; + } else { /* ymax unchanged. */ + bb_coord_new->ymax = curr_bb_coord->ymax; + bb_edge_new->ymax = curr_bb_edge->ymax; + } + /* End of move up case. */ + + } else { /* ynew == yold -- no y motion. */ + bb_coord_new->ymin = curr_bb_coord->ymin; + bb_coord_new->ymax = curr_bb_coord->ymax; + bb_edge_new->ymin = curr_bb_edge->ymin; + bb_edge_new->ymax = curr_bb_edge->ymax; + } + + if (bb_updated_before[net_id] == NOT_UPDATED_YET) { + bb_updated_before[net_id] = UPDATED_ONCE; + } +} + +static void alloc_legal_placements() { + auto& device_ctx = g_vpr_ctx.device(); + auto& place_ctx = g_vpr_ctx.mutable_placement(); + + legal_pos = (t_legal_pos **) vtr::malloc(device_ctx.num_block_types * sizeof(t_legal_pos *)); + num_legal_pos = (int *) vtr::calloc(device_ctx.num_block_types, sizeof(int)); + + /* Initialize all occupancy to zero. */ + + for (size_t i = 0; i < device_ctx.grid.width(); i++) { + for (size_t j = 0; j < device_ctx.grid.height(); j++) { + place_ctx.grid_blocks[i][j].usage = 0; + + for (int k = 0; k < device_ctx.grid[i][j].type->capacity; k++) { + + if (place_ctx.grid_blocks[i][j].blocks[k] != INVALID_BLOCK_ID) { + place_ctx.grid_blocks[i][j].blocks[k] = EMPTY_BLOCK_ID; + if (device_ctx.grid[i][j].width_offset == 0 && device_ctx.grid[i][j].height_offset == 0) { + num_legal_pos[device_ctx.grid[i][j].type->index]++; + } + } + } + } + } + + for (int i = 0; i < device_ctx.num_block_types; i++) { + legal_pos[i] = (t_legal_pos *) vtr::malloc(num_legal_pos[i] * sizeof(t_legal_pos)); + } +} + +static void load_legal_placements() { + auto& device_ctx = g_vpr_ctx.device(); + auto& place_ctx = g_vpr_ctx.placement(); + + int* index = (int *) vtr::calloc(device_ctx.num_block_types, sizeof(int)); + + for (size_t i = 0; i < device_ctx.grid.width(); i++) { + for (size_t j = 0; j < device_ctx.grid.height(); j++) { + for (int k = 0; k < device_ctx.grid[i][j].type->capacity; k++) { + if (place_ctx.grid_blocks[i][j].blocks[k] == INVALID_BLOCK_ID) { + continue; + } + if (device_ctx.grid[i][j].width_offset == 0 && device_ctx.grid[i][j].height_offset == 0) { + int itype = device_ctx.grid[i][j].type->index; + legal_pos[itype][index[itype]].x = i; + legal_pos[itype][index[itype]].y = j; + legal_pos[itype][index[itype]].z = k; + index[itype]++; + } + } + } + } + free(index); +} + +static void free_legal_placements() { + auto& device_ctx = g_vpr_ctx.device(); + + for (int i = 0; i < device_ctx.num_block_types; i++) { + free(legal_pos[i]); + } + free(legal_pos); /* Free the mapping list */ + free(num_legal_pos); +} + + + +static int check_macro_can_be_placed(int imacro, int itype, int x, int y, int z) { + + int imember; + size_t member_x, member_y, member_z; + + auto& device_ctx = g_vpr_ctx.device(); + auto& place_ctx = g_vpr_ctx.placement(); + + // Every macro can be placed until proven otherwise + int macro_can_be_placed = true; + + // Check whether all the members can be placed + for (imember = 0; imember < pl_macros[imacro].num_blocks; imember++) { + member_x = x + pl_macros[imacro].members[imember].x_offset; + member_y = y + pl_macros[imacro].members[imember].y_offset; + member_z = z + pl_macros[imacro].members[imember].z_offset; + + // Check whether the location could accept block of this type + // Then check whether the location could still accomodate more blocks + // Also check whether the member position is valid, that is the member's location + // still within the chip's dimemsion and the member_z is allowed at that location on the grid + if (member_x < device_ctx.grid.width() && member_y < device_ctx.grid.height() + && device_ctx.grid[member_x][member_y].type->index == itype + && place_ctx.grid_blocks[member_x][member_y].blocks[member_z] == EMPTY_BLOCK_ID) { + // Can still accomodate blocks here, check the next position + continue; + } else { + // Cant be placed here - skip to the next try + macro_can_be_placed = false; + break; + } + } + + return (macro_can_be_placed); +} +#endif + + +static int try_place_macro(/*int itype, int ipos, int imacro*/ + Context *ctx, CellInfo* cell, const std::string& loc_name) { + +// int x, y, z, member_x, member_y, member_z, imember; +// +// auto& place_ctx = g_vpr_ctx.mutable_placement(); +// +// int macro_placed = false; +// +// // Choose a random position for the head +// x = legal_pos[itype][ipos].x; +// y = legal_pos[itype][ipos].y; +// z = legal_pos[itype][ipos].z; +// +// // If that location is occupied, do nothing. +// if (place_ctx.grid_blocks[x][y].blocks[z] != EMPTY_BLOCK_ID) { +// return (macro_placed); +// } +// +// int macro_can_be_placed = check_macro_can_be_placed(imacro, itype, x, y, z); +// +// if (macro_can_be_placed) { +// +// // Place down the macro +// macro_placed = true; +// for (imember = 0; imember < pl_macros[imacro].num_blocks; imember++) { +// +// member_x = x + pl_macros[imacro].members[imember].x_offset; +// member_y = y + pl_macros[imacro].members[imember].y_offset; +// member_z = z + pl_macros[imacro].members[imember].z_offset; +// +// ClusterBlockId iblk = pl_macros[imacro].members[imember].blk_index; +// place_ctx.block_locs[iblk].x = member_x; +// place_ctx.block_locs[iblk].y = member_y; +// place_ctx.block_locs[iblk].z = member_z; +// +// place_ctx.grid_blocks[member_x][member_y].blocks[member_z] = pl_macros[imacro].members[imember].blk_index; +// place_ctx.grid_blocks[member_x][member_y].usage++; +// +// // Could not ensure that the randomiser would not pick this location again +// // So, would have to do a lazy removal - whenever I come across a block that could not be placed, +// // go ahead and remove it from the legal_pos[][] array +// +// } // Finish placing all the members in the macro +// +// } // End of this choice of legal_pos +// +// return (macro_placed); + + auto bel = ctx->getBelByName(ctx->id(loc_name)); + if (bel == BelId()) { + log_error("No Bel named \'%s\' located for " + "this chip (processing BEL attribute on \'%s\')\n", + loc_name.c_str(), cell->name.c_str(ctx)); + } + + auto bel_type = ctx->getBelType(bel); + if (bel_type != ctx->belTypeFromId(cell->type)) { + log_error("Bel \'%s\' of type \'%s\' does not match cell " + "\'%s\' of type \'%s\'", + loc_name.c_str(), ctx->belTypeToId(bel_type).c_str(ctx), cell->name.c_str(ctx), + cell->type.c_str(ctx)); + } + + ctx->bindBel(bel, cell->name, STRENGTH_USER); + + return true; +} + +static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_locations*/ + Context *ctx, std::vector>& free_locations) { + +// int macro_placed; +// int imacro, itype, itry, ipos; +// ClusterBlockId blk_id; +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& device_ctx = g_vpr_ctx.device(); + + /* Macros are harder to place. Do them first */ +// for (imacro = 0; imacro < num_pl_macros; imacro++) { + for (auto &cell_entry : ctx->cells) { + auto cell = cell_entry.second.get(); + +// // Every macro are not placed in the beginnning +// macro_placed = false; +// +// // Assume that all the blocks in the macro are of the same type +// blk_id = pl_macros[imacro].members[0].blk_index; +// itype = cluster_ctx.clb_nlist.block_type(blk_id)->index; +// if (free_locations[itype] < pl_macros[imacro].num_blocks) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "Initial placement failed.\n" +// "Could not place macro length %d with head block %s (#%zu); not enough free locations of type %s (#%d).\n" +// "VPR cannot auto-size for your circuit, please resize the FPGA manually.\n", +// pl_macros[imacro].num_blocks, cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id), device_ctx.block_types[itype].name, itype); +// } +// +// // Try to place the macro first, if can be placed - place them, otherwise try again +// for (itry = 0; itry < macros_max_num_tries && macro_placed == false; itry++) { +// +// // Choose a random position for the head +// ipos = vtr::irand(free_locations[itype] - 1); +// +// // Try to place the macro +// macro_placed = try_place_macro(itype, ipos, imacro); +// +// } // Finished all tries + + auto loc = cell->attrs.find(ctx->id("BEL")); + if (loc == cell->attrs.end()) + continue; + + try_place_macro(ctx, cell, loc->second); + +// if (macro_placed == false){ +// // if a macro still could not be placed after macros_max_num_tries times, +// // go through the chip exhaustively to find a legal placement for the macro +// // place the macro on the first location that is legal +// // then set macro_placed = true; +// // if there are no legal positions, error out +// +// // Exhaustive placement of carry macros +// for (ipos = 0; ipos < free_locations[itype] && macro_placed == false; ipos++) { +// +// // Try to place the macro +// macro_placed = try_place_macro(itype, ipos, imacro); +// +// } // Exhausted all the legal placement position for this macro +// +// // If macro could not be placed after exhaustive placement, error out +// if (macro_placed == false) { +// // Error out +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "Initial placement failed.\n" +// "Could not place macro length %d with head block %s (#%zu); not enough free locations of type %s (#%d).\n" +// "Please manually size the FPGA because VPR can't do this yet.\n", +// pl_macros[imacro].num_blocks, cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id), device_ctx.block_types[itype].name, itype); +// } +// +// } else { +// // This macro has been placed successfully, proceed to place the next macro +// continue; +// } + } // Finish placing all the pl_macros successfully +} + +/* Place blocks that are NOT a part of any macro. +* We'll randomly place each block in the clustered netlist, one by one. */ +static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type pad_loc_type*/ + Context* ctx, + std::vector>& free_locations, + const std::unordered_map &bel_types) { +// int itype, ipos, x, y, z; +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& place_ctx = g_vpr_ctx.mutable_placement(); +// auto& device_ctx = g_vpr_ctx.device(); +// +// for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { + for (auto& cell_entry : ctx->cells) { + auto cell = cell_entry.second.get(); +// if (place_ctx.block_locs[blk_id].x != -1) { // -1 is a sentinel for an empty block +// // block placed. +// continue; +// } + + if (cell->belStrength == STRENGTH_USER) + continue; + +// /* Don't do IOs if the user specifies IOs; we'll read those locations later. */ +// if (!(is_io_type(cluster_ctx.clb_nlist.block_type(blk_id)) && pad_loc_type == USER)) { +// +// /* Randomly select a free location of the appropriate type for blk_id. +// * We have a linearized list of all the free locations that can +// * accomodate a block of that type in free_locations[itype]. +// * Choose one randomly and put blk_id there. Then we don't want to pick +// * that location again, so remove it from the free_locations array. +// */ +// itype = cluster_ctx.clb_nlist.block_type(blk_id)->index; +// if (free_locations[itype] <= 0) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "Initial placement failed.\n" +// "Could not place block %s (#%zu); no free locations of type %s (#%d).\n", +// cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id), device_ctx.block_types[itype].name, itype); +// } +// + BelId bel; + initial_placement_location(ctx, free_locations, cell, bel_types, bel); +// +// // Make sure that the position is EMPTY_BLOCK before placing the block down +// VTR_ASSERT(place_ctx.grid_blocks[x][y].blocks[z] == EMPTY_BLOCK_ID); + + NPNR_ASSERT(ctx->checkBelAvail(bel)); + +// place_ctx.grid_blocks[x][y].blocks[z] = blk_id; +// place_ctx.grid_blocks[x][y].usage++; +// +// place_ctx.block_locs[blk_id].x = x; +// place_ctx.block_locs[blk_id].y = y; +// place_ctx.block_locs[blk_id].z = z; +// +// //Mark IOs as fixed if specifying a (fixed) random placement +// if(is_io_type(cluster_ctx.clb_nlist.block_type(blk_id)) && pad_loc_type == RANDOM) { +// place_ctx.block_locs[blk_id].is_fixed = true; +// } + + if (ctx->isIO(cell)) + ctx->bindBel(bel, cell->name, STRENGTH_LOCKED); + else + ctx->bindBel(bel, cell->name, STRENGTH_WEAK); + +// /* Ensure randomizer doesn't pick this location again, since it's occupied. Could shift all the +// * legal positions in legal_pos to remove the entry (choice) we just used, but faster to +// * just move the last entry in legal_pos to the spot we just used and decrement the +// * count of free_locations. */ +// legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; /* overwrite used block position */ +// free_locations[itype]--; + + auto type = ctx->belTypeFromId(cell->type); + auto itype = bel_types.at(type); + free_locations[itype].pop_back(); + +// } + } +} + +static void initial_placement_location(/*int * free_locations, ClusterBlockId blk_id, + int *pipos, int *px, int *py, int *pz*/ + Context *ctx, + const std::vector> &free_locations, + CellInfo *cell, + const std::unordered_map &bel_types, + BelId &bel) { + +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// int itype = cluster_ctx.clb_nlist.block_type(blk_id)->index; +// +// *pipos = vtr::irand(free_locations[itype] - 1); +// *px_to = legal_pos[itype][*pipos].x; +// *py_to = legal_pos[itype][*pipos].y; +// *pz_to = legal_pos[itype][*pipos].z; + + auto type = ctx->belTypeFromId(cell->type); + auto itype = bel_types.at(type); + + bel = free_locations[itype].back(); +} + +static void initial_placement(/*enum e_pad_loc_type pad_loc_type, + const char *pad_loc_file*/ + Context *ctx, + const std::unordered_map &bel_types) { + +// /* Randomly places the blocks to create an initial placement. We rely on +// * the legal_pos array already being loaded. That legal_pos[itype] is an +// * array that gives every legal value of (x,y,z) that can accomodate a block. +// * The number of such locations is given by num_legal_pos[itype]. +// */ +// int itype, x, y, z, ipos; +// int *free_locations; /* [0..device_ctx.num_block_types-1]. +// * Stores how many locations there are for this type that *might* still be free. +// * That is, this stores the number of entries in legal_pos[itype] that are worth considering +// * as you look for a free location. +// */ +// auto& device_ctx = g_vpr_ctx.device(); +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& place_ctx = g_vpr_ctx.mutable_placement(); +// +// free_locations = (int *) vtr::malloc(device_ctx.num_block_types * sizeof(int)); +// for (itype = 0; itype < device_ctx.num_block_types; itype++) { +// free_locations[itype] = num_legal_pos[itype]; +// } + + std::vector> free_locations; + for (auto bel : ctx->getBels()) { + auto type = ctx->getBelType(bel); + int type_idx = bel_types.at(type); + if (int(free_locations.size()) < type_idx + 1) + free_locations.resize(type_idx + 1); + free_locations[type_idx].push_back(bel); + } + +// /* We'll use the grid to record where everything goes. Initialize to the grid has no +// * blocks placed anywhere. +// */ +// for (size_t i = 0; i < device_ctx.grid.width(); i++) { +// for (size_t j = 0; j < device_ctx.grid.height(); j++) { +// place_ctx.grid_blocks[i][j].usage = 0; +// itype = device_ctx.grid[i][j].type->index; +// for (int k = 0; k < device_ctx.block_types[itype].capacity; k++) { +// if (place_ctx.grid_blocks[i][j].blocks[k] != INVALID_BLOCK_ID) { +// place_ctx.grid_blocks[i][j].blocks[k] = EMPTY_BLOCK_ID; +// } +// } +// } +// } +// +// /* Similarly, mark all blocks as not being placed yet. */ +// for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { +// place_ctx.block_locs[blk_id].x = OPEN; +// place_ctx.block_locs[blk_id].y = OPEN; +// place_ctx.block_locs[blk_id].z = OPEN; +// } + + initial_placement_pl_macros(/*MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY,*/ ctx, free_locations); + +// // All the macros are placed, update the legal_pos[][] array +// for (itype = 0; itype < device_ctx.num_block_types; itype++) { +// VTR_ASSERT(free_locations[itype] >= 0); +// for (ipos = 0; ipos < free_locations[itype]; ipos++) { +// x = legal_pos[itype][ipos].x; +// y = legal_pos[itype][ipos].y; +// z = legal_pos[itype][ipos].z; +// +// // Check if that location is occupied. If it is, remove from legal_pos +// if (place_ctx.grid_blocks[x][y].blocks[z] != EMPTY_BLOCK_ID && place_ctx.grid_blocks[x][y].blocks[z] != INVALID_BLOCK_ID) { +// legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; +// free_locations[itype]--; +// +// // After the move, I need to check this particular entry again +// ipos--; +// continue; +// } +// } +// } // Finish updating the legal_pos[][] and free_locations[] array + + for (auto itype = free_locations.begin(); itype != free_locations.end(); itype++) { + for (auto ipos = itype->begin(); ipos != itype->end(); ) { + auto cell_name = ctx->getBoundBelCell(*ipos); + if (cell_name != IdString()) { + auto cell = ctx->cells[cell_name].get(); + if (cell->belStrength == STRENGTH_USER) { + ipos = itype->erase(ipos); + continue; + } + } + ipos++; + } + } + + initial_placement_blocks(ctx, free_locations, bel_types); + +// if (pad_loc_type == USER) { +// read_user_pad_loc(pad_loc_file); +// } +// +// /* Restore legal_pos */ +// load_legal_placements(); +// +//#ifdef VERBOSE +// vtr::printf_info("At end of initial_placement.\n"); +// if (getEchoEnabled() && isEchoFileEnabled(E_ECHO_INITIAL_CLB_PLACEMENT)) { +// print_clb_placement(getEchoFileName(E_ECHO_INITIAL_CLB_PLACEMENT)); +// } +//#endif +// free(free_locations); +} + +#if 0 +static void free_fast_cost_update() { + auto& device_ctx = g_vpr_ctx.device(); + + for (size_t i = 0; i < device_ctx.grid.height(); i++) { + free(chanx_place_cost_fac[i]); + } + free(chanx_place_cost_fac); + chanx_place_cost_fac = nullptr; + + for (size_t i = 0; i < device_ctx.grid.width(); i++) { + free(chany_place_cost_fac[i]); + } + free(chany_place_cost_fac); + chany_place_cost_fac = nullptr; +} + +static void alloc_and_load_for_fast_cost_update(float place_cost_exp) { + + /* Allocates and loads the chanx_place_cost_fac and chany_place_cost_fac * + * arrays with the inverse of the average number of tracks per channel * + * between [subhigh] and [sublow]. This is only useful for the cost * + * function that takes the length of the net bounding box in each * + * dimension divided by the average number of tracks in that direction. * + * For other cost functions, you don't have to bother calling this * + * routine; when using the cost function described above, however, you * + * must always call this routine after you call init_chan and before * + * you do any placement cost determination. The place_cost_exp factor * + * specifies to what power the width of the channel should be taken -- * + * larger numbers make narrower channels more expensive. */ + + auto& device_ctx = g_vpr_ctx.device(); + + /* Access arrays below as chan?_place_cost_fac[subhigh][sublow]. Since * + * subhigh must be greater than or equal to sublow, we only need to * + * allocate storage for the lower half of a matrix. */ + + chanx_place_cost_fac = (float **) vtr::malloc((device_ctx.grid.height()) * sizeof(float *)); + for (size_t i = 0; i < device_ctx.grid.height(); i++) + chanx_place_cost_fac[i] = (float *) vtr::malloc((i + 1) * sizeof(float)); + + chany_place_cost_fac = (float **) vtr::malloc((device_ctx.grid.width() + 1) * sizeof(float *)); + for (size_t i = 0; i < device_ctx.grid.width(); i++) + chany_place_cost_fac[i] = (float *) vtr::malloc((i + 1) * sizeof(float)); + + /* First compute the number of tracks between channel high and channel * + * low, inclusive, in an efficient manner. */ + + chanx_place_cost_fac[0][0] = device_ctx.chan_width.x_list[0]; + + for (size_t high = 1; high < device_ctx.grid.height(); high++) { + chanx_place_cost_fac[high][high] = device_ctx.chan_width.x_list[high]; + for (size_t low = 0; low < high; low++) { + chanx_place_cost_fac[high][low] = + chanx_place_cost_fac[high - 1][low] + device_ctx.chan_width.x_list[high]; + } + } + + /* Now compute the inverse of the average number of tracks per channel * + * between high and low. The cost function divides by the average * + * number of tracks per channel, so by storing the inverse I convert * + * this to a faster multiplication. Take this final number to the * + * place_cost_exp power -- numbers other than one mean this is no * + * longer a simple "average number of tracks"; it is some power of * + * that, allowing greater penalization of narrow channels. */ + + for (size_t high = 0; high < device_ctx.grid.height(); high++) + for (size_t low = 0; low <= high; low++) { + chanx_place_cost_fac[high][low] = (high - low + 1.) + / chanx_place_cost_fac[high][low]; + chanx_place_cost_fac[high][low] = pow( + (double) chanx_place_cost_fac[high][low], + (double) place_cost_exp); + } + + /* Now do the same thing for the y-directed channels. First get the * + * number of tracks between channel high and channel low, inclusive. */ + + chany_place_cost_fac[0][0] = device_ctx.chan_width.y_list[0]; + + for (size_t high = 1; high < device_ctx.grid.width(); high++) { + chany_place_cost_fac[high][high] = device_ctx.chan_width.y_list[high]; + for (size_t low = 0; low < high; low++) { + chany_place_cost_fac[high][low] = + chany_place_cost_fac[high - 1][low] + device_ctx.chan_width.y_list[high]; + } + } + + /* Now compute the inverse of the average number of tracks per channel * + * between high and low. Take to specified power. */ + + for (size_t high = 0; high < device_ctx.grid.width(); high++) + for (size_t low = 0; low <= high; low++) { + chany_place_cost_fac[high][low] = (high - low + 1.) + / chany_place_cost_fac[high][low]; + chany_place_cost_fac[high][low] = pow( + (double) chany_place_cost_fac[high][low], + (double) place_cost_exp); + } +} + +static void check_place(float bb_cost, float timing_cost, + enum e_place_algorithm place_algorithm, + float delay_cost) { + + /* Checks that the placement has not confused our data structures. * + * i.e. the clb and block structures agree about the locations of * + * 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. */ + + vtr::vector bdone; + int error = 0; + ClusterBlockId bnum, head_iblk, member_iblk; + float bb_cost_check; + int usage_check; + float timing_cost_check, delay_cost_check; + int imacro, imember, member_x, member_y, member_z; + + bb_cost_check = comp_bb_cost(CHECK); + //vtr::printf_info("bb_cost recomputed from scratch: %g\n", bb_cost_check); + if (fabs(bb_cost_check - bb_cost) > bb_cost * ERROR_TOL) { + vtr::printf_error(__FILE__, __LINE__, + "bb_cost_check: %g and bb_cost: %g differ in check_place.\n", + bb_cost_check, bb_cost); + error++; + } + + if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + comp_td_costs(&timing_cost_check, &delay_cost_check); + //vtr::printf_info("timing_cost recomputed from scratch: %g\n", timing_cost_check); + if (fabs(timing_cost_check - timing_cost) > timing_cost * ERROR_TOL) { + vtr::printf_error(__FILE__, __LINE__, + "timing_cost_check: %g and timing_cost: %g differ in check_place.\n", + timing_cost_check, timing_cost); + error++; + } + //vtr::printf_info("delay_cost recomputed from scratch: %g\n", delay_cost_check); + if (fabs(delay_cost_check - delay_cost) > delay_cost * ERROR_TOL) { + vtr::printf_error(__FILE__, __LINE__, + "delay_cost_check: %g and delay_cost: %g differ in check_place.\n", + delay_cost_check, delay_cost); + error++; + } + } + + auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& place_ctx = g_vpr_ctx.placement(); + auto& device_ctx = g_vpr_ctx.device(); + + bdone.resize(cluster_ctx.clb_nlist.blocks().size(), 0); + + /* Step through device grid and placement. Check it against blocks */ + for (size_t i = 0; i < device_ctx.grid.width(); i++) + for (size_t j = 0; j < device_ctx.grid.height(); j++) { + if (place_ctx.grid_blocks[i][j].usage > device_ctx.grid[i][j].type->capacity) { + vtr::printf_error(__FILE__, __LINE__, + "Block at grid location (%zu,%zu) overused. Usage is %d.\n", + i, j, place_ctx.grid_blocks[i][j].usage); + error++; + } + usage_check = 0; + for (int k = 0; k < device_ctx.grid[i][j].type->capacity; k++) { + bnum = place_ctx.grid_blocks[i][j].blocks[k]; + if (EMPTY_BLOCK_ID == bnum || INVALID_BLOCK_ID == bnum) + continue; + + if (cluster_ctx.clb_nlist.block_type(bnum) != device_ctx.grid[i][j].type) { + vtr::printf_error(__FILE__, __LINE__, + "Block %zu type (%s) does not match grid location (%zu,%zu) type (%s).\n", + size_t(bnum), cluster_ctx.clb_nlist.block_type(bnum)->name, i, j, device_ctx.grid[i][j].type->name); + error++; + } + if ((place_ctx.block_locs[bnum].x != int(i)) || (place_ctx.block_locs[bnum].y != int(j))) { + vtr::printf_error(__FILE__, __LINE__, + "Block %zu location conflicts with grid(%zu,%zu) data.\n", + size_t(bnum), i, j); + error++; + } + ++usage_check; + bdone[bnum]++; + } + if (usage_check != place_ctx.grid_blocks[i][j].usage) { + vtr::printf_error(__FILE__, __LINE__, + "Location (%zu,%zu) usage is %d, but has actual usage %d.\n", + i, j, place_ctx.grid_blocks[i][j].usage, usage_check); + error++; + } + } + + /* Check that every block exists in the device_ctx.grid and cluster_ctx.blocks arrays somewhere. */ + for (auto blk_id : cluster_ctx.clb_nlist.blocks()) + if (bdone[blk_id] != 1) { + vtr::printf_error(__FILE__, __LINE__, + "Block %zu listed %d times in data structures.\n", + size_t(blk_id), bdone[blk_id]); + error++; + } + bdone.clear(); + + /* Check the pl_macro placement are legal - blocks are in the proper relative position. */ + for (imacro = 0; imacro < num_pl_macros; imacro++) { + + head_iblk = pl_macros[imacro].members[0].blk_index; + + for (imember = 0; imember < pl_macros[imacro].num_blocks; imember++) { + + member_iblk = pl_macros[imacro].members[imember].blk_index; + + // Compute the suppossed member's x,y,z location + member_x = place_ctx.block_locs[head_iblk].x + pl_macros[imacro].members[imember].x_offset; + member_y = place_ctx.block_locs[head_iblk].y + pl_macros[imacro].members[imember].y_offset; + member_z = place_ctx.block_locs[head_iblk].z + pl_macros[imacro].members[imember].z_offset; + + // Check the place_ctx.block_locs data structure first + if (place_ctx.block_locs[member_iblk].x != member_x + || place_ctx.block_locs[member_iblk].y != member_y + || place_ctx.block_locs[member_iblk].z != member_z) { + vtr::printf_error(__FILE__, __LINE__, + "Block %zu in pl_macro #%d is not placed in the proper orientation.\n", + size_t(member_iblk), imacro); + error++; + } + + // Then check the place_ctx.grid data structure + if (place_ctx.grid_blocks[member_x][member_y].blocks[member_z] != member_iblk) { + vtr::printf_error(__FILE__, __LINE__, + "Block %zu in pl_macro #%d 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 + + if (error == 0) { + vtr::printf_info("\n"); + vtr::printf_info("Completed placement consistency check successfully.\n"); + + } else { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "\nCompleted placement consistency check, %d errors found.\n" + "Aborting program.\n", error); + } + +} + +#ifdef VERBOSE +static void print_clb_placement(const char *fname) { + + /* Prints out the clb placements to a file. */ + FILE *fp; + auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& place_ctx = g_vpr_ctx.placement(); + + fp = vtr::fopen(fname, "w"); + fprintf(fp, "Complex block placements:\n\n"); + + fprintf(fp, "Block #\tName\t(X, Y, Z).\n"); + for(auto i : cluster_ctx.clb_nlist.blocks()) { + fprintf(fp, "#%d\t%s\t(%d, %d, %d).\n", i, cluster_ctx.clb_nlist.block_name(i), place_ctx.block_locs[i].x, place_ctx.block_locs[i].y, place_ctx.block_locs[i].z); + } + + fclose(fp); +} +#endif + +static void free_try_swap_arrays() { + if(blocks_affected.moved_blocks != nullptr) { + free(blocks_affected.moved_blocks); + + blocks_affected.moved_blocks = nullptr; + blocks_affected.num_moved_blocks = 0; + } +} + +#endif From bcd9e394aeaa6cd407a740b6eb756f88ce533103 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 13 Jul 2018 18:04:57 -0700 Subject: [PATCH 013/116] Comment out unused bits --- common/place_vpr.inc | 5903 +++++++++++++++++++++--------------------- 1 file changed, 2949 insertions(+), 2954 deletions(-) diff --git a/common/place_vpr.inc b/common/place_vpr.inc index 45bb20bd19..6c71217fc9 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -1,167 +1,166 @@ -#if 0 -#include -#include -#include -#include -using namespace std; - -#include "vtr_assert.h" -#include "vtr_log.h" -#include "vtr_util.h" -#include "vtr_random.h" -#include "vtr_matrix.h" - -#include "vpr_types.h" -#include "vpr_error.h" -#include "vpr_utils.h" - -#include "globals.h" -#include "place.h" -#include "read_place.h" -#include "draw.h" -#include "place_and_route.h" -#include "net_delay.h" -#include "path_delay.h" -#include "timing_place_lookup.h" -#include "timing_place.h" -#include "read_xml_arch_file.h" -#include "echo_files.h" -#include "vpr_utils.h" -#include "place_macro.h" -#include "histogram.h" -#include "place_util.h" - -#include "PlacementDelayCalculator.h" -#include "timing_util.h" -#include "timing_info.h" -#include "tatum/echo_writer.hpp" - -/************** Types and defines local to place.c ***************************/ - -/* Cut off for incremental bounding box updates. * - * 4 is fastest -- I checked. */ -/* To turn off incremental bounding box updates, set this to a huge value */ -#define SMALL_NET 4 - -/* This defines the error tolerance for floating points variables used in * - * cost computation. 0.01 means that there is a 1% error tolerance. */ -#define ERROR_TOL .01 - -/* This defines the maximum number of swap attempts before invoking the * - * once-in-a-while placement legality check as well as floating point * - * variables round-offs check. */ -#define MAX_MOVES_BEFORE_RECOMPUTE 50000 - -/* The maximum number of tries when trying to place a carry chain at a * - * random location before trying exhaustive placement - find the fist * - * legal position and place it during initial placement. */ -#define MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY 4 - -/* Flags for the states of the bounding box. * - * Stored as char for memory efficiency. */ -#define NOT_UPDATED_YET 'N' -#define UPDATED_ONCE 'U' -#define GOT_FROM_SCRATCH 'S' - -/* For comp_cost. NORMAL means use the method that generates updateable * - * bounding boxes for speed. CHECK means compute all bounding boxes from * - * scratch using a very simple routine to allow checks of the other * - * costs. */ -enum e_cost_methods { - NORMAL, CHECK -}; - -/* This is for the placement swap routines. A swap attempt could be * - * rejected, accepted or aborted (due to the limitations placed on the * - * carry chain support at this point). */ -enum e_swap_result { - REJECTED, ACCEPTED, ABORTED -}; - -struct t_placer_statistics { - double av_cost, av_bb_cost, av_timing_cost, - sum_of_squares, av_delay_cost; - int success_sum; -}; - -#define MAX_INV_TIMING_COST 1.e9 -/* Stops inverse timing cost from going to infinity with very lax timing constraints, -which avoids multiplying by a gigantic inverse_prev_timing_cost when auto-normalizing. -The exact value of this cost has relatively little impact, but should not be -large enough to be on the order of timing costs for normal constraints. */ - -/********************** Variables local to place.c ***************************/ - -/* Cost of a net, and a temporary cost of a net used during move assessment. */ -static vtr::vector net_cost, temp_net_cost; - -static t_legal_pos **legal_pos = nullptr; /* [0..device_ctx.num_block_types-1][0..type_tsize - 1] */ -static int *num_legal_pos = nullptr; /* [0..num_legal_pos-1] */ - -/* [0...cluster_ctx.clb_nlist.nets().size()-1] * - * A flag array to indicate whether the specific bounding box has been updated * - * in this particular swap or not. If it has been updated before, the code * - * must use the updated data, instead of the out-of-date data passed into the * - * subroutine, particularly used in try_swap(). The value NOT_UPDATED_YET * - * indicates that the net has not been updated before, UPDATED_ONCE indicated * - * that the net has been updated once, if it is going to be updated again, the * - * values from the previous update must be used. GOT_FROM_SCRATCH is only * - * applicable for nets larger than SMALL_NETS and it indicates that the * - * particular bounding box cannot be updated incrementally before, hence the * - * bounding box is got from scratch, so the bounding box would definitely be * - * right, DO NOT update again. */ -static vtr::vector bb_updated_before; - -/* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1]. What is the value of the timing */ -/* driven portion of the cost function. These arrays will be set to */ -/* (criticality * delay) for each point to point connection. */ - -static vtr::vector point_to_point_timing_cost; -static vtr::vector temp_point_to_point_timing_cost; - -/* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1]. What is the value of the delay */ -/* for each connection in the circuit */ -static vtr::vector_map point_to_point_delay_cost; -static vtr::vector_map temp_point_to_point_delay_cost; - -/* [0..cluster_ctx.clb_nlist.blocks().size()-1][0..pins_per_clb-1]. Indicates which pin on the net */ -/* this block corresponds to, this is only required during timing-driven */ -/* placement. It is used to allow us to update individual connections on */ -/* each net */ -static vtr::vector> net_pin_indices; - -/* [0..cluster_ctx.clb_nlist.nets().size()-1]. Store the bounding box coordinates and the number of * - * blocks on each of a net's bounding box (to allow efficient updates), * - * respectively. */ - -static vtr::vector bb_coords, bb_num_on_edges; - -/* Store the information on the blocks to be moved in a swap during * - * placement, in the form of array of structs instead of struct with * - * arrays for cache effifiency * - */ -static t_pl_blocks_to_be_moved blocks_affected; - -/* The arrays below are used to precompute the inverse of the average * - * number of tracks per channel between [subhigh] and [sublow]. Access * - * them as chan?_place_cost_fac[subhigh][sublow]. They are used to * - * speed up the computation of the cost function that takes the length * - * of the net bounding box in each dimension, divided by the average * - * number of tracks in that direction; for other cost functions they * - * will never be used. * - */ -static float** chanx_place_cost_fac; //[0...device_ctx.grid.width()-2] -static float** chany_place_cost_fac; //[0...device_ctx.grid.height()-2] - -/* The following arrays are used by the try_swap function for speed. */ -/* [0...cluster_ctx.clb_nlist.nets().size()-1] */ -static vtr::vector ts_bb_coord_new, ts_bb_edge_new; -static std::vector ts_nets_to_update; - -/* The pl_macros array stores all the carry chains placement macros. * - * [0...num_pl_macros-1] */ -static t_pl_macro * pl_macros = nullptr; -static int num_pl_macros; +//#include +//#include +//#include +//#include +//using namespace std; +// +//#include "vtr_assert.h" +//#include "vtr_log.h" +//#include "vtr_util.h" +//#include "vtr_random.h" +//#include "vtr_matrix.h" +// +//#include "vpr_types.h" +//#include "vpr_error.h" +//#include "vpr_utils.h" +// +//#include "globals.h" +//#include "place.h" +//#include "read_place.h" +//#include "draw.h" +//#include "place_and_route.h" +//#include "net_delay.h" +//#include "path_delay.h" +//#include "timing_place_lookup.h" +//#include "timing_place.h" +//#include "read_xml_arch_file.h" +//#include "echo_files.h" +//#include "vpr_utils.h" +//#include "place_macro.h" +//#include "histogram.h" +//#include "place_util.h" +// +//#include "PlacementDelayCalculator.h" +//#include "timing_util.h" +//#include "timing_info.h" +//#include "tatum/echo_writer.hpp" +// +///************** Types and defines local to place.c ***************************/ +// +///* Cut off for incremental bounding box updates. * +// * 4 is fastest -- I checked. */ +///* To turn off incremental bounding box updates, set this to a huge value */ +//#define SMALL_NET 4 +// +///* This defines the error tolerance for floating points variables used in * +// * cost computation. 0.01 means that there is a 1% error tolerance. */ +//#define ERROR_TOL .01 +// +///* This defines the maximum number of swap attempts before invoking the * +// * once-in-a-while placement legality check as well as floating point * +// * variables round-offs check. */ +//#define MAX_MOVES_BEFORE_RECOMPUTE 50000 +// +///* The maximum number of tries when trying to place a carry chain at a * +// * random location before trying exhaustive placement - find the fist * +// * legal position and place it during initial placement. */ +//#define MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY 4 +// +///* Flags for the states of the bounding box. * +// * Stored as char for memory efficiency. */ +//#define NOT_UPDATED_YET 'N' +//#define UPDATED_ONCE 'U' +//#define GOT_FROM_SCRATCH 'S' +// +///* For comp_cost. NORMAL means use the method that generates updateable * +// * bounding boxes for speed. CHECK means compute all bounding boxes from * +// * scratch using a very simple routine to allow checks of the other * +// * costs. */ +//enum e_cost_methods { +// NORMAL, CHECK +//}; +// +///* This is for the placement swap routines. A swap attempt could be * +// * rejected, accepted or aborted (due to the limitations placed on the * +// * carry chain support at this point). */ +//enum e_swap_result { +// REJECTED, ACCEPTED, ABORTED +//}; +// +//struct t_placer_statistics { +// double av_cost, av_bb_cost, av_timing_cost, +// sum_of_squares, av_delay_cost; +// int success_sum; +//}; +// +//#define MAX_INV_TIMING_COST 1.e9 +///* Stops inverse timing cost from going to infinity with very lax timing constraints, +//which avoids multiplying by a gigantic inverse_prev_timing_cost when auto-normalizing. +//The exact value of this cost has relatively little impact, but should not be +//large enough to be on the order of timing costs for normal constraints. */ +// +///********************** Variables local to place.c ***************************/ +// +///* Cost of a net, and a temporary cost of a net used during move assessment. */ +//static vtr::vector net_cost, temp_net_cost; +// +//static t_legal_pos **legal_pos = nullptr; /* [0..device_ctx.num_block_types-1][0..type_tsize - 1] */ +//static int *num_legal_pos = nullptr; /* [0..num_legal_pos-1] */ +// +///* [0...cluster_ctx.clb_nlist.nets().size()-1] * +// * A flag array to indicate whether the specific bounding box has been updated * +// * in this particular swap or not. If it has been updated before, the code * +// * must use the updated data, instead of the out-of-date data passed into the * +// * subroutine, particularly used in try_swap(). The value NOT_UPDATED_YET * +// * indicates that the net has not been updated before, UPDATED_ONCE indicated * +// * that the net has been updated once, if it is going to be updated again, the * +// * values from the previous update must be used. GOT_FROM_SCRATCH is only * +// * applicable for nets larger than SMALL_NETS and it indicates that the * +// * particular bounding box cannot be updated incrementally before, hence the * +// * bounding box is got from scratch, so the bounding box would definitely be * +// * right, DO NOT update again. */ +//static vtr::vector bb_updated_before; +// +///* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1]. What is the value of the timing */ +///* driven portion of the cost function. These arrays will be set to */ +///* (criticality * delay) for each point to point connection. */ +// +//static vtr::vector point_to_point_timing_cost; +//static vtr::vector temp_point_to_point_timing_cost; +// +///* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1]. What is the value of the delay */ +///* for each connection in the circuit */ +//static vtr::vector_map point_to_point_delay_cost; +//static vtr::vector_map temp_point_to_point_delay_cost; +// +///* [0..cluster_ctx.clb_nlist.blocks().size()-1][0..pins_per_clb-1]. Indicates which pin on the net */ +///* this block corresponds to, this is only required during timing-driven */ +///* placement. It is used to allow us to update individual connections on */ +///* each net */ +//static vtr::vector> net_pin_indices; +// +///* [0..cluster_ctx.clb_nlist.nets().size()-1]. Store the bounding box coordinates and the number of * +// * blocks on each of a net's bounding box (to allow efficient updates), * +// * respectively. */ +// +//static vtr::vector bb_coords, bb_num_on_edges; +// +///* Store the information on the blocks to be moved in a swap during * +// * placement, in the form of array of structs instead of struct with * +// * arrays for cache effifiency * +// */ +//static t_pl_blocks_to_be_moved blocks_affected; +// +///* The arrays below are used to precompute the inverse of the average * +// * number of tracks per channel between [subhigh] and [sublow]. Access * +// * them as chan?_place_cost_fac[subhigh][sublow]. They are used to * +// * speed up the computation of the cost function that takes the length * +// * of the net bounding box in each dimension, divided by the average * +// * number of tracks in that direction; for other cost functions they * +// * will never be used. * +// */ +//static float** chanx_place_cost_fac; //[0...device_ctx.grid.width()-2] +//static float** chany_place_cost_fac; //[0...device_ctx.grid.height()-2] +// +///* The following arrays are used by the try_swap function for speed. */ +///* [0...cluster_ctx.clb_nlist.nets().size()-1] */ +//static vtr::vector ts_bb_coord_new, ts_bb_edge_new; +//static std::vector ts_nets_to_update; +// +///* The pl_macros array stores all the carry chains placement macros. * +// * [0...num_pl_macros-1] */ +//static t_pl_macro * pl_macros = nullptr; +//static int num_pl_macros; /* These file-scoped variables keep track of the number of swaps * * rejected, accepted or aborted. The total number of swap attempts * @@ -171,44 +170,43 @@ static int num_swap_accepted = 0; static int num_swap_aborted = 0; static int num_ts_called = 0; -/* Expected crossing counts for nets with different #'s of pins. From * - * ICCAD 94 pp. 690 - 695 (with linear interpolation applied by me). * - * Multiplied to bounding box of a net to better estimate wire length * - * for higher fanout nets. Each entry is the correction factor for the * - * fanout index-1 */ -static const float cross_count[50] = { /* [0..49] */1.0, 1.0, 1.0, 1.0828, 1.1536, 1.2206, 1.2823, 1.3385, 1.3991, 1.4493, 1.4974, - 1.5455, 1.5937, 1.6418, 1.6899, 1.7304, 1.7709, 1.8114, 1.8519, 1.8924, - 1.9288, 1.9652, 2.0015, 2.0379, 2.0743, 2.1061, 2.1379, 2.1698, 2.2016, - 2.2334, 2.2646, 2.2958, 2.3271, 2.3583, 2.3895, 2.4187, 2.4479, 2.4772, - 2.5064, 2.5356, 2.5610, 2.5864, 2.6117, 2.6371, 2.6625, 2.6887, 2.7148, - 2.7410, 2.7671, 2.7933 }; - -/********************* Static subroutines local to place.c *******************/ -#ifdef VERBOSE - static void print_clb_placement(const char *fname); -#endif - -static void alloc_and_load_placement_structs( - float place_cost_exp, t_placer_opts placer_opts, - t_direct_inf *directs, int num_directs); - -static void alloc_and_load_net_pin_indices(); - -static void alloc_and_load_try_swap_structs(); - -static void free_placement_structs(t_placer_opts placer_opts); - -static void alloc_and_load_for_fast_cost_update(float place_cost_exp); - -static void free_fast_cost_update(); - -static void alloc_legal_placements(); -static void load_legal_placements(); - -static void free_legal_placements(); - -static int check_macro_can_be_placed(int imacro, int itype, int x, int y, int z); -#endif +///* Expected crossing counts for nets with different #'s of pins. From * +// * ICCAD 94 pp. 690 - 695 (with linear interpolation applied by me). * +// * Multiplied to bounding box of a net to better estimate wire length * +// * for higher fanout nets. Each entry is the correction factor for the * +// * fanout index-1 */ +//static const float cross_count[50] = { /* [0..49] */1.0, 1.0, 1.0, 1.0828, 1.1536, 1.2206, 1.2823, 1.3385, 1.3991, 1.4493, 1.4974, +// 1.5455, 1.5937, 1.6418, 1.6899, 1.7304, 1.7709, 1.8114, 1.8519, 1.8924, +// 1.9288, 1.9652, 2.0015, 2.0379, 2.0743, 2.1061, 2.1379, 2.1698, 2.2016, +// 2.2334, 2.2646, 2.2958, 2.3271, 2.3583, 2.3895, 2.4187, 2.4479, 2.4772, +// 2.5064, 2.5356, 2.5610, 2.5864, 2.6117, 2.6371, 2.6625, 2.6887, 2.7148, +// 2.7410, 2.7671, 2.7933 }; +// +///********************* Static subroutines local to place.c *******************/ +//#ifdef VERBOSE +// static void print_clb_placement(const char *fname); +//#endif +// +//static void alloc_and_load_placement_structs( +// float place_cost_exp, t_placer_opts placer_opts, +// t_direct_inf *directs, int num_directs); +// +//static void alloc_and_load_net_pin_indices(); +// +//static void alloc_and_load_try_swap_structs(); +// +//static void free_placement_structs(t_placer_opts placer_opts); +// +//static void alloc_and_load_for_fast_cost_update(float place_cost_exp); +// +//static void free_fast_cost_update(); +// +//static void alloc_legal_placements(); +//static void load_legal_placements(); +// +//static void free_legal_placements(); +// +//static int check_macro_can_be_placed(int imacro, int itype, int x, int y, int z); static int try_place_macro(/*int itype, int ipos, int imacro*/ Context *ctx, CellInfo* cell, const std::string& loc_name); @@ -234,124 +232,125 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, Context *ctx, const std::unordered_map &bel_types); -#if 0 -static float comp_bb_cost(e_cost_methods method); - -static int setup_blocks_affected(ClusterBlockId b_from, int x_to, int y_to, int z_to); - -static int find_affected_blocks(ClusterBlockId b_from, int x_to, int y_to, int z_to); - -static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, - float rlim, - enum e_place_algorithm place_algorithm, float timing_tradeoff, - float inverse_prev_bb_cost, float inverse_prev_timing_cost, - float *delay_cost); - -static ClusterBlockId pick_from_block(); - -static void check_place(float bb_cost, float timing_cost, - enum e_place_algorithm place_algorithm, - float delay_cost); - -static float starting_t(float *cost_ptr, float *bb_cost_ptr, - float *timing_cost_ptr, - t_annealing_sched annealing_sched, int max_moves, float rlim, - enum e_place_algorithm place_algorithm, float timing_tradeoff, - float inverse_prev_bb_cost, float inverse_prev_timing_cost, - float *delay_cost_ptr); - -static void update_t(float *t, float rlim, float success_rat, - t_annealing_sched annealing_sched); - -static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid); - -static int exit_crit(float t, float cost, - t_annealing_sched annealing_sched); - -static int count_connections(); - -static double get_std_dev(int n, double sum_x_squared, double av_x); - -static float recompute_bb_cost(); - -static float comp_td_point_to_point_delay(ClusterNetId net_id, int ipin); - -static void comp_td_point_to_point_delays(); - -static void update_td_cost(); - -static bool driven_by_moved_block(const ClusterNetId net); - -static void comp_td_costs(float *timing_cost, float *connection_delay_sum); - -static e_swap_result assess_swap(float delta_c, float t); - -static bool find_to(t_type_ptr type, float rlim, - int x_from, int y_from, - int *px_to, int *py_to, int *pz_to); -static void find_to_location(t_type_ptr type, float rlim, - int x_from, int y_from, - int *px_to, int *py_to, int *pz_to); - -static void get_non_updateable_bb(ClusterNetId net_id, t_bb *bb_coord_new); - -static void update_bb(ClusterNetId net_id, t_bb *bb_coord_new, - t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew); - -static int find_affected_nets_and_update_costs(e_place_algorithm place_algorithm, float& bb_delta_c, float& timing_delta_c, float& delay_delta_c); - -static void record_affected_net(const ClusterNetId net, int& num_affected_nets); - -static void update_net_bb(const ClusterNetId net, int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin); -static void update_td_delta_costs(const ClusterNetId net, const ClusterPinId pin, float& delta_timing_cost, float& delta_delay_cost); - -static float get_net_cost(ClusterNetId net_id, t_bb *bb_ptr); - -static void get_bb_from_scratch(ClusterNetId net_id, t_bb *coords, - t_bb *num_on_edges); - -static double get_net_wirelength_estimate(ClusterNetId net_id, t_bb *bbptr); - -static void free_try_swap_arrays(); - -static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, - int num_connections, float crit_exponent, float bb_cost, - float * place_delay_value, float * timing_cost, float * delay_cost, - int * outer_crit_iter_count, float * inverse_prev_timing_cost, - float * inverse_prev_bb_cost, - const ClusteredPinAtomPinsLookup& netlist_pin_lookup, -#ifdef ENABLE_CLASSIC_VPR_STA - t_slack* slacks, - t_timing_inf timing_inf, -#endif - SetupTimingInfo& timing_info); - -static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, - float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, - float crit_exponent, int inner_recompute_limit, - t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, - float * delay_cost, -#ifdef ENABLE_CLASSIC_VPR_STA - t_slack* slacks, - t_timing_inf timing_inf, -#endif - const ClusteredPinAtomPinsLookup& netlist_pin_lookup, - SetupTimingInfo& timing_info); +//static float comp_bb_cost(e_cost_methods method); +// +//static int setup_blocks_affected(ClusterBlockId b_from, int x_to, int y_to, int z_to); +// +//static int find_affected_blocks(ClusterBlockId b_from, int x_to, int y_to, int z_to); +// +//static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, +// float rlim, +// enum e_place_algorithm place_algorithm, float timing_tradeoff, +// float inverse_prev_bb_cost, float inverse_prev_timing_cost, +// float *delay_cost); +// +//static ClusterBlockId pick_from_block(); +// +//static void check_place(float bb_cost, float timing_cost, +// enum e_place_algorithm place_algorithm, +// float delay_cost); +// +//static float starting_t(float *cost_ptr, float *bb_cost_ptr, +// float *timing_cost_ptr, +// t_annealing_sched annealing_sched, int max_moves, float rlim, +// enum e_place_algorithm place_algorithm, float timing_tradeoff, +// float inverse_prev_bb_cost, float inverse_prev_timing_cost, +// float *delay_cost_ptr); +// +//static void update_t(float *t, float rlim, float success_rat, +// t_annealing_sched annealing_sched); +// +//static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid); +// +//static int exit_crit(float t, float cost, +// t_annealing_sched annealing_sched); +// +//static int count_connections(); +// +//static double get_std_dev(int n, double sum_x_squared, double av_x); +// +//static float recompute_bb_cost(); +// +//static float comp_td_point_to_point_delay(ClusterNetId net_id, int ipin); +// +//static void comp_td_point_to_point_delays(); +// +//static void update_td_cost(); +// +//static bool driven_by_moved_block(const ClusterNetId net); +// +//static void comp_td_costs(float *timing_cost, float *connection_delay_sum); +// +//static e_swap_result assess_swap(float delta_c, float t); +// +//static bool find_to(t_type_ptr type, float rlim, +// int x_from, int y_from, +// int *px_to, int *py_to, int *pz_to); +//static void find_to_location(t_type_ptr type, float rlim, +// int x_from, int y_from, +// int *px_to, int *py_to, int *pz_to); +// +//static void get_non_updateable_bb(ClusterNetId net_id, t_bb *bb_coord_new); +// +//static void update_bb(ClusterNetId net_id, t_bb *bb_coord_new, +// t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew); +// +//static int find_affected_nets_and_update_costs(e_place_algorithm place_algorithm, float& bb_delta_c, float& timing_delta_c, float& delay_delta_c); +// +//static void record_affected_net(const ClusterNetId net, int& num_affected_nets); +// +//static void update_net_bb(const ClusterNetId net, int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin); +//static void update_td_delta_costs(const ClusterNetId net, const ClusterPinId pin, float& delta_timing_cost, float& delta_delay_cost); +// +//static float get_net_cost(ClusterNetId net_id, t_bb *bb_ptr); +// +//static void get_bb_from_scratch(ClusterNetId net_id, t_bb *coords, +// t_bb *num_on_edges); +// +//static double get_net_wirelength_estimate(ClusterNetId net_id, t_bb *bbptr); +// +//static void free_try_swap_arrays(); +// +//static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, +// int num_connections, float crit_exponent, float bb_cost, +// float * place_delay_value, float * timing_cost, float * delay_cost, +// int * outer_crit_iter_count, float * inverse_prev_timing_cost, +// float * inverse_prev_bb_cost, +// const ClusteredPinAtomPinsLookup& netlist_pin_lookup, +//#ifdef ENABLE_CLASSIC_VPR_STA +// t_slack* slacks, +// t_timing_inf timing_inf, +//#endif +// SetupTimingInfo& timing_info); +// +//static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, +// float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, +// float crit_exponent, int inner_recompute_limit, +// t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, +// float * delay_cost, +//#ifdef ENABLE_CLASSIC_VPR_STA +// t_slack* slacks, +// t_timing_inf timing_inf, +//#endif +// const ClusteredPinAtomPinsLookup& netlist_pin_lookup, +// SetupTimingInfo& timing_info); /*****************************************************************************/ -void try_place(t_placer_opts placer_opts, +void try_place(/*t_placer_opts placer_opts, t_annealing_sched annealing_sched, t_chan_width_dist chan_width_dist, t_router_opts router_opts, t_det_routing_arch *det_routing_arch, t_segment_inf * segment_inf, #ifdef ENABLE_CLASSIC_VPR_STA t_timing_inf timing_inf, #endif - t_direct_inf *directs, int num_directs) { + t_direct_inf *directs, int num_directs*/ + Context *ctx, + const std::unordered_map &bel_types) { - /* Does almost all the work of placing a circuit. Width_fac gives the * - * width of the widest channel. Place_cost_exp says what exponent the * - * width should be taken to when calculating costs. This allows a * - * greater bias for anisotropic architectures. */ +// /* Does almost all the work of placing a circuit. Width_fac gives the * +// * width of the widest channel. Place_cost_exp says what exponent the * +// * width should be taken to when calculating costs. This allows a * +// * greater bias for anisotropic architectures. */ int tot_iter, move_lim, moves_since_cost_recompute, width_fac, num_connections, outer_crit_iter_count, inner_recompute_limit; @@ -360,26 +359,26 @@ void try_place(t_placer_opts placer_opts, delay_cost, new_delay_cost, place_delay_value, inverse_prev_bb_cost, inverse_prev_timing_cost, oldt, crit_exponent, first_rlim, final_rlim, inverse_delta_rlim; - tatum::TimingPathInfo critical_path; - float sTNS = NAN; - float sWNS = NAN; - +// tatum::TimingPathInfo critical_path; +// float sTNS = NAN; +// float sWNS = NAN; +// double std_dev; - char msg[vtr::bufsize]; - t_placer_statistics stats; -#ifdef ENABLE_CLASSIC_VPR_STA - t_slack * slacks = NULL; -#endif - - auto& device_ctx = g_vpr_ctx.device(); - auto& cluster_ctx = g_vpr_ctx.clustering(); - - std::shared_ptr timing_info; - std::shared_ptr placement_delay_calc; - - /* Allocated here because it goes into timing critical code where each memory allocation is expensive */ - IntraLbPbPinLookup pb_gpin_lookup(device_ctx.block_types, device_ctx.num_block_types); - +// char msg[vtr::bufsize]; +// t_placer_statistics stats; +//#ifdef ENABLE_CLASSIC_VPR_STA +// t_slack * slacks = NULL; +//#endif +// +// auto& device_ctx = g_vpr_ctx.device(); +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// std::shared_ptr timing_info; +// std::shared_ptr placement_delay_calc; +// +// /* Allocated here because it goes into timing critical code where each memory allocation is expensive */ +// IntraLbPbPinLookup pb_gpin_lookup(device_ctx.block_types, device_ctx.num_block_types); +// /* init file scope variables */ num_swap_rejected = 0; @@ -387,2362 +386,2361 @@ void try_place(t_placer_opts placer_opts, num_swap_aborted = 0; num_ts_called = 0; - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE - || placer_opts.enable_timing_computations) { - /*do this before the initial placement to avoid messing up the initial placement */ - alloc_lookups_and_criticalities(chan_width_dist, router_opts, det_routing_arch, segment_inf, directs, num_directs); - -#ifdef ENABLE_CLASSIC_VPR_STA - slacks = alloc_and_load_timing_graph(timing_inf); -#endif - } - - width_fac = placer_opts.place_chan_width; - - init_chan(width_fac, chan_width_dist); - - alloc_and_load_placement_structs(placer_opts.place_cost_exp, placer_opts, - directs, num_directs); - - initial_placement(placer_opts.pad_loc_type, placer_opts.pad_loc_file.c_str()); - init_draw_coords((float) width_fac); - - //Enables fast look-up of atom pins connect to CLB pins - ClusteredPinAtomPinsLookup netlist_pin_lookup(cluster_ctx.clb_nlist, pb_gpin_lookup); - - /* Gets initial cost and loads bounding boxes. */ - - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE || placer_opts.enable_timing_computations) { - bb_cost = comp_bb_cost(NORMAL); - - crit_exponent = placer_opts.td_place_exp_first; /*this will be modified when rlim starts to change */ - - num_connections = count_connections(); - vtr::printf_info("\n"); - vtr::printf_info("There are %d point to point connections in this circuit.\n", num_connections); - vtr::printf_info("\n"); - - place_delay_value = 0; - - //Update the point-to-point delays from the initial placement - comp_td_point_to_point_delays(); - - /* - * Initialize timing analysis - */ - auto& atom_ctx = g_vpr_ctx.atom(); - placement_delay_calc = std::make_shared(atom_ctx.nlist, atom_ctx.lookup, point_to_point_delay_cost); - timing_info = make_setup_timing_info(placement_delay_calc); - - timing_info->update(); - timing_info->set_warn_unconstrained(false); //Don't warn again about unconstrained nodes again during placement - - //Initial slack estimates - load_criticalities(*timing_info, crit_exponent, netlist_pin_lookup); - - critical_path = timing_info->least_slack_critical_path(); - - //Write out the initial timing echo file - if(isEchoFileEnabled(E_ECHO_INITIAL_PLACEMENT_TIMING_GRAPH)) { - auto& timing_ctx = g_vpr_ctx.timing(); - - tatum::write_echo(getEchoFileName(E_ECHO_INITIAL_PLACEMENT_TIMING_GRAPH), - *timing_ctx.graph, *timing_ctx.constraints, *placement_delay_calc, timing_info->analyzer()); - } - -#ifdef ENABLE_CLASSIC_VPR_STA - load_timing_graph_net_delays(point_to_point_delay_cost); - do_timing_analysis(slacks, timing_inf, false, true); - - float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); - if(cpd_diff_ns > ERROR_TOL) { - print_classic_cpds(); - print_tatum_cpds(timing_info->critical_paths()); - - vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); - } -#endif - - /*now we can properly compute costs */ - comp_td_costs(&timing_cost, &delay_cost); /*also updates values in point_to_point_delay_cost */ - - if (getEchoEnabled()) { -#ifdef ENABLE_CLASSIC_VPR_STA - if(isEchoFileEnabled(E_ECHO_INITIAL_PLACEMENT_SLACK)) - print_slack(slacks->slack, false, getEchoFileName(E_ECHO_INITIAL_PLACEMENT_SLACK)); - if(isEchoFileEnabled(E_ECHO_INITIAL_PLACEMENT_CRITICALITY)) - print_criticality(slacks, getEchoFileName(E_ECHO_INITIAL_PLACEMENT_CRITICALITY)); -#endif - } - outer_crit_iter_count = 1; - - inverse_prev_timing_cost = 1 / timing_cost; - inverse_prev_bb_cost = 1 / bb_cost; - cost = 1; /*our new cost function uses normalized values of */ - /*bb_cost and timing_cost, the value of cost will be reset */ - /*to 1 at each temperature when *_TIMING_DRIVEN_PLACE is true */ - } else { /*BOUNDING_BOX_PLACE */ - cost = bb_cost = comp_bb_cost(NORMAL); - timing_cost = 0; - delay_cost = 0; - place_delay_value = 0; - outer_crit_iter_count = 0; - num_connections = 0; - crit_exponent = 0; - - inverse_prev_timing_cost = 0; /*inverses not used */ - inverse_prev_bb_cost = 0; - } - - //Sanity check that initial placement is legal - check_place(bb_cost, timing_cost, placer_opts.place_algorithm, delay_cost); - - //Initial pacement statistics - vtr::printf_info("Initial placement cost: %g bb_cost: %g td_cost: %g delay_cost: %g\n", - cost, bb_cost, timing_cost, delay_cost); - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - vtr::printf_info("Initial placement estimated Critical Path Delay (CPD): %g ns\n", - 1e9*critical_path.delay()); - vtr::printf_info("Initial placement estimated setup Total Negative Slack (sTNS): %g ns\n", - 1e9*timing_info->setup_total_negative_slack()); - vtr::printf_info("Initial placement estimated setup Worst Negative Slack (sWNS): %g ns\n", - 1e9*timing_info->setup_worst_negative_slack()); - vtr::printf_info("\n"); - - vtr::printf_info("Initial placement estimated setup slack histogram:\n"); - print_histogram(create_setup_slack_histogram(*timing_info->setup_analyzer())); - } - vtr::printf_info("\n"); - - //Table header - vtr::printf_info("%7s " - "%7s %10s %10s %10s " - "%10s %7s %10s %8s " - "%7s %7s %7s %6s " - "%9s %6s\n", - "-------", - "-------", "----------", "----------", "----------", - "----------", "-------", "----------", "--------", - "-------", "-------", "-------", "------", - "---------", "------"); - vtr::printf_info("%7s " - "%7s %10s %10s %10s " - "%10s %7s %10s %8s " - "%7s %7s %7s %6s " - "%9s %6s\n", - "T", - "Cost", "Av BB Cost", "Av TD Cost", "Av Tot Del", - "P to P Del", "CPD", "sTNS", "sWNS", - "Ac Rate", "Std Dev", "R limit", "Exp", - "Tot Moves", "Alpha"); - vtr::printf_info("%7s " - "%7s %10s %10s %10s " - "%10s %7s %10s %8s " - "%7s %7s %7s %6s " - "%9s %6s\n", - "-------", - "-------", "----------", "----------", "----------", - "----------", "-------", "----------", "--------", - "-------", "-------", "-------", "------", - "---------", "------"); - - sprintf(msg, "Initial Placement. Cost: %g BB Cost: %g TD Cost %g Delay Cost: %g \t Channel Factor: %d", - cost, bb_cost, timing_cost, delay_cost, width_fac); - - //Draw the initial placement - update_screen(ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info); - - move_lim = (int) (annealing_sched.inner_num * pow(cluster_ctx.clb_nlist.blocks().size(), 1.3333)); - - /* Sometimes I want to run the router with a random placement. Avoid * - * using 0 moves to stop division by 0 and 0 length vector problems, * - * by setting move_lim to 1 (which is still too small to do any * - * significant optimization). */ - if (move_lim <= 0) - move_lim = 1; - - if (placer_opts.inner_loop_recompute_divider != 0) { - inner_recompute_limit = (int) - (0.5 + (float) move_lim / (float) placer_opts.inner_loop_recompute_divider); - } else { - /*don't do an inner recompute */ - inner_recompute_limit = move_lim + 1; - } +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE +// || placer_opts.enable_timing_computations) { +// /*do this before the initial placement to avoid messing up the initial placement */ +// alloc_lookups_and_criticalities(chan_width_dist, router_opts, det_routing_arch, segment_inf, directs, num_directs); +// +//#ifdef ENABLE_CLASSIC_VPR_STA +// slacks = alloc_and_load_timing_graph(timing_inf); +//#endif +// } +// +// width_fac = placer_opts.place_chan_width; +// +// init_chan(width_fac, chan_width_dist); +// +// alloc_and_load_placement_structs(placer_opts.place_cost_exp, placer_opts, +// directs, num_directs); +// + initial_placement(ctx, bel_types); +// init_draw_coords((float) width_fac); +// +// //Enables fast look-up of atom pins connect to CLB pins +// ClusteredPinAtomPinsLookup netlist_pin_lookup(cluster_ctx.clb_nlist, pb_gpin_lookup); +// +// /* Gets initial cost and loads bounding boxes. */ +// +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE || placer_opts.enable_timing_computations) { +// bb_cost = comp_bb_cost(NORMAL); +// +// crit_exponent = placer_opts.td_place_exp_first; /*this will be modified when rlim starts to change */ +// +// num_connections = count_connections(); +// vtr::printf_info("\n"); +// vtr::printf_info("There are %d point to point connections in this circuit.\n", num_connections); +// vtr::printf_info("\n"); +// +// place_delay_value = 0; +// +// //Update the point-to-point delays from the initial placement +// comp_td_point_to_point_delays(); +// +// /* +// * Initialize timing analysis +// */ +// auto& atom_ctx = g_vpr_ctx.atom(); +// placement_delay_calc = std::make_shared(atom_ctx.nlist, atom_ctx.lookup, point_to_point_delay_cost); +// timing_info = make_setup_timing_info(placement_delay_calc); +// +// timing_info->update(); +// timing_info->set_warn_unconstrained(false); //Don't warn again about unconstrained nodes again during placement +// +// //Initial slack estimates +// load_criticalities(*timing_info, crit_exponent, netlist_pin_lookup); +// +// critical_path = timing_info->least_slack_critical_path(); +// +// //Write out the initial timing echo file +// if(isEchoFileEnabled(E_ECHO_INITIAL_PLACEMENT_TIMING_GRAPH)) { +// auto& timing_ctx = g_vpr_ctx.timing(); +// +// tatum::write_echo(getEchoFileName(E_ECHO_INITIAL_PLACEMENT_TIMING_GRAPH), +// *timing_ctx.graph, *timing_ctx.constraints, *placement_delay_calc, timing_info->analyzer()); +// } +// +//#ifdef ENABLE_CLASSIC_VPR_STA +// load_timing_graph_net_delays(point_to_point_delay_cost); +// do_timing_analysis(slacks, timing_inf, false, true); +// +// float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); +// if(cpd_diff_ns > ERROR_TOL) { +// print_classic_cpds(); +// print_tatum_cpds(timing_info->critical_paths()); +// +// vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); +// } +//#endif +// +// /*now we can properly compute costs */ +// comp_td_costs(&timing_cost, &delay_cost); /*also updates values in point_to_point_delay_cost */ +// +// if (getEchoEnabled()) { +//#ifdef ENABLE_CLASSIC_VPR_STA +// if(isEchoFileEnabled(E_ECHO_INITIAL_PLACEMENT_SLACK)) +// print_slack(slacks->slack, false, getEchoFileName(E_ECHO_INITIAL_PLACEMENT_SLACK)); +// if(isEchoFileEnabled(E_ECHO_INITIAL_PLACEMENT_CRITICALITY)) +// print_criticality(slacks, getEchoFileName(E_ECHO_INITIAL_PLACEMENT_CRITICALITY)); +//#endif +// } +// outer_crit_iter_count = 1; +// +// inverse_prev_timing_cost = 1 / timing_cost; +// inverse_prev_bb_cost = 1 / bb_cost; +// cost = 1; /*our new cost function uses normalized values of */ +// /*bb_cost and timing_cost, the value of cost will be reset */ +// /*to 1 at each temperature when *_TIMING_DRIVEN_PLACE is true */ +// } else { /*BOUNDING_BOX_PLACE */ +// cost = bb_cost = comp_bb_cost(NORMAL); +// timing_cost = 0; +// delay_cost = 0; +// place_delay_value = 0; +// outer_crit_iter_count = 0; +// num_connections = 0; +// crit_exponent = 0; +// +// inverse_prev_timing_cost = 0; /*inverses not used */ +// inverse_prev_bb_cost = 0; +// } +// +// //Sanity check that initial placement is legal +// check_place(bb_cost, timing_cost, placer_opts.place_algorithm, delay_cost); +// +// //Initial pacement statistics +// vtr::printf_info("Initial placement cost: %g bb_cost: %g td_cost: %g delay_cost: %g\n", +// cost, bb_cost, timing_cost, delay_cost); +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// vtr::printf_info("Initial placement estimated Critical Path Delay (CPD): %g ns\n", +// 1e9*critical_path.delay()); +// vtr::printf_info("Initial placement estimated setup Total Negative Slack (sTNS): %g ns\n", +// 1e9*timing_info->setup_total_negative_slack()); +// vtr::printf_info("Initial placement estimated setup Worst Negative Slack (sWNS): %g ns\n", +// 1e9*timing_info->setup_worst_negative_slack()); +// vtr::printf_info("\n"); +// +// vtr::printf_info("Initial placement estimated setup slack histogram:\n"); +// print_histogram(create_setup_slack_histogram(*timing_info->setup_analyzer())); +// } +// vtr::printf_info("\n"); +// +// //Table header +// vtr::printf_info("%7s " +// "%7s %10s %10s %10s " +// "%10s %7s %10s %8s " +// "%7s %7s %7s %6s " +// "%9s %6s\n", +// "-------", +// "-------", "----------", "----------", "----------", +// "----------", "-------", "----------", "--------", +// "-------", "-------", "-------", "------", +// "---------", "------"); +// vtr::printf_info("%7s " +// "%7s %10s %10s %10s " +// "%10s %7s %10s %8s " +// "%7s %7s %7s %6s " +// "%9s %6s\n", +// "T", +// "Cost", "Av BB Cost", "Av TD Cost", "Av Tot Del", +// "P to P Del", "CPD", "sTNS", "sWNS", +// "Ac Rate", "Std Dev", "R limit", "Exp", +// "Tot Moves", "Alpha"); +// vtr::printf_info("%7s " +// "%7s %10s %10s %10s " +// "%10s %7s %10s %8s " +// "%7s %7s %7s %6s " +// "%9s %6s\n", +// "-------", +// "-------", "----------", "----------", "----------", +// "----------", "-------", "----------", "--------", +// "-------", "-------", "-------", "------", +// "---------", "------"); +// +// sprintf(msg, "Initial Placement. Cost: %g BB Cost: %g TD Cost %g Delay Cost: %g \t Channel Factor: %d", +// cost, bb_cost, timing_cost, delay_cost, width_fac); +// +// //Draw the initial placement +// update_screen(ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info); +// +// move_lim = (int) (annealing_sched.inner_num * pow(cluster_ctx.clb_nlist.blocks().size(), 1.3333)); +// +// /* Sometimes I want to run the router with a random placement. Avoid * +// * using 0 moves to stop division by 0 and 0 length vector problems, * +// * by setting move_lim to 1 (which is still too small to do any * +// * significant optimization). */ +// if (move_lim <= 0) +// move_lim = 1; +// +// if (placer_opts.inner_loop_recompute_divider != 0) { +// inner_recompute_limit = (int) +// (0.5 + (float) move_lim / (float) placer_opts.inner_loop_recompute_divider); +// } else { +// /*don't do an inner recompute */ +// inner_recompute_limit = move_lim + 1; +// } - rlim = (float) max(device_ctx.grid.width() - 1, device_ctx.grid.height() - 1); + rlim = (float) std::max(ctx->chip_info->width, ctx->chip_info->height); first_rlim = rlim; /*used in timing-driven placement for exponent computation */ final_rlim = 1; inverse_delta_rlim = 1 / (first_rlim - final_rlim); - t = starting_t(&cost, &bb_cost, &timing_cost, - annealing_sched, move_lim, rlim, - placer_opts.place_algorithm, placer_opts.timing_tradeoff, - inverse_prev_bb_cost, inverse_prev_timing_cost, &delay_cost); - - tot_iter = 0; - moves_since_cost_recompute = 0; - - - - - /* Outer loop of the simmulated annealing begins */ - while (exit_crit(t, cost, annealing_sched) == 0) { - - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - cost = 1; - } - - outer_loop_recompute_criticalities(placer_opts, num_connections, - crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, - &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, - netlist_pin_lookup, -#ifdef ENABLE_CLASSIC_VPR_STA - slacks, - timing_inf, -#endif - *timing_info); - - placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, - move_lim, crit_exponent, inner_recompute_limit, &stats, - &cost, &bb_cost, &timing_cost, &delay_cost, -#ifdef ENABLE_CLASSIC_VPR_STA - slacks, - timing_inf, -#endif - netlist_pin_lookup, - *timing_info); - - /* Lines below prevent too much round-off error from accumulating * - * in the cost over many iterations. This round-off can lead to * - * error checks failing because the cost is different from what * - * you get when you recompute from scratch. */ - - moves_since_cost_recompute += move_lim; - if (moves_since_cost_recompute > MAX_MOVES_BEFORE_RECOMPUTE) { - new_bb_cost = recompute_bb_cost(); - if (fabs(new_bb_cost - bb_cost) > bb_cost * ERROR_TOL) { - vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, - "in try_place: new_bb_cost = %g, old bb_cost = %g\n", - new_bb_cost, bb_cost); - } - bb_cost = new_bb_cost; - - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - comp_td_costs(&new_timing_cost, &new_delay_cost); - if (fabs(new_timing_cost - timing_cost) > timing_cost * ERROR_TOL) { - vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, - "in try_place: new_timing_cost = %g, old timing_cost = %g, ERROR_TOL = %g\n", - new_timing_cost, timing_cost, ERROR_TOL); - } - if (fabs(new_delay_cost - delay_cost) > delay_cost * ERROR_TOL) { - vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, - "in try_place: new_delay_cost = %g, old delay_cost = %g, ERROR_TOL = %g\n", - new_delay_cost, delay_cost, ERROR_TOL); - } - timing_cost = new_timing_cost; - } - - if (placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { - cost = new_bb_cost; - } - moves_since_cost_recompute = 0; - } - - tot_iter += move_lim; - success_rat = ((float) stats.success_sum) / move_lim; - if (stats.success_sum == 0) { - stats.av_cost = cost; - stats.av_bb_cost = bb_cost; - stats.av_timing_cost = timing_cost; - stats.av_delay_cost = delay_cost; - } else { - stats.av_cost /= stats.success_sum; - stats.av_bb_cost /= stats.success_sum; - stats.av_timing_cost /= stats.success_sum; - stats.av_delay_cost /= stats.success_sum; - } - std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); - - oldt = t; /* for finding and printing alpha. */ - update_t(&t, rlim, success_rat, annealing_sched); - - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - critical_path = timing_info->least_slack_critical_path(); - sTNS = timing_info->setup_total_negative_slack(); - sWNS = timing_info->setup_worst_negative_slack(); - } - - vtr::printf_info("%7.3f " - "%7.4f %10.4f %-10.5g %-10.5g " - "%-10.5g %7.3f % 10.3g % 8.3f " - "%7.4f %7.4f %7.4f %6.3f" - "%9d %6.3f\n", - oldt, - stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, - place_delay_value, 1e9*critical_path.delay(), 1e9*sTNS, 1e9*sWNS, - success_rat, std_dev, rlim, crit_exponent, - tot_iter, t / oldt); - -#ifdef ENABLE_CLASSIC_VPR_STA - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); - if(cpd_diff_ns > ERROR_TOL) { - print_classic_cpds(); - print_tatum_cpds(timing_info->critical_paths()); - - vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); - } - } -#endif - - sprintf(msg, "Cost: %g BB Cost %g TD Cost %g Temperature: %g", - cost, bb_cost, timing_cost, t); - update_screen(ScreenUpdatePriority::MINOR, msg, PLACEMENT, timing_info); - update_rlim(&rlim, success_rat, device_ctx.grid); - - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - crit_exponent = (1 - (rlim - final_rlim) * inverse_delta_rlim) - * (placer_opts.td_place_exp_last - placer_opts.td_place_exp_first) - + placer_opts.td_place_exp_first; - } -#ifdef VERBOSE - if (getEchoEnabled()) { - print_clb_placement("first_iteration_clb_placement.echo"); - } -#endif - } - /* Outer loop of the simmulated annealing ends */ - - - outer_loop_recompute_criticalities(placer_opts, num_connections, - crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, - &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, - netlist_pin_lookup, -#ifdef ENABLE_CLASSIC_VPR_STA - slacks, - timing_inf, -#endif - *timing_info); - - t = 0; /* freeze out */ - - /* Run inner loop again with temperature = 0 so as to accept only swaps - * which reduce the cost of the placement */ - placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, - move_lim, crit_exponent, inner_recompute_limit, &stats, - &cost, &bb_cost, &timing_cost, &delay_cost, -#ifdef ENABLE_CLASSIC_VPR_STA - slacks, - timing_inf, -#endif - netlist_pin_lookup, - *timing_info); - - tot_iter += move_lim; - success_rat = ((float) stats.success_sum) / move_lim; - if (stats.success_sum == 0) { - stats.av_cost = cost; - stats.av_bb_cost = bb_cost; - stats.av_delay_cost = delay_cost; - stats.av_timing_cost = timing_cost; - } else { - stats.av_cost /= stats.success_sum; - stats.av_bb_cost /= stats.success_sum; - stats.av_delay_cost /= stats.success_sum; - stats.av_timing_cost /= stats.success_sum; - } - - std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); - - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - critical_path = timing_info->least_slack_critical_path(); - sTNS = timing_info->setup_total_negative_slack(); - sWNS = timing_info->setup_worst_negative_slack(); - } - - vtr::printf_info("%7.3f " - "%7.4f %10.4f %-10.5g %-10.5g " - "%-10.5g %7.3f % 10.3g % 8.3f " - "%7.4f %7.4f %7.4f %6.3f" - "%9d %6.3f\n", - t, - stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, - place_delay_value, 1e9*critical_path.delay(), 1e9*sTNS, 1e9*sWNS, - success_rat, std_dev, rlim, crit_exponent, - tot_iter, 0.); - - // TODO: - // 1. print a message about number of aborted moves. - // 2. add some subroutine hierarchy! Too big! - -#ifdef VERBOSE - if (getEchoEnabled() && isEchoFileEnabled(E_ECHO_END_CLB_PLACEMENT)) { - print_clb_placement(getEchoFileName(E_ECHO_END_CLB_PLACEMENT)); - } -#endif - - check_place(bb_cost, timing_cost, placer_opts.place_algorithm, delay_cost); - - //Some stats - vtr::printf_info("\n"); - vtr::printf_info("Swaps called: %d\n", num_ts_called); - - if (placer_opts.enable_timing_computations - && placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { - /*need this done since the timing data has not been kept up to date* - *in bounding_box mode */ - for (auto net_id : cluster_ctx.clb_nlist.nets()) { - for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) - set_timing_place_crit(net_id, ipin, 0); /*dummy crit values */ - } - comp_td_costs(&timing_cost, &delay_cost); /*computes point_to_point_delay_cost */ - } - - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE - || placer_opts.enable_timing_computations) { - - //Final timing estimate - VTR_ASSERT(timing_info); - timing_info->update(); //Tatum - critical_path = timing_info->least_slack_critical_path(); - - if(isEchoFileEnabled(E_ECHO_FINAL_PLACEMENT_TIMING_GRAPH)) { - auto& timing_ctx = g_vpr_ctx.timing(); - - tatum::write_echo(getEchoFileName(E_ECHO_FINAL_PLACEMENT_TIMING_GRAPH), - *timing_ctx.graph, *timing_ctx.constraints, *placement_delay_calc, timing_info->analyzer()); - } - -#ifdef ENABLE_CLASSIC_VPR_STA - //Old VPR analyzer - load_timing_graph_net_delays(point_to_point_delay_cost); - do_timing_analysis(slacks, timing_inf, false, true); -#endif - - - /* Print critical path delay. */ - vtr::printf_info("\n"); - vtr::printf_info("Placement estimated critical path delay: %g ns", - 1e9*critical_path.delay(), get_critical_path_delay()); -#ifdef ENABLE_CLASSIC_VPR_STA - vtr::printf_info(" (classic VPR STA %g ns)", get_critical_path_delay()); -#endif - vtr::printf("\n"); - vtr::printf_info("Placement estimated setup Total Negative Slack (sTNS): %g ns\n", - 1e9*timing_info->setup_total_negative_slack()); - vtr::printf_info("Placement estimated setup Worst Negative Slack (sWNS): %g ns\n", - 1e9*timing_info->setup_worst_negative_slack()); - vtr::printf_info("\n"); - - vtr::printf_info("Placement estimated setup slack histogram:\n"); - print_histogram(create_setup_slack_histogram(*timing_info->setup_analyzer())); - vtr::printf_info("\n"); - -#ifdef ENABLE_CLASSIC_VPR_STA - float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); - if(cpd_diff_ns > ERROR_TOL) { - print_classic_cpds(); - print_tatum_cpds(timing_info->critical_paths()); - - vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); - } -#endif - } - - sprintf(msg, "Placement. Cost: %g bb_cost: %g td_cost: %g Channel Factor: %d", - cost, bb_cost, timing_cost, width_fac); - vtr::printf_info("Placement cost: %g, bb_cost: %g, td_cost: %g, delay_cost: %g\n", - cost, bb_cost, timing_cost, delay_cost); - update_screen(ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info); - - // Print out swap statistics - size_t total_swap_attempts = num_swap_rejected + num_swap_accepted + num_swap_aborted; - VTR_ASSERT(total_swap_attempts > 0); - - size_t num_swap_print_digits = ceil(log10(total_swap_attempts)); - float reject_rate = (float) num_swap_rejected / total_swap_attempts; - float accept_rate = (float) num_swap_accepted / total_swap_attempts; - float abort_rate = (float) num_swap_aborted / total_swap_attempts; - vtr::printf_info("Placement total # of swap attempts: %*d\n", num_swap_print_digits, total_swap_attempts); - vtr::printf_info("\tSwaps accepted: %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_accepted, 100*accept_rate); - vtr::printf_info("\tSwaps rejected: %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_rejected, 100*reject_rate); - vtr::printf_info("\tSwaps aborted : %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_aborted, 100*abort_rate); - - free_placement_structs(placer_opts); - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE - || placer_opts.enable_timing_computations) { - -#ifdef ENABLE_CLASSIC_VPR_STA - free_timing_graph(slacks); -#endif - - free_lookups_and_criticalities(); - } - - free_try_swap_arrays(); -} - -/* Function to recompute the criticalities before the inner loop of the annealing */ -static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, - int num_connections, float crit_exponent, float bb_cost, - float * place_delay_value, float * timing_cost, float * delay_cost, - int * outer_crit_iter_count, float * inverse_prev_timing_cost, - float * inverse_prev_bb_cost, - const ClusteredPinAtomPinsLookup& netlist_pin_lookup, -#ifdef ENABLE_CLASSIC_VPR_STA - t_slack* slacks, - t_timing_inf timing_inf, -#endif - SetupTimingInfo& timing_info) { - - if (placer_opts.place_algorithm != PATH_TIMING_DRIVEN_PLACE) - return; - - /*at each temperature change we update these values to be used */ - /*for normalizing the tradeoff between timing and wirelength (bb) */ - if (*outer_crit_iter_count >= placer_opts.recompute_crit_iter - || placer_opts.inner_loop_recompute_divider != 0) { -#ifdef VERBOSE - vtr::printf_info("Outer loop recompute criticalities\n"); -#endif - num_connections = std::max(num_connections, 1); //Avoid division by zero - VTR_ASSERT(num_connections > 0); - - *place_delay_value = (*delay_cost) / num_connections; - - //Per-temperature timing update - timing_info.update(); - load_criticalities(timing_info, crit_exponent, netlist_pin_lookup); - -#ifdef ENABLE_CLASSIC_VPR_STA - load_timing_graph_net_delays(point_to_point_delay_cost); - do_timing_analysis(slacks, timing_inf, false, true); -#endif - - /*recompute costs from scratch, based on new criticalities */ - comp_td_costs(timing_cost, delay_cost); - *outer_crit_iter_count = 0; - } - (*outer_crit_iter_count)++; - - /*at each temperature change we update these values to be used */ - /*for normalizing the tradeoff between timing and wirelength (bb) */ -*inverse_prev_bb_cost = 1 / bb_cost; -/*Prevent inverse timing cost from going to infinity */ -*inverse_prev_timing_cost = min(1 / (*timing_cost), (float)MAX_INV_TIMING_COST); -} - -/* Function which contains the inner loop of the simulated annealing */ -static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, - float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, - float crit_exponent, int inner_recompute_limit, - t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, - float * delay_cost, -#ifdef ENABLE_CLASSIC_VPR_STA - t_slack* slacks, - t_timing_inf timing_inf, -#endif - const ClusteredPinAtomPinsLookup& netlist_pin_lookup, - SetupTimingInfo& timing_info) { - - int inner_crit_iter_count, inner_iter; - - stats->av_cost = 0.; - stats->av_bb_cost = 0.; - stats->av_delay_cost = 0.; - stats->av_timing_cost = 0.; - stats->sum_of_squares = 0.; - stats->success_sum = 0; - - inner_crit_iter_count = 1; - - /* Inner loop begins */ - for (inner_iter = 0; inner_iter < move_lim; inner_iter++) { - e_swap_result swap_result = try_swap(t, cost, bb_cost, timing_cost, rlim, - placer_opts.place_algorithm, placer_opts.timing_tradeoff, - inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost); - - if (swap_result == ACCEPTED) { - /* Move was accepted. Update statistics that are useful for the annealing schedule. */ - stats->success_sum++; - stats->av_cost += *cost; - stats->av_bb_cost += *bb_cost; - stats->av_timing_cost += *timing_cost; - stats->av_delay_cost += *delay_cost; - stats->sum_of_squares += (*cost) * (*cost); - num_swap_accepted++; - } - else if (swap_result == ABORTED) { - num_swap_aborted++; - } - else { // swap_result == REJECTED - num_swap_rejected++; - } - - - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - - /* Do we want to re-timing analyze the circuit to get updated slack and criticality values? - * We do this only once in a while, since it is expensive. - */ - if (inner_crit_iter_count >= inner_recompute_limit - && inner_iter != move_lim - 1) { /*on last iteration don't recompute */ - - inner_crit_iter_count = 0; -#ifdef VERBOSE - vtr::printf("Inner loop recompute criticalities\n"); -#endif - /* Using the delays in net_delay, do a timing analysis to update slacks and - * criticalities; then update the timing cost since it will change. - */ - //Inner loop timing update - timing_info.update(); - load_criticalities(timing_info, crit_exponent, netlist_pin_lookup); - -#ifdef ENABLE_CLASSIC_VPR_STA - load_timing_graph_net_delays(point_to_point_delay_cost); - do_timing_analysis(slacks, timing_inf, false, true); -#endif - - comp_td_costs(timing_cost, delay_cost); - } - inner_crit_iter_count++; - } -#ifdef VERBOSE - vtr::printf("t = %g cost = %g bb_cost = %g timing_cost = %g move = %d dmax = %g\n", - t, *cost, *bb_cost, *timing_cost, inner_iter, *delay_cost); - if (fabs((*bb_cost) - comp_bb_cost(CHECK)) > (*bb_cost) * ERROR_TOL) - vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, - "fabs((*bb_cost) - comp_bb_cost(CHECK)) > (*bb_cost) * ERROR_TOL"); -#endif - } - /* Inner loop ends */ -} - -/*only count non-global connections */ -static int count_connections() { - - int count = 0; - - auto& cluster_ctx = g_vpr_ctx.clustering(); - for (auto net_id : cluster_ctx.clb_nlist.nets()) { - if (cluster_ctx.clb_nlist.net_is_global(net_id)) - continue; - - count += cluster_ctx.clb_nlist.net_sinks(net_id).size(); - } - - return (count); -} - -static double get_std_dev(int n, double sum_x_squared, double av_x) { - - /* Returns the standard deviation of data set x. There are n sample points, * - * sum_x_squared is the summation over n of x^2 and av_x is the average x. * - * All operations are done in double precision, since round off error can be * - * a problem in the initial temp. std_dev calculation for big circuits. */ - - double std_dev; - - if (n <= 1) - std_dev = 0.; - else - std_dev = (sum_x_squared - n * av_x * av_x) / (double) (n - 1); - - if (std_dev > 0.) /* Very small variances sometimes round negative */ - std_dev = sqrt(std_dev); - else - std_dev = 0.; - - return (std_dev); -} - -static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid) { - - /* Update the range limited to keep acceptance prob. near 0.44. Use * - * a floating point rlim to allow gradual transitions at low temps. */ - - float upper_lim; - - *rlim = (*rlim) * (1. - 0.44 + success_rat); - upper_lim = max(grid.width() - 1, grid.height() - 1); - *rlim = min(*rlim, upper_lim); - *rlim = max(*rlim, (float)1.); -} - -/* Update the temperature according to the annealing schedule selected. */ -static void update_t(float *t, float rlim, float success_rat, - t_annealing_sched annealing_sched) { - - /* float fac; */ - - if (annealing_sched.type == USER_SCHED) { - *t = annealing_sched.alpha_t * (*t); - } else { /* AUTO_SCHED */ - if (success_rat > 0.96) { - *t = (*t) * 0.5; - } else if (success_rat > 0.8) { - *t = (*t) * 0.9; - } else if (success_rat > 0.15 || rlim > 1.) { - *t = (*t) * 0.95; - } else { - *t = (*t) * 0.8; - } - } -} - -static int exit_crit(float t, float cost, - t_annealing_sched annealing_sched) { - - /* Return 1 when the exit criterion is met. */ - - if (annealing_sched.type == USER_SCHED) { - if (t < annealing_sched.exit_t) { - return (1); - } else { - return (0); - } - } - - auto& cluster_ctx = g_vpr_ctx.clustering(); - - /* Automatic annealing schedule */ - float t_exit = 0.005 * cost / cluster_ctx.clb_nlist.nets().size(); - - if (t < t_exit) { - return (1); - } else if (std::isnan(t_exit)) { - //May get nan if there are no nets - return (1); - } else { - return (0); - } -} - -static float starting_t(float *cost_ptr, float *bb_cost_ptr, - float *timing_cost_ptr, - t_annealing_sched annealing_sched, int max_moves, float rlim, - enum e_place_algorithm place_algorithm, float timing_tradeoff, - float inverse_prev_bb_cost, float inverse_prev_timing_cost, - float *delay_cost_ptr) { - - /* Finds the starting temperature (hot condition). */ - - int i, num_accepted, move_lim; - double std_dev, av, sum_of_squares; /* Double important to avoid round off */ - - if (annealing_sched.type == USER_SCHED) - return (annealing_sched.init_t); - - auto& cluster_ctx = g_vpr_ctx.clustering(); - - move_lim = min(max_moves, (int) cluster_ctx.clb_nlist.blocks().size()); - - num_accepted = 0; - av = 0.; - sum_of_squares = 0.; - - /* Try one move per block. Set t high so essentially all accepted. */ - - for (i = 0; i < move_lim; i++) { - e_swap_result swap_result = try_swap(HUGE_POSITIVE_FLOAT, cost_ptr, bb_cost_ptr, timing_cost_ptr, rlim, - place_algorithm, timing_tradeoff, - inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost_ptr); - - if (swap_result == ACCEPTED) { - num_accepted++; - av += *cost_ptr; - sum_of_squares += *cost_ptr * (*cost_ptr); - num_swap_accepted++; - } else if (swap_result == ABORTED) { - num_swap_aborted++; - } else { - num_swap_rejected++; - } - } - - if (num_accepted != 0) - av /= num_accepted; - else - av = 0.; - - std_dev = get_std_dev(num_accepted, sum_of_squares, av); - - if (num_accepted != move_lim) { - vtr::printf_warning(__FILE__, __LINE__, - "Starting t: %d of %d configurations accepted.\n", num_accepted, move_lim); - } - -#ifdef VERBOSE - vtr::printf_info("std_dev: %g, average cost: %g, starting temp: %g\n", std_dev, av, 20. * std_dev); -#endif - - /* Set the initial temperature to 20 times the standard of deviation */ - /* so that the initial temperature adjusts according to the circuit */ - return (20. * std_dev); +// t = starting_t(&cost, &bb_cost, &timing_cost, +// /*annealing_sched,*/ move_lim, rlim, +// /*placer_opts.place_algorithm, placer_opts.timing_tradeoff,*/ +// inverse_prev_bb_cost, inverse_prev_timing_cost, &delay_cost); +// +// tot_iter = 0; +// moves_since_cost_recompute = 0; +// +// +// +// +// /* Outer loop of the simmulated annealing begins */ +// while (exit_crit(t, cost, annealing_sched) == 0) { +// +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// cost = 1; +// } +// +// outer_loop_recompute_criticalities(placer_opts, num_connections, +// crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, +// &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, +// netlist_pin_lookup, +//#ifdef ENABLE_CLASSIC_VPR_STA +// slacks, +// timing_inf, +//#endif +// *timing_info); +// +// placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, +// move_lim, crit_exponent, inner_recompute_limit, &stats, +// &cost, &bb_cost, &timing_cost, &delay_cost, +//#ifdef ENABLE_CLASSIC_VPR_STA +// slacks, +// timing_inf, +//#endif +// netlist_pin_lookup, +// *timing_info); +// +// /* Lines below prevent too much round-off error from accumulating * +// * in the cost over many iterations. This round-off can lead to * +// * error checks failing because the cost is different from what * +// * you get when you recompute from scratch. */ +// +// moves_since_cost_recompute += move_lim; +// if (moves_since_cost_recompute > MAX_MOVES_BEFORE_RECOMPUTE) { +// new_bb_cost = recompute_bb_cost(); +// if (fabs(new_bb_cost - bb_cost) > bb_cost * ERROR_TOL) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "in try_place: new_bb_cost = %g, old bb_cost = %g\n", +// new_bb_cost, bb_cost); +// } +// bb_cost = new_bb_cost; +// +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// comp_td_costs(&new_timing_cost, &new_delay_cost); +// if (fabs(new_timing_cost - timing_cost) > timing_cost * ERROR_TOL) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "in try_place: new_timing_cost = %g, old timing_cost = %g, ERROR_TOL = %g\n", +// new_timing_cost, timing_cost, ERROR_TOL); +// } +// if (fabs(new_delay_cost - delay_cost) > delay_cost * ERROR_TOL) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "in try_place: new_delay_cost = %g, old delay_cost = %g, ERROR_TOL = %g\n", +// new_delay_cost, delay_cost, ERROR_TOL); +// } +// timing_cost = new_timing_cost; +// } +// +// if (placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { +// cost = new_bb_cost; +// } +// moves_since_cost_recompute = 0; +// } +// +// tot_iter += move_lim; +// success_rat = ((float) stats.success_sum) / move_lim; +// if (stats.success_sum == 0) { +// stats.av_cost = cost; +// stats.av_bb_cost = bb_cost; +// stats.av_timing_cost = timing_cost; +// stats.av_delay_cost = delay_cost; +// } else { +// stats.av_cost /= stats.success_sum; +// stats.av_bb_cost /= stats.success_sum; +// stats.av_timing_cost /= stats.success_sum; +// stats.av_delay_cost /= stats.success_sum; +// } +// std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); +// +// oldt = t; /* for finding and printing alpha. */ +// update_t(&t, rlim, success_rat, annealing_sched); +// +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// critical_path = timing_info->least_slack_critical_path(); +// sTNS = timing_info->setup_total_negative_slack(); +// sWNS = timing_info->setup_worst_negative_slack(); +// } +// +// vtr::printf_info("%7.3f " +// "%7.4f %10.4f %-10.5g %-10.5g " +// "%-10.5g %7.3f % 10.3g % 8.3f " +// "%7.4f %7.4f %7.4f %6.3f" +// "%9d %6.3f\n", +// oldt, +// stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, +// place_delay_value, 1e9*critical_path.delay(), 1e9*sTNS, 1e9*sWNS, +// success_rat, std_dev, rlim, crit_exponent, +// tot_iter, t / oldt); +// +//#ifdef ENABLE_CLASSIC_VPR_STA +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); +// if(cpd_diff_ns > ERROR_TOL) { +// print_classic_cpds(); +// print_tatum_cpds(timing_info->critical_paths()); +// +// vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); +// } +// } +//#endif +// +// sprintf(msg, "Cost: %g BB Cost %g TD Cost %g Temperature: %g", +// cost, bb_cost, timing_cost, t); +// update_screen(ScreenUpdatePriority::MINOR, msg, PLACEMENT, timing_info); +// update_rlim(&rlim, success_rat, device_ctx.grid); +// +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// crit_exponent = (1 - (rlim - final_rlim) * inverse_delta_rlim) +// * (placer_opts.td_place_exp_last - placer_opts.td_place_exp_first) +// + placer_opts.td_place_exp_first; +// } +//#ifdef VERBOSE +// if (getEchoEnabled()) { +// print_clb_placement("first_iteration_clb_placement.echo"); +// } +//#endif +// } +// /* Outer loop of the simmulated annealing ends */ +// +// +// outer_loop_recompute_criticalities(placer_opts, num_connections, +// crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, +// &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, +// netlist_pin_lookup, +//#ifdef ENABLE_CLASSIC_VPR_STA +// slacks, +// timing_inf, +//#endif +// *timing_info); +// +// t = 0; /* freeze out */ +// +// /* Run inner loop again with temperature = 0 so as to accept only swaps +// * which reduce the cost of the placement */ +// placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, +// move_lim, crit_exponent, inner_recompute_limit, &stats, +// &cost, &bb_cost, &timing_cost, &delay_cost, +//#ifdef ENABLE_CLASSIC_VPR_STA +// slacks, +// timing_inf, +//#endif +// netlist_pin_lookup, +// *timing_info); +// +// tot_iter += move_lim; +// success_rat = ((float) stats.success_sum) / move_lim; +// if (stats.success_sum == 0) { +// stats.av_cost = cost; +// stats.av_bb_cost = bb_cost; +// stats.av_delay_cost = delay_cost; +// stats.av_timing_cost = timing_cost; +// } else { +// stats.av_cost /= stats.success_sum; +// stats.av_bb_cost /= stats.success_sum; +// stats.av_delay_cost /= stats.success_sum; +// stats.av_timing_cost /= stats.success_sum; +// } +// +// std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); +// +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// critical_path = timing_info->least_slack_critical_path(); +// sTNS = timing_info->setup_total_negative_slack(); +// sWNS = timing_info->setup_worst_negative_slack(); +// } +// +// vtr::printf_info("%7.3f " +// "%7.4f %10.4f %-10.5g %-10.5g " +// "%-10.5g %7.3f % 10.3g % 8.3f " +// "%7.4f %7.4f %7.4f %6.3f" +// "%9d %6.3f\n", +// t, +// stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, +// place_delay_value, 1e9*critical_path.delay(), 1e9*sTNS, 1e9*sWNS, +// success_rat, std_dev, rlim, crit_exponent, +// tot_iter, 0.); +// +// // TODO: +// // 1. print a message about number of aborted moves. +// // 2. add some subroutine hierarchy! Too big! +// +//#ifdef VERBOSE +// if (getEchoEnabled() && isEchoFileEnabled(E_ECHO_END_CLB_PLACEMENT)) { +// print_clb_placement(getEchoFileName(E_ECHO_END_CLB_PLACEMENT)); +// } +//#endif +// +// check_place(bb_cost, timing_cost, placer_opts.place_algorithm, delay_cost); +// +// //Some stats +// vtr::printf_info("\n"); +// vtr::printf_info("Swaps called: %d\n", num_ts_called); +// +// if (placer_opts.enable_timing_computations +// && placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { +// /*need this done since the timing data has not been kept up to date* +// *in bounding_box mode */ +// for (auto net_id : cluster_ctx.clb_nlist.nets()) { +// for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) +// set_timing_place_crit(net_id, ipin, 0); /*dummy crit values */ +// } +// comp_td_costs(&timing_cost, &delay_cost); /*computes point_to_point_delay_cost */ +// } +// +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE +// || placer_opts.enable_timing_computations) { +// +// //Final timing estimate +// VTR_ASSERT(timing_info); +// timing_info->update(); //Tatum +// critical_path = timing_info->least_slack_critical_path(); +// +// if(isEchoFileEnabled(E_ECHO_FINAL_PLACEMENT_TIMING_GRAPH)) { +// auto& timing_ctx = g_vpr_ctx.timing(); +// +// tatum::write_echo(getEchoFileName(E_ECHO_FINAL_PLACEMENT_TIMING_GRAPH), +// *timing_ctx.graph, *timing_ctx.constraints, *placement_delay_calc, timing_info->analyzer()); +// } +// +//#ifdef ENABLE_CLASSIC_VPR_STA +// //Old VPR analyzer +// load_timing_graph_net_delays(point_to_point_delay_cost); +// do_timing_analysis(slacks, timing_inf, false, true); +//#endif +// +// +// /* Print critical path delay. */ +// vtr::printf_info("\n"); +// vtr::printf_info("Placement estimated critical path delay: %g ns", +// 1e9*critical_path.delay(), get_critical_path_delay()); +//#ifdef ENABLE_CLASSIC_VPR_STA +// vtr::printf_info(" (classic VPR STA %g ns)", get_critical_path_delay()); +//#endif +// vtr::printf("\n"); +// vtr::printf_info("Placement estimated setup Total Negative Slack (sTNS): %g ns\n", +// 1e9*timing_info->setup_total_negative_slack()); +// vtr::printf_info("Placement estimated setup Worst Negative Slack (sWNS): %g ns\n", +// 1e9*timing_info->setup_worst_negative_slack()); +// vtr::printf_info("\n"); +// +// vtr::printf_info("Placement estimated setup slack histogram:\n"); +// print_histogram(create_setup_slack_histogram(*timing_info->setup_analyzer())); +// vtr::printf_info("\n"); +// +//#ifdef ENABLE_CLASSIC_VPR_STA +// float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); +// if(cpd_diff_ns > ERROR_TOL) { +// print_classic_cpds(); +// print_tatum_cpds(timing_info->critical_paths()); +// +// vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); +// } +//#endif +// } +// +// sprintf(msg, "Placement. Cost: %g bb_cost: %g td_cost: %g Channel Factor: %d", +// cost, bb_cost, timing_cost, width_fac); +// vtr::printf_info("Placement cost: %g, bb_cost: %g, td_cost: %g, delay_cost: %g\n", +// cost, bb_cost, timing_cost, delay_cost); +// update_screen(ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info); +// +// // Print out swap statistics +// size_t total_swap_attempts = num_swap_rejected + num_swap_accepted + num_swap_aborted; +// VTR_ASSERT(total_swap_attempts > 0); +// +// size_t num_swap_print_digits = ceil(log10(total_swap_attempts)); +// float reject_rate = (float) num_swap_rejected / total_swap_attempts; +// float accept_rate = (float) num_swap_accepted / total_swap_attempts; +// float abort_rate = (float) num_swap_aborted / total_swap_attempts; +// vtr::printf_info("Placement total # of swap attempts: %*d\n", num_swap_print_digits, total_swap_attempts); +// vtr::printf_info("\tSwaps accepted: %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_accepted, 100*accept_rate); +// vtr::printf_info("\tSwaps rejected: %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_rejected, 100*reject_rate); +// vtr::printf_info("\tSwaps aborted : %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_aborted, 100*abort_rate); +// +// free_placement_structs(placer_opts); +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE +// || placer_opts.enable_timing_computations) { +// +//#ifdef ENABLE_CLASSIC_VPR_STA +// free_timing_graph(slacks); +//#endif +// +// free_lookups_and_criticalities(); +// } +// +// free_try_swap_arrays(); } - -static int setup_blocks_affected(ClusterBlockId b_from, int x_to, int y_to, int z_to) { - - /* Find all the blocks affected when b_from is swapped with b_to. - * Returns abort_swap. */ - - int imoved_blk, imacro; - int x_from, y_from, z_from; - ClusterBlockId b_to; - int abort_swap = false; - - auto& place_ctx = g_vpr_ctx.mutable_placement(); - - x_from = place_ctx.block_locs[b_from].x; - y_from = place_ctx.block_locs[b_from].y; - z_from = place_ctx.block_locs[b_from].z; - - b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; - - // Check whether the to_location is empty - if (b_to == EMPTY_BLOCK_ID) { - - // Swap the block, dont swap the nets yet - place_ctx.block_locs[b_from].x = x_to; - place_ctx.block_locs[b_from].y = y_to; - place_ctx.block_locs[b_from].z = z_to; - - // Sets up the blocks moved - imoved_blk = blocks_affected.num_moved_blocks; - blocks_affected.moved_blocks[imoved_blk].block_num = b_from; - blocks_affected.moved_blocks[imoved_blk].xold = x_from; - blocks_affected.moved_blocks[imoved_blk].xnew = x_to; - blocks_affected.moved_blocks[imoved_blk].yold = y_from; - blocks_affected.moved_blocks[imoved_blk].ynew = y_to; - blocks_affected.moved_blocks[imoved_blk].zold = z_from; - blocks_affected.moved_blocks[imoved_blk].znew = z_to; - blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = true; - blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = true; - blocks_affected.num_moved_blocks ++; - - } else if (b_to != INVALID_BLOCK_ID) { - - // Does not allow a swap with a macro yet - get_imacro_from_iblk(&imacro, b_to, pl_macros, num_pl_macros); - if (imacro != -1) { - abort_swap = true; - return (abort_swap); - } - - // Swap the block, dont swap the nets yet - place_ctx.block_locs[b_to].x = x_from; - place_ctx.block_locs[b_to].y = y_from; - place_ctx.block_locs[b_to].z = z_from; - - place_ctx.block_locs[b_from].x = x_to; - place_ctx.block_locs[b_from].y = y_to; - place_ctx.block_locs[b_from].z = z_to; - - // Sets up the blocks moved - imoved_blk = blocks_affected.num_moved_blocks; - blocks_affected.moved_blocks[imoved_blk].block_num = b_from; - blocks_affected.moved_blocks[imoved_blk].xold = x_from; - blocks_affected.moved_blocks[imoved_blk].xnew = x_to; - blocks_affected.moved_blocks[imoved_blk].yold = y_from; - blocks_affected.moved_blocks[imoved_blk].ynew = y_to; - blocks_affected.moved_blocks[imoved_blk].zold = z_from; - blocks_affected.moved_blocks[imoved_blk].znew = z_to; - blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = false; - blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = false; - blocks_affected.num_moved_blocks ++; - - imoved_blk = blocks_affected.num_moved_blocks; - blocks_affected.moved_blocks[imoved_blk].block_num = b_to; - blocks_affected.moved_blocks[imoved_blk].xold = x_to; - blocks_affected.moved_blocks[imoved_blk].xnew = x_from; - blocks_affected.moved_blocks[imoved_blk].yold = y_to; - blocks_affected.moved_blocks[imoved_blk].ynew = y_from; - blocks_affected.moved_blocks[imoved_blk].zold = z_to; - blocks_affected.moved_blocks[imoved_blk].znew = z_from; - blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = false; - blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = false; - blocks_affected.num_moved_blocks ++; - - } // Finish swapping the blocks and setting up blocks_affected - - return (abort_swap); - -} - -static int find_affected_blocks(ClusterBlockId b_from, int x_to, int y_to, int z_to) { - - /* Finds and set ups the affected_blocks array. - * Returns abort_swap. */ - - int imacro, imember; - int x_swap_offset, y_swap_offset, z_swap_offset, x_from, y_from, z_from; - ClusterBlockId curr_b_from; - int curr_x_from, curr_y_from, curr_z_from, curr_x_to, curr_y_to, curr_z_to; - int abort_swap = false; - - auto& place_ctx = g_vpr_ctx.placement(); - auto& device_ctx = g_vpr_ctx.device(); - auto& cluster_ctx = g_vpr_ctx.clustering(); - - x_from = place_ctx.block_locs[b_from].x; - y_from = place_ctx.block_locs[b_from].y; - z_from = place_ctx.block_locs[b_from].z; - - get_imacro_from_iblk(&imacro, b_from, pl_macros, num_pl_macros); - if ( imacro != -1) { - // b_from is part of a macro, I need to swap the whole macro - - // Record down the relative position of the swap - x_swap_offset = x_to - x_from; - y_swap_offset = y_to - y_from; - z_swap_offset = z_to - z_from; - - for (imember = 0; imember < pl_macros[imacro].num_blocks && abort_swap == false; imember++) { - - // Gets the new from and to info for every block in the macro - // cannot use the old from and to info - curr_b_from = pl_macros[imacro].members[imember].blk_index; - - curr_x_from = place_ctx.block_locs[curr_b_from].x; - curr_y_from = place_ctx.block_locs[curr_b_from].y; - curr_z_from = place_ctx.block_locs[curr_b_from].z; - - curr_x_to = curr_x_from + x_swap_offset; - curr_y_to = curr_y_from + y_swap_offset; - curr_z_to = curr_z_from + z_swap_offset; - - //Make sure that the swap_to location is valid - //It must be: - // * chip, and - // * match the correct block type - // - //Note that we need to explicitly check that the types match, since the device floorplan is not - //(neccessarily) translationally invariant for an arbitrary macro - if ( curr_x_to < 1 || curr_x_to >= int(device_ctx.grid.width()) - || curr_y_to < 1 || curr_y_to >= int(device_ctx.grid.height()) - || curr_z_to < 0 - || device_ctx.grid[curr_x_to][curr_y_to].type != cluster_ctx.clb_nlist.block_type(curr_b_from)) { - abort_swap = true; - } else { - abort_swap = setup_blocks_affected(curr_b_from, curr_x_to, curr_y_to, curr_z_to); - } - } // Finish going through all the blocks in the macro - - } else { - // This is not a macro - I could use the from and to info from before - abort_swap = setup_blocks_affected(b_from, x_to, y_to, z_to); - - } // Finish handling cases for blocks in macro and otherwise - - return (abort_swap); - -} - -static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, - float rlim, - enum e_place_algorithm place_algorithm, float timing_tradeoff, - float inverse_prev_bb_cost, float inverse_prev_timing_cost, - float *delay_cost) { - - /* Picks some block and moves it to another spot. If this spot is * - * occupied, switch the blocks. Assess the change in cost function. * - * rlim is the range limiter. * - * Returns whether the swap is accepted, rejected or aborted. * - * Passes back the new value of the cost functions. */ - - auto& cluster_ctx = g_vpr_ctx.clustering(); - auto& place_ctx = g_vpr_ctx.mutable_placement(); - - num_ts_called ++; - - /* I'm using negative values of temp_net_cost as a flag, so DO NOT * - * use cost functions that can go negative. */ - - float delta_c = 0; /* Change in cost due to this swap. */ - float bb_delta_c = 0; - float timing_delta_c = 0; - float delay_delta_c = 0.0; - - /* Pick a random block to be swapped with another random block. */ - ClusterBlockId b_from = pick_from_block(); - if (!b_from) { - return ABORTED; //No movable block found - } - - int x_from = place_ctx.block_locs[b_from].x; - int y_from = place_ctx.block_locs[b_from].y; - int z_from = place_ctx.block_locs[b_from].z; - - int x_to = OPEN; - int y_to = OPEN; - int z_to = OPEN; - - auto cluster_from_type = cluster_ctx.clb_nlist.block_type(b_from); - auto grid_from_type = g_vpr_ctx.device().grid[x_from][y_from].type; - VTR_ASSERT(cluster_from_type == grid_from_type); - - if (!find_to(cluster_ctx.clb_nlist.block_type(b_from), rlim, x_from, y_from, &x_to, &y_to, &z_to)) - return REJECTED; - -#if 0 - auto& grid = g_vpr_ctx.device().grid; - int b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; - vtr::printf_info( "swap [%d][%d][%d] %s \"%s\" <=> [%d][%d][%d] %s \"%s\"\n", - x_from, y_from, z_from, grid[x_from][y_from].type->name, (b_from != -1 ? cluster_ctx.blocks[b_from].name : ""), - x_to, y_to, z_to, grid[x_to][y_to].type->name, (b_to != -1 ? cluster_ctx.blocks[b_to].name : "")); -#endif - - /* Make the switch in order to make computing the new bounding * - * box simpler. If the cost increase is too high, switch them * - * back. (place_ctx.block_locs data structures switched, clbs not switched * - * until success of move is determined.) * - * Also check that whether those are the only 2 blocks * - * to be moved - check for carry chains and other placement * - * macros. */ - - /* Check whether the from_block is part of a macro first. * - * If it is, the whole macro has to be moved. Calculate the * - * x, y, z offsets of the swap to maintain relative placements * - * of the blocks. Abort the swap if the to_block is part of a * - * macro (not supported yet). */ - - bool abort_swap = find_affected_blocks(b_from, x_to, y_to, z_to); - - if (abort_swap == false) { - - // Find all the nets affected by this swap and update thier bounding box - int num_nets_affected = find_affected_nets_and_update_costs(place_algorithm, bb_delta_c, timing_delta_c, delay_delta_c); - - if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - /*in this case we redefine delta_c as a combination of timing and bb. * - *additionally, we normalize all values, therefore delta_c is in * - *relation to 1*/ - - delta_c = (1 - timing_tradeoff) * bb_delta_c * inverse_prev_bb_cost - + timing_tradeoff * timing_delta_c * inverse_prev_timing_cost; - } else { - delta_c = bb_delta_c; - } - - /* 1 -> move accepted, 0 -> rejected. */ - e_swap_result keep_switch = assess_swap(delta_c, t); - - if (keep_switch == ACCEPTED) { - *cost = *cost + delta_c; - *bb_cost = *bb_cost + bb_delta_c; - - if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - /*update the point_to_point_timing_cost and point_to_point_delay_cost - * values from the temporary values */ - *timing_cost = *timing_cost + timing_delta_c; - *delay_cost = *delay_cost + delay_delta_c; - - update_td_cost(); - } - - /* update net cost functions and reset flags. */ - for (int inet_affected = 0; inet_affected < num_nets_affected; inet_affected++) { - ClusterNetId net_id = ts_nets_to_update[inet_affected]; - - bb_coords[net_id] = ts_bb_coord_new[net_id]; - if (cluster_ctx.clb_nlist.net_sinks(net_id).size() >= SMALL_NET) - bb_num_on_edges[net_id] = ts_bb_edge_new[net_id]; - - net_cost[net_id] = temp_net_cost[net_id]; - - /* negative temp_net_cost value is acting as a flag. */ - temp_net_cost[net_id] = -1; - bb_updated_before[net_id] = NOT_UPDATED_YET; - } - - /* Update clb data structures since we kept the move. */ - /* Swap physical location */ - for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { - - x_to = blocks_affected.moved_blocks[iblk].xnew; - y_to = blocks_affected.moved_blocks[iblk].ynew; - z_to = blocks_affected.moved_blocks[iblk].znew; - - x_from = blocks_affected.moved_blocks[iblk].xold; - y_from = blocks_affected.moved_blocks[iblk].yold; - z_from = blocks_affected.moved_blocks[iblk].zold; - - b_from = blocks_affected.moved_blocks[iblk].block_num; - - place_ctx.grid_blocks[x_to][y_to].blocks[z_to] = b_from; - - if (blocks_affected.moved_blocks[iblk].swapped_to_was_empty) { - place_ctx.grid_blocks[x_to][y_to].usage++; - } - if (blocks_affected.moved_blocks[iblk].swapped_from_is_empty) { - place_ctx.grid_blocks[x_from][y_from].usage--; - place_ctx.grid_blocks[x_from][y_from].blocks[z_from] = EMPTY_BLOCK_ID; - } - - } // Finish updating clb for all blocks - - } else { /* Move was rejected. */ - - /* Reset the net cost function flags first. */ - for (int inet_affected = 0; inet_affected < num_nets_affected; inet_affected++) { - ClusterNetId net_id = ts_nets_to_update[inet_affected]; - temp_net_cost[net_id] = -1; - bb_updated_before[net_id] = NOT_UPDATED_YET; - } - - /* Restore the place_ctx.block_locs data structures to their state before the move. */ - for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { - b_from = blocks_affected.moved_blocks[iblk].block_num; - - place_ctx.block_locs[b_from].x = blocks_affected.moved_blocks[iblk].xold; - place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; - place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; - } - } - - /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ - blocks_affected.num_moved_blocks = 0; - -#if 0 - //Check that each accepted swap yields a valid placement - check_place(*bb_cost, *timing_cost, place_algorithm, *delay_cost); -#endif - - return (keep_switch); - } else { - - /* Restore the place_ctx.block_locs data structures to their state before the move. */ - for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { - b_from = blocks_affected.moved_blocks[iblk].block_num; - - place_ctx.block_locs[b_from].x = blocks_affected.moved_blocks[iblk].xold; - place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; - place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; - } - - /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ - blocks_affected.num_moved_blocks = 0; - - return ABORTED; - } -} - -//Pick a random block to be swapped with another random block. -//If none is found return ClusterBlockId::INVALID() -static ClusterBlockId pick_from_block() { - /* Some blocks may be fixed, and should never be moved from their * - * initial positions. If we randomly selected such a block try * - * another random block. * - * * - * We need to track the blocks we have tried to avoid an infinite * - * loop if all blocks are fixed. */ - auto& cluster_ctx = g_vpr_ctx.clustering(); - auto& place_ctx = g_vpr_ctx.mutable_placement(); - - std::unordered_set tried_from_blocks; - - //So long as untried blocks remain - while (tried_from_blocks.size() < cluster_ctx.clb_nlist.blocks().size()) { - - //Pick a block at random - ClusterBlockId b_from = ClusterBlockId(vtr::irand((int) cluster_ctx.clb_nlist.blocks().size() - 1)); - - //Record it as tried - tried_from_blocks.insert(b_from); - - if (place_ctx.block_locs[b_from].is_fixed) { - continue; //Fixed location, try again - } - - //Found a movable block - return b_from; - } - - //No movable blocks found - return ClusterBlockId::INVALID(); -} - -//Puts all the nets changed by the current swap into nets_to_update, -//and updates their bounding box. -// -//Returns the number of affected nets. -static int find_affected_nets_and_update_costs(e_place_algorithm place_algorithm, float& bb_delta_c, float& timing_delta_c, float& delay_delta_c) { - VTR_ASSERT_SAFE(bb_delta_c == 0.); - VTR_ASSERT_SAFE(timing_delta_c == 0.); - VTR_ASSERT_SAFE(delay_delta_c == 0.); - auto& cluster_ctx = g_vpr_ctx.clustering(); - - int num_affected_nets = 0; - - //Go through all the blocks moved - for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { - ClusterBlockId blk = blocks_affected.moved_blocks[iblk].block_num; - - //Go through all the pins in the moved block - for (ClusterPinId blk_pin : cluster_ctx.clb_nlist.block_pins(blk)) { - ClusterNetId net_id = cluster_ctx.clb_nlist.pin_net(blk_pin); - VTR_ASSERT_SAFE_MSG(net_id, "Only valid nets should be found in compressed netlist block pins"); - - if (cluster_ctx.clb_nlist.net_is_global(net_id)) - continue; //Global nets are assumed to span the whole chip, and do not effect costs - - //Record effected nets - record_affected_net(net_id, num_affected_nets); - - //Update the net bounding boxes - // - //Do not update the net cost here since it should only be updated - //once per net, not once per pin. - update_net_bb(net_id, iblk, blk, blk_pin); - - if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - //Determine the change in timing costs if required - update_td_delta_costs(net_id, blk_pin, timing_delta_c, delay_delta_c); - } - } - } - - /* Now update the bounding box costs (since the net bounding boxes are up-to-date). - * The cost is only updated once per net. - */ - for (int inet_affected = 0; inet_affected < num_affected_nets; inet_affected++) { - ClusterNetId net_id = ts_nets_to_update[inet_affected]; - - temp_net_cost[net_id] = get_net_cost(net_id, &ts_bb_coord_new[net_id]); - bb_delta_c += temp_net_cost[net_id] - net_cost[net_id]; - } - - return num_affected_nets; -} - -static void record_affected_net(const ClusterNetId net, int& num_affected_nets) { - //Record effected nets - if (temp_net_cost[net] < 0.) { - //Net not marked yet. - ts_nets_to_update[num_affected_nets] = net; - num_affected_nets++; - - //Flag to say we've marked this net. - temp_net_cost[net] = 1.; - } -} - -static void update_net_bb(const ClusterNetId net, int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin) { - auto& cluster_ctx = g_vpr_ctx.clustering(); - - if (cluster_ctx.clb_nlist.net_sinks(net).size() < SMALL_NET) { - //For small nets brute-force bounding box update is faster - - if(bb_updated_before[net] == NOT_UPDATED_YET) { //Only once per-net - get_non_updateable_bb(net, &ts_bb_coord_new[net]); - } - } else { - //For large nets, update bounding box incrementally - int iblk_pin = cluster_ctx.clb_nlist.pin_physical_index(blk_pin); - - t_type_ptr blk_type = cluster_ctx.clb_nlist.block_type(blk); - int pin_width_offset = blk_type->pin_width_offset[iblk_pin]; - int pin_height_offset = blk_type->pin_height_offset[iblk_pin]; - - //Incremental bounding box update - update_bb(net, &ts_bb_coord_new[net], - &ts_bb_edge_new[net], - blocks_affected.moved_blocks[iblk].xold + pin_width_offset, - blocks_affected.moved_blocks[iblk].yold + pin_height_offset, - blocks_affected.moved_blocks[iblk].xnew + pin_width_offset, - blocks_affected.moved_blocks[iblk].ynew + pin_height_offset); - } - -} - -static void update_td_delta_costs(const ClusterNetId net, const ClusterPinId pin, float& delta_timing_cost, float& delta_delay_cost) { - auto& cluster_ctx = g_vpr_ctx.clustering(); - - if (cluster_ctx.clb_nlist.pin_type(pin) == PinType::DRIVER) { - //This pin is a net driver on a moved block. - //Re-compute all point to point connections for this net. - for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net).size(); ipin++) { - float temp_delay = comp_td_point_to_point_delay(net, ipin); - temp_point_to_point_delay_cost[net][ipin] = temp_delay; - - temp_point_to_point_timing_cost[net][ipin] = get_timing_place_crit(net, ipin) * temp_delay; - delta_timing_cost += temp_point_to_point_timing_cost[net][ipin] - point_to_point_timing_cost[net][ipin]; - delta_delay_cost += temp_point_to_point_delay_cost[net][ipin] - point_to_point_delay_cost[net][ipin]; - - } - } else { - //This pin is a net sink on a moved block - VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.pin_type(pin) == PinType::SINK); - - //If this net is being driven by a moved block, we do not - //need to compute the change in the timing cost (here) since it will - //be computed by the net's driver pin (since the driver block moved). - // - //Computing it here would double count the change, and mess up the - //delta_timing_cost value. - if (!driven_by_moved_block(net)) { - int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin); - - float temp_delay = comp_td_point_to_point_delay(net, net_pin); - temp_point_to_point_delay_cost[net][net_pin] = temp_delay; - - temp_point_to_point_timing_cost[net][net_pin] = get_timing_place_crit(net, net_pin) * temp_delay; - delta_timing_cost += temp_point_to_point_timing_cost[net][net_pin] - point_to_point_timing_cost[net][net_pin]; - delta_delay_cost += temp_point_to_point_delay_cost[net][net_pin] - point_to_point_delay_cost[net][net_pin]; - } - } -} - -static bool find_to(t_type_ptr type, float rlim, - int x_from, int y_from, - int *px_to, int *py_to, int *pz_to) { - - /* Returns the point to which I want to swap, properly range limited. - * rlim must always be between 1 and device_ctx.grid.width() - 2 (inclusive) for this routine - * to work. Note -2 for no perim channels - */ - - int min_x, max_x, min_y, max_y; - int num_tries; - int active_area; - bool is_legal; - int itype; - - auto& grid = g_vpr_ctx.device().grid; - auto& place_ctx = g_vpr_ctx.placement(); - - auto grid_type = grid[x_from][y_from].type; - VTR_ASSERT(type == grid_type); - - int rlx = min(grid.width() - 1, rlim); - int rly = min(grid.height() - 1, rlim); /* Added rly for aspect_ratio != 1 case. */ - active_area = 4 * rlx * rly; - - min_x = max(0, x_from - rlx); - max_x = min(grid.width() - 1, x_from + rlx); - min_y = max(0, y_from - rly); - max_y = min(grid.height() - 1, y_from + rly); - - if (rlx < 1 || rlx > int(grid.width() - 1)) { - vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in find_to: rlx = %d out of range\n", rlx); - } - if (rly < 1 || rly > int(grid.height() - 1)) { - vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in find_to: rly = %d out of range\n", rly); - } - - num_tries = 0; - itype = type->index; - - do { /* Until legal */ - is_legal = true; - - /* Limit the number of tries when searching for an alternative position */ - if(num_tries >= 2 * min(active_area / (type->width * type->height), num_legal_pos[itype]) + 10) { - /* Tried randomly searching for a suitable position */ - return false; - } else { - num_tries++; - } - - find_to_location(type, rlim, x_from, y_from, - px_to, py_to, pz_to); - - if((x_from == *px_to) && (y_from == *py_to)) { - is_legal = false; - } else if(*px_to > max_x || *px_to < min_x || *py_to > max_y || *py_to < min_y) { - is_legal = false; - } else if(grid[*px_to][*py_to].type != grid[x_from][y_from].type) { - is_legal = false; - } else { - /* Find z_to and test to validate that the "to" block is *not* fixed */ - *pz_to = 0; - if (grid[*px_to][*py_to].type->capacity > 1) { - *pz_to = vtr::irand(grid[*px_to][*py_to].type->capacity - 1); - } - ClusterBlockId b_to = place_ctx.grid_blocks[*px_to][*py_to].blocks[*pz_to]; - if ((b_to != EMPTY_BLOCK_ID) && (place_ctx.block_locs[b_to].is_fixed == true)) { - is_legal = false; - } - } - - VTR_ASSERT(*px_to >= 0 && *px_to < int(grid.width())); - VTR_ASSERT(*py_to >= 0 && *py_to < int(grid.height())); - } while (is_legal == false); - - if (*px_to < 0 || *px_to > int(grid.width() - 1) || *py_to < 0 || *py_to > int(grid.height() - 1)) { - vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in routine find_to: (x_to,y_to) = (%d,%d)\n", *px_to, *py_to); - } - - VTR_ASSERT(type == grid[*px_to][*py_to].type); - return true; -} - -static void find_to_location(t_type_ptr type, float rlim, - int x_from, int y_from, - int *px_to, int *py_to, int *pz_to) { - - auto& device_ctx = g_vpr_ctx.device(); - auto& grid = device_ctx.grid; - - int itype = type->index; - - - int rlx = min(grid.width() - 1, rlim); - int rly = min(grid.height() - 1, rlim); /* Added rly for aspect_ratio != 1 case. */ - int active_area = 4 * rlx * rly; - - int min_x = max(0, x_from - rlx); - int max_x = min(grid.width() - 1, x_from + rlx); - int min_y = max(0, y_from - rly); - int max_y = min(grid.height() - 1, y_from + rly); - - *pz_to = 0; - if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || num_legal_pos[itype] < active_area) { - int ipos = vtr::irand(num_legal_pos[itype] - 1); - *px_to = legal_pos[itype][ipos].x; - *py_to = legal_pos[itype][ipos].y; - *pz_to = legal_pos[itype][ipos].z; - } else { - int x_rel = vtr::irand(max(0, max_x - min_x)); - int y_rel = vtr::irand(max(0, max_y - min_y)); - *px_to = min_x + x_rel; - *py_to = min_y + y_rel; - *px_to = (*px_to) - grid[*px_to][*py_to].width_offset; /* align it */ - *py_to = (*py_to) - grid[*px_to][*py_to].height_offset; /* align it */ - } -} - -static e_swap_result assess_swap(float delta_c, float t) { - - /* Returns: 1 -> move accepted, 0 -> rejected. */ - - e_swap_result accept; - float prob_fac, fnum; - - if (delta_c <= 0) { - - /* Reduce variation in final solution due to round off */ - fnum = vtr::frand(); - - accept = ACCEPTED; - return (accept); - } - - if (t == 0.) - return (REJECTED); - - fnum = vtr::frand(); - prob_fac = exp(-delta_c / t); - if (prob_fac > fnum) { - accept = ACCEPTED; - } - else { - accept = REJECTED; - } - return (accept); -} - -static float recompute_bb_cost() { - /* Recomputes the cost to eliminate roundoff that may have accrued. * - * This routine does as little work as possible to compute this new * - * cost. */ - - float cost = 0; - - auto& cluster_ctx = g_vpr_ctx.clustering(); - - for (auto net_id : cluster_ctx.clb_nlist.nets()) { /* for each net ... */ - if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ - /* Bounding boxes don't have to be recomputed; they're correct. */ - cost += net_cost[net_id]; - } - } - - return (cost); -} - -/*returns the delay of one point to point connection */ -static float comp_td_point_to_point_delay(ClusterNetId net_id, int ipin) { - auto& cluster_ctx = g_vpr_ctx.clustering(); - auto& place_ctx = g_vpr_ctx.placement(); - - float delay_source_to_sink = 0.; - - if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { - //Only estimate delay for signals routed through the inter-block - //routing network. Global signals are assumed to have zero delay. - - ClusterBlockId source_block = cluster_ctx.clb_nlist.net_driver_block(net_id); - ClusterBlockId sink_block = cluster_ctx.clb_nlist.net_pin_block(net_id, ipin); - - VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.block_type(source_block) != nullptr); - VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.block_type(sink_block) != nullptr); - - int delta_x = abs(place_ctx.block_locs[sink_block].x - place_ctx.block_locs[source_block].x); - int delta_y = abs(place_ctx.block_locs[sink_block].y - place_ctx.block_locs[source_block].y); - - /* Note: This heuristic only considers delta_x and delta_y, a much better heuristic - * would be to to create a more comprehensive lookup table. - * - * In particular this aproach does not accurately capture the effect of fast - * carry-chain connections. - */ - delay_source_to_sink = get_delta_delay(delta_x, delta_y); - if (delay_source_to_sink < 0) { - vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, - "in comp_td_point_to_point_delay: Bad delay_source_to_sink value delta(%d, %d) delay of %g\n" - "in comp_td_point_to_point_delay: Delay is less than 0\n", - delta_x, delta_y, delay_source_to_sink); - } - } - - - return (delay_source_to_sink); -} - -//Recompute all point to point delays, updating point_to_point_delay_cost -static void comp_td_point_to_point_delays() { - auto& cluster_ctx = g_vpr_ctx.clustering(); - - for (auto net_id : cluster_ctx.clb_nlist.nets()) { - for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ++ipin) { - point_to_point_delay_cost[net_id][ipin] = comp_td_point_to_point_delay(net_id, ipin); - } - } -} - -/* Update the point_to_point_timing_cost values from the temporary * -* values for all connections that have changed. */ -static void update_td_cost() { - auto& cluster_ctx = g_vpr_ctx.clustering(); - - /* Go through all the blocks moved. */ - for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { - ClusterBlockId bnum = blocks_affected.moved_blocks[iblk].block_num; - for (ClusterPinId pin_id : cluster_ctx.clb_nlist.block_pins(bnum)) { - ClusterNetId net_id = cluster_ctx.clb_nlist.pin_net(pin_id); - - if (cluster_ctx.clb_nlist.net_is_global(net_id)) - continue; - - if (cluster_ctx.clb_nlist.pin_type(pin_id) == PinType::DRIVER) { - //This net is being driven by a moved block, recompute - //all point to point connections on this net. - for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { - point_to_point_delay_cost[net_id][ipin] = temp_point_to_point_delay_cost[net_id][ipin]; - temp_point_to_point_delay_cost[net_id][ipin] = -1; - point_to_point_timing_cost[net_id][ipin] = temp_point_to_point_timing_cost[net_id][ipin]; - temp_point_to_point_timing_cost[net_id][ipin] = -1; - } - } else { - //This pin is a net sink on a moved block - VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.pin_type(pin_id) == PinType::SINK); - - /* The following "if" prevents the value from being updated twice. */ - if (!driven_by_moved_block(net_id)) { - int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin_id); - - point_to_point_delay_cost[net_id][net_pin] = temp_point_to_point_delay_cost[net_id][net_pin]; - temp_point_to_point_delay_cost[net_id][net_pin] = -1; - point_to_point_timing_cost[net_id][net_pin] = temp_point_to_point_timing_cost[net_id][net_pin]; - temp_point_to_point_timing_cost[net_id][net_pin] = -1; - } - } - } /* Finished going through all the pins in the moved block */ - } /* Finished going through all the blocks moved */ -} - -static bool driven_by_moved_block(const ClusterNetId net) { - auto& cluster_ctx = g_vpr_ctx.clustering(); - - ClusterBlockId net_driver_block = cluster_ctx.clb_nlist.net_driver_block(net); - for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { - if (net_driver_block == blocks_affected.moved_blocks[iblk].block_num) { - return true; - } - } - return false; -} - -static void comp_td_costs(float *timing_cost, float *connection_delay_sum) { - /* Computes the cost (from scratch) due to the delays and criticalities * - * on all point to point connections, we define the timing cost of * - * each connection as criticality*delay. */ - - unsigned ipin; - float loc_timing_cost, loc_connection_delay_sum, temp_delay_cost, - temp_timing_cost; - - auto& cluster_ctx = g_vpr_ctx.clustering(); - - loc_timing_cost = 0.; - loc_connection_delay_sum = 0.; - - for (auto net_id : cluster_ctx.clb_nlist.nets()) { /* For each net ... */ - - if (cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ - continue; - } - - for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { - temp_delay_cost = comp_td_point_to_point_delay(net_id, ipin); - temp_timing_cost = temp_delay_cost * get_timing_place_crit(net_id, ipin); - - loc_connection_delay_sum += temp_delay_cost; - point_to_point_delay_cost[net_id][ipin] = temp_delay_cost; - temp_point_to_point_delay_cost[net_id][ipin] = -1; /* Undefined */ - - point_to_point_timing_cost[net_id][ipin] = temp_timing_cost; - temp_point_to_point_timing_cost[net_id][ipin] = -1; /* Undefined */ - loc_timing_cost += temp_timing_cost; - } - } - - /* Make sure timing cost does not go above MIN_TIMING_COST. */ - *timing_cost = loc_timing_cost; - - *connection_delay_sum = loc_connection_delay_sum; -} - - -/* Finds the cost from scratch. Done only when the placement * -* has been radically changed (i.e. after initial placement). * -* Otherwise find the cost change incrementally. If method * -* check is NORMAL, we find bounding boxes that are updateable * -* for the larger nets. If method is CHECK, all bounding boxes * -* are found via the non_updateable_bb routine, to provide a * -* cost which can be used to check the correctness of the * -* other routine. */ -static float comp_bb_cost(e_cost_methods method) { - float cost = 0; - double expected_wirelength = 0.0; - auto& cluster_ctx = g_vpr_ctx.clustering(); - - for (auto net_id : cluster_ctx.clb_nlist.nets()) { /* for each net ... */ - if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ - /* Small nets don't use incremental updating on their bounding boxes, * - * so they can use a fast bounding box calculator. */ - if (cluster_ctx.clb_nlist.net_sinks(net_id).size() >= SMALL_NET && method == NORMAL) { - get_bb_from_scratch(net_id, &bb_coords[net_id], - &bb_num_on_edges[net_id]); - } - else { - get_non_updateable_bb(net_id, &bb_coords[net_id]); - } - - net_cost[net_id] = get_net_cost(net_id, &bb_coords[net_id]); - cost += net_cost[net_id]; - if (method == CHECK) - expected_wirelength += get_net_wirelength_estimate(net_id, &bb_coords[net_id]); - } - } - - if (method == CHECK) { - vtr::printf_info("\n"); - vtr::printf_info("BB estimate of min-dist (placement) wire length: %.0f\n", expected_wirelength); - } - return cost; -} - - -/* Frees the major structures needed by the placer (and not needed * -* elsewhere). */ -static void free_placement_structs(t_placer_opts placer_opts) { - int imacro; - - auto& cluster_ctx = g_vpr_ctx.clustering(); - - free_legal_placements(); - free_fast_cost_update(); - - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE - || placer_opts.enable_timing_computations) { - - for (auto net_id : cluster_ctx.clb_nlist.nets()) { - /*add one to the address since it is indexed from 1 not 0 */ - point_to_point_timing_cost[net_id]++; - free(point_to_point_timing_cost[net_id]); - - temp_point_to_point_timing_cost[net_id]++; - free(temp_point_to_point_timing_cost[net_id]); - - point_to_point_delay_cost[net_id]++; - free(point_to_point_delay_cost[net_id]); - - temp_point_to_point_delay_cost[net_id]++; - free(temp_point_to_point_delay_cost[net_id]); - } - - point_to_point_timing_cost.clear(); - point_to_point_delay_cost.clear(); - temp_point_to_point_timing_cost.clear(); - temp_point_to_point_delay_cost.clear(); - - net_pin_indices.clear(); - } - - free_placement_macros_structs(); - - for (imacro = 0; imacro < num_pl_macros; imacro++) - free(pl_macros[imacro].members); - free(pl_macros); - - /* Defensive coding. */ - pl_macros = nullptr; - - /* Frees up all the data structure used in vpr_utils. */ - free_port_pin_from_blk_pin(); - free_blk_pin_from_port_pin(); - -} - -/* Allocates the major structures needed only by the placer, primarily for * -* computing costs quickly and such. */ -static void alloc_and_load_placement_structs( - float place_cost_exp, t_placer_opts placer_opts, - t_direct_inf *directs, int num_directs) { - - int max_pins_per_clb, i; - unsigned int ipin; - - auto& device_ctx = g_vpr_ctx.device(); - auto& cluster_ctx = g_vpr_ctx.clustering(); - - size_t num_nets = cluster_ctx.clb_nlist.nets().size(); - - init_placement_context(); - - alloc_legal_placements(); - load_legal_placements(); - - max_pins_per_clb = 0; - for (i = 0; i < device_ctx.num_block_types; i++) { - max_pins_per_clb = max(max_pins_per_clb, device_ctx.block_types[i].num_pins); - } - - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE - || placer_opts.enable_timing_computations) { - /* Allocate structures associated with timing driven placement */ - /* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1] */ - point_to_point_delay_cost.resize(num_nets); - temp_point_to_point_delay_cost.resize(num_nets); - - point_to_point_timing_cost.resize(num_nets); - temp_point_to_point_timing_cost.resize(num_nets); - - for (auto net_id : cluster_ctx.clb_nlist.nets()) { - size_t num_sinks = cluster_ctx.clb_nlist.net_sinks(net_id).size(); - /* In the following, subract one so index starts at * - * 1 instead of 0 */ - point_to_point_delay_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); - point_to_point_delay_cost[net_id]--; - - temp_point_to_point_delay_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); - temp_point_to_point_delay_cost[net_id]--; - - point_to_point_timing_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); - point_to_point_timing_cost[net_id]--; - - temp_point_to_point_timing_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); - temp_point_to_point_timing_cost[net_id]--; - } - for (auto net_id : cluster_ctx.clb_nlist.nets()) { - for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { - point_to_point_delay_cost[net_id][ipin] = 0; - temp_point_to_point_delay_cost[net_id][ipin] = 0; - } - } - } - - net_cost.resize(num_nets, -1.); - temp_net_cost.resize(num_nets, -1.); - bb_coords.resize(num_nets, t_bb()); - bb_num_on_edges.resize(num_nets, t_bb()); - - /* Used to store costs for moves not yet made and to indicate when a net's * - * cost has been recomputed. temp_net_cost[inet] < 0 means net's cost hasn't * - * been recomputed. */ - bb_updated_before.resize(num_nets, NOT_UPDATED_YET); - - alloc_and_load_for_fast_cost_update(place_cost_exp); - - alloc_and_load_net_pin_indices(); - - alloc_and_load_try_swap_structs(); - - num_pl_macros = alloc_and_load_placement_macros(directs, num_directs, &pl_macros); -} - -/* Allocates and loads net_pin_indices array, this array allows us to quickly * -* find what pin on the net a block pin corresponds to. Returns the pointer * -* to the 2D net_pin_indices array. */ -static void alloc_and_load_net_pin_indices() { - unsigned int netpin; - int itype, max_pins_per_clb = 0; - - auto& device_ctx = g_vpr_ctx.device(); - auto& cluster_ctx = g_vpr_ctx.clustering(); - - /* Compute required size. */ - for (itype = 0; itype < device_ctx.num_block_types; itype++) - max_pins_per_clb = max(max_pins_per_clb, device_ctx.block_types[itype].num_pins); - - /* Allocate for maximum size. */ - net_pin_indices.resize(cluster_ctx.clb_nlist.blocks().size()); - - for (auto blk_id : cluster_ctx.clb_nlist.blocks()) - net_pin_indices[blk_id].resize(max_pins_per_clb); - - /* Load the values */ - for (auto net_id : cluster_ctx.clb_nlist.nets()) { - if (cluster_ctx.clb_nlist.net_is_global(net_id)) - continue; - netpin = 0; - for (auto pin_id : cluster_ctx.clb_nlist.net_pins(net_id)) { - int pin_index = cluster_ctx.clb_nlist.pin_physical_index(pin_id); - ClusterBlockId block_id = cluster_ctx.clb_nlist.pin_block(pin_id); - net_pin_indices[block_id][pin_index] = netpin; - netpin++; - } - } -} - -static void alloc_and_load_try_swap_structs() { - /* Allocate the local bb_coordinate storage, etc. only once. */ - /* Allocate with size cluster_ctx.clb_nlist.nets().size() for any number of nets affected. */ - auto& cluster_ctx = g_vpr_ctx.clustering(); - - size_t num_nets = cluster_ctx.clb_nlist.nets().size(); - - ts_bb_coord_new.resize(num_nets, t_bb()); - ts_bb_edge_new.resize(num_nets, t_bb()); - ts_nets_to_update.resize(num_nets, ClusterNetId::INVALID()); - - /* Allocate with size cluster_ctx.clb_nlist.blocks().size() for any number of moved blocks. */ - blocks_affected.moved_blocks = (t_pl_moved_block*) vtr::calloc((int) cluster_ctx.clb_nlist.blocks().size(), sizeof(t_pl_moved_block)); - blocks_affected.num_moved_blocks = 0; - -} - -/* This routine finds the bounding box of each net from scratch (i.e. * -* from only the block location information). It updates both the * -* coordinate and number of pins on each edge information. It * -* should only be called when the bounding box information is not valid. */ -static void get_bb_from_scratch(ClusterNetId net_id, t_bb *coords, - t_bb *num_on_edges) { - int pnum, x, y, xmin, xmax, ymin, ymax; - int xmin_edge, xmax_edge, ymin_edge, ymax_edge; - - auto& cluster_ctx = g_vpr_ctx.clustering(); - auto& place_ctx = g_vpr_ctx.placement(); - auto& device_ctx = g_vpr_ctx.device(); - auto& grid = device_ctx.grid; - - ClusterBlockId bnum = cluster_ctx.clb_nlist.net_driver_block(net_id); - pnum = cluster_ctx.clb_nlist.net_pin_physical_index(net_id, 0); - x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; - y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; - - x = max(min(x, grid.width() - 2), 1); - y = max(min(y, grid.height() - 2), 1); - - xmin = x; - ymin = y; - xmax = x; - ymax = y; - xmin_edge = 1; - ymin_edge = 1; - xmax_edge = 1; - ymax_edge = 1; - - for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) { - bnum = cluster_ctx.clb_nlist.pin_block(pin_id); - pnum = cluster_ctx.clb_nlist.pin_physical_index(pin_id); - x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; - y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; - - /* Code below counts IO blocks as being within the 1..grid.width()-2, 1..grid.height()-2 clb array. * - * This is because channels do not go out of the 0..grid.width()-2, 0..grid.height()-2 range, and * - * I always take all channels impinging on the bounding box to be within * - * that bounding box. Hence, this "movement" of IO blocks does not affect * - * the which channels are included within the bounding box, and it * - * simplifies the code a lot. */ - - x = max(min(x, grid.width() - 2), 1); //-2 for no perim channels - y = max(min(y, grid.height() - 2), 1); //-2 for no perim channels - - if (x == xmin) { - xmin_edge++; - } - if (x == xmax) { /* Recall that xmin could equal xmax -- don't use else */ - xmax_edge++; - } - else if (x < xmin) { - xmin = x; - xmin_edge = 1; - } - else if (x > xmax) { - xmax = x; - xmax_edge = 1; - } - - if (y == ymin) { - ymin_edge++; - } - if (y == ymax) { - ymax_edge++; - } - else if (y < ymin) { - ymin = y; - ymin_edge = 1; - } - else if (y > ymax) { - ymax = y; - ymax_edge = 1; - } - } - - /* Copy the coordinates and number on edges information into the proper * - * structures. */ - coords->xmin = xmin; - coords->xmax = xmax; - coords->ymin = ymin; - coords->ymax = ymax; - - num_on_edges->xmin = xmin_edge; - num_on_edges->xmax = xmax_edge; - num_on_edges->ymin = ymin_edge; - num_on_edges->ymax = ymax_edge; -} - -static double get_net_wirelength_estimate(ClusterNetId net_id, t_bb *bbptr) { - - /* WMF: Finds the estimate of wirelength due to one net by looking at * - * its coordinate bounding box. */ - - double ncost, crossing; - auto& cluster_ctx = g_vpr_ctx.clustering(); - - /* Get the expected "crossing count" of a net, based on its number * - * of pins. Extrapolate for very large nets. */ - - if (((cluster_ctx.clb_nlist.net_pins(net_id).size()) > 50) - && ((cluster_ctx.clb_nlist.net_pins(net_id).size()) < 85)) { - crossing = 2.7933 + 0.02616 * ((cluster_ctx.clb_nlist.net_pins(net_id).size()) - 50); - } else if ((cluster_ctx.clb_nlist.net_pins(net_id).size()) >= 85) { - crossing = 2.7933 + 0.011 * (cluster_ctx.clb_nlist.net_pins(net_id).size()) - - 0.0000018 * (cluster_ctx.clb_nlist.net_pins(net_id).size()) - * (cluster_ctx.clb_nlist.net_pins(net_id).size()); - } else { - crossing = cross_count[cluster_ctx.clb_nlist.net_pins(net_id).size() - 1]; - } - - /* Could insert a check for xmin == xmax. In that case, assume * - * connection will be made with no bends and hence no x-cost. * - * Same thing for y-cost. */ - - /* Cost = wire length along channel * cross_count / average * - * channel capacity. Do this for x, then y direction and add. */ - - ncost = (bbptr->xmax - bbptr->xmin + 1) * crossing; - - ncost += (bbptr->ymax - bbptr->ymin + 1) * crossing; - - return (ncost); -} - -static float get_net_cost(ClusterNetId net_id, t_bb *bbptr) { - - /* Finds the cost due to one net by looking at its coordinate bounding * - * box. */ - - float ncost, crossing; - auto& cluster_ctx = g_vpr_ctx.clustering(); - - /* Get the expected "crossing count" of a net, based on its number * - * of pins. Extrapolate for very large nets. */ - - if ((cluster_ctx.clb_nlist.net_pins(net_id).size()) > 50) { - crossing = 2.7933 + 0.02616 * ((cluster_ctx.clb_nlist.net_pins(net_id).size()) - 50); - /* crossing = 3.0; Old value */ - } else { - crossing = cross_count[(cluster_ctx.clb_nlist.net_pins(net_id).size()) - 1]; - } - - /* Could insert a check for xmin == xmax. In that case, assume * - * connection will be made with no bends and hence no x-cost. * - * Same thing for y-cost. */ - - /* Cost = wire length along channel * cross_count / average * - * channel capacity. Do this for x, then y direction and add. */ - - ncost = (bbptr->xmax - bbptr->xmin + 1) * crossing - * chanx_place_cost_fac[bbptr->ymax][bbptr->ymin - 1]; - - ncost += (bbptr->ymax - bbptr->ymin + 1) * crossing - * chany_place_cost_fac[bbptr->xmax][bbptr->xmin - 1]; - - return (ncost); -} - -/* Finds the bounding box of a net and stores its coordinates in the * -* bb_coord_new data structure. This routine should only be called * -* for small nets, since it does not determine enough information for * -* the bounding box to be updated incrementally later. * -* Currently assumes channels on both sides of the CLBs forming the * -* edges of the bounding box can be used. Essentially, I am assuming * -* the pins always lie on the outside of the bounding box. */ -static void get_non_updateable_bb(ClusterNetId net_id, t_bb *bb_coord_new) { - //TODO: account for multiple physical pin instances per logical pin - - int xmax, ymax, xmin, ymin, x, y; - int pnum; - - auto& cluster_ctx = g_vpr_ctx.clustering(); - auto& place_ctx = g_vpr_ctx.placement(); - auto& device_ctx = g_vpr_ctx.device(); - - ClusterBlockId bnum = cluster_ctx.clb_nlist.net_driver_block(net_id); - pnum = cluster_ctx.clb_nlist.net_pin_physical_index(net_id, 0); - x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; - y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; - - xmin = x; - ymin = y; - xmax = x; - ymax = y; - - for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) { - bnum = cluster_ctx.clb_nlist.pin_block(pin_id); - pnum = cluster_ctx.clb_nlist.pin_physical_index(pin_id); - x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; - y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; - - if (x < xmin) { - xmin = x; - } else if (x > xmax) { - xmax = x; - } - - if (y < ymin) { - ymin = y; - } else if (y > ymax) { - ymax = y; - } - } - - /* Now I've found the coordinates of the bounding box. There are no * - * channels beyond device_ctx.grid.width()-2 and * - * device_ctx.grid.height() - 2, so I want to clip to that. As well,* - * since I'll always include the channel immediately below and the * - * channel immediately to the left of the bounding box, I want to * - * clip to 1 in both directions as well (since minimum channel index * - * is 0). See route_common.cpp for a channel diagram. */ - - bb_coord_new->xmin = max(min(xmin, device_ctx.grid.width() - 2), 1); //-2 for no perim channels - bb_coord_new->ymin = max(min(ymin, device_ctx.grid.height() - 2), 1); //-2 for no perim channels - bb_coord_new->xmax = max(min(xmax, device_ctx.grid.width() - 2), 1); //-2 for no perim channels - bb_coord_new->ymax = max(min(ymax, device_ctx.grid.height() - 2), 1); //-2 for no perim channels -} - -static void update_bb(ClusterNetId net_id, t_bb *bb_coord_new, - t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew) { - - /* Updates the bounding box of a net by storing its coordinates in * - * the bb_coord_new data structure and the number of blocks on each * - * edge in the bb_edge_new data structure. This routine should only * - * be called for large nets, since it has some overhead relative to * - * just doing a brute force bounding box calculation. The bounding * - * box coordinate and edge information for inet must be valid before * - * this routine is called. * - * Currently assumes channels on both sides of the CLBs forming the * - * edges of the bounding box can be used. Essentially, I am assuming * - * the pins always lie on the outside of the bounding box. * - * The x and y coordinates are the pin's x and y coordinates. */ - /* IO blocks are considered to be one cell in for simplicity. */ - //TODO: account for multiple physical pin instances per logical pin - - t_bb *curr_bb_edge, *curr_bb_coord; - - auto& device_ctx = g_vpr_ctx.device(); - - xnew = max(min(xnew, device_ctx.grid.width() - 2), 1); //-2 for no perim channels - ynew = max(min(ynew, device_ctx.grid.height() - 2), 1); //-2 for no perim channels - xold = max(min(xold, device_ctx.grid.width() - 2), 1); //-2 for no perim channels - yold = max(min(yold, device_ctx.grid.height() - 2), 1); //-2 for no perim channels - - /* Check if the net had been updated before. */ - if (bb_updated_before[net_id] == GOT_FROM_SCRATCH) { - /* The net had been updated from scratch, DO NOT update again! */ - return; - } else if (bb_updated_before[net_id] == NOT_UPDATED_YET) { - /* The net had NOT been updated before, could use the old values */ - curr_bb_coord = &bb_coords[net_id]; - curr_bb_edge = &bb_num_on_edges[net_id]; - bb_updated_before[net_id] = UPDATED_ONCE; - } else { - /* The net had been updated before, must use the new values */ - curr_bb_coord = bb_coord_new; - curr_bb_edge = bb_edge_new; - } - - /* Check if I can update the bounding box incrementally. */ - - if (xnew < xold) { /* Move to left. */ - - /* Update the xmax fields for coordinates and number of edges first. */ - - if (xold == curr_bb_coord->xmax) { /* Old position at xmax. */ - if (curr_bb_edge->xmax == 1) { - get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); - bb_updated_before[net_id] = GOT_FROM_SCRATCH; - return; - } else { - bb_edge_new->xmax = curr_bb_edge->xmax - 1; - bb_coord_new->xmax = curr_bb_coord->xmax; - } - } else { /* Move to left, old postion was not at xmax. */ - bb_coord_new->xmax = curr_bb_coord->xmax; - bb_edge_new->xmax = curr_bb_edge->xmax; - } - - /* Now do the xmin fields for coordinates and number of edges. */ - - if (xnew < curr_bb_coord->xmin) { /* Moved past xmin */ - bb_coord_new->xmin = xnew; - bb_edge_new->xmin = 1; - } else if (xnew == curr_bb_coord->xmin) { /* Moved to xmin */ - bb_coord_new->xmin = xnew; - bb_edge_new->xmin = curr_bb_edge->xmin + 1; - } else { /* Xmin unchanged. */ - bb_coord_new->xmin = curr_bb_coord->xmin; - bb_edge_new->xmin = curr_bb_edge->xmin; - } - /* End of move to left case. */ - - } else if (xnew > xold) { /* Move to right. */ - - /* Update the xmin fields for coordinates and number of edges first. */ - - if (xold == curr_bb_coord->xmin) { /* Old position at xmin. */ - if (curr_bb_edge->xmin == 1) { - get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); - bb_updated_before[net_id] = GOT_FROM_SCRATCH; - return; - } else { - bb_edge_new->xmin = curr_bb_edge->xmin - 1; - bb_coord_new->xmin = curr_bb_coord->xmin; - } - } else { /* Move to right, old position was not at xmin. */ - bb_coord_new->xmin = curr_bb_coord->xmin; - bb_edge_new->xmin = curr_bb_edge->xmin; - } - - /* Now do the xmax fields for coordinates and number of edges. */ - - if (xnew > curr_bb_coord->xmax) { /* Moved past xmax. */ - bb_coord_new->xmax = xnew; - bb_edge_new->xmax = 1; - } else if (xnew == curr_bb_coord->xmax) { /* Moved to xmax */ - bb_coord_new->xmax = xnew; - bb_edge_new->xmax = curr_bb_edge->xmax + 1; - } else { /* Xmax unchanged. */ - bb_coord_new->xmax = curr_bb_coord->xmax; - bb_edge_new->xmax = curr_bb_edge->xmax; - } - /* End of move to right case. */ - - } else { /* xnew == xold -- no x motion. */ - bb_coord_new->xmin = curr_bb_coord->xmin; - bb_coord_new->xmax = curr_bb_coord->xmax; - bb_edge_new->xmin = curr_bb_edge->xmin; - bb_edge_new->xmax = curr_bb_edge->xmax; - } - - /* Now account for the y-direction motion. */ - - if (ynew < yold) { /* Move down. */ - - /* Update the ymax fields for coordinates and number of edges first. */ - - if (yold == curr_bb_coord->ymax) { /* Old position at ymax. */ - if (curr_bb_edge->ymax == 1) { - get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); - bb_updated_before[net_id] = GOT_FROM_SCRATCH; - return; - } else { - bb_edge_new->ymax = curr_bb_edge->ymax - 1; - bb_coord_new->ymax = curr_bb_coord->ymax; - } - } else { /* Move down, old postion was not at ymax. */ - bb_coord_new->ymax = curr_bb_coord->ymax; - bb_edge_new->ymax = curr_bb_edge->ymax; - } - - /* Now do the ymin fields for coordinates and number of edges. */ - - if (ynew < curr_bb_coord->ymin) { /* Moved past ymin */ - bb_coord_new->ymin = ynew; - bb_edge_new->ymin = 1; - } else if (ynew == curr_bb_coord->ymin) { /* Moved to ymin */ - bb_coord_new->ymin = ynew; - bb_edge_new->ymin = curr_bb_edge->ymin + 1; - } else { /* ymin unchanged. */ - bb_coord_new->ymin = curr_bb_coord->ymin; - bb_edge_new->ymin = curr_bb_edge->ymin; - } - /* End of move down case. */ - - } else if (ynew > yold) { /* Moved up. */ - - /* Update the ymin fields for coordinates and number of edges first. */ - - if (yold == curr_bb_coord->ymin) { /* Old position at ymin. */ - if (curr_bb_edge->ymin == 1) { - get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); - bb_updated_before[net_id] = GOT_FROM_SCRATCH; - return; - } else { - bb_edge_new->ymin = curr_bb_edge->ymin - 1; - bb_coord_new->ymin = curr_bb_coord->ymin; - } - } else { /* Moved up, old position was not at ymin. */ - bb_coord_new->ymin = curr_bb_coord->ymin; - bb_edge_new->ymin = curr_bb_edge->ymin; - } - - /* Now do the ymax fields for coordinates and number of edges. */ - - if (ynew > curr_bb_coord->ymax) { /* Moved past ymax. */ - bb_coord_new->ymax = ynew; - bb_edge_new->ymax = 1; - } else if (ynew == curr_bb_coord->ymax) { /* Moved to ymax */ - bb_coord_new->ymax = ynew; - bb_edge_new->ymax = curr_bb_edge->ymax + 1; - } else { /* ymax unchanged. */ - bb_coord_new->ymax = curr_bb_coord->ymax; - bb_edge_new->ymax = curr_bb_edge->ymax; - } - /* End of move up case. */ - - } else { /* ynew == yold -- no y motion. */ - bb_coord_new->ymin = curr_bb_coord->ymin; - bb_coord_new->ymax = curr_bb_coord->ymax; - bb_edge_new->ymin = curr_bb_edge->ymin; - bb_edge_new->ymax = curr_bb_edge->ymax; - } - - if (bb_updated_before[net_id] == NOT_UPDATED_YET) { - bb_updated_before[net_id] = UPDATED_ONCE; - } -} - -static void alloc_legal_placements() { - auto& device_ctx = g_vpr_ctx.device(); - auto& place_ctx = g_vpr_ctx.mutable_placement(); - - legal_pos = (t_legal_pos **) vtr::malloc(device_ctx.num_block_types * sizeof(t_legal_pos *)); - num_legal_pos = (int *) vtr::calloc(device_ctx.num_block_types, sizeof(int)); - - /* Initialize all occupancy to zero. */ - - for (size_t i = 0; i < device_ctx.grid.width(); i++) { - for (size_t j = 0; j < device_ctx.grid.height(); j++) { - place_ctx.grid_blocks[i][j].usage = 0; - - for (int k = 0; k < device_ctx.grid[i][j].type->capacity; k++) { - - if (place_ctx.grid_blocks[i][j].blocks[k] != INVALID_BLOCK_ID) { - place_ctx.grid_blocks[i][j].blocks[k] = EMPTY_BLOCK_ID; - if (device_ctx.grid[i][j].width_offset == 0 && device_ctx.grid[i][j].height_offset == 0) { - num_legal_pos[device_ctx.grid[i][j].type->index]++; - } - } - } - } - } - - for (int i = 0; i < device_ctx.num_block_types; i++) { - legal_pos[i] = (t_legal_pos *) vtr::malloc(num_legal_pos[i] * sizeof(t_legal_pos)); - } -} - -static void load_legal_placements() { - auto& device_ctx = g_vpr_ctx.device(); - auto& place_ctx = g_vpr_ctx.placement(); - - int* index = (int *) vtr::calloc(device_ctx.num_block_types, sizeof(int)); - - for (size_t i = 0; i < device_ctx.grid.width(); i++) { - for (size_t j = 0; j < device_ctx.grid.height(); j++) { - for (int k = 0; k < device_ctx.grid[i][j].type->capacity; k++) { - if (place_ctx.grid_blocks[i][j].blocks[k] == INVALID_BLOCK_ID) { - continue; - } - if (device_ctx.grid[i][j].width_offset == 0 && device_ctx.grid[i][j].height_offset == 0) { - int itype = device_ctx.grid[i][j].type->index; - legal_pos[itype][index[itype]].x = i; - legal_pos[itype][index[itype]].y = j; - legal_pos[itype][index[itype]].z = k; - index[itype]++; - } - } - } - } - free(index); -} - -static void free_legal_placements() { - auto& device_ctx = g_vpr_ctx.device(); - - for (int i = 0; i < device_ctx.num_block_types; i++) { - free(legal_pos[i]); - } - free(legal_pos); /* Free the mapping list */ - free(num_legal_pos); -} - - - -static int check_macro_can_be_placed(int imacro, int itype, int x, int y, int z) { - - int imember; - size_t member_x, member_y, member_z; - - auto& device_ctx = g_vpr_ctx.device(); - auto& place_ctx = g_vpr_ctx.placement(); - - // Every macro can be placed until proven otherwise - int macro_can_be_placed = true; - - // Check whether all the members can be placed - for (imember = 0; imember < pl_macros[imacro].num_blocks; imember++) { - member_x = x + pl_macros[imacro].members[imember].x_offset; - member_y = y + pl_macros[imacro].members[imember].y_offset; - member_z = z + pl_macros[imacro].members[imember].z_offset; - - // Check whether the location could accept block of this type - // Then check whether the location could still accomodate more blocks - // Also check whether the member position is valid, that is the member's location - // still within the chip's dimemsion and the member_z is allowed at that location on the grid - if (member_x < device_ctx.grid.width() && member_y < device_ctx.grid.height() - && device_ctx.grid[member_x][member_y].type->index == itype - && place_ctx.grid_blocks[member_x][member_y].blocks[member_z] == EMPTY_BLOCK_ID) { - // Can still accomodate blocks here, check the next position - continue; - } else { - // Cant be placed here - skip to the next try - macro_can_be_placed = false; - break; - } - } - - return (macro_can_be_placed); -} -#endif +///* Function to recompute the criticalities before the inner loop of the annealing */ +//static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, +// int num_connections, float crit_exponent, float bb_cost, +// float * place_delay_value, float * timing_cost, float * delay_cost, +// int * outer_crit_iter_count, float * inverse_prev_timing_cost, +// float * inverse_prev_bb_cost, +// const ClusteredPinAtomPinsLookup& netlist_pin_lookup, +//#ifdef ENABLE_CLASSIC_VPR_STA +// t_slack* slacks, +// t_timing_inf timing_inf, +//#endif +// SetupTimingInfo& timing_info) { +// +// if (placer_opts.place_algorithm != PATH_TIMING_DRIVEN_PLACE) +// return; +// +// /*at each temperature change we update these values to be used */ +// /*for normalizing the tradeoff between timing and wirelength (bb) */ +// if (*outer_crit_iter_count >= placer_opts.recompute_crit_iter +// || placer_opts.inner_loop_recompute_divider != 0) { +//#ifdef VERBOSE +// vtr::printf_info("Outer loop recompute criticalities\n"); +//#endif +// num_connections = std::max(num_connections, 1); //Avoid division by zero +// VTR_ASSERT(num_connections > 0); +// +// *place_delay_value = (*delay_cost) / num_connections; +// +// //Per-temperature timing update +// timing_info.update(); +// load_criticalities(timing_info, crit_exponent, netlist_pin_lookup); +// +//#ifdef ENABLE_CLASSIC_VPR_STA +// load_timing_graph_net_delays(point_to_point_delay_cost); +// do_timing_analysis(slacks, timing_inf, false, true); +//#endif +// +// /*recompute costs from scratch, based on new criticalities */ +// comp_td_costs(timing_cost, delay_cost); +// *outer_crit_iter_count = 0; +// } +// (*outer_crit_iter_count)++; +// +// /*at each temperature change we update these values to be used */ +// /*for normalizing the tradeoff between timing and wirelength (bb) */ +//*inverse_prev_bb_cost = 1 / bb_cost; +///*Prevent inverse timing cost from going to infinity */ +//*inverse_prev_timing_cost = min(1 / (*timing_cost), (float)MAX_INV_TIMING_COST); +//} +// +///* Function which contains the inner loop of the simulated annealing */ +//static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, +// float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, +// float crit_exponent, int inner_recompute_limit, +// t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, +// float * delay_cost, +//#ifdef ENABLE_CLASSIC_VPR_STA +// t_slack* slacks, +// t_timing_inf timing_inf, +//#endif +// const ClusteredPinAtomPinsLookup& netlist_pin_lookup, +// SetupTimingInfo& timing_info) { +// +// int inner_crit_iter_count, inner_iter; +// +// stats->av_cost = 0.; +// stats->av_bb_cost = 0.; +// stats->av_delay_cost = 0.; +// stats->av_timing_cost = 0.; +// stats->sum_of_squares = 0.; +// stats->success_sum = 0; +// +// inner_crit_iter_count = 1; +// +// /* Inner loop begins */ +// for (inner_iter = 0; inner_iter < move_lim; inner_iter++) { +// e_swap_result swap_result = try_swap(t, cost, bb_cost, timing_cost, rlim, +// placer_opts.place_algorithm, placer_opts.timing_tradeoff, +// inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost); +// +// if (swap_result == ACCEPTED) { +// /* Move was accepted. Update statistics that are useful for the annealing schedule. */ +// stats->success_sum++; +// stats->av_cost += *cost; +// stats->av_bb_cost += *bb_cost; +// stats->av_timing_cost += *timing_cost; +// stats->av_delay_cost += *delay_cost; +// stats->sum_of_squares += (*cost) * (*cost); +// num_swap_accepted++; +// } +// else if (swap_result == ABORTED) { +// num_swap_aborted++; +// } +// else { // swap_result == REJECTED +// num_swap_rejected++; +// } +// +// +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// +// /* Do we want to re-timing analyze the circuit to get updated slack and criticality values? +// * We do this only once in a while, since it is expensive. +// */ +// if (inner_crit_iter_count >= inner_recompute_limit +// && inner_iter != move_lim - 1) { /*on last iteration don't recompute */ +// +// inner_crit_iter_count = 0; +//#ifdef VERBOSE +// vtr::printf("Inner loop recompute criticalities\n"); +//#endif +// /* Using the delays in net_delay, do a timing analysis to update slacks and +// * criticalities; then update the timing cost since it will change. +// */ +// //Inner loop timing update +// timing_info.update(); +// load_criticalities(timing_info, crit_exponent, netlist_pin_lookup); +// +//#ifdef ENABLE_CLASSIC_VPR_STA +// load_timing_graph_net_delays(point_to_point_delay_cost); +// do_timing_analysis(slacks, timing_inf, false, true); +//#endif +// +// comp_td_costs(timing_cost, delay_cost); +// } +// inner_crit_iter_count++; +// } +//#ifdef VERBOSE +// vtr::printf("t = %g cost = %g bb_cost = %g timing_cost = %g move = %d dmax = %g\n", +// t, *cost, *bb_cost, *timing_cost, inner_iter, *delay_cost); +// if (fabs((*bb_cost) - comp_bb_cost(CHECK)) > (*bb_cost) * ERROR_TOL) +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "fabs((*bb_cost) - comp_bb_cost(CHECK)) > (*bb_cost) * ERROR_TOL"); +//#endif +// } +// /* Inner loop ends */ +//} +// +///*only count non-global connections */ +//static int count_connections() { +// +// int count = 0; +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// for (auto net_id : cluster_ctx.clb_nlist.nets()) { +// if (cluster_ctx.clb_nlist.net_is_global(net_id)) +// continue; +// +// count += cluster_ctx.clb_nlist.net_sinks(net_id).size(); +// } +// +// return (count); +//} +// +//static double get_std_dev(int n, double sum_x_squared, double av_x) { +// +// /* Returns the standard deviation of data set x. There are n sample points, * +// * sum_x_squared is the summation over n of x^2 and av_x is the average x. * +// * All operations are done in double precision, since round off error can be * +// * a problem in the initial temp. std_dev calculation for big circuits. */ +// +// double std_dev; +// +// if (n <= 1) +// std_dev = 0.; +// else +// std_dev = (sum_x_squared - n * av_x * av_x) / (double) (n - 1); +// +// if (std_dev > 0.) /* Very small variances sometimes round negative */ +// std_dev = sqrt(std_dev); +// else +// std_dev = 0.; +// +// return (std_dev); +//} +// +//static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid) { +// +// /* Update the range limited to keep acceptance prob. near 0.44. Use * +// * a floating point rlim to allow gradual transitions at low temps. */ +// +// float upper_lim; +// +// *rlim = (*rlim) * (1. - 0.44 + success_rat); +// upper_lim = max(grid.width() - 1, grid.height() - 1); +// *rlim = min(*rlim, upper_lim); +// *rlim = max(*rlim, (float)1.); +//} +// +///* Update the temperature according to the annealing schedule selected. */ +//static void update_t(float *t, float rlim, float success_rat, +// t_annealing_sched annealing_sched) { +// +// /* float fac; */ +// +// if (annealing_sched.type == USER_SCHED) { +// *t = annealing_sched.alpha_t * (*t); +// } else { /* AUTO_SCHED */ +// if (success_rat > 0.96) { +// *t = (*t) * 0.5; +// } else if (success_rat > 0.8) { +// *t = (*t) * 0.9; +// } else if (success_rat > 0.15 || rlim > 1.) { +// *t = (*t) * 0.95; +// } else { +// *t = (*t) * 0.8; +// } +// } +//} +// +//static int exit_crit(float t, float cost, +// t_annealing_sched annealing_sched) { +// +// /* Return 1 when the exit criterion is met. */ +// +// if (annealing_sched.type == USER_SCHED) { +// if (t < annealing_sched.exit_t) { +// return (1); +// } else { +// return (0); +// } +// } +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// /* Automatic annealing schedule */ +// float t_exit = 0.005 * cost / cluster_ctx.clb_nlist.nets().size(); +// +// if (t < t_exit) { +// return (1); +// } else if (std::isnan(t_exit)) { +// //May get nan if there are no nets +// return (1); +// } else { +// return (0); +// } +//} +// +//static float starting_t(float *cost_ptr, float *bb_cost_ptr, +// float *timing_cost_ptr, +// t_annealing_sched annealing_sched, int max_moves, float rlim, +// enum e_place_algorithm place_algorithm, float timing_tradeoff, +// float inverse_prev_bb_cost, float inverse_prev_timing_cost, +// float *delay_cost_ptr) { +// +// /* Finds the starting temperature (hot condition). */ +// +// int i, num_accepted, move_lim; +// double std_dev, av, sum_of_squares; /* Double important to avoid round off */ +// +// if (annealing_sched.type == USER_SCHED) +// return (annealing_sched.init_t); +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// move_lim = min(max_moves, (int) cluster_ctx.clb_nlist.blocks().size()); +// +// num_accepted = 0; +// av = 0.; +// sum_of_squares = 0.; +// +// /* Try one move per block. Set t high so essentially all accepted. */ +// +// for (i = 0; i < move_lim; i++) { +// e_swap_result swap_result = try_swap(HUGE_POSITIVE_FLOAT, cost_ptr, bb_cost_ptr, timing_cost_ptr, rlim, +// place_algorithm, timing_tradeoff, +// inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost_ptr); +// +// if (swap_result == ACCEPTED) { +// num_accepted++; +// av += *cost_ptr; +// sum_of_squares += *cost_ptr * (*cost_ptr); +// num_swap_accepted++; +// } else if (swap_result == ABORTED) { +// num_swap_aborted++; +// } else { +// num_swap_rejected++; +// } +// } +// +// if (num_accepted != 0) +// av /= num_accepted; +// else +// av = 0.; +// +// std_dev = get_std_dev(num_accepted, sum_of_squares, av); +// +// if (num_accepted != move_lim) { +// vtr::printf_warning(__FILE__, __LINE__, +// "Starting t: %d of %d configurations accepted.\n", num_accepted, move_lim); +// } +// +//#ifdef VERBOSE +// vtr::printf_info("std_dev: %g, average cost: %g, starting temp: %g\n", std_dev, av, 20. * std_dev); +//#endif +// +// /* Set the initial temperature to 20 times the standard of deviation */ +// /* so that the initial temperature adjusts according to the circuit */ +// return (20. * std_dev); +//} +// +// +//static int setup_blocks_affected(ClusterBlockId b_from, int x_to, int y_to, int z_to) { +// +// /* Find all the blocks affected when b_from is swapped with b_to. +// * Returns abort_swap. */ +// +// int imoved_blk, imacro; +// int x_from, y_from, z_from; +// ClusterBlockId b_to; +// int abort_swap = false; +// +// auto& place_ctx = g_vpr_ctx.mutable_placement(); +// +// x_from = place_ctx.block_locs[b_from].x; +// y_from = place_ctx.block_locs[b_from].y; +// z_from = place_ctx.block_locs[b_from].z; +// +// b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; +// +// // Check whether the to_location is empty +// if (b_to == EMPTY_BLOCK_ID) { +// +// // Swap the block, dont swap the nets yet +// place_ctx.block_locs[b_from].x = x_to; +// place_ctx.block_locs[b_from].y = y_to; +// place_ctx.block_locs[b_from].z = z_to; +// +// // Sets up the blocks moved +// imoved_blk = blocks_affected.num_moved_blocks; +// blocks_affected.moved_blocks[imoved_blk].block_num = b_from; +// blocks_affected.moved_blocks[imoved_blk].xold = x_from; +// blocks_affected.moved_blocks[imoved_blk].xnew = x_to; +// blocks_affected.moved_blocks[imoved_blk].yold = y_from; +// blocks_affected.moved_blocks[imoved_blk].ynew = y_to; +// blocks_affected.moved_blocks[imoved_blk].zold = z_from; +// blocks_affected.moved_blocks[imoved_blk].znew = z_to; +// blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = true; +// blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = true; +// blocks_affected.num_moved_blocks ++; +// +// } else if (b_to != INVALID_BLOCK_ID) { +// +// // Does not allow a swap with a macro yet +// get_imacro_from_iblk(&imacro, b_to, pl_macros, num_pl_macros); +// if (imacro != -1) { +// abort_swap = true; +// return (abort_swap); +// } +// +// // Swap the block, dont swap the nets yet +// place_ctx.block_locs[b_to].x = x_from; +// place_ctx.block_locs[b_to].y = y_from; +// place_ctx.block_locs[b_to].z = z_from; +// +// place_ctx.block_locs[b_from].x = x_to; +// place_ctx.block_locs[b_from].y = y_to; +// place_ctx.block_locs[b_from].z = z_to; +// +// // Sets up the blocks moved +// imoved_blk = blocks_affected.num_moved_blocks; +// blocks_affected.moved_blocks[imoved_blk].block_num = b_from; +// blocks_affected.moved_blocks[imoved_blk].xold = x_from; +// blocks_affected.moved_blocks[imoved_blk].xnew = x_to; +// blocks_affected.moved_blocks[imoved_blk].yold = y_from; +// blocks_affected.moved_blocks[imoved_blk].ynew = y_to; +// blocks_affected.moved_blocks[imoved_blk].zold = z_from; +// blocks_affected.moved_blocks[imoved_blk].znew = z_to; +// blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = false; +// blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = false; +// blocks_affected.num_moved_blocks ++; +// +// imoved_blk = blocks_affected.num_moved_blocks; +// blocks_affected.moved_blocks[imoved_blk].block_num = b_to; +// blocks_affected.moved_blocks[imoved_blk].xold = x_to; +// blocks_affected.moved_blocks[imoved_blk].xnew = x_from; +// blocks_affected.moved_blocks[imoved_blk].yold = y_to; +// blocks_affected.moved_blocks[imoved_blk].ynew = y_from; +// blocks_affected.moved_blocks[imoved_blk].zold = z_to; +// blocks_affected.moved_blocks[imoved_blk].znew = z_from; +// blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = false; +// blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = false; +// blocks_affected.num_moved_blocks ++; +// +// } // Finish swapping the blocks and setting up blocks_affected +// +// return (abort_swap); +// +//} +// +//static int find_affected_blocks(ClusterBlockId b_from, int x_to, int y_to, int z_to) { +// +// /* Finds and set ups the affected_blocks array. +// * Returns abort_swap. */ +// +// int imacro, imember; +// int x_swap_offset, y_swap_offset, z_swap_offset, x_from, y_from, z_from; +// ClusterBlockId curr_b_from; +// int curr_x_from, curr_y_from, curr_z_from, curr_x_to, curr_y_to, curr_z_to; +// int abort_swap = false; +// +// auto& place_ctx = g_vpr_ctx.placement(); +// auto& device_ctx = g_vpr_ctx.device(); +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// x_from = place_ctx.block_locs[b_from].x; +// y_from = place_ctx.block_locs[b_from].y; +// z_from = place_ctx.block_locs[b_from].z; +// +// get_imacro_from_iblk(&imacro, b_from, pl_macros, num_pl_macros); +// if ( imacro != -1) { +// // b_from is part of a macro, I need to swap the whole macro +// +// // Record down the relative position of the swap +// x_swap_offset = x_to - x_from; +// y_swap_offset = y_to - y_from; +// z_swap_offset = z_to - z_from; +// +// for (imember = 0; imember < pl_macros[imacro].num_blocks && abort_swap == false; imember++) { +// +// // Gets the new from and to info for every block in the macro +// // cannot use the old from and to info +// curr_b_from = pl_macros[imacro].members[imember].blk_index; +// +// curr_x_from = place_ctx.block_locs[curr_b_from].x; +// curr_y_from = place_ctx.block_locs[curr_b_from].y; +// curr_z_from = place_ctx.block_locs[curr_b_from].z; +// +// curr_x_to = curr_x_from + x_swap_offset; +// curr_y_to = curr_y_from + y_swap_offset; +// curr_z_to = curr_z_from + z_swap_offset; +// +// //Make sure that the swap_to location is valid +// //It must be: +// // * chip, and +// // * match the correct block type +// // +// //Note that we need to explicitly check that the types match, since the device floorplan is not +// //(neccessarily) translationally invariant for an arbitrary macro +// if ( curr_x_to < 1 || curr_x_to >= int(device_ctx.grid.width()) +// || curr_y_to < 1 || curr_y_to >= int(device_ctx.grid.height()) +// || curr_z_to < 0 +// || device_ctx.grid[curr_x_to][curr_y_to].type != cluster_ctx.clb_nlist.block_type(curr_b_from)) { +// abort_swap = true; +// } else { +// abort_swap = setup_blocks_affected(curr_b_from, curr_x_to, curr_y_to, curr_z_to); +// } +// } // Finish going through all the blocks in the macro +// +// } else { +// // This is not a macro - I could use the from and to info from before +// abort_swap = setup_blocks_affected(b_from, x_to, y_to, z_to); +// +// } // Finish handling cases for blocks in macro and otherwise +// +// return (abort_swap); +// +//} +// +//static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, +// float rlim, +// enum e_place_algorithm place_algorithm, float timing_tradeoff, +// float inverse_prev_bb_cost, float inverse_prev_timing_cost, +// float *delay_cost) { +// +// /* Picks some block and moves it to another spot. If this spot is * +// * occupied, switch the blocks. Assess the change in cost function. * +// * rlim is the range limiter. * +// * Returns whether the swap is accepted, rejected or aborted. * +// * Passes back the new value of the cost functions. */ +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& place_ctx = g_vpr_ctx.mutable_placement(); +// +// num_ts_called ++; +// +// /* I'm using negative values of temp_net_cost as a flag, so DO NOT * +// * use cost functions that can go negative. */ +// +// float delta_c = 0; /* Change in cost due to this swap. */ +// float bb_delta_c = 0; +// float timing_delta_c = 0; +// float delay_delta_c = 0.0; +// +// /* Pick a random block to be swapped with another random block. */ +// ClusterBlockId b_from = pick_from_block(); +// if (!b_from) { +// return ABORTED; //No movable block found +// } +// +// int x_from = place_ctx.block_locs[b_from].x; +// int y_from = place_ctx.block_locs[b_from].y; +// int z_from = place_ctx.block_locs[b_from].z; +// +// int x_to = OPEN; +// int y_to = OPEN; +// int z_to = OPEN; +// +// auto cluster_from_type = cluster_ctx.clb_nlist.block_type(b_from); +// auto grid_from_type = g_vpr_ctx.device().grid[x_from][y_from].type; +// VTR_ASSERT(cluster_from_type == grid_from_type); +// +// if (!find_to(cluster_ctx.clb_nlist.block_type(b_from), rlim, x_from, y_from, &x_to, &y_to, &z_to)) +// return REJECTED; +// +//#if 0 +// auto& grid = g_vpr_ctx.device().grid; +// int b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; +// vtr::printf_info( "swap [%d][%d][%d] %s \"%s\" <=> [%d][%d][%d] %s \"%s\"\n", +// x_from, y_from, z_from, grid[x_from][y_from].type->name, (b_from != -1 ? cluster_ctx.blocks[b_from].name : ""), +// x_to, y_to, z_to, grid[x_to][y_to].type->name, (b_to != -1 ? cluster_ctx.blocks[b_to].name : "")); +//#endif +// +// /* Make the switch in order to make computing the new bounding * +// * box simpler. If the cost increase is too high, switch them * +// * back. (place_ctx.block_locs data structures switched, clbs not switched * +// * until success of move is determined.) * +// * Also check that whether those are the only 2 blocks * +// * to be moved - check for carry chains and other placement * +// * macros. */ +// +// /* Check whether the from_block is part of a macro first. * +// * If it is, the whole macro has to be moved. Calculate the * +// * x, y, z offsets of the swap to maintain relative placements * +// * of the blocks. Abort the swap if the to_block is part of a * +// * macro (not supported yet). */ +// +// bool abort_swap = find_affected_blocks(b_from, x_to, y_to, z_to); +// +// if (abort_swap == false) { +// +// // Find all the nets affected by this swap and update thier bounding box +// int num_nets_affected = find_affected_nets_and_update_costs(place_algorithm, bb_delta_c, timing_delta_c, delay_delta_c); +// +// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// /*in this case we redefine delta_c as a combination of timing and bb. * +// *additionally, we normalize all values, therefore delta_c is in * +// *relation to 1*/ +// +// delta_c = (1 - timing_tradeoff) * bb_delta_c * inverse_prev_bb_cost +// + timing_tradeoff * timing_delta_c * inverse_prev_timing_cost; +// } else { +// delta_c = bb_delta_c; +// } +// +// /* 1 -> move accepted, 0 -> rejected. */ +// e_swap_result keep_switch = assess_swap(delta_c, t); +// +// if (keep_switch == ACCEPTED) { +// *cost = *cost + delta_c; +// *bb_cost = *bb_cost + bb_delta_c; +// +// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// /*update the point_to_point_timing_cost and point_to_point_delay_cost +// * values from the temporary values */ +// *timing_cost = *timing_cost + timing_delta_c; +// *delay_cost = *delay_cost + delay_delta_c; +// +// update_td_cost(); +// } +// +// /* update net cost functions and reset flags. */ +// for (int inet_affected = 0; inet_affected < num_nets_affected; inet_affected++) { +// ClusterNetId net_id = ts_nets_to_update[inet_affected]; +// +// bb_coords[net_id] = ts_bb_coord_new[net_id]; +// if (cluster_ctx.clb_nlist.net_sinks(net_id).size() >= SMALL_NET) +// bb_num_on_edges[net_id] = ts_bb_edge_new[net_id]; +// +// net_cost[net_id] = temp_net_cost[net_id]; +// +// /* negative temp_net_cost value is acting as a flag. */ +// temp_net_cost[net_id] = -1; +// bb_updated_before[net_id] = NOT_UPDATED_YET; +// } +// +// /* Update clb data structures since we kept the move. */ +// /* Swap physical location */ +// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { +// +// x_to = blocks_affected.moved_blocks[iblk].xnew; +// y_to = blocks_affected.moved_blocks[iblk].ynew; +// z_to = blocks_affected.moved_blocks[iblk].znew; +// +// x_from = blocks_affected.moved_blocks[iblk].xold; +// y_from = blocks_affected.moved_blocks[iblk].yold; +// z_from = blocks_affected.moved_blocks[iblk].zold; +// +// b_from = blocks_affected.moved_blocks[iblk].block_num; +// +// place_ctx.grid_blocks[x_to][y_to].blocks[z_to] = b_from; +// +// if (blocks_affected.moved_blocks[iblk].swapped_to_was_empty) { +// place_ctx.grid_blocks[x_to][y_to].usage++; +// } +// if (blocks_affected.moved_blocks[iblk].swapped_from_is_empty) { +// place_ctx.grid_blocks[x_from][y_from].usage--; +// place_ctx.grid_blocks[x_from][y_from].blocks[z_from] = EMPTY_BLOCK_ID; +// } +// +// } // Finish updating clb for all blocks +// +// } else { /* Move was rejected. */ +// +// /* Reset the net cost function flags first. */ +// for (int inet_affected = 0; inet_affected < num_nets_affected; inet_affected++) { +// ClusterNetId net_id = ts_nets_to_update[inet_affected]; +// temp_net_cost[net_id] = -1; +// bb_updated_before[net_id] = NOT_UPDATED_YET; +// } +// +// /* Restore the place_ctx.block_locs data structures to their state before the move. */ +// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { +// b_from = blocks_affected.moved_blocks[iblk].block_num; +// +// place_ctx.block_locs[b_from].x = blocks_affected.moved_blocks[iblk].xold; +// place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; +// place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; +// } +// } +// +// /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ +// blocks_affected.num_moved_blocks = 0; +// +//#if 0 +// //Check that each accepted swap yields a valid placement +// check_place(*bb_cost, *timing_cost, place_algorithm, *delay_cost); +//#endif +// +// return (keep_switch); +// } else { +// +// /* Restore the place_ctx.block_locs data structures to their state before the move. */ +// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { +// b_from = blocks_affected.moved_blocks[iblk].block_num; +// +// place_ctx.block_locs[b_from].x = blocks_affected.moved_blocks[iblk].xold; +// place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; +// place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; +// } +// +// /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ +// blocks_affected.num_moved_blocks = 0; +// +// return ABORTED; +// } +//} +// +////Pick a random block to be swapped with another random block. +////If none is found return ClusterBlockId::INVALID() +//static ClusterBlockId pick_from_block() { +// /* Some blocks may be fixed, and should never be moved from their * +// * initial positions. If we randomly selected such a block try * +// * another random block. * +// * * +// * We need to track the blocks we have tried to avoid an infinite * +// * loop if all blocks are fixed. */ +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& place_ctx = g_vpr_ctx.mutable_placement(); +// +// std::unordered_set tried_from_blocks; +// +// //So long as untried blocks remain +// while (tried_from_blocks.size() < cluster_ctx.clb_nlist.blocks().size()) { +// +// //Pick a block at random +// ClusterBlockId b_from = ClusterBlockId(vtr::irand((int) cluster_ctx.clb_nlist.blocks().size() - 1)); +// +// //Record it as tried +// tried_from_blocks.insert(b_from); +// +// if (place_ctx.block_locs[b_from].is_fixed) { +// continue; //Fixed location, try again +// } +// +// //Found a movable block +// return b_from; +// } +// +// //No movable blocks found +// return ClusterBlockId::INVALID(); +//} +// +////Puts all the nets changed by the current swap into nets_to_update, +////and updates their bounding box. +//// +////Returns the number of affected nets. +//static int find_affected_nets_and_update_costs(e_place_algorithm place_algorithm, float& bb_delta_c, float& timing_delta_c, float& delay_delta_c) { +// VTR_ASSERT_SAFE(bb_delta_c == 0.); +// VTR_ASSERT_SAFE(timing_delta_c == 0.); +// VTR_ASSERT_SAFE(delay_delta_c == 0.); +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// int num_affected_nets = 0; +// +// //Go through all the blocks moved +// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { +// ClusterBlockId blk = blocks_affected.moved_blocks[iblk].block_num; +// +// //Go through all the pins in the moved block +// for (ClusterPinId blk_pin : cluster_ctx.clb_nlist.block_pins(blk)) { +// ClusterNetId net_id = cluster_ctx.clb_nlist.pin_net(blk_pin); +// VTR_ASSERT_SAFE_MSG(net_id, "Only valid nets should be found in compressed netlist block pins"); +// +// if (cluster_ctx.clb_nlist.net_is_global(net_id)) +// continue; //Global nets are assumed to span the whole chip, and do not effect costs +// +// //Record effected nets +// record_affected_net(net_id, num_affected_nets); +// +// //Update the net bounding boxes +// // +// //Do not update the net cost here since it should only be updated +// //once per net, not once per pin. +// update_net_bb(net_id, iblk, blk, blk_pin); +// +// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// //Determine the change in timing costs if required +// update_td_delta_costs(net_id, blk_pin, timing_delta_c, delay_delta_c); +// } +// } +// } +// +// /* Now update the bounding box costs (since the net bounding boxes are up-to-date). +// * The cost is only updated once per net. +// */ +// for (int inet_affected = 0; inet_affected < num_affected_nets; inet_affected++) { +// ClusterNetId net_id = ts_nets_to_update[inet_affected]; +// +// temp_net_cost[net_id] = get_net_cost(net_id, &ts_bb_coord_new[net_id]); +// bb_delta_c += temp_net_cost[net_id] - net_cost[net_id]; +// } +// +// return num_affected_nets; +//} +// +//static void record_affected_net(const ClusterNetId net, int& num_affected_nets) { +// //Record effected nets +// if (temp_net_cost[net] < 0.) { +// //Net not marked yet. +// ts_nets_to_update[num_affected_nets] = net; +// num_affected_nets++; +// +// //Flag to say we've marked this net. +// temp_net_cost[net] = 1.; +// } +//} +// +//static void update_net_bb(const ClusterNetId net, int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin) { +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// if (cluster_ctx.clb_nlist.net_sinks(net).size() < SMALL_NET) { +// //For small nets brute-force bounding box update is faster +// +// if(bb_updated_before[net] == NOT_UPDATED_YET) { //Only once per-net +// get_non_updateable_bb(net, &ts_bb_coord_new[net]); +// } +// } else { +// //For large nets, update bounding box incrementally +// int iblk_pin = cluster_ctx.clb_nlist.pin_physical_index(blk_pin); +// +// t_type_ptr blk_type = cluster_ctx.clb_nlist.block_type(blk); +// int pin_width_offset = blk_type->pin_width_offset[iblk_pin]; +// int pin_height_offset = blk_type->pin_height_offset[iblk_pin]; +// +// //Incremental bounding box update +// update_bb(net, &ts_bb_coord_new[net], +// &ts_bb_edge_new[net], +// blocks_affected.moved_blocks[iblk].xold + pin_width_offset, +// blocks_affected.moved_blocks[iblk].yold + pin_height_offset, +// blocks_affected.moved_blocks[iblk].xnew + pin_width_offset, +// blocks_affected.moved_blocks[iblk].ynew + pin_height_offset); +// } +// +//} +// +//static void update_td_delta_costs(const ClusterNetId net, const ClusterPinId pin, float& delta_timing_cost, float& delta_delay_cost) { +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// if (cluster_ctx.clb_nlist.pin_type(pin) == PinType::DRIVER) { +// //This pin is a net driver on a moved block. +// //Re-compute all point to point connections for this net. +// for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net).size(); ipin++) { +// float temp_delay = comp_td_point_to_point_delay(net, ipin); +// temp_point_to_point_delay_cost[net][ipin] = temp_delay; +// +// temp_point_to_point_timing_cost[net][ipin] = get_timing_place_crit(net, ipin) * temp_delay; +// delta_timing_cost += temp_point_to_point_timing_cost[net][ipin] - point_to_point_timing_cost[net][ipin]; +// delta_delay_cost += temp_point_to_point_delay_cost[net][ipin] - point_to_point_delay_cost[net][ipin]; +// +// } +// } else { +// //This pin is a net sink on a moved block +// VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.pin_type(pin) == PinType::SINK); +// +// //If this net is being driven by a moved block, we do not +// //need to compute the change in the timing cost (here) since it will +// //be computed by the net's driver pin (since the driver block moved). +// // +// //Computing it here would double count the change, and mess up the +// //delta_timing_cost value. +// if (!driven_by_moved_block(net)) { +// int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin); +// +// float temp_delay = comp_td_point_to_point_delay(net, net_pin); +// temp_point_to_point_delay_cost[net][net_pin] = temp_delay; +// +// temp_point_to_point_timing_cost[net][net_pin] = get_timing_place_crit(net, net_pin) * temp_delay; +// delta_timing_cost += temp_point_to_point_timing_cost[net][net_pin] - point_to_point_timing_cost[net][net_pin]; +// delta_delay_cost += temp_point_to_point_delay_cost[net][net_pin] - point_to_point_delay_cost[net][net_pin]; +// } +// } +//} +// +//static bool find_to(t_type_ptr type, float rlim, +// int x_from, int y_from, +// int *px_to, int *py_to, int *pz_to) { +// +// /* Returns the point to which I want to swap, properly range limited. +// * rlim must always be between 1 and device_ctx.grid.width() - 2 (inclusive) for this routine +// * to work. Note -2 for no perim channels +// */ +// +// int min_x, max_x, min_y, max_y; +// int num_tries; +// int active_area; +// bool is_legal; +// int itype; +// +// auto& grid = g_vpr_ctx.device().grid; +// auto& place_ctx = g_vpr_ctx.placement(); +// +// auto grid_type = grid[x_from][y_from].type; +// VTR_ASSERT(type == grid_type); +// +// int rlx = min(grid.width() - 1, rlim); +// int rly = min(grid.height() - 1, rlim); /* Added rly for aspect_ratio != 1 case. */ +// active_area = 4 * rlx * rly; +// +// min_x = max(0, x_from - rlx); +// max_x = min(grid.width() - 1, x_from + rlx); +// min_y = max(0, y_from - rly); +// max_y = min(grid.height() - 1, y_from + rly); +// +// if (rlx < 1 || rlx > int(grid.width() - 1)) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in find_to: rlx = %d out of range\n", rlx); +// } +// if (rly < 1 || rly > int(grid.height() - 1)) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in find_to: rly = %d out of range\n", rly); +// } +// +// num_tries = 0; +// itype = type->index; +// +// do { /* Until legal */ +// is_legal = true; +// +// /* Limit the number of tries when searching for an alternative position */ +// if(num_tries >= 2 * min(active_area / (type->width * type->height), num_legal_pos[itype]) + 10) { +// /* Tried randomly searching for a suitable position */ +// return false; +// } else { +// num_tries++; +// } +// +// find_to_location(type, rlim, x_from, y_from, +// px_to, py_to, pz_to); +// +// if((x_from == *px_to) && (y_from == *py_to)) { +// is_legal = false; +// } else if(*px_to > max_x || *px_to < min_x || *py_to > max_y || *py_to < min_y) { +// is_legal = false; +// } else if(grid[*px_to][*py_to].type != grid[x_from][y_from].type) { +// is_legal = false; +// } else { +// /* Find z_to and test to validate that the "to" block is *not* fixed */ +// *pz_to = 0; +// if (grid[*px_to][*py_to].type->capacity > 1) { +// *pz_to = vtr::irand(grid[*px_to][*py_to].type->capacity - 1); +// } +// ClusterBlockId b_to = place_ctx.grid_blocks[*px_to][*py_to].blocks[*pz_to]; +// if ((b_to != EMPTY_BLOCK_ID) && (place_ctx.block_locs[b_to].is_fixed == true)) { +// is_legal = false; +// } +// } +// +// VTR_ASSERT(*px_to >= 0 && *px_to < int(grid.width())); +// VTR_ASSERT(*py_to >= 0 && *py_to < int(grid.height())); +// } while (is_legal == false); +// +// if (*px_to < 0 || *px_to > int(grid.width() - 1) || *py_to < 0 || *py_to > int(grid.height() - 1)) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in routine find_to: (x_to,y_to) = (%d,%d)\n", *px_to, *py_to); +// } +// +// VTR_ASSERT(type == grid[*px_to][*py_to].type); +// return true; +//} +// +//static void find_to_location(t_type_ptr type, float rlim, +// int x_from, int y_from, +// int *px_to, int *py_to, int *pz_to) { +// +// auto& device_ctx = g_vpr_ctx.device(); +// auto& grid = device_ctx.grid; +// +// int itype = type->index; +// +// +// int rlx = min(grid.width() - 1, rlim); +// int rly = min(grid.height() - 1, rlim); /* Added rly for aspect_ratio != 1 case. */ +// int active_area = 4 * rlx * rly; +// +// int min_x = max(0, x_from - rlx); +// int max_x = min(grid.width() - 1, x_from + rlx); +// int min_y = max(0, y_from - rly); +// int max_y = min(grid.height() - 1, y_from + rly); +// +// *pz_to = 0; +// if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || num_legal_pos[itype] < active_area) { +// int ipos = vtr::irand(num_legal_pos[itype] - 1); +// *px_to = legal_pos[itype][ipos].x; +// *py_to = legal_pos[itype][ipos].y; +// *pz_to = legal_pos[itype][ipos].z; +// } else { +// int x_rel = vtr::irand(max(0, max_x - min_x)); +// int y_rel = vtr::irand(max(0, max_y - min_y)); +// *px_to = min_x + x_rel; +// *py_to = min_y + y_rel; +// *px_to = (*px_to) - grid[*px_to][*py_to].width_offset; /* align it */ +// *py_to = (*py_to) - grid[*px_to][*py_to].height_offset; /* align it */ +// } +//} +// +//static e_swap_result assess_swap(float delta_c, float t) { +// +// /* Returns: 1 -> move accepted, 0 -> rejected. */ +// +// e_swap_result accept; +// float prob_fac, fnum; +// +// if (delta_c <= 0) { +// +// /* Reduce variation in final solution due to round off */ +// fnum = vtr::frand(); +// +// accept = ACCEPTED; +// return (accept); +// } +// +// if (t == 0.) +// return (REJECTED); +// +// fnum = vtr::frand(); +// prob_fac = exp(-delta_c / t); +// if (prob_fac > fnum) { +// accept = ACCEPTED; +// } +// else { +// accept = REJECTED; +// } +// return (accept); +//} +// +//static float recompute_bb_cost() { +// /* Recomputes the cost to eliminate roundoff that may have accrued. * +// * This routine does as little work as possible to compute this new * +// * cost. */ +// +// float cost = 0; +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// for (auto net_id : cluster_ctx.clb_nlist.nets()) { /* for each net ... */ +// if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ +// /* Bounding boxes don't have to be recomputed; they're correct. */ +// cost += net_cost[net_id]; +// } +// } +// +// return (cost); +//} +// +///*returns the delay of one point to point connection */ +//static float comp_td_point_to_point_delay(ClusterNetId net_id, int ipin) { +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& place_ctx = g_vpr_ctx.placement(); +// +// float delay_source_to_sink = 0.; +// +// if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { +// //Only estimate delay for signals routed through the inter-block +// //routing network. Global signals are assumed to have zero delay. +// +// ClusterBlockId source_block = cluster_ctx.clb_nlist.net_driver_block(net_id); +// ClusterBlockId sink_block = cluster_ctx.clb_nlist.net_pin_block(net_id, ipin); +// +// VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.block_type(source_block) != nullptr); +// VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.block_type(sink_block) != nullptr); +// +// int delta_x = abs(place_ctx.block_locs[sink_block].x - place_ctx.block_locs[source_block].x); +// int delta_y = abs(place_ctx.block_locs[sink_block].y - place_ctx.block_locs[source_block].y); +// +// /* Note: This heuristic only considers delta_x and delta_y, a much better heuristic +// * would be to to create a more comprehensive lookup table. +// * +// * In particular this aproach does not accurately capture the effect of fast +// * carry-chain connections. +// */ +// delay_source_to_sink = get_delta_delay(delta_x, delta_y); +// if (delay_source_to_sink < 0) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "in comp_td_point_to_point_delay: Bad delay_source_to_sink value delta(%d, %d) delay of %g\n" +// "in comp_td_point_to_point_delay: Delay is less than 0\n", +// delta_x, delta_y, delay_source_to_sink); +// } +// } +// +// +// return (delay_source_to_sink); +//} +// +////Recompute all point to point delays, updating point_to_point_delay_cost +//static void comp_td_point_to_point_delays() { +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// for (auto net_id : cluster_ctx.clb_nlist.nets()) { +// for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ++ipin) { +// point_to_point_delay_cost[net_id][ipin] = comp_td_point_to_point_delay(net_id, ipin); +// } +// } +//} +// +///* Update the point_to_point_timing_cost values from the temporary * +//* values for all connections that have changed. */ +//static void update_td_cost() { +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// /* Go through all the blocks moved. */ +// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { +// ClusterBlockId bnum = blocks_affected.moved_blocks[iblk].block_num; +// for (ClusterPinId pin_id : cluster_ctx.clb_nlist.block_pins(bnum)) { +// ClusterNetId net_id = cluster_ctx.clb_nlist.pin_net(pin_id); +// +// if (cluster_ctx.clb_nlist.net_is_global(net_id)) +// continue; +// +// if (cluster_ctx.clb_nlist.pin_type(pin_id) == PinType::DRIVER) { +// //This net is being driven by a moved block, recompute +// //all point to point connections on this net. +// for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { +// point_to_point_delay_cost[net_id][ipin] = temp_point_to_point_delay_cost[net_id][ipin]; +// temp_point_to_point_delay_cost[net_id][ipin] = -1; +// point_to_point_timing_cost[net_id][ipin] = temp_point_to_point_timing_cost[net_id][ipin]; +// temp_point_to_point_timing_cost[net_id][ipin] = -1; +// } +// } else { +// //This pin is a net sink on a moved block +// VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.pin_type(pin_id) == PinType::SINK); +// +// /* The following "if" prevents the value from being updated twice. */ +// if (!driven_by_moved_block(net_id)) { +// int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin_id); +// +// point_to_point_delay_cost[net_id][net_pin] = temp_point_to_point_delay_cost[net_id][net_pin]; +// temp_point_to_point_delay_cost[net_id][net_pin] = -1; +// point_to_point_timing_cost[net_id][net_pin] = temp_point_to_point_timing_cost[net_id][net_pin]; +// temp_point_to_point_timing_cost[net_id][net_pin] = -1; +// } +// } +// } /* Finished going through all the pins in the moved block */ +// } /* Finished going through all the blocks moved */ +//} +// +//static bool driven_by_moved_block(const ClusterNetId net) { +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// ClusterBlockId net_driver_block = cluster_ctx.clb_nlist.net_driver_block(net); +// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { +// if (net_driver_block == blocks_affected.moved_blocks[iblk].block_num) { +// return true; +// } +// } +// return false; +//} +// +//static void comp_td_costs(float *timing_cost, float *connection_delay_sum) { +// /* Computes the cost (from scratch) due to the delays and criticalities * +// * on all point to point connections, we define the timing cost of * +// * each connection as criticality*delay. */ +// +// unsigned ipin; +// float loc_timing_cost, loc_connection_delay_sum, temp_delay_cost, +// temp_timing_cost; +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// loc_timing_cost = 0.; +// loc_connection_delay_sum = 0.; +// +// for (auto net_id : cluster_ctx.clb_nlist.nets()) { /* For each net ... */ +// +// if (cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ +// continue; +// } +// +// for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { +// temp_delay_cost = comp_td_point_to_point_delay(net_id, ipin); +// temp_timing_cost = temp_delay_cost * get_timing_place_crit(net_id, ipin); +// +// loc_connection_delay_sum += temp_delay_cost; +// point_to_point_delay_cost[net_id][ipin] = temp_delay_cost; +// temp_point_to_point_delay_cost[net_id][ipin] = -1; /* Undefined */ +// +// point_to_point_timing_cost[net_id][ipin] = temp_timing_cost; +// temp_point_to_point_timing_cost[net_id][ipin] = -1; /* Undefined */ +// loc_timing_cost += temp_timing_cost; +// } +// } +// +// /* Make sure timing cost does not go above MIN_TIMING_COST. */ +// *timing_cost = loc_timing_cost; +// +// *connection_delay_sum = loc_connection_delay_sum; +//} +// +// +///* Finds the cost from scratch. Done only when the placement * +//* has been radically changed (i.e. after initial placement). * +//* Otherwise find the cost change incrementally. If method * +//* check is NORMAL, we find bounding boxes that are updateable * +//* for the larger nets. If method is CHECK, all bounding boxes * +//* are found via the non_updateable_bb routine, to provide a * +//* cost which can be used to check the correctness of the * +//* other routine. */ +//static float comp_bb_cost(e_cost_methods method) { +// float cost = 0; +// double expected_wirelength = 0.0; +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// for (auto net_id : cluster_ctx.clb_nlist.nets()) { /* for each net ... */ +// if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ +// /* Small nets don't use incremental updating on their bounding boxes, * +// * so they can use a fast bounding box calculator. */ +// if (cluster_ctx.clb_nlist.net_sinks(net_id).size() >= SMALL_NET && method == NORMAL) { +// get_bb_from_scratch(net_id, &bb_coords[net_id], +// &bb_num_on_edges[net_id]); +// } +// else { +// get_non_updateable_bb(net_id, &bb_coords[net_id]); +// } +// +// net_cost[net_id] = get_net_cost(net_id, &bb_coords[net_id]); +// cost += net_cost[net_id]; +// if (method == CHECK) +// expected_wirelength += get_net_wirelength_estimate(net_id, &bb_coords[net_id]); +// } +// } +// +// if (method == CHECK) { +// vtr::printf_info("\n"); +// vtr::printf_info("BB estimate of min-dist (placement) wire length: %.0f\n", expected_wirelength); +// } +// return cost; +//} +// +// +///* Frees the major structures needed by the placer (and not needed * +//* elsewhere). */ +//static void free_placement_structs(t_placer_opts placer_opts) { +// int imacro; +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// free_legal_placements(); +// free_fast_cost_update(); +// +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE +// || placer_opts.enable_timing_computations) { +// +// for (auto net_id : cluster_ctx.clb_nlist.nets()) { +// /*add one to the address since it is indexed from 1 not 0 */ +// point_to_point_timing_cost[net_id]++; +// free(point_to_point_timing_cost[net_id]); +// +// temp_point_to_point_timing_cost[net_id]++; +// free(temp_point_to_point_timing_cost[net_id]); +// +// point_to_point_delay_cost[net_id]++; +// free(point_to_point_delay_cost[net_id]); +// +// temp_point_to_point_delay_cost[net_id]++; +// free(temp_point_to_point_delay_cost[net_id]); +// } +// +// point_to_point_timing_cost.clear(); +// point_to_point_delay_cost.clear(); +// temp_point_to_point_timing_cost.clear(); +// temp_point_to_point_delay_cost.clear(); +// +// net_pin_indices.clear(); +// } +// +// free_placement_macros_structs(); +// +// for (imacro = 0; imacro < num_pl_macros; imacro++) +// free(pl_macros[imacro].members); +// free(pl_macros); +// +// /* Defensive coding. */ +// pl_macros = nullptr; +// +// /* Frees up all the data structure used in vpr_utils. */ +// free_port_pin_from_blk_pin(); +// free_blk_pin_from_port_pin(); +// +//} +// +///* Allocates the major structures needed only by the placer, primarily for * +//* computing costs quickly and such. */ +//static void alloc_and_load_placement_structs( +// float place_cost_exp, t_placer_opts placer_opts, +// t_direct_inf *directs, int num_directs) { +// +// int max_pins_per_clb, i; +// unsigned int ipin; +// +// auto& device_ctx = g_vpr_ctx.device(); +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// size_t num_nets = cluster_ctx.clb_nlist.nets().size(); +// +// init_placement_context(); +// +// alloc_legal_placements(); +// load_legal_placements(); +// +// max_pins_per_clb = 0; +// for (i = 0; i < device_ctx.num_block_types; i++) { +// max_pins_per_clb = max(max_pins_per_clb, device_ctx.block_types[i].num_pins); +// } +// +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE +// || placer_opts.enable_timing_computations) { +// /* Allocate structures associated with timing driven placement */ +// /* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1] */ +// point_to_point_delay_cost.resize(num_nets); +// temp_point_to_point_delay_cost.resize(num_nets); +// +// point_to_point_timing_cost.resize(num_nets); +// temp_point_to_point_timing_cost.resize(num_nets); +// +// for (auto net_id : cluster_ctx.clb_nlist.nets()) { +// size_t num_sinks = cluster_ctx.clb_nlist.net_sinks(net_id).size(); +// /* In the following, subract one so index starts at * +// * 1 instead of 0 */ +// point_to_point_delay_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); +// point_to_point_delay_cost[net_id]--; +// +// temp_point_to_point_delay_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); +// temp_point_to_point_delay_cost[net_id]--; +// +// point_to_point_timing_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); +// point_to_point_timing_cost[net_id]--; +// +// temp_point_to_point_timing_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); +// temp_point_to_point_timing_cost[net_id]--; +// } +// for (auto net_id : cluster_ctx.clb_nlist.nets()) { +// for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { +// point_to_point_delay_cost[net_id][ipin] = 0; +// temp_point_to_point_delay_cost[net_id][ipin] = 0; +// } +// } +// } +// +// net_cost.resize(num_nets, -1.); +// temp_net_cost.resize(num_nets, -1.); +// bb_coords.resize(num_nets, t_bb()); +// bb_num_on_edges.resize(num_nets, t_bb()); +// +// /* Used to store costs for moves not yet made and to indicate when a net's * +// * cost has been recomputed. temp_net_cost[inet] < 0 means net's cost hasn't * +// * been recomputed. */ +// bb_updated_before.resize(num_nets, NOT_UPDATED_YET); +// +// alloc_and_load_for_fast_cost_update(place_cost_exp); +// +// alloc_and_load_net_pin_indices(); +// +// alloc_and_load_try_swap_structs(); +// +// num_pl_macros = alloc_and_load_placement_macros(directs, num_directs, &pl_macros); +//} +// +///* Allocates and loads net_pin_indices array, this array allows us to quickly * +//* find what pin on the net a block pin corresponds to. Returns the pointer * +//* to the 2D net_pin_indices array. */ +//static void alloc_and_load_net_pin_indices() { +// unsigned int netpin; +// int itype, max_pins_per_clb = 0; +// +// auto& device_ctx = g_vpr_ctx.device(); +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// /* Compute required size. */ +// for (itype = 0; itype < device_ctx.num_block_types; itype++) +// max_pins_per_clb = max(max_pins_per_clb, device_ctx.block_types[itype].num_pins); +// +// /* Allocate for maximum size. */ +// net_pin_indices.resize(cluster_ctx.clb_nlist.blocks().size()); +// +// for (auto blk_id : cluster_ctx.clb_nlist.blocks()) +// net_pin_indices[blk_id].resize(max_pins_per_clb); +// +// /* Load the values */ +// for (auto net_id : cluster_ctx.clb_nlist.nets()) { +// if (cluster_ctx.clb_nlist.net_is_global(net_id)) +// continue; +// netpin = 0; +// for (auto pin_id : cluster_ctx.clb_nlist.net_pins(net_id)) { +// int pin_index = cluster_ctx.clb_nlist.pin_physical_index(pin_id); +// ClusterBlockId block_id = cluster_ctx.clb_nlist.pin_block(pin_id); +// net_pin_indices[block_id][pin_index] = netpin; +// netpin++; +// } +// } +//} +// +//static void alloc_and_load_try_swap_structs() { +// /* Allocate the local bb_coordinate storage, etc. only once. */ +// /* Allocate with size cluster_ctx.clb_nlist.nets().size() for any number of nets affected. */ +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// size_t num_nets = cluster_ctx.clb_nlist.nets().size(); +// +// ts_bb_coord_new.resize(num_nets, t_bb()); +// ts_bb_edge_new.resize(num_nets, t_bb()); +// ts_nets_to_update.resize(num_nets, ClusterNetId::INVALID()); +// +// /* Allocate with size cluster_ctx.clb_nlist.blocks().size() for any number of moved blocks. */ +// blocks_affected.moved_blocks = (t_pl_moved_block*) vtr::calloc((int) cluster_ctx.clb_nlist.blocks().size(), sizeof(t_pl_moved_block)); +// blocks_affected.num_moved_blocks = 0; +// +//} +// +///* This routine finds the bounding box of each net from scratch (i.e. * +//* from only the block location information). It updates both the * +//* coordinate and number of pins on each edge information. It * +//* should only be called when the bounding box information is not valid. */ +//static void get_bb_from_scratch(ClusterNetId net_id, t_bb *coords, +// t_bb *num_on_edges) { +// int pnum, x, y, xmin, xmax, ymin, ymax; +// int xmin_edge, xmax_edge, ymin_edge, ymax_edge; +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& place_ctx = g_vpr_ctx.placement(); +// auto& device_ctx = g_vpr_ctx.device(); +// auto& grid = device_ctx.grid; +// +// ClusterBlockId bnum = cluster_ctx.clb_nlist.net_driver_block(net_id); +// pnum = cluster_ctx.clb_nlist.net_pin_physical_index(net_id, 0); +// x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; +// y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; +// +// x = max(min(x, grid.width() - 2), 1); +// y = max(min(y, grid.height() - 2), 1); +// +// xmin = x; +// ymin = y; +// xmax = x; +// ymax = y; +// xmin_edge = 1; +// ymin_edge = 1; +// xmax_edge = 1; +// ymax_edge = 1; +// +// for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) { +// bnum = cluster_ctx.clb_nlist.pin_block(pin_id); +// pnum = cluster_ctx.clb_nlist.pin_physical_index(pin_id); +// x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; +// y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; +// +// /* Code below counts IO blocks as being within the 1..grid.width()-2, 1..grid.height()-2 clb array. * +// * This is because channels do not go out of the 0..grid.width()-2, 0..grid.height()-2 range, and * +// * I always take all channels impinging on the bounding box to be within * +// * that bounding box. Hence, this "movement" of IO blocks does not affect * +// * the which channels are included within the bounding box, and it * +// * simplifies the code a lot. */ +// +// x = max(min(x, grid.width() - 2), 1); //-2 for no perim channels +// y = max(min(y, grid.height() - 2), 1); //-2 for no perim channels +// +// if (x == xmin) { +// xmin_edge++; +// } +// if (x == xmax) { /* Recall that xmin could equal xmax -- don't use else */ +// xmax_edge++; +// } +// else if (x < xmin) { +// xmin = x; +// xmin_edge = 1; +// } +// else if (x > xmax) { +// xmax = x; +// xmax_edge = 1; +// } +// +// if (y == ymin) { +// ymin_edge++; +// } +// if (y == ymax) { +// ymax_edge++; +// } +// else if (y < ymin) { +// ymin = y; +// ymin_edge = 1; +// } +// else if (y > ymax) { +// ymax = y; +// ymax_edge = 1; +// } +// } +// +// /* Copy the coordinates and number on edges information into the proper * +// * structures. */ +// coords->xmin = xmin; +// coords->xmax = xmax; +// coords->ymin = ymin; +// coords->ymax = ymax; +// +// num_on_edges->xmin = xmin_edge; +// num_on_edges->xmax = xmax_edge; +// num_on_edges->ymin = ymin_edge; +// num_on_edges->ymax = ymax_edge; +//} +// +//static double get_net_wirelength_estimate(ClusterNetId net_id, t_bb *bbptr) { +// +// /* WMF: Finds the estimate of wirelength due to one net by looking at * +// * its coordinate bounding box. */ +// +// double ncost, crossing; +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// /* Get the expected "crossing count" of a net, based on its number * +// * of pins. Extrapolate for very large nets. */ +// +// if (((cluster_ctx.clb_nlist.net_pins(net_id).size()) > 50) +// && ((cluster_ctx.clb_nlist.net_pins(net_id).size()) < 85)) { +// crossing = 2.7933 + 0.02616 * ((cluster_ctx.clb_nlist.net_pins(net_id).size()) - 50); +// } else if ((cluster_ctx.clb_nlist.net_pins(net_id).size()) >= 85) { +// crossing = 2.7933 + 0.011 * (cluster_ctx.clb_nlist.net_pins(net_id).size()) +// - 0.0000018 * (cluster_ctx.clb_nlist.net_pins(net_id).size()) +// * (cluster_ctx.clb_nlist.net_pins(net_id).size()); +// } else { +// crossing = cross_count[cluster_ctx.clb_nlist.net_pins(net_id).size() - 1]; +// } +// +// /* Could insert a check for xmin == xmax. In that case, assume * +// * connection will be made with no bends and hence no x-cost. * +// * Same thing for y-cost. */ +// +// /* Cost = wire length along channel * cross_count / average * +// * channel capacity. Do this for x, then y direction and add. */ +// +// ncost = (bbptr->xmax - bbptr->xmin + 1) * crossing; +// +// ncost += (bbptr->ymax - bbptr->ymin + 1) * crossing; +// +// return (ncost); +//} +// +//static float get_net_cost(ClusterNetId net_id, t_bb *bbptr) { +// +// /* Finds the cost due to one net by looking at its coordinate bounding * +// * box. */ +// +// float ncost, crossing; +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// /* Get the expected "crossing count" of a net, based on its number * +// * of pins. Extrapolate for very large nets. */ +// +// if ((cluster_ctx.clb_nlist.net_pins(net_id).size()) > 50) { +// crossing = 2.7933 + 0.02616 * ((cluster_ctx.clb_nlist.net_pins(net_id).size()) - 50); +// /* crossing = 3.0; Old value */ +// } else { +// crossing = cross_count[(cluster_ctx.clb_nlist.net_pins(net_id).size()) - 1]; +// } +// +// /* Could insert a check for xmin == xmax. In that case, assume * +// * connection will be made with no bends and hence no x-cost. * +// * Same thing for y-cost. */ +// +// /* Cost = wire length along channel * cross_count / average * +// * channel capacity. Do this for x, then y direction and add. */ +// +// ncost = (bbptr->xmax - bbptr->xmin + 1) * crossing +// * chanx_place_cost_fac[bbptr->ymax][bbptr->ymin - 1]; +// +// ncost += (bbptr->ymax - bbptr->ymin + 1) * crossing +// * chany_place_cost_fac[bbptr->xmax][bbptr->xmin - 1]; +// +// return (ncost); +//} +// +///* Finds the bounding box of a net and stores its coordinates in the * +//* bb_coord_new data structure. This routine should only be called * +//* for small nets, since it does not determine enough information for * +//* the bounding box to be updated incrementally later. * +//* Currently assumes channels on both sides of the CLBs forming the * +//* edges of the bounding box can be used. Essentially, I am assuming * +//* the pins always lie on the outside of the bounding box. */ +//static void get_non_updateable_bb(ClusterNetId net_id, t_bb *bb_coord_new) { +// //TODO: account for multiple physical pin instances per logical pin +// +// int xmax, ymax, xmin, ymin, x, y; +// int pnum; +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& place_ctx = g_vpr_ctx.placement(); +// auto& device_ctx = g_vpr_ctx.device(); +// +// ClusterBlockId bnum = cluster_ctx.clb_nlist.net_driver_block(net_id); +// pnum = cluster_ctx.clb_nlist.net_pin_physical_index(net_id, 0); +// x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; +// y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; +// +// xmin = x; +// ymin = y; +// xmax = x; +// ymax = y; +// +// for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) { +// bnum = cluster_ctx.clb_nlist.pin_block(pin_id); +// pnum = cluster_ctx.clb_nlist.pin_physical_index(pin_id); +// x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; +// y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; +// +// if (x < xmin) { +// xmin = x; +// } else if (x > xmax) { +// xmax = x; +// } +// +// if (y < ymin) { +// ymin = y; +// } else if (y > ymax) { +// ymax = y; +// } +// } +// +// /* Now I've found the coordinates of the bounding box. There are no * +// * channels beyond device_ctx.grid.width()-2 and * +// * device_ctx.grid.height() - 2, so I want to clip to that. As well,* +// * since I'll always include the channel immediately below and the * +// * channel immediately to the left of the bounding box, I want to * +// * clip to 1 in both directions as well (since minimum channel index * +// * is 0). See route_common.cpp for a channel diagram. */ +// +// bb_coord_new->xmin = max(min(xmin, device_ctx.grid.width() - 2), 1); //-2 for no perim channels +// bb_coord_new->ymin = max(min(ymin, device_ctx.grid.height() - 2), 1); //-2 for no perim channels +// bb_coord_new->xmax = max(min(xmax, device_ctx.grid.width() - 2), 1); //-2 for no perim channels +// bb_coord_new->ymax = max(min(ymax, device_ctx.grid.height() - 2), 1); //-2 for no perim channels +//} +// +//static void update_bb(ClusterNetId net_id, t_bb *bb_coord_new, +// t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew) { +// +// /* Updates the bounding box of a net by storing its coordinates in * +// * the bb_coord_new data structure and the number of blocks on each * +// * edge in the bb_edge_new data structure. This routine should only * +// * be called for large nets, since it has some overhead relative to * +// * just doing a brute force bounding box calculation. The bounding * +// * box coordinate and edge information for inet must be valid before * +// * this routine is called. * +// * Currently assumes channels on both sides of the CLBs forming the * +// * edges of the bounding box can be used. Essentially, I am assuming * +// * the pins always lie on the outside of the bounding box. * +// * The x and y coordinates are the pin's x and y coordinates. */ +// /* IO blocks are considered to be one cell in for simplicity. */ +// //TODO: account for multiple physical pin instances per logical pin +// +// t_bb *curr_bb_edge, *curr_bb_coord; +// +// auto& device_ctx = g_vpr_ctx.device(); +// +// xnew = max(min(xnew, device_ctx.grid.width() - 2), 1); //-2 for no perim channels +// ynew = max(min(ynew, device_ctx.grid.height() - 2), 1); //-2 for no perim channels +// xold = max(min(xold, device_ctx.grid.width() - 2), 1); //-2 for no perim channels +// yold = max(min(yold, device_ctx.grid.height() - 2), 1); //-2 for no perim channels +// +// /* Check if the net had been updated before. */ +// if (bb_updated_before[net_id] == GOT_FROM_SCRATCH) { +// /* The net had been updated from scratch, DO NOT update again! */ +// return; +// } else if (bb_updated_before[net_id] == NOT_UPDATED_YET) { +// /* The net had NOT been updated before, could use the old values */ +// curr_bb_coord = &bb_coords[net_id]; +// curr_bb_edge = &bb_num_on_edges[net_id]; +// bb_updated_before[net_id] = UPDATED_ONCE; +// } else { +// /* The net had been updated before, must use the new values */ +// curr_bb_coord = bb_coord_new; +// curr_bb_edge = bb_edge_new; +// } +// +// /* Check if I can update the bounding box incrementally. */ +// +// if (xnew < xold) { /* Move to left. */ +// +// /* Update the xmax fields for coordinates and number of edges first. */ +// +// if (xold == curr_bb_coord->xmax) { /* Old position at xmax. */ +// if (curr_bb_edge->xmax == 1) { +// get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); +// bb_updated_before[net_id] = GOT_FROM_SCRATCH; +// return; +// } else { +// bb_edge_new->xmax = curr_bb_edge->xmax - 1; +// bb_coord_new->xmax = curr_bb_coord->xmax; +// } +// } else { /* Move to left, old postion was not at xmax. */ +// bb_coord_new->xmax = curr_bb_coord->xmax; +// bb_edge_new->xmax = curr_bb_edge->xmax; +// } +// +// /* Now do the xmin fields for coordinates and number of edges. */ +// +// if (xnew < curr_bb_coord->xmin) { /* Moved past xmin */ +// bb_coord_new->xmin = xnew; +// bb_edge_new->xmin = 1; +// } else if (xnew == curr_bb_coord->xmin) { /* Moved to xmin */ +// bb_coord_new->xmin = xnew; +// bb_edge_new->xmin = curr_bb_edge->xmin + 1; +// } else { /* Xmin unchanged. */ +// bb_coord_new->xmin = curr_bb_coord->xmin; +// bb_edge_new->xmin = curr_bb_edge->xmin; +// } +// /* End of move to left case. */ +// +// } else if (xnew > xold) { /* Move to right. */ +// +// /* Update the xmin fields for coordinates and number of edges first. */ +// +// if (xold == curr_bb_coord->xmin) { /* Old position at xmin. */ +// if (curr_bb_edge->xmin == 1) { +// get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); +// bb_updated_before[net_id] = GOT_FROM_SCRATCH; +// return; +// } else { +// bb_edge_new->xmin = curr_bb_edge->xmin - 1; +// bb_coord_new->xmin = curr_bb_coord->xmin; +// } +// } else { /* Move to right, old position was not at xmin. */ +// bb_coord_new->xmin = curr_bb_coord->xmin; +// bb_edge_new->xmin = curr_bb_edge->xmin; +// } +// +// /* Now do the xmax fields for coordinates and number of edges. */ +// +// if (xnew > curr_bb_coord->xmax) { /* Moved past xmax. */ +// bb_coord_new->xmax = xnew; +// bb_edge_new->xmax = 1; +// } else if (xnew == curr_bb_coord->xmax) { /* Moved to xmax */ +// bb_coord_new->xmax = xnew; +// bb_edge_new->xmax = curr_bb_edge->xmax + 1; +// } else { /* Xmax unchanged. */ +// bb_coord_new->xmax = curr_bb_coord->xmax; +// bb_edge_new->xmax = curr_bb_edge->xmax; +// } +// /* End of move to right case. */ +// +// } else { /* xnew == xold -- no x motion. */ +// bb_coord_new->xmin = curr_bb_coord->xmin; +// bb_coord_new->xmax = curr_bb_coord->xmax; +// bb_edge_new->xmin = curr_bb_edge->xmin; +// bb_edge_new->xmax = curr_bb_edge->xmax; +// } +// +// /* Now account for the y-direction motion. */ +// +// if (ynew < yold) { /* Move down. */ +// +// /* Update the ymax fields for coordinates and number of edges first. */ +// +// if (yold == curr_bb_coord->ymax) { /* Old position at ymax. */ +// if (curr_bb_edge->ymax == 1) { +// get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); +// bb_updated_before[net_id] = GOT_FROM_SCRATCH; +// return; +// } else { +// bb_edge_new->ymax = curr_bb_edge->ymax - 1; +// bb_coord_new->ymax = curr_bb_coord->ymax; +// } +// } else { /* Move down, old postion was not at ymax. */ +// bb_coord_new->ymax = curr_bb_coord->ymax; +// bb_edge_new->ymax = curr_bb_edge->ymax; +// } +// +// /* Now do the ymin fields for coordinates and number of edges. */ +// +// if (ynew < curr_bb_coord->ymin) { /* Moved past ymin */ +// bb_coord_new->ymin = ynew; +// bb_edge_new->ymin = 1; +// } else if (ynew == curr_bb_coord->ymin) { /* Moved to ymin */ +// bb_coord_new->ymin = ynew; +// bb_edge_new->ymin = curr_bb_edge->ymin + 1; +// } else { /* ymin unchanged. */ +// bb_coord_new->ymin = curr_bb_coord->ymin; +// bb_edge_new->ymin = curr_bb_edge->ymin; +// } +// /* End of move down case. */ +// +// } else if (ynew > yold) { /* Moved up. */ +// +// /* Update the ymin fields for coordinates and number of edges first. */ +// +// if (yold == curr_bb_coord->ymin) { /* Old position at ymin. */ +// if (curr_bb_edge->ymin == 1) { +// get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); +// bb_updated_before[net_id] = GOT_FROM_SCRATCH; +// return; +// } else { +// bb_edge_new->ymin = curr_bb_edge->ymin - 1; +// bb_coord_new->ymin = curr_bb_coord->ymin; +// } +// } else { /* Moved up, old position was not at ymin. */ +// bb_coord_new->ymin = curr_bb_coord->ymin; +// bb_edge_new->ymin = curr_bb_edge->ymin; +// } +// +// /* Now do the ymax fields for coordinates and number of edges. */ +// +// if (ynew > curr_bb_coord->ymax) { /* Moved past ymax. */ +// bb_coord_new->ymax = ynew; +// bb_edge_new->ymax = 1; +// } else if (ynew == curr_bb_coord->ymax) { /* Moved to ymax */ +// bb_coord_new->ymax = ynew; +// bb_edge_new->ymax = curr_bb_edge->ymax + 1; +// } else { /* ymax unchanged. */ +// bb_coord_new->ymax = curr_bb_coord->ymax; +// bb_edge_new->ymax = curr_bb_edge->ymax; +// } +// /* End of move up case. */ +// +// } else { /* ynew == yold -- no y motion. */ +// bb_coord_new->ymin = curr_bb_coord->ymin; +// bb_coord_new->ymax = curr_bb_coord->ymax; +// bb_edge_new->ymin = curr_bb_edge->ymin; +// bb_edge_new->ymax = curr_bb_edge->ymax; +// } +// +// if (bb_updated_before[net_id] == NOT_UPDATED_YET) { +// bb_updated_before[net_id] = UPDATED_ONCE; +// } +//} +// +//static void alloc_legal_placements() { +// auto& device_ctx = g_vpr_ctx.device(); +// auto& place_ctx = g_vpr_ctx.mutable_placement(); +// +// legal_pos = (t_legal_pos **) vtr::malloc(device_ctx.num_block_types * sizeof(t_legal_pos *)); +// num_legal_pos = (int *) vtr::calloc(device_ctx.num_block_types, sizeof(int)); +// +// /* Initialize all occupancy to zero. */ +// +// for (size_t i = 0; i < device_ctx.grid.width(); i++) { +// for (size_t j = 0; j < device_ctx.grid.height(); j++) { +// place_ctx.grid_blocks[i][j].usage = 0; +// +// for (int k = 0; k < device_ctx.grid[i][j].type->capacity; k++) { +// +// if (place_ctx.grid_blocks[i][j].blocks[k] != INVALID_BLOCK_ID) { +// place_ctx.grid_blocks[i][j].blocks[k] = EMPTY_BLOCK_ID; +// if (device_ctx.grid[i][j].width_offset == 0 && device_ctx.grid[i][j].height_offset == 0) { +// num_legal_pos[device_ctx.grid[i][j].type->index]++; +// } +// } +// } +// } +// } +// +// for (int i = 0; i < device_ctx.num_block_types; i++) { +// legal_pos[i] = (t_legal_pos *) vtr::malloc(num_legal_pos[i] * sizeof(t_legal_pos)); +// } +//} +// +//static void load_legal_placements() { +// auto& device_ctx = g_vpr_ctx.device(); +// auto& place_ctx = g_vpr_ctx.placement(); +// +// int* index = (int *) vtr::calloc(device_ctx.num_block_types, sizeof(int)); +// +// for (size_t i = 0; i < device_ctx.grid.width(); i++) { +// for (size_t j = 0; j < device_ctx.grid.height(); j++) { +// for (int k = 0; k < device_ctx.grid[i][j].type->capacity; k++) { +// if (place_ctx.grid_blocks[i][j].blocks[k] == INVALID_BLOCK_ID) { +// continue; +// } +// if (device_ctx.grid[i][j].width_offset == 0 && device_ctx.grid[i][j].height_offset == 0) { +// int itype = device_ctx.grid[i][j].type->index; +// legal_pos[itype][index[itype]].x = i; +// legal_pos[itype][index[itype]].y = j; +// legal_pos[itype][index[itype]].z = k; +// index[itype]++; +// } +// } +// } +// } +// free(index); +//} +// +//static void free_legal_placements() { +// auto& device_ctx = g_vpr_ctx.device(); +// +// for (int i = 0; i < device_ctx.num_block_types; i++) { +// free(legal_pos[i]); +// } +// free(legal_pos); /* Free the mapping list */ +// free(num_legal_pos); +//} +// +// +// +//static int check_macro_can_be_placed(int imacro, int itype, int x, int y, int z) { +// +// int imember; +// size_t member_x, member_y, member_z; +// +// auto& device_ctx = g_vpr_ctx.device(); +// auto& place_ctx = g_vpr_ctx.placement(); +// +// // Every macro can be placed until proven otherwise +// int macro_can_be_placed = true; +// +// // Check whether all the members can be placed +// for (imember = 0; imember < pl_macros[imacro].num_blocks; imember++) { +// member_x = x + pl_macros[imacro].members[imember].x_offset; +// member_y = y + pl_macros[imacro].members[imember].y_offset; +// member_z = z + pl_macros[imacro].members[imember].z_offset; +// +// // Check whether the location could accept block of this type +// // Then check whether the location could still accomodate more blocks +// // Also check whether the member position is valid, that is the member's location +// // still within the chip's dimemsion and the member_z is allowed at that location on the grid +// if (member_x < device_ctx.grid.width() && member_y < device_ctx.grid.height() +// && device_ctx.grid[member_x][member_y].type->index == itype +// && place_ctx.grid_blocks[member_x][member_y].blocks[member_z] == EMPTY_BLOCK_ID) { +// // Can still accomodate blocks here, check the next position +// continue; +// } else { +// // Cant be placed here - skip to the next try +// macro_can_be_placed = false; +// break; +// } +// } +// +// return (macro_can_be_placed); +//} static int try_place_macro(/*int itype, int ipos, int imacro*/ @@ -3104,279 +3102,276 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // free(free_locations); } -#if 0 -static void free_fast_cost_update() { - auto& device_ctx = g_vpr_ctx.device(); - - for (size_t i = 0; i < device_ctx.grid.height(); i++) { - free(chanx_place_cost_fac[i]); - } - free(chanx_place_cost_fac); - chanx_place_cost_fac = nullptr; - - for (size_t i = 0; i < device_ctx.grid.width(); i++) { - free(chany_place_cost_fac[i]); - } - free(chany_place_cost_fac); - chany_place_cost_fac = nullptr; -} - -static void alloc_and_load_for_fast_cost_update(float place_cost_exp) { - - /* Allocates and loads the chanx_place_cost_fac and chany_place_cost_fac * - * arrays with the inverse of the average number of tracks per channel * - * between [subhigh] and [sublow]. This is only useful for the cost * - * function that takes the length of the net bounding box in each * - * dimension divided by the average number of tracks in that direction. * - * For other cost functions, you don't have to bother calling this * - * routine; when using the cost function described above, however, you * - * must always call this routine after you call init_chan and before * - * you do any placement cost determination. The place_cost_exp factor * - * specifies to what power the width of the channel should be taken -- * - * larger numbers make narrower channels more expensive. */ - - auto& device_ctx = g_vpr_ctx.device(); - - /* Access arrays below as chan?_place_cost_fac[subhigh][sublow]. Since * - * subhigh must be greater than or equal to sublow, we only need to * - * allocate storage for the lower half of a matrix. */ - - chanx_place_cost_fac = (float **) vtr::malloc((device_ctx.grid.height()) * sizeof(float *)); - for (size_t i = 0; i < device_ctx.grid.height(); i++) - chanx_place_cost_fac[i] = (float *) vtr::malloc((i + 1) * sizeof(float)); - - chany_place_cost_fac = (float **) vtr::malloc((device_ctx.grid.width() + 1) * sizeof(float *)); - for (size_t i = 0; i < device_ctx.grid.width(); i++) - chany_place_cost_fac[i] = (float *) vtr::malloc((i + 1) * sizeof(float)); - - /* First compute the number of tracks between channel high and channel * - * low, inclusive, in an efficient manner. */ - - chanx_place_cost_fac[0][0] = device_ctx.chan_width.x_list[0]; - - for (size_t high = 1; high < device_ctx.grid.height(); high++) { - chanx_place_cost_fac[high][high] = device_ctx.chan_width.x_list[high]; - for (size_t low = 0; low < high; low++) { - chanx_place_cost_fac[high][low] = - chanx_place_cost_fac[high - 1][low] + device_ctx.chan_width.x_list[high]; - } - } - - /* Now compute the inverse of the average number of tracks per channel * - * between high and low. The cost function divides by the average * - * number of tracks per channel, so by storing the inverse I convert * - * this to a faster multiplication. Take this final number to the * - * place_cost_exp power -- numbers other than one mean this is no * - * longer a simple "average number of tracks"; it is some power of * - * that, allowing greater penalization of narrow channels. */ - - for (size_t high = 0; high < device_ctx.grid.height(); high++) - for (size_t low = 0; low <= high; low++) { - chanx_place_cost_fac[high][low] = (high - low + 1.) - / chanx_place_cost_fac[high][low]; - chanx_place_cost_fac[high][low] = pow( - (double) chanx_place_cost_fac[high][low], - (double) place_cost_exp); - } - - /* Now do the same thing for the y-directed channels. First get the * - * number of tracks between channel high and channel low, inclusive. */ - - chany_place_cost_fac[0][0] = device_ctx.chan_width.y_list[0]; - - for (size_t high = 1; high < device_ctx.grid.width(); high++) { - chany_place_cost_fac[high][high] = device_ctx.chan_width.y_list[high]; - for (size_t low = 0; low < high; low++) { - chany_place_cost_fac[high][low] = - chany_place_cost_fac[high - 1][low] + device_ctx.chan_width.y_list[high]; - } - } - - /* Now compute the inverse of the average number of tracks per channel * - * between high and low. Take to specified power. */ - - for (size_t high = 0; high < device_ctx.grid.width(); high++) - for (size_t low = 0; low <= high; low++) { - chany_place_cost_fac[high][low] = (high - low + 1.) - / chany_place_cost_fac[high][low]; - chany_place_cost_fac[high][low] = pow( - (double) chany_place_cost_fac[high][low], - (double) place_cost_exp); - } -} - -static void check_place(float bb_cost, float timing_cost, - enum e_place_algorithm place_algorithm, - float delay_cost) { - - /* Checks that the placement has not confused our data structures. * - * i.e. the clb and block structures agree about the locations of * - * 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. */ - - vtr::vector bdone; - int error = 0; - ClusterBlockId bnum, head_iblk, member_iblk; - float bb_cost_check; - int usage_check; - float timing_cost_check, delay_cost_check; - int imacro, imember, member_x, member_y, member_z; - - bb_cost_check = comp_bb_cost(CHECK); - //vtr::printf_info("bb_cost recomputed from scratch: %g\n", bb_cost_check); - if (fabs(bb_cost_check - bb_cost) > bb_cost * ERROR_TOL) { - vtr::printf_error(__FILE__, __LINE__, - "bb_cost_check: %g and bb_cost: %g differ in check_place.\n", - bb_cost_check, bb_cost); - error++; - } - - if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - comp_td_costs(&timing_cost_check, &delay_cost_check); - //vtr::printf_info("timing_cost recomputed from scratch: %g\n", timing_cost_check); - if (fabs(timing_cost_check - timing_cost) > timing_cost * ERROR_TOL) { - vtr::printf_error(__FILE__, __LINE__, - "timing_cost_check: %g and timing_cost: %g differ in check_place.\n", - timing_cost_check, timing_cost); - error++; - } - //vtr::printf_info("delay_cost recomputed from scratch: %g\n", delay_cost_check); - if (fabs(delay_cost_check - delay_cost) > delay_cost * ERROR_TOL) { - vtr::printf_error(__FILE__, __LINE__, - "delay_cost_check: %g and delay_cost: %g differ in check_place.\n", - delay_cost_check, delay_cost); - error++; - } - } - - auto& cluster_ctx = g_vpr_ctx.clustering(); - auto& place_ctx = g_vpr_ctx.placement(); - auto& device_ctx = g_vpr_ctx.device(); - - bdone.resize(cluster_ctx.clb_nlist.blocks().size(), 0); - - /* Step through device grid and placement. Check it against blocks */ - for (size_t i = 0; i < device_ctx.grid.width(); i++) - for (size_t j = 0; j < device_ctx.grid.height(); j++) { - if (place_ctx.grid_blocks[i][j].usage > device_ctx.grid[i][j].type->capacity) { - vtr::printf_error(__FILE__, __LINE__, - "Block at grid location (%zu,%zu) overused. Usage is %d.\n", - i, j, place_ctx.grid_blocks[i][j].usage); - error++; - } - usage_check = 0; - for (int k = 0; k < device_ctx.grid[i][j].type->capacity; k++) { - bnum = place_ctx.grid_blocks[i][j].blocks[k]; - if (EMPTY_BLOCK_ID == bnum || INVALID_BLOCK_ID == bnum) - continue; - - if (cluster_ctx.clb_nlist.block_type(bnum) != device_ctx.grid[i][j].type) { - vtr::printf_error(__FILE__, __LINE__, - "Block %zu type (%s) does not match grid location (%zu,%zu) type (%s).\n", - size_t(bnum), cluster_ctx.clb_nlist.block_type(bnum)->name, i, j, device_ctx.grid[i][j].type->name); - error++; - } - if ((place_ctx.block_locs[bnum].x != int(i)) || (place_ctx.block_locs[bnum].y != int(j))) { - vtr::printf_error(__FILE__, __LINE__, - "Block %zu location conflicts with grid(%zu,%zu) data.\n", - size_t(bnum), i, j); - error++; - } - ++usage_check; - bdone[bnum]++; - } - if (usage_check != place_ctx.grid_blocks[i][j].usage) { - vtr::printf_error(__FILE__, __LINE__, - "Location (%zu,%zu) usage is %d, but has actual usage %d.\n", - i, j, place_ctx.grid_blocks[i][j].usage, usage_check); - error++; - } - } - - /* Check that every block exists in the device_ctx.grid and cluster_ctx.blocks arrays somewhere. */ - for (auto blk_id : cluster_ctx.clb_nlist.blocks()) - if (bdone[blk_id] != 1) { - vtr::printf_error(__FILE__, __LINE__, - "Block %zu listed %d times in data structures.\n", - size_t(blk_id), bdone[blk_id]); - error++; - } - bdone.clear(); - - /* Check the pl_macro placement are legal - blocks are in the proper relative position. */ - for (imacro = 0; imacro < num_pl_macros; imacro++) { - - head_iblk = pl_macros[imacro].members[0].blk_index; - - for (imember = 0; imember < pl_macros[imacro].num_blocks; imember++) { - - member_iblk = pl_macros[imacro].members[imember].blk_index; - - // Compute the suppossed member's x,y,z location - member_x = place_ctx.block_locs[head_iblk].x + pl_macros[imacro].members[imember].x_offset; - member_y = place_ctx.block_locs[head_iblk].y + pl_macros[imacro].members[imember].y_offset; - member_z = place_ctx.block_locs[head_iblk].z + pl_macros[imacro].members[imember].z_offset; - - // Check the place_ctx.block_locs data structure first - if (place_ctx.block_locs[member_iblk].x != member_x - || place_ctx.block_locs[member_iblk].y != member_y - || place_ctx.block_locs[member_iblk].z != member_z) { - vtr::printf_error(__FILE__, __LINE__, - "Block %zu in pl_macro #%d is not placed in the proper orientation.\n", - size_t(member_iblk), imacro); - error++; - } - - // Then check the place_ctx.grid data structure - if (place_ctx.grid_blocks[member_x][member_y].blocks[member_z] != member_iblk) { - vtr::printf_error(__FILE__, __LINE__, - "Block %zu in pl_macro #%d 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 - - if (error == 0) { - vtr::printf_info("\n"); - vtr::printf_info("Completed placement consistency check successfully.\n"); - - } else { - vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, - "\nCompleted placement consistency check, %d errors found.\n" - "Aborting program.\n", error); - } - -} - -#ifdef VERBOSE -static void print_clb_placement(const char *fname) { - - /* Prints out the clb placements to a file. */ - FILE *fp; - auto& cluster_ctx = g_vpr_ctx.clustering(); - auto& place_ctx = g_vpr_ctx.placement(); - - fp = vtr::fopen(fname, "w"); - fprintf(fp, "Complex block placements:\n\n"); - - fprintf(fp, "Block #\tName\t(X, Y, Z).\n"); - for(auto i : cluster_ctx.clb_nlist.blocks()) { - fprintf(fp, "#%d\t%s\t(%d, %d, %d).\n", i, cluster_ctx.clb_nlist.block_name(i), place_ctx.block_locs[i].x, place_ctx.block_locs[i].y, place_ctx.block_locs[i].z); - } - - fclose(fp); -} -#endif - -static void free_try_swap_arrays() { - if(blocks_affected.moved_blocks != nullptr) { - free(blocks_affected.moved_blocks); - - blocks_affected.moved_blocks = nullptr; - blocks_affected.num_moved_blocks = 0; - } -} - -#endif +//static void free_fast_cost_update() { +// auto& device_ctx = g_vpr_ctx.device(); +// +// for (size_t i = 0; i < device_ctx.grid.height(); i++) { +// free(chanx_place_cost_fac[i]); +// } +// free(chanx_place_cost_fac); +// chanx_place_cost_fac = nullptr; +// +// for (size_t i = 0; i < device_ctx.grid.width(); i++) { +// free(chany_place_cost_fac[i]); +// } +// free(chany_place_cost_fac); +// chany_place_cost_fac = nullptr; +//} +// +//static void alloc_and_load_for_fast_cost_update(float place_cost_exp) { +// +// /* Allocates and loads the chanx_place_cost_fac and chany_place_cost_fac * +// * arrays with the inverse of the average number of tracks per channel * +// * between [subhigh] and [sublow]. This is only useful for the cost * +// * function that takes the length of the net bounding box in each * +// * dimension divided by the average number of tracks in that direction. * +// * For other cost functions, you don't have to bother calling this * +// * routine; when using the cost function described above, however, you * +// * must always call this routine after you call init_chan and before * +// * you do any placement cost determination. The place_cost_exp factor * +// * specifies to what power the width of the channel should be taken -- * +// * larger numbers make narrower channels more expensive. */ +// +// auto& device_ctx = g_vpr_ctx.device(); +// +// /* Access arrays below as chan?_place_cost_fac[subhigh][sublow]. Since * +// * subhigh must be greater than or equal to sublow, we only need to * +// * allocate storage for the lower half of a matrix. */ +// +// chanx_place_cost_fac = (float **) vtr::malloc((device_ctx.grid.height()) * sizeof(float *)); +// for (size_t i = 0; i < device_ctx.grid.height(); i++) +// chanx_place_cost_fac[i] = (float *) vtr::malloc((i + 1) * sizeof(float)); +// +// chany_place_cost_fac = (float **) vtr::malloc((device_ctx.grid.width() + 1) * sizeof(float *)); +// for (size_t i = 0; i < device_ctx.grid.width(); i++) +// chany_place_cost_fac[i] = (float *) vtr::malloc((i + 1) * sizeof(float)); +// +// /* First compute the number of tracks between channel high and channel * +// * low, inclusive, in an efficient manner. */ +// +// chanx_place_cost_fac[0][0] = device_ctx.chan_width.x_list[0]; +// +// for (size_t high = 1; high < device_ctx.grid.height(); high++) { +// chanx_place_cost_fac[high][high] = device_ctx.chan_width.x_list[high]; +// for (size_t low = 0; low < high; low++) { +// chanx_place_cost_fac[high][low] = +// chanx_place_cost_fac[high - 1][low] + device_ctx.chan_width.x_list[high]; +// } +// } +// +// /* Now compute the inverse of the average number of tracks per channel * +// * between high and low. The cost function divides by the average * +// * number of tracks per channel, so by storing the inverse I convert * +// * this to a faster multiplication. Take this final number to the * +// * place_cost_exp power -- numbers other than one mean this is no * +// * longer a simple "average number of tracks"; it is some power of * +// * that, allowing greater penalization of narrow channels. */ +// +// for (size_t high = 0; high < device_ctx.grid.height(); high++) +// for (size_t low = 0; low <= high; low++) { +// chanx_place_cost_fac[high][low] = (high - low + 1.) +// / chanx_place_cost_fac[high][low]; +// chanx_place_cost_fac[high][low] = pow( +// (double) chanx_place_cost_fac[high][low], +// (double) place_cost_exp); +// } +// +// /* Now do the same thing for the y-directed channels. First get the * +// * number of tracks between channel high and channel low, inclusive. */ +// +// chany_place_cost_fac[0][0] = device_ctx.chan_width.y_list[0]; +// +// for (size_t high = 1; high < device_ctx.grid.width(); high++) { +// chany_place_cost_fac[high][high] = device_ctx.chan_width.y_list[high]; +// for (size_t low = 0; low < high; low++) { +// chany_place_cost_fac[high][low] = +// chany_place_cost_fac[high - 1][low] + device_ctx.chan_width.y_list[high]; +// } +// } +// +// /* Now compute the inverse of the average number of tracks per channel * +// * between high and low. Take to specified power. */ +// +// for (size_t high = 0; high < device_ctx.grid.width(); high++) +// for (size_t low = 0; low <= high; low++) { +// chany_place_cost_fac[high][low] = (high - low + 1.) +// / chany_place_cost_fac[high][low]; +// chany_place_cost_fac[high][low] = pow( +// (double) chany_place_cost_fac[high][low], +// (double) place_cost_exp); +// } +//} +// +//static void check_place(float bb_cost, float timing_cost, +// enum e_place_algorithm place_algorithm, +// float delay_cost) { +// +// /* Checks that the placement has not confused our data structures. * +// * i.e. the clb and block structures agree about the locations of * +// * 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. */ +// +// vtr::vector bdone; +// int error = 0; +// ClusterBlockId bnum, head_iblk, member_iblk; +// float bb_cost_check; +// int usage_check; +// float timing_cost_check, delay_cost_check; +// int imacro, imember, member_x, member_y, member_z; +// +// bb_cost_check = comp_bb_cost(CHECK); +// //vtr::printf_info("bb_cost recomputed from scratch: %g\n", bb_cost_check); +// if (fabs(bb_cost_check - bb_cost) > bb_cost * ERROR_TOL) { +// vtr::printf_error(__FILE__, __LINE__, +// "bb_cost_check: %g and bb_cost: %g differ in check_place.\n", +// bb_cost_check, bb_cost); +// error++; +// } +// +// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// comp_td_costs(&timing_cost_check, &delay_cost_check); +// //vtr::printf_info("timing_cost recomputed from scratch: %g\n", timing_cost_check); +// if (fabs(timing_cost_check - timing_cost) > timing_cost * ERROR_TOL) { +// vtr::printf_error(__FILE__, __LINE__, +// "timing_cost_check: %g and timing_cost: %g differ in check_place.\n", +// timing_cost_check, timing_cost); +// error++; +// } +// //vtr::printf_info("delay_cost recomputed from scratch: %g\n", delay_cost_check); +// if (fabs(delay_cost_check - delay_cost) > delay_cost * ERROR_TOL) { +// vtr::printf_error(__FILE__, __LINE__, +// "delay_cost_check: %g and delay_cost: %g differ in check_place.\n", +// delay_cost_check, delay_cost); +// error++; +// } +// } +// +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& place_ctx = g_vpr_ctx.placement(); +// auto& device_ctx = g_vpr_ctx.device(); +// +// bdone.resize(cluster_ctx.clb_nlist.blocks().size(), 0); +// +// /* Step through device grid and placement. Check it against blocks */ +// for (size_t i = 0; i < device_ctx.grid.width(); i++) +// for (size_t j = 0; j < device_ctx.grid.height(); j++) { +// if (place_ctx.grid_blocks[i][j].usage > device_ctx.grid[i][j].type->capacity) { +// vtr::printf_error(__FILE__, __LINE__, +// "Block at grid location (%zu,%zu) overused. Usage is %d.\n", +// i, j, place_ctx.grid_blocks[i][j].usage); +// error++; +// } +// usage_check = 0; +// for (int k = 0; k < device_ctx.grid[i][j].type->capacity; k++) { +// bnum = place_ctx.grid_blocks[i][j].blocks[k]; +// if (EMPTY_BLOCK_ID == bnum || INVALID_BLOCK_ID == bnum) +// continue; +// +// if (cluster_ctx.clb_nlist.block_type(bnum) != device_ctx.grid[i][j].type) { +// vtr::printf_error(__FILE__, __LINE__, +// "Block %zu type (%s) does not match grid location (%zu,%zu) type (%s).\n", +// size_t(bnum), cluster_ctx.clb_nlist.block_type(bnum)->name, i, j, device_ctx.grid[i][j].type->name); +// error++; +// } +// if ((place_ctx.block_locs[bnum].x != int(i)) || (place_ctx.block_locs[bnum].y != int(j))) { +// vtr::printf_error(__FILE__, __LINE__, +// "Block %zu location conflicts with grid(%zu,%zu) data.\n", +// size_t(bnum), i, j); +// error++; +// } +// ++usage_check; +// bdone[bnum]++; +// } +// if (usage_check != place_ctx.grid_blocks[i][j].usage) { +// vtr::printf_error(__FILE__, __LINE__, +// "Location (%zu,%zu) usage is %d, but has actual usage %d.\n", +// i, j, place_ctx.grid_blocks[i][j].usage, usage_check); +// error++; +// } +// } +// +// /* Check that every block exists in the device_ctx.grid and cluster_ctx.blocks arrays somewhere. */ +// for (auto blk_id : cluster_ctx.clb_nlist.blocks()) +// if (bdone[blk_id] != 1) { +// vtr::printf_error(__FILE__, __LINE__, +// "Block %zu listed %d times in data structures.\n", +// size_t(blk_id), bdone[blk_id]); +// error++; +// } +// bdone.clear(); +// +// /* Check the pl_macro placement are legal - blocks are in the proper relative position. */ +// for (imacro = 0; imacro < num_pl_macros; imacro++) { +// +// head_iblk = pl_macros[imacro].members[0].blk_index; +// +// for (imember = 0; imember < pl_macros[imacro].num_blocks; imember++) { +// +// member_iblk = pl_macros[imacro].members[imember].blk_index; +// +// // Compute the suppossed member's x,y,z location +// member_x = place_ctx.block_locs[head_iblk].x + pl_macros[imacro].members[imember].x_offset; +// member_y = place_ctx.block_locs[head_iblk].y + pl_macros[imacro].members[imember].y_offset; +// member_z = place_ctx.block_locs[head_iblk].z + pl_macros[imacro].members[imember].z_offset; +// +// // Check the place_ctx.block_locs data structure first +// if (place_ctx.block_locs[member_iblk].x != member_x +// || place_ctx.block_locs[member_iblk].y != member_y +// || place_ctx.block_locs[member_iblk].z != member_z) { +// vtr::printf_error(__FILE__, __LINE__, +// "Block %zu in pl_macro #%d is not placed in the proper orientation.\n", +// size_t(member_iblk), imacro); +// error++; +// } +// +// // Then check the place_ctx.grid data structure +// if (place_ctx.grid_blocks[member_x][member_y].blocks[member_z] != member_iblk) { +// vtr::printf_error(__FILE__, __LINE__, +// "Block %zu in pl_macro #%d 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 +// +// if (error == 0) { +// vtr::printf_info("\n"); +// vtr::printf_info("Completed placement consistency check successfully.\n"); +// +// } else { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "\nCompleted placement consistency check, %d errors found.\n" +// "Aborting program.\n", error); +// } +// +//} +// +//#ifdef VERBOSE +//static void print_clb_placement(const char *fname) { +// +// /* Prints out the clb placements to a file. */ +// FILE *fp; +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& place_ctx = g_vpr_ctx.placement(); +// +// fp = vtr::fopen(fname, "w"); +// fprintf(fp, "Complex block placements:\n\n"); +// +// fprintf(fp, "Block #\tName\t(X, Y, Z).\n"); +// for(auto i : cluster_ctx.clb_nlist.blocks()) { +// fprintf(fp, "#%d\t%s\t(%d, %d, %d).\n", i, cluster_ctx.clb_nlist.block_name(i), place_ctx.block_locs[i].x, place_ctx.block_locs[i].y, place_ctx.block_locs[i].z); +// } +// +// fclose(fp); +//} +//#endif +// +//static void free_try_swap_arrays() { +// if(blocks_affected.moved_blocks != nullptr) { +// free(blocks_affected.moved_blocks); +// +// blocks_affected.moved_blocks = nullptr; +// blocks_affected.num_moved_blocks = 0; +// } +//} From 25f296c2490528a326ecf779b573980748b5867b Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 13 Jul 2018 18:07:18 -0700 Subject: [PATCH 014/116] try_place() to accept grid_width and grid_height --- common/place_vpr.inc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/place_vpr.inc b/common/place_vpr.inc index 6c71217fc9..c413ef0831 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -345,7 +345,9 @@ void try_place(/*t_placer_opts placer_opts, #endif t_direct_inf *directs, int num_directs*/ Context *ctx, - const std::unordered_map &bel_types) { + const std::unordered_map &bel_types, + const int grid_width, + const int grid_height) { // /* Does almost all the work of placing a circuit. Width_fac gives the * // * width of the widest channel. Place_cost_exp says what exponent the * @@ -568,7 +570,7 @@ void try_place(/*t_placer_opts placer_opts, // inner_recompute_limit = move_lim + 1; // } - rlim = (float) std::max(ctx->chip_info->width, ctx->chip_info->height); + rlim = (float) std::max(grid_width, grid_height); first_rlim = rlim; /*used in timing-driven placement for exponent computation */ final_rlim = 1; From 92c97efdd6e35dcb52c80d69f4feb5613e6d4477 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 13 Jul 2018 19:07:54 -0700 Subject: [PATCH 015/116] Start implementing more VPR functions... --- common/place_vpr.cc | 2 +- common/place_vpr.inc | 778 ++++++++++++++++++++++--------------------- 2 files changed, 405 insertions(+), 375 deletions(-) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index 5c9a4e2b3a..f821db51cc 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -113,7 +113,7 @@ class VPRPlacer { log_break(); - vpr::initial_placement(ctx, bel_types); + vpr::try_place(ctx, bel_types, max_x + 1, max_y + 1); size_t placed_cells = 0; // Initial constraints placer diff --git a/common/place_vpr.inc b/common/place_vpr.inc index c413ef0831..edc57e588a 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -69,14 +69,14 @@ //enum e_cost_methods { // NORMAL, CHECK //}; -// -///* This is for the placement swap routines. A swap attempt could be * -// * rejected, accepted or aborted (due to the limitations placed on the * -// * carry chain support at this point). */ -//enum e_swap_result { -// REJECTED, ACCEPTED, ABORTED -//}; -// + +/* This is for the placement swap routines. A swap attempt could be * + * rejected, accepted or aborted (due to the limitations placed on the * + * carry chain support at this point). */ +enum e_swap_result { + REJECTED, ACCEPTED, ABORTED +}; + //struct t_placer_statistics { // double av_cost, av_bb_cost, av_timing_cost, // sum_of_squares, av_delay_cost; @@ -209,19 +209,17 @@ static int num_ts_called = 0; //static int check_macro_can_be_placed(int imacro, int itype, int x, int y, int z); static int try_place_macro(/*int itype, int ipos, int imacro*/ - Context *ctx, CellInfo* cell, const std::string& loc_name); + CellInfo* cell, const std::string& loc_name); static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_locations*/ - Context *ctx, std::vector> &free_locations); + std::vector> &free_locations); static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type pad_loc_type*/ - Context* ctx, std::vector> &free_locations, const std::unordered_map &bel_types); static void initial_placement_location(/*int * free_locations, ClusterBlockId blk_id, int *pipos, int *px, int *py, int *pz*/ - Context *ctx, const std::vector> &free_locations, CellInfo *cell, const std::unordered_map &bel_types, @@ -229,34 +227,35 @@ static void initial_placement_location(/*int * free_locations, ClusterBlockId bl static void initial_placement(/*enum e_pad_loc_type pad_loc_type, const char *pad_loc_file*/ - Context *ctx, const std::unordered_map &bel_types); //static float comp_bb_cost(e_cost_methods method); -// -//static int setup_blocks_affected(ClusterBlockId b_from, int x_to, int y_to, int z_to); -// -//static int find_affected_blocks(ClusterBlockId b_from, int x_to, int y_to, int z_to); -// -//static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, -// float rlim, -// enum e_place_algorithm place_algorithm, float timing_tradeoff, -// float inverse_prev_bb_cost, float inverse_prev_timing_cost, -// float *delay_cost); -// -//static ClusterBlockId pick_from_block(); -// + +static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, int y_to, int z_to*/ BelId bel_to); + +static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, int y_to, int z_to*/ BelId bel_to); + +static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, + float rlim, + /*enum e_place_algorithm place_algorithm, float timing_tradeoff,*/ + float inverse_prev_bb_cost, float inverse_prev_timing_cost, + float *delay_cost, + const int grid_width, const int grid_height); + +static /*ClusterBlockId*/ CellInfo* pick_from_block(); + //static void check_place(float bb_cost, float timing_cost, // enum e_place_algorithm place_algorithm, // float delay_cost); -// -//static float starting_t(float *cost_ptr, float *bb_cost_ptr, -// float *timing_cost_ptr, -// t_annealing_sched annealing_sched, int max_moves, float rlim, -// enum e_place_algorithm place_algorithm, float timing_tradeoff, -// float inverse_prev_bb_cost, float inverse_prev_timing_cost, -// float *delay_cost_ptr); -// + +static float starting_t(float *cost_ptr, float *bb_cost_ptr, + float *timing_cost_ptr, + /*t_annealing_sched annealing_sched,*/ int max_moves, float rlim, + /*enum e_place_algorithm place_algorithm, float timing_tradeoff,*/ + float inverse_prev_bb_cost, float inverse_prev_timing_cost, + float *delay_cost_ptr, + const int grid_width, const int grid_height); + //static void update_t(float *t, float rlim, float success_rat, // t_annealing_sched annealing_sched); // @@ -266,9 +265,9 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // t_annealing_sched annealing_sched); // //static int count_connections(); -// -//static double get_std_dev(int n, double sum_x_squared, double av_x); -// + +static double get_std_dev(int n, double sum_x_squared, double av_x); + //static float recompute_bb_cost(); // //static float comp_td_point_to_point_delay(ClusterNetId net_id, int ipin); @@ -283,20 +282,23 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // //static e_swap_result assess_swap(float delta_c, float t); // -//static bool find_to(t_type_ptr type, float rlim, -// int x_from, int y_from, -// int *px_to, int *py_to, int *pz_to); -//static void find_to_location(t_type_ptr type, float rlim, -// int x_from, int y_from, -// int *px_to, int *py_to, int *pz_to); -// +static bool find_to(/*t_type_ptr type,*/ float rlim, + int x_from, int y_from, + /*int *px_to, int *py_to, int *pz_to*/ + CellInfo* cell_from, BelId& bel_to, + const int grid_width, const int grid_height); +static void find_to_location(/*t_type_ptr type,*/ float rlim, + int x_from, int y_from/*, + int *px_to, int *py_to, int *pz_to*/); + //static void get_non_updateable_bb(ClusterNetId net_id, t_bb *bb_coord_new); // //static void update_bb(ClusterNetId net_id, t_bb *bb_coord_new, // t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew); // -//static int find_affected_nets_and_update_costs(e_place_algorithm place_algorithm, float& bb_delta_c, float& timing_delta_c, float& delay_delta_c); -// +static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorithm,*/ float& bb_delta_c, float& timing_delta_c, float& delay_delta_c, + CellInfo* b_from, BelId bel_to); + //static void record_affected_net(const ClusterNetId net, int& num_affected_nets); // //static void update_net_bb(const ClusterNetId net, int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin); @@ -335,6 +337,16 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // const ClusteredPinAtomPinsLookup& netlist_pin_lookup, // SetupTimingInfo& timing_info); +static Context* npnr_ctx = NULL; +#define VTR_ASSERT NPNR_ASSERT +#define VTR_ASSERT_SAFE NPNR_ASSERT +#define vpr_throw(__a, __b, __c, ...) log_error(__VA_ARGS__) + +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "in try_place: new_bb_cost = %g, old bb_cost = %g\n", +// new_bb_cost, bb_cost); + + /*****************************************************************************/ void try_place(/*t_placer_opts placer_opts, t_annealing_sched annealing_sched, @@ -388,6 +400,8 @@ void try_place(/*t_placer_opts placer_opts, num_swap_aborted = 0; num_ts_called = 0; + npnr_ctx = ctx; + // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE // || placer_opts.enable_timing_computations) { // /*do this before the initial placement to avoid messing up the initial placement */ @@ -404,8 +418,10 @@ void try_place(/*t_placer_opts placer_opts, // // alloc_and_load_placement_structs(placer_opts.place_cost_exp, placer_opts, // directs, num_directs); -// - initial_placement(ctx, bel_types); + + initial_placement(bel_types); + return; + // init_draw_coords((float) width_fac); // // //Enables fast look-up of atom pins connect to CLB pins @@ -576,11 +592,12 @@ void try_place(/*t_placer_opts placer_opts, final_rlim = 1; inverse_delta_rlim = 1 / (first_rlim - final_rlim); -// t = starting_t(&cost, &bb_cost, &timing_cost, -// /*annealing_sched,*/ move_lim, rlim, -// /*placer_opts.place_algorithm, placer_opts.timing_tradeoff,*/ -// inverse_prev_bb_cost, inverse_prev_timing_cost, &delay_cost); -// + t = starting_t(&cost, &bb_cost, &timing_cost, + /*annealing_sched,*/ move_lim, rlim, + /*placer_opts.place_algorithm, placer_opts.timing_tradeoff,*/ + inverse_prev_bb_cost, inverse_prev_timing_cost, &delay_cost, + grid_width, grid_height); + // tot_iter = 0; // moves_since_cost_recompute = 0; // @@ -1036,29 +1053,29 @@ void try_place(/*t_placer_opts placer_opts, // // return (count); //} -// -//static double get_std_dev(int n, double sum_x_squared, double av_x) { -// -// /* Returns the standard deviation of data set x. There are n sample points, * -// * sum_x_squared is the summation over n of x^2 and av_x is the average x. * -// * All operations are done in double precision, since round off error can be * -// * a problem in the initial temp. std_dev calculation for big circuits. */ -// -// double std_dev; -// -// if (n <= 1) -// std_dev = 0.; -// else -// std_dev = (sum_x_squared - n * av_x * av_x) / (double) (n - 1); -// -// if (std_dev > 0.) /* Very small variances sometimes round negative */ -// std_dev = sqrt(std_dev); -// else -// std_dev = 0.; -// -// return (std_dev); -//} -// + +static double get_std_dev(int n, double sum_x_squared, double av_x) { + + /* Returns the standard deviation of data set x. There are n sample points, * + * sum_x_squared is the summation over n of x^2 and av_x is the average x. * + * All operations are done in double precision, since round off error can be * + * a problem in the initial temp. std_dev calculation for big circuits. */ + + double std_dev; + + if (n <= 1) + std_dev = 0.; + else + std_dev = (sum_x_squared - n * av_x * av_x) / (double) (n - 1); + + if (std_dev > 0.) /* Very small variances sometimes round negative */ + std_dev = sqrt(std_dev); + else + std_dev = 0.; + + return (std_dev); +} + //static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid) { // // /* Update the range limited to keep acceptance prob. near 0.44. Use * @@ -1120,81 +1137,83 @@ void try_place(/*t_placer_opts placer_opts, // return (0); // } //} -// -//static float starting_t(float *cost_ptr, float *bb_cost_ptr, -// float *timing_cost_ptr, -// t_annealing_sched annealing_sched, int max_moves, float rlim, -// enum e_place_algorithm place_algorithm, float timing_tradeoff, -// float inverse_prev_bb_cost, float inverse_prev_timing_cost, -// float *delay_cost_ptr) { -// -// /* Finds the starting temperature (hot condition). */ -// -// int i, num_accepted, move_lim; -// double std_dev, av, sum_of_squares; /* Double important to avoid round off */ -// + +static float starting_t(float *cost_ptr, float *bb_cost_ptr, + float *timing_cost_ptr, + /*t_annealing_sched annealing_sched,*/ int max_moves, float rlim, + /*enum e_place_algorithm place_algorithm, float timing_tradeoff,*/ + float inverse_prev_bb_cost, float inverse_prev_timing_cost, + float *delay_cost_ptr, + const int grid_width, const int grid_height) { + + /* Finds the starting temperature (hot condition). */ + + int i, num_accepted, move_lim; + double std_dev, av, sum_of_squares; /* Double important to avoid round off */ + // if (annealing_sched.type == USER_SCHED) // return (annealing_sched.init_t); // // auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// move_lim = min(max_moves, (int) cluster_ctx.clb_nlist.blocks().size()); -// -// num_accepted = 0; -// av = 0.; -// sum_of_squares = 0.; -// -// /* Try one move per block. Set t high so essentially all accepted. */ -// -// for (i = 0; i < move_lim; i++) { -// e_swap_result swap_result = try_swap(HUGE_POSITIVE_FLOAT, cost_ptr, bb_cost_ptr, timing_cost_ptr, rlim, -// place_algorithm, timing_tradeoff, -// inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost_ptr); -// -// if (swap_result == ACCEPTED) { -// num_accepted++; -// av += *cost_ptr; -// sum_of_squares += *cost_ptr * (*cost_ptr); -// num_swap_accepted++; -// } else if (swap_result == ABORTED) { -// num_swap_aborted++; -// } else { -// num_swap_rejected++; -// } -// } -// -// if (num_accepted != 0) -// av /= num_accepted; -// else -// av = 0.; -// -// std_dev = get_std_dev(num_accepted, sum_of_squares, av); -// -// if (num_accepted != move_lim) { + + move_lim = std::min(max_moves, (int) npnr_ctx->cells.size()); + + num_accepted = 0; + av = 0.; + sum_of_squares = 0.; + + /* Try one move per block. Set t high so essentially all accepted. */ + + for (i = 0; i < move_lim; i++) { + e_swap_result swap_result = try_swap(std::numeric_limits::max(), cost_ptr, bb_cost_ptr, timing_cost_ptr, rlim, + /*place_algorithm, timing_tradeoff,*/ + inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost_ptr, + grid_width, grid_height); + + if (swap_result == ACCEPTED) { + num_accepted++; + av += *cost_ptr; + sum_of_squares += *cost_ptr * (*cost_ptr); + num_swap_accepted++; + } else if (swap_result == ABORTED) { + num_swap_aborted++; + } else { + num_swap_rejected++; + } + } + + if (num_accepted != 0) + av /= num_accepted; + else + av = 0.; + + std_dev = get_std_dev(num_accepted, sum_of_squares, av); + + if (num_accepted != move_lim) { // vtr::printf_warning(__FILE__, __LINE__, // "Starting t: %d of %d configurations accepted.\n", num_accepted, move_lim); -// } -// -//#ifdef VERBOSE -// vtr::printf_info("std_dev: %g, average cost: %g, starting temp: %g\n", std_dev, av, 20. * std_dev); -//#endif -// -// /* Set the initial temperature to 20 times the standard of deviation */ -// /* so that the initial temperature adjusts according to the circuit */ -// return (20. * std_dev); -//} -// -// -//static int setup_blocks_affected(ClusterBlockId b_from, int x_to, int y_to, int z_to) { -// -// /* Find all the blocks affected when b_from is swapped with b_to. -// * Returns abort_swap. */ -// -// int imoved_blk, imacro; -// int x_from, y_from, z_from; -// ClusterBlockId b_to; -// int abort_swap = false; -// + } + +#ifdef VERBOSE + vtr::printf_info("std_dev: %g, average cost: %g, starting temp: %g\n", std_dev, av, 20. * std_dev); +#endif + + /* Set the initial temperature to 20 times the standard of deviation */ + /* so that the initial temperature adjusts according to the circuit */ + return (20. * std_dev); +} + + +static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, int y_to, int z_to*/ BelId bel_to) { + + /* Find all the blocks affected when b_from is swapped with b_to. + * Returns abort_swap. */ + + int imoved_blk, imacro; + int x_from, y_from, z_from; + /*ClusterBlockId*/ CellInfo* b_to; + int abort_swap = false; + // auto& place_ctx = g_vpr_ctx.mutable_placement(); // // x_from = place_ctx.block_locs[b_from].x; @@ -1202,9 +1221,11 @@ void try_place(/*t_placer_opts placer_opts, // z_from = place_ctx.block_locs[b_from].z; // // b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; -// -// // Check whether the to_location is empty -// if (b_to == EMPTY_BLOCK_ID) { + auto b_to_id = npnr_ctx->getBoundBelCell(bel_to); + b_to = (b_to_id == IdString() ? NULL : npnr_ctx->cells[b_to_id].get()); + + // Check whether the to_location is empty +// if (b_to == NULL) { // // // Swap the block, dont swap the nets yet // place_ctx.block_locs[b_from].x = x_to; @@ -1268,26 +1289,26 @@ void try_place(/*t_placer_opts placer_opts, // blocks_affected.num_moved_blocks ++; // // } // Finish swapping the blocks and setting up blocks_affected -// -// return (abort_swap); -// -//} -// -//static int find_affected_blocks(ClusterBlockId b_from, int x_to, int y_to, int z_to) { -// -// /* Finds and set ups the affected_blocks array. -// * Returns abort_swap. */ -// -// int imacro, imember; -// int x_swap_offset, y_swap_offset, z_swap_offset, x_from, y_from, z_from; + + return (abort_swap); + +} + +static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, int y_to, int z_to*/ BelId bel_to) { + + /* Finds and set ups the affected_blocks array. + * Returns abort_swap. */ + + int imacro, imember; + int x_swap_offset, y_swap_offset, z_swap_offset, x_from, y_from, z_from; // ClusterBlockId curr_b_from; -// int curr_x_from, curr_y_from, curr_z_from, curr_x_to, curr_y_to, curr_z_to; -// int abort_swap = false; -// + int curr_x_from, curr_y_from, curr_z_from, curr_x_to, curr_y_to, curr_z_to; + int abort_swap = false; + // auto& place_ctx = g_vpr_ctx.placement(); // auto& device_ctx = g_vpr_ctx.device(); // auto& cluster_ctx = g_vpr_ctx.clustering(); -// + // x_from = place_ctx.block_locs[b_from].x; // y_from = place_ctx.block_locs[b_from].y; // z_from = place_ctx.block_locs[b_from].z; @@ -1333,69 +1354,75 @@ void try_place(/*t_placer_opts placer_opts, // } // Finish going through all the blocks in the macro // // } else { -// // This is not a macro - I could use the from and to info from before -// abort_swap = setup_blocks_affected(b_from, x_to, y_to, z_to); -// + // This is not a macro - I could use the from and to info from before + abort_swap = setup_blocks_affected(b_from, /*x_to, y_to, z_to*/ bel_to); + // } // Finish handling cases for blocks in macro and otherwise -// -// return (abort_swap); -// -//} -// -//static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, -// float rlim, -// enum e_place_algorithm place_algorithm, float timing_tradeoff, -// float inverse_prev_bb_cost, float inverse_prev_timing_cost, -// float *delay_cost) { -// -// /* Picks some block and moves it to another spot. If this spot is * -// * occupied, switch the blocks. Assess the change in cost function. * -// * rlim is the range limiter. * -// * Returns whether the swap is accepted, rejected or aborted. * -// * Passes back the new value of the cost functions. */ -// + + return (abort_swap); + +} + +static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, + float rlim, + /*enum e_place_algorithm place_algorithm, float timing_tradeoff,*/ + float inverse_prev_bb_cost, float inverse_prev_timing_cost, + float *delay_cost, + const int grid_width, const int grid_height) { + + /* Picks some block and moves it to another spot. If this spot is * + * occupied, switch the blocks. Assess the change in cost function. * + * rlim is the range limiter. * + * Returns whether the swap is accepted, rejected or aborted. * + * Passes back the new value of the cost functions. */ + // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); -// -// num_ts_called ++; -// -// /* I'm using negative values of temp_net_cost as a flag, so DO NOT * -// * use cost functions that can go negative. */ -// -// float delta_c = 0; /* Change in cost due to this swap. */ -// float bb_delta_c = 0; -// float timing_delta_c = 0; -// float delay_delta_c = 0.0; -// -// /* Pick a random block to be swapped with another random block. */ -// ClusterBlockId b_from = pick_from_block(); -// if (!b_from) { -// return ABORTED; //No movable block found -// } -// -// int x_from = place_ctx.block_locs[b_from].x; -// int y_from = place_ctx.block_locs[b_from].y; + + num_ts_called ++; + + /* I'm using negative values of temp_net_cost as a flag, so DO NOT * + * use cost functions that can go negative. */ + + float delta_c = 0; /* Change in cost due to this swap. */ + float bb_delta_c = 0; + float timing_delta_c = 0; + float delay_delta_c = 0.0; + + /* Pick a random block to be swapped with another random block. */ + auto b_from = pick_from_block(); + if (!b_from) { + return ABORTED; //No movable block found + } + + int x_from /*= place_ctx.block_locs[b_from].x*/; + int y_from /*= place_ctx.block_locs[b_from].y*/; // int z_from = place_ctx.block_locs[b_from].z; -// + + bool gb; + npnr_ctx->estimatePosition(b_from->bel, x_from, y_from, gb); + // int x_to = OPEN; // int y_to = OPEN; // int z_to = OPEN; -// + // auto cluster_from_type = cluster_ctx.clb_nlist.block_type(b_from); // auto grid_from_type = g_vpr_ctx.device().grid[x_from][y_from].type; // VTR_ASSERT(cluster_from_type == grid_from_type); -// -// if (!find_to(cluster_ctx.clb_nlist.block_type(b_from), rlim, x_from, y_from, &x_to, &y_to, &z_to)) -// return REJECTED; -// -//#if 0 -// auto& grid = g_vpr_ctx.device().grid; -// int b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; -// vtr::printf_info( "swap [%d][%d][%d] %s \"%s\" <=> [%d][%d][%d] %s \"%s\"\n", -// x_from, y_from, z_from, grid[x_from][y_from].type->name, (b_from != -1 ? cluster_ctx.blocks[b_from].name : ""), -// x_to, y_to, z_to, grid[x_to][y_to].type->name, (b_to != -1 ? cluster_ctx.blocks[b_to].name : "")); -//#endif -// + + BelId bel_to; + if (!find_to(/*cluster_ctx.clb_nlist.block_type(b_from),*/ rlim, x_from, y_from, /*&x_to, &y_to, &z_to*/ + b_from, bel_to, grid_width, grid_height)) + return REJECTED; + +#if 0 + auto& grid = g_vpr_ctx.device().grid; + int b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; + vtr::printf_info( "swap [%d][%d][%d] %s \"%s\" <=> [%d][%d][%d] %s \"%s\"\n", + x_from, y_from, z_from, grid[x_from][y_from].type->name, (b_from != -1 ? cluster_ctx.blocks[b_from].name : ""), + x_to, y_to, z_to, grid[x_to][y_to].type->name, (b_to != -1 ? cluster_ctx.blocks[b_to].name : "")); +#endif + // /* Make the switch in order to make computing the new bounding * // * box simpler. If the cost increase is too high, switch them * // * back. (place_ctx.block_locs data structures switched, clbs not switched * @@ -1409,14 +1436,15 @@ void try_place(/*t_placer_opts placer_opts, // * x, y, z offsets of the swap to maintain relative placements * // * of the blocks. Abort the swap if the to_block is part of a * // * macro (not supported yet). */ -// -// bool abort_swap = find_affected_blocks(b_from, x_to, y_to, z_to); -// -// if (abort_swap == false) { -// -// // Find all the nets affected by this swap and update thier bounding box -// int num_nets_affected = find_affected_nets_and_update_costs(place_algorithm, bb_delta_c, timing_delta_c, delay_delta_c); -// + + bool abort_swap = find_affected_blocks(b_from, /*x_to, y_to, z_to*/ bel_to); + + if (abort_swap == false) { + + // Find all the nets affected by this swap and update thier bounding box + int num_nets_affected = find_affected_nets_and_update_costs(/*place_algorithm,*/ bb_delta_c, timing_delta_c, delay_delta_c, + b_from, bel_to); + // if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // /*in this case we redefine delta_c as a combination of timing and bb. * // *additionally, we normalize all values, therefore delta_c is in * @@ -1425,15 +1453,15 @@ void try_place(/*t_placer_opts placer_opts, // delta_c = (1 - timing_tradeoff) * bb_delta_c * inverse_prev_bb_cost // + timing_tradeoff * timing_delta_c * inverse_prev_timing_cost; // } else { -// delta_c = bb_delta_c; + delta_c = bb_delta_c; // } -// -// /* 1 -> move accepted, 0 -> rejected. */ -// e_swap_result keep_switch = assess_swap(delta_c, t); -// -// if (keep_switch == ACCEPTED) { -// *cost = *cost + delta_c; -// *bb_cost = *bb_cost + bb_delta_c; + + /* 1 -> move accepted, 0 -> rejected. */ + e_swap_result keep_switch = /*assess_swap(delta_c, t)*/ ACCEPTED; + + if (keep_switch == ACCEPTED) { + *cost = *cost + delta_c; + *bb_cost = *bb_cost + bb_delta_c; // // if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // /*update the point_to_point_timing_cost and point_to_point_delay_cost @@ -1484,10 +1512,10 @@ void try_place(/*t_placer_opts placer_opts, // } // // } // Finish updating clb for all blocks -// -// } else { /* Move was rejected. */ -// -// /* Reset the net cost function flags first. */ + + } else { /* Move was rejected. */ + + /* Reset the net cost function flags first. */ // for (int inet_affected = 0; inet_affected < num_nets_affected; inet_affected++) { // ClusterNetId net_id = ts_nets_to_update[inet_affected]; // temp_net_cost[net_id] = -1; @@ -1502,19 +1530,19 @@ void try_place(/*t_placer_opts placer_opts, // place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; // place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; // } -// } -// + } + // /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ // blocks_affected.num_moved_blocks = 0; -// -//#if 0 -// //Check that each accepted swap yields a valid placement -// check_place(*bb_cost, *timing_cost, place_algorithm, *delay_cost); -//#endif -// -// return (keep_switch); -// } else { -// + +#if 0 + //Check that each accepted swap yields a valid placement + check_place(*bb_cost, *timing_cost, place_algorithm, *delay_cost); +#endif + + return (keep_switch); + } else { + // /* Restore the place_ctx.block_locs data structures to their state before the move. */ // for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { // b_from = blocks_affected.moved_blocks[iblk].block_num; @@ -1526,58 +1554,62 @@ void try_place(/*t_placer_opts placer_opts, // // /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ // blocks_affected.num_moved_blocks = 0; -// -// return ABORTED; -// } -//} -// -////Pick a random block to be swapped with another random block. -////If none is found return ClusterBlockId::INVALID() -//static ClusterBlockId pick_from_block() { -// /* Some blocks may be fixed, and should never be moved from their * -// * initial positions. If we randomly selected such a block try * -// * another random block. * -// * * -// * We need to track the blocks we have tried to avoid an infinite * -// * loop if all blocks are fixed. */ + + return ABORTED; + } +} + +//Pick a random block to be swapped with another random block. +//If none is found return ClusterBlockId::INVALID() +static /*ClusterBlockId*/ CellInfo* pick_from_block() { + /* Some blocks may be fixed, and should never be moved from their * + * initial positions. If we randomly selected such a block try * + * another random block. * + * * + * We need to track the blocks we have tried to avoid an infinite * + * loop if all blocks are fixed. */ // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); -// -// std::unordered_set tried_from_blocks; -// -// //So long as untried blocks remain -// while (tried_from_blocks.size() < cluster_ctx.clb_nlist.blocks().size()) { -// -// //Pick a block at random -// ClusterBlockId b_from = ClusterBlockId(vtr::irand((int) cluster_ctx.clb_nlist.blocks().size() - 1)); -// -// //Record it as tried -// tried_from_blocks.insert(b_from); -// -// if (place_ctx.block_locs[b_from].is_fixed) { -// continue; //Fixed location, try again -// } -// -// //Found a movable block -// return b_from; -// } -// -// //No movable blocks found -// return ClusterBlockId::INVALID(); -//} -// -////Puts all the nets changed by the current swap into nets_to_update, -////and updates their bounding box. -//// -////Returns the number of affected nets. -//static int find_affected_nets_and_update_costs(e_place_algorithm place_algorithm, float& bb_delta_c, float& timing_delta_c, float& delay_delta_c) { -// VTR_ASSERT_SAFE(bb_delta_c == 0.); -// VTR_ASSERT_SAFE(timing_delta_c == 0.); -// VTR_ASSERT_SAFE(delay_delta_c == 0.); + + std::unordered_set tried_from_blocks; + + //So long as untried blocks remain + while (tried_from_blocks.size() < npnr_ctx->cells.size()) { + + //Pick a block at random + //ClusterBlockId b_from = ClusterBlockId(vtr::irand((int) cluster_ctx.clb_nlist.blocks().size() - 1)); + auto it = npnr_ctx->cells.cbegin(); + std::advance(it, npnr_ctx->rng(npnr_ctx->cells.size())); + auto b_from = it->second.get(); + + //Record it as tried + tried_from_blocks.insert(b_from); + + if (b_from->belStrength > STRENGTH_WEAK) { + continue; //Fixed location, try again + } + + //Found a movable block + return b_from; + } + + //No movable blocks found + return NULL; +} + +//Puts all the nets changed by the current swap into nets_to_update, +//and updates their bounding box. +// +//Returns the number of affected nets. +static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorithm,*/ float& bb_delta_c, float& timing_delta_c, float& delay_delta_c, + CellInfo* b_from, BelId bel_to) { + VTR_ASSERT_SAFE(bb_delta_c == 0.); + VTR_ASSERT_SAFE(timing_delta_c == 0.); + VTR_ASSERT_SAFE(delay_delta_c == 0.); // auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// int num_affected_nets = 0; -// + + int num_affected_nets = 0; + // //Go through all the blocks moved // for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { // ClusterBlockId blk = blocks_affected.moved_blocks[iblk].block_num; @@ -1615,9 +1647,9 @@ void try_place(/*t_placer_opts placer_opts, // temp_net_cost[net_id] = get_net_cost(net_id, &ts_bb_coord_new[net_id]); // bb_delta_c += temp_net_cost[net_id] - net_cost[net_id]; // } -// -// return num_affected_nets; -//} + + return num_affected_nets; +} // //static void record_affected_net(const ClusterNetId net, int& num_affected_nets) { // //Record effected nets @@ -1696,45 +1728,47 @@ void try_place(/*t_placer_opts placer_opts, // } // } //} -// -//static bool find_to(t_type_ptr type, float rlim, -// int x_from, int y_from, -// int *px_to, int *py_to, int *pz_to) { -// -// /* Returns the point to which I want to swap, properly range limited. -// * rlim must always be between 1 and device_ctx.grid.width() - 2 (inclusive) for this routine -// * to work. Note -2 for no perim channels -// */ -// -// int min_x, max_x, min_y, max_y; -// int num_tries; -// int active_area; -// bool is_legal; -// int itype; -// + +static bool find_to(/*t_type_ptr type,*/ float rlim, + int x_from, int y_from, + /*int *px_to, int *py_to, int *pz_to*/ + CellInfo* cell_from, BelId& bel_to, + const int grid_width, const int grid_height) { + + /* Returns the point to which I want to swap, properly range limited. + * rlim must always be between 1 and device_ctx.grid.width() - 2 (inclusive) for this routine + * to work. Note -2 for no perim channels + */ + + int min_x, max_x, min_y, max_y; + int num_tries; + int active_area; + bool is_legal; + int itype; + // auto& grid = g_vpr_ctx.device().grid; // auto& place_ctx = g_vpr_ctx.placement(); // // auto grid_type = grid[x_from][y_from].type; // VTR_ASSERT(type == grid_type); -// -// int rlx = min(grid.width() - 1, rlim); -// int rly = min(grid.height() - 1, rlim); /* Added rly for aspect_ratio != 1 case. */ -// active_area = 4 * rlx * rly; -// -// min_x = max(0, x_from - rlx); -// max_x = min(grid.width() - 1, x_from + rlx); -// min_y = max(0, y_from - rly); -// max_y = min(grid.height() - 1, y_from + rly); -// -// if (rlx < 1 || rlx > int(grid.width() - 1)) { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in find_to: rlx = %d out of range\n", rlx); -// } -// if (rly < 1 || rly > int(grid.height() - 1)) { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in find_to: rly = %d out of range\n", rly); -// } -// -// num_tries = 0; + + int rlx = std::min(grid_width - 1, rlim); + int rly = std::min(grid_height - 1, rlim); /* Added rly for aspect_ratio != 1 case. */ + active_area = 4 * rlx * rly; + + min_x = std::max(0, x_from - rlx); + max_x = std::min(grid_width - 1, x_from + rlx); + min_y = std::max(0, y_from - rly); + max_y = std::min(grid_height - 1, y_from + rly); + + if (rlx < 1 || rlx > int(grid_width - 1)) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in find_to: rlx = %d out of range\n", rlx); + } + if (rly < 1 || rly > int(grid_height - 1)) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in find_to: rly = %d out of range\n", rly); + } + + num_tries = 0; // itype = type->index; // // do { /* Until legal */ @@ -1748,8 +1782,8 @@ void try_place(/*t_placer_opts placer_opts, // num_tries++; // } // -// find_to_location(type, rlim, x_from, y_from, -// px_to, py_to, pz_to); +// find_to_location(type, rlim, x_from, y_from/*, +// px_to, py_to, pz_to*/); // // if((x_from == *px_to) && (y_from == *py_to)) { // is_legal = false; @@ -1778,13 +1812,13 @@ void try_place(/*t_placer_opts placer_opts, // } // // VTR_ASSERT(type == grid[*px_to][*py_to].type); -// return true; -//} -// -//static void find_to_location(t_type_ptr type, float rlim, -// int x_from, int y_from, -// int *px_to, int *py_to, int *pz_to) { -// + return true; +} + +static void find_to_location(/*t_type_ptr type,*/ float rlim, + int x_from, int y_from/*, + int *px_to, int *py_to, int *pz_to*/) { + // auto& device_ctx = g_vpr_ctx.device(); // auto& grid = device_ctx.grid; // @@ -1814,7 +1848,7 @@ void try_place(/*t_placer_opts placer_opts, // *px_to = (*px_to) - grid[*px_to][*py_to].width_offset; /* align it */ // *py_to = (*py_to) - grid[*px_to][*py_to].height_offset; /* align it */ // } -//} +} // //static e_swap_result assess_swap(float delta_c, float t) { // @@ -2746,7 +2780,7 @@ void try_place(/*t_placer_opts placer_opts, static int try_place_macro(/*int itype, int ipos, int imacro*/ - Context *ctx, CellInfo* cell, const std::string& loc_name) { + CellInfo* cell, const std::string& loc_name) { // int x, y, z, member_x, member_y, member_z, imember; // @@ -2794,28 +2828,28 @@ static int try_place_macro(/*int itype, int ipos, int imacro*/ // // return (macro_placed); - auto bel = ctx->getBelByName(ctx->id(loc_name)); + auto bel = npnr_ctx->getBelByName(npnr_ctx->id(loc_name)); if (bel == BelId()) { log_error("No Bel named \'%s\' located for " "this chip (processing BEL attribute on \'%s\')\n", - loc_name.c_str(), cell->name.c_str(ctx)); + loc_name.c_str(), cell->name.c_str(npnr_ctx)); } - auto bel_type = ctx->getBelType(bel); - if (bel_type != ctx->belTypeFromId(cell->type)) { + auto bel_type = npnr_ctx->getBelType(bel); + if (bel_type != npnr_ctx->belTypeFromId(cell->type)) { log_error("Bel \'%s\' of type \'%s\' does not match cell " "\'%s\' of type \'%s\'", - loc_name.c_str(), ctx->belTypeToId(bel_type).c_str(ctx), cell->name.c_str(ctx), - cell->type.c_str(ctx)); + loc_name.c_str(), npnr_ctx->belTypeToId(bel_type).c_str(npnr_ctx), cell->name.c_str(npnr_ctx), + cell->type.c_str(npnr_ctx)); } - ctx->bindBel(bel, cell->name, STRENGTH_USER); + npnr_ctx->bindBel(bel, cell->name, STRENGTH_USER); return true; } static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_locations*/ - Context *ctx, std::vector>& free_locations) { + std::vector>& free_locations) { // int macro_placed; // int imacro, itype, itry, ipos; @@ -2826,7 +2860,7 @@ static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_l /* Macros are harder to place. Do them first */ // for (imacro = 0; imacro < num_pl_macros; imacro++) { - for (auto &cell_entry : ctx->cells) { + for (auto &cell_entry : npnr_ctx->cells) { auto cell = cell_entry.second.get(); // // Every macro are not placed in the beginnning @@ -2854,11 +2888,11 @@ static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_l // // } // Finished all tries - auto loc = cell->attrs.find(ctx->id("BEL")); + auto loc = cell->attrs.find(npnr_ctx->id("BEL")); if (loc == cell->attrs.end()) continue; - try_place_macro(ctx, cell, loc->second); + try_place_macro(cell, loc->second); // if (macro_placed == false){ // // if a macro still could not be placed after macros_max_num_tries times, @@ -2895,7 +2929,6 @@ static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_l /* Place blocks that are NOT a part of any macro. * We'll randomly place each block in the clustered netlist, one by one. */ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type pad_loc_type*/ - Context* ctx, std::vector>& free_locations, const std::unordered_map &bel_types) { // int itype, ipos, x, y, z; @@ -2904,7 +2937,7 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // auto& device_ctx = g_vpr_ctx.device(); // // for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { - for (auto& cell_entry : ctx->cells) { + for (auto& cell_entry : npnr_ctx->cells) { auto cell = cell_entry.second.get(); // if (place_ctx.block_locs[blk_id].x != -1) { // -1 is a sentinel for an empty block // // block placed. @@ -2932,12 +2965,11 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // } // BelId bel; - initial_placement_location(ctx, free_locations, cell, bel_types, bel); + initial_placement_location(free_locations, cell, bel_types, bel); // // // Make sure that the position is EMPTY_BLOCK before placing the block down // VTR_ASSERT(place_ctx.grid_blocks[x][y].blocks[z] == EMPTY_BLOCK_ID); - - NPNR_ASSERT(ctx->checkBelAvail(bel)); + VTR_ASSERT(npnr_ctx->checkBelAvail(bel)); // place_ctx.grid_blocks[x][y].blocks[z] = blk_id; // place_ctx.grid_blocks[x][y].usage++; @@ -2951,10 +2983,10 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // place_ctx.block_locs[blk_id].is_fixed = true; // } - if (ctx->isIO(cell)) - ctx->bindBel(bel, cell->name, STRENGTH_LOCKED); + if (npnr_ctx->isIO(cell)) + npnr_ctx->bindBel(bel, cell->name, /*STRENGTH_LOCKED*/STRENGTH_STRONG); else - ctx->bindBel(bel, cell->name, STRENGTH_WEAK); + npnr_ctx->bindBel(bel, cell->name, STRENGTH_WEAK); // /* Ensure randomizer doesn't pick this location again, since it's occupied. Could shift all the // * legal positions in legal_pos to remove the entry (choice) we just used, but faster to @@ -2963,7 +2995,7 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; /* overwrite used block position */ // free_locations[itype]--; - auto type = ctx->belTypeFromId(cell->type); + auto type = npnr_ctx->belTypeFromId(cell->type); auto itype = bel_types.at(type); free_locations[itype].pop_back(); @@ -2973,7 +3005,6 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type static void initial_placement_location(/*int * free_locations, ClusterBlockId blk_id, int *pipos, int *px, int *py, int *pz*/ - Context *ctx, const std::vector> &free_locations, CellInfo *cell, const std::unordered_map &bel_types, @@ -2988,7 +3019,7 @@ static void initial_placement_location(/*int * free_locations, ClusterBlockId bl // *py_to = legal_pos[itype][*pipos].y; // *pz_to = legal_pos[itype][*pipos].z; - auto type = ctx->belTypeFromId(cell->type); + auto type = npnr_ctx->belTypeFromId(cell->type); auto itype = bel_types.at(type); bel = free_locations[itype].back(); @@ -2996,7 +3027,6 @@ static void initial_placement_location(/*int * free_locations, ClusterBlockId bl static void initial_placement(/*enum e_pad_loc_type pad_loc_type, const char *pad_loc_file*/ - Context *ctx, const std::unordered_map &bel_types) { // /* Randomly places the blocks to create an initial placement. We rely on @@ -3020,8 +3050,8 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // } std::vector> free_locations; - for (auto bel : ctx->getBels()) { - auto type = ctx->getBelType(bel); + for (auto bel : npnr_ctx->getBels()) { + auto type = npnr_ctx->getBelType(bel); int type_idx = bel_types.at(type); if (int(free_locations.size()) < type_idx + 1) free_locations.resize(type_idx + 1); @@ -3050,7 +3080,7 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // place_ctx.block_locs[blk_id].z = OPEN; // } - initial_placement_pl_macros(/*MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY,*/ ctx, free_locations); + initial_placement_pl_macros(/*MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY,*/ free_locations); // // All the macros are placed, update the legal_pos[][] array // for (itype = 0; itype < device_ctx.num_block_types; itype++) { @@ -3074,9 +3104,9 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, for (auto itype = free_locations.begin(); itype != free_locations.end(); itype++) { for (auto ipos = itype->begin(); ipos != itype->end(); ) { - auto cell_name = ctx->getBoundBelCell(*ipos); + auto cell_name = npnr_ctx->getBoundBelCell(*ipos); if (cell_name != IdString()) { - auto cell = ctx->cells[cell_name].get(); + auto cell = npnr_ctx->cells[cell_name].get(); if (cell->belStrength == STRENGTH_USER) { ipos = itype->erase(ipos); continue; @@ -3086,7 +3116,7 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, } } - initial_placement_blocks(ctx, free_locations, bel_types); + initial_placement_blocks(free_locations, bel_types); // if (pad_loc_type == USER) { // read_user_pad_loc(pad_loc_file); From f8fac8900ff4c41087b56fcfa7b5fef6f676e4e3 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 13 Jul 2018 19:11:37 -0700 Subject: [PATCH 016/116] Make grid_width and grid_height global too --- common/place_vpr.inc | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/common/place_vpr.inc b/common/place_vpr.inc index edc57e588a..fde7fd98c7 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -239,8 +239,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin float rlim, /*enum e_place_algorithm place_algorithm, float timing_tradeoff,*/ float inverse_prev_bb_cost, float inverse_prev_timing_cost, - float *delay_cost, - const int grid_width, const int grid_height); + float *delay_cost); static /*ClusterBlockId*/ CellInfo* pick_from_block(); @@ -253,8 +252,7 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, /*t_annealing_sched annealing_sched,*/ int max_moves, float rlim, /*enum e_place_algorithm place_algorithm, float timing_tradeoff,*/ float inverse_prev_bb_cost, float inverse_prev_timing_cost, - float *delay_cost_ptr, - const int grid_width, const int grid_height); + float *delay_cost_ptr); //static void update_t(float *t, float rlim, float success_rat, // t_annealing_sched annealing_sched); @@ -285,8 +283,7 @@ static double get_std_dev(int n, double sum_x_squared, double av_x); static bool find_to(/*t_type_ptr type,*/ float rlim, int x_from, int y_from, /*int *px_to, int *py_to, int *pz_to*/ - CellInfo* cell_from, BelId& bel_to, - const int grid_width, const int grid_height); + CellInfo* cell_from, BelId& bel_to); static void find_to_location(/*t_type_ptr type,*/ float rlim, int x_from, int y_from/*, int *px_to, int *py_to, int *pz_to*/); @@ -338,6 +335,8 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit // SetupTimingInfo& timing_info); static Context* npnr_ctx = NULL; +static int grid_width = 0; +static int grid_height = 0; #define VTR_ASSERT NPNR_ASSERT #define VTR_ASSERT_SAFE NPNR_ASSERT #define vpr_throw(__a, __b, __c, ...) log_error(__VA_ARGS__) @@ -356,10 +355,10 @@ void try_place(/*t_placer_opts placer_opts, t_timing_inf timing_inf, #endif t_direct_inf *directs, int num_directs*/ - Context *ctx, + Context *npnr_ctx_, const std::unordered_map &bel_types, - const int grid_width, - const int grid_height) { + const int grid_width_, + const int grid_height_) { // /* Does almost all the work of placing a circuit. Width_fac gives the * // * width of the widest channel. Place_cost_exp says what exponent the * @@ -400,7 +399,9 @@ void try_place(/*t_placer_opts placer_opts, num_swap_aborted = 0; num_ts_called = 0; - npnr_ctx = ctx; + npnr_ctx = npnr_ctx_; + grid_width = grid_width_; + grid_height = grid_height_; // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE // || placer_opts.enable_timing_computations) { @@ -420,7 +421,6 @@ void try_place(/*t_placer_opts placer_opts, // directs, num_directs); initial_placement(bel_types); - return; // init_draw_coords((float) width_fac); // @@ -595,8 +595,7 @@ void try_place(/*t_placer_opts placer_opts, t = starting_t(&cost, &bb_cost, &timing_cost, /*annealing_sched,*/ move_lim, rlim, /*placer_opts.place_algorithm, placer_opts.timing_tradeoff,*/ - inverse_prev_bb_cost, inverse_prev_timing_cost, &delay_cost, - grid_width, grid_height); + inverse_prev_bb_cost, inverse_prev_timing_cost, &delay_cost); // tot_iter = 0; // moves_since_cost_recompute = 0; @@ -1143,8 +1142,7 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, /*t_annealing_sched annealing_sched,*/ int max_moves, float rlim, /*enum e_place_algorithm place_algorithm, float timing_tradeoff,*/ float inverse_prev_bb_cost, float inverse_prev_timing_cost, - float *delay_cost_ptr, - const int grid_width, const int grid_height) { + float *delay_cost_ptr) { /* Finds the starting temperature (hot condition). */ @@ -1167,8 +1165,7 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, for (i = 0; i < move_lim; i++) { e_swap_result swap_result = try_swap(std::numeric_limits::max(), cost_ptr, bb_cost_ptr, timing_cost_ptr, rlim, /*place_algorithm, timing_tradeoff,*/ - inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost_ptr, - grid_width, grid_height); + inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost_ptr); if (swap_result == ACCEPTED) { num_accepted++; @@ -1367,8 +1364,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin float rlim, /*enum e_place_algorithm place_algorithm, float timing_tradeoff,*/ float inverse_prev_bb_cost, float inverse_prev_timing_cost, - float *delay_cost, - const int grid_width, const int grid_height) { + float *delay_cost) { /* Picks some block and moves it to another spot. If this spot is * * occupied, switch the blocks. Assess the change in cost function. * @@ -1412,7 +1408,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin BelId bel_to; if (!find_to(/*cluster_ctx.clb_nlist.block_type(b_from),*/ rlim, x_from, y_from, /*&x_to, &y_to, &z_to*/ - b_from, bel_to, grid_width, grid_height)) + b_from, bel_to)) return REJECTED; #if 0 @@ -1732,8 +1728,7 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit static bool find_to(/*t_type_ptr type,*/ float rlim, int x_from, int y_from, /*int *px_to, int *py_to, int *pz_to*/ - CellInfo* cell_from, BelId& bel_to, - const int grid_width, const int grid_height) { + CellInfo* cell_from, BelId& bel_to) { /* Returns the point to which I want to swap, properly range limited. * rlim must always be between 1 and device_ctx.grid.width() - 2 (inclusive) for this routine From 3248ed7aed87f65f663648b69bc0a3c131758a65 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 13 Jul 2018 22:32:01 -0700 Subject: [PATCH 017/116] Fix compile errors --- common/place_vpr.cc | 2 +- common/place_vpr.inc | 417 ++++++++++++++++++++++--------------------- 2 files changed, 215 insertions(+), 204 deletions(-) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index f821db51cc..d336a6b9ed 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -113,7 +113,7 @@ class VPRPlacer { log_break(); - vpr::try_place(ctx, bel_types, max_x + 1, max_y + 1); + vpr::try_place(ctx, max_x + 1, max_y + 1); size_t placed_cells = 0; // Initial constraints placer diff --git a/common/place_vpr.inc b/common/place_vpr.inc index fde7fd98c7..5f118fb3cd 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -90,11 +90,12 @@ enum e_swap_result { //large enough to be on the order of timing costs for normal constraints. */ // ///********************** Variables local to place.c ***************************/ -// -///* Cost of a net, and a temporary cost of a net used during move assessment. */ -//static vtr::vector net_cost, temp_net_cost; -// + +/* Cost of a net, and a temporary cost of a net used during move assessment. */ +static std::unordered_map net_cost, temp_net_cost; + //static t_legal_pos **legal_pos = nullptr; /* [0..device_ctx.num_block_types-1][0..type_tsize - 1] */ +static std::unordered_map> legal_pos; //static int *num_legal_pos = nullptr; /* [0..num_legal_pos-1] */ // ///* [0...cluster_ctx.clb_nlist.nets().size()-1] * @@ -134,13 +135,13 @@ enum e_swap_result { // * respectively. */ // //static vtr::vector bb_coords, bb_num_on_edges; -// -///* Store the information on the blocks to be moved in a swap during * -// * placement, in the form of array of structs instead of struct with * -// * arrays for cache effifiency * -// */ -//static t_pl_blocks_to_be_moved blocks_affected; -// + +/* Store the information on the blocks to be moved in a swap during * + * placement, in the form of array of structs instead of struct with * + * arrays for cache effifiency * + */ +static std::vector> blocks_affected; + ///* The arrays below are used to precompute the inverse of the average * // * number of tracks per channel between [subhigh] and [sublow]. Access * // * them as chan?_place_cost_fac[subhigh][sublow]. They are used to * @@ -155,8 +156,8 @@ enum e_swap_result { ///* The following arrays are used by the try_swap function for speed. */ ///* [0...cluster_ctx.clb_nlist.nets().size()-1] */ //static vtr::vector ts_bb_coord_new, ts_bb_edge_new; -//static std::vector ts_nets_to_update; -// +static std::vector ts_nets_to_update; + ///* The pl_macros array stores all the carry chains placement macros. * // * [0...num_pl_macros-1] */ //static t_pl_macro * pl_macros = nullptr; @@ -186,11 +187,11 @@ static int num_ts_called = 0; //#ifdef VERBOSE // static void print_clb_placement(const char *fname); //#endif -// -//static void alloc_and_load_placement_structs( -// float place_cost_exp, t_placer_opts placer_opts, -// t_direct_inf *directs, int num_directs); -// + +static void alloc_and_load_placement_structs( + /*float place_cost_exp, t_placer_opts placer_opts, + t_direct_inf *directs, int num_directs*/); + //static void alloc_and_load_net_pin_indices(); // //static void alloc_and_load_try_swap_structs(); @@ -202,7 +203,7 @@ static int num_ts_called = 0; //static void free_fast_cost_update(); // //static void alloc_legal_placements(); -//static void load_legal_placements(); +static void load_legal_placements(); // //static void free_legal_placements(); // @@ -211,23 +212,19 @@ static int num_ts_called = 0; static int try_place_macro(/*int itype, int ipos, int imacro*/ CellInfo* cell, const std::string& loc_name); -static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_locations*/ - std::vector> &free_locations); +static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_locations*/); static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type pad_loc_type*/ - std::vector> &free_locations, - const std::unordered_map &bel_types); + std::unordered_map> &free_locations); static void initial_placement_location(/*int * free_locations, ClusterBlockId blk_id, int *pipos, int *px, int *py, int *pz*/ - const std::vector> &free_locations, + const std::unordered_map> &free_locations, CellInfo *cell, - const std::unordered_map &bel_types, BelId& bel); static void initial_placement(/*enum e_pad_loc_type pad_loc_type, - const char *pad_loc_file*/ - const std::unordered_map &bel_types); + const char *pad_loc_file*/); //static float comp_bb_cost(e_cost_methods method); @@ -278,8 +275,8 @@ static double get_std_dev(int n, double sum_x_squared, double av_x); // //static void comp_td_costs(float *timing_cost, float *connection_delay_sum); // -//static e_swap_result assess_swap(float delta_c, float t); -// +static e_swap_result assess_swap(float delta_c, float t); + static bool find_to(/*t_type_ptr type,*/ float rlim, int x_from, int y_from, /*int *px_to, int *py_to, int *pz_to*/ @@ -296,8 +293,8 @@ static void find_to_location(/*t_type_ptr type,*/ float rlim, static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorithm,*/ float& bb_delta_c, float& timing_delta_c, float& delay_delta_c, CellInfo* b_from, BelId bel_to); -//static void record_affected_net(const ClusterNetId net, int& num_affected_nets); -// +static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_affected_nets); + //static void update_net_bb(const ClusterNetId net, int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin); //static void update_td_delta_costs(const ClusterNetId net, const ClusterPinId pin, float& delta_timing_cost, float& delta_delay_cost); // @@ -337,13 +334,20 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit static Context* npnr_ctx = NULL; static int grid_width = 0; static int grid_height = 0; +static struct { + const float inner_num = 10; +} annealing_sched; + #define VTR_ASSERT NPNR_ASSERT #define VTR_ASSERT_SAFE NPNR_ASSERT +#define VTR_ASSERT_SAFE_MSG NPNR_ASSERT_MSG #define vpr_throw(__a, __b, __c, ...) log_error(__VA_ARGS__) - -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "in try_place: new_bb_cost = %g, old bb_cost = %g\n", -// new_bb_cost, bb_cost); +namespace vtr { + template + inline void printf_warning(const char*, unsigned, const char* fmt, Args... args) { + log_warning(fmt, std::forward(args)...); + } +} /*****************************************************************************/ @@ -356,7 +360,6 @@ void try_place(/*t_placer_opts placer_opts, #endif t_direct_inf *directs, int num_directs*/ Context *npnr_ctx_, - const std::unordered_map &bel_types, const int grid_width_, const int grid_height_) { @@ -416,11 +419,11 @@ void try_place(/*t_placer_opts placer_opts, // width_fac = placer_opts.place_chan_width; // // init_chan(width_fac, chan_width_dist); -// -// alloc_and_load_placement_structs(placer_opts.place_cost_exp, placer_opts, -// directs, num_directs); - initial_placement(bel_types); + alloc_and_load_placement_structs(/*placer_opts.place_cost_exp, placer_opts, + directs, num_directs*/); + + initial_placement(/*placer_opts.pad_loc_type, placer_opts.pad_loc_file.c_str()*/); // init_draw_coords((float) width_fac); // @@ -568,16 +571,16 @@ void try_place(/*t_placer_opts placer_opts, // // //Draw the initial placement // update_screen(ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info); -// -// move_lim = (int) (annealing_sched.inner_num * pow(cluster_ctx.clb_nlist.blocks().size(), 1.3333)); -// -// /* Sometimes I want to run the router with a random placement. Avoid * -// * using 0 moves to stop division by 0 and 0 length vector problems, * -// * by setting move_lim to 1 (which is still too small to do any * -// * significant optimization). */ -// if (move_lim <= 0) -// move_lim = 1; -// + + move_lim = (int) (annealing_sched.inner_num * pow(npnr_ctx->cells.size(), 1.3333)); + + /* Sometimes I want to run the router with a random placement. Avoid * + * using 0 moves to stop division by 0 and 0 length vector problems, * + * by setting move_lim to 1 (which is still too small to do any * + * significant optimization). */ + if (move_lim <= 0) + move_lim = 1; + // if (placer_opts.inner_loop_recompute_divider != 0) { // inner_recompute_limit = (int) // (0.5 + (float) move_lim / (float) placer_opts.inner_loop_recompute_divider); @@ -597,12 +600,14 @@ void try_place(/*t_placer_opts placer_opts, /*placer_opts.place_algorithm, placer_opts.timing_tradeoff,*/ inverse_prev_bb_cost, inverse_prev_timing_cost, &delay_cost); -// tot_iter = 0; -// moves_since_cost_recompute = 0; -// -// -// -// + tot_iter = 0; + moves_since_cost_recompute = 0; + + printf("starting_t = %f\n", starting_t); + + return; + + // /* Outer loop of the simmulated annealing begins */ // while (exit_crit(t, cost, annealing_sched) == 0) { // @@ -1187,10 +1192,12 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, std_dev = get_std_dev(num_accepted, sum_of_squares, av); if (num_accepted != move_lim) { -// vtr::printf_warning(__FILE__, __LINE__, -// "Starting t: %d of %d configurations accepted.\n", num_accepted, move_lim); + vtr::printf_warning(__FILE__, __LINE__, + "Starting t: %d of %d configurations accepted.\n", num_accepted, move_lim); } + log_info("%d %d\n", num_accepted, move_lim); + #ifdef VERBOSE vtr::printf_info("std_dev: %g, average cost: %g, starting temp: %g\n", std_dev, av, 20. * std_dev); #endif @@ -1222,8 +1229,8 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to b_to = (b_to_id == IdString() ? NULL : npnr_ctx->cells[b_to_id].get()); // Check whether the to_location is empty -// if (b_to == NULL) { -// + if (b_to == NULL) { + // // Swap the block, dont swap the nets yet // place_ctx.block_locs[b_from].x = x_to; // place_ctx.block_locs[b_from].y = y_to; @@ -1241,9 +1248,11 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to // blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = true; // blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = true; // blocks_affected.num_moved_blocks ++; -// -// } else if (b_to != INVALID_BLOCK_ID) { -// + + blocks_affected.emplace_back(b_from, bel_to); + + } else if (b_to != /*INVALID_BLOCK_ID*/ NULL) { + // // Does not allow a swap with a macro yet // get_imacro_from_iblk(&imacro, b_to, pl_macros, num_pl_macros); // if (imacro != -1) { @@ -1284,8 +1293,11 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to // blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = false; // blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = false; // blocks_affected.num_moved_blocks ++; -// -// } // Finish swapping the blocks and setting up blocks_affected + + blocks_affected.emplace_back(b_from, bel_to); + blocks_affected.emplace_back(b_to, b_from->bel); + + } // Finish swapping the blocks and setting up blocks_affected return (abort_swap); @@ -1453,7 +1465,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin // } /* 1 -> move accepted, 0 -> rejected. */ - e_swap_result keep_switch = /*assess_swap(delta_c, t)*/ ACCEPTED; + e_swap_result keep_switch = assess_swap(delta_c, t); if (keep_switch == ACCEPTED) { *cost = *cost + delta_c; @@ -1467,26 +1479,27 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin // // update_td_cost(); // } -// -// /* update net cost functions and reset flags. */ -// for (int inet_affected = 0; inet_affected < num_nets_affected; inet_affected++) { -// ClusterNetId net_id = ts_nets_to_update[inet_affected]; -// + + /* update net cost functions and reset flags. */ + for (auto net : ts_nets_to_update) { // bb_coords[net_id] = ts_bb_coord_new[net_id]; // if (cluster_ctx.clb_nlist.net_sinks(net_id).size() >= SMALL_NET) // bb_num_on_edges[net_id] = ts_bb_edge_new[net_id]; -// -// net_cost[net_id] = temp_net_cost[net_id]; -// -// /* negative temp_net_cost value is acting as a flag. */ -// temp_net_cost[net_id] = -1; + + net_cost[net] = temp_net_cost[net]; + + /* negative temp_net_cost value is acting as a flag. */ + temp_net_cost[net] = -1; // bb_updated_before[net_id] = NOT_UPDATED_YET; -// } -// + } + + // /* Update clb data structures since we kept the move. */ // /* Swap physical location */ -// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { -// + for (const auto& b : blocks_affected) { + auto blk = b.first; + auto bel = b.second; + // x_to = blocks_affected.moved_blocks[iblk].xnew; // y_to = blocks_affected.moved_blocks[iblk].ynew; // z_to = blocks_affected.moved_blocks[iblk].znew; @@ -1498,7 +1511,9 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin // b_from = blocks_affected.moved_blocks[iblk].block_num; // // place_ctx.grid_blocks[x_to][y_to].blocks[z_to] = b_from; -// + + npnr_ctx->bindBel(bel, blk->name, STRENGTH_WEAK); + // if (blocks_affected.moved_blocks[iblk].swapped_to_was_empty) { // place_ctx.grid_blocks[x_to][y_to].usage++; // } @@ -1506,18 +1521,17 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin // place_ctx.grid_blocks[x_from][y_from].usage--; // place_ctx.grid_blocks[x_from][y_from].blocks[z_from] = EMPTY_BLOCK_ID; // } -// -// } // Finish updating clb for all blocks + + } // Finish updating clb for all blocks } else { /* Move was rejected. */ /* Reset the net cost function flags first. */ -// for (int inet_affected = 0; inet_affected < num_nets_affected; inet_affected++) { -// ClusterNetId net_id = ts_nets_to_update[inet_affected]; -// temp_net_cost[net_id] = -1; + for (auto net : ts_nets_to_update) { + temp_net_cost[net] = -1; // bb_updated_before[net_id] = NOT_UPDATED_YET; -// } -// + } + // /* Restore the place_ctx.block_locs data structures to their state before the move. */ // for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { // b_from = blocks_affected.moved_blocks[iblk].block_num; @@ -1529,7 +1543,8 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin } // /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ -// blocks_affected.num_moved_blocks = 0; + blocks_affected.clear(); + ts_nets_to_update.clear(); #if 0 //Check that each accepted swap yields a valid placement @@ -1606,21 +1621,27 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit int num_affected_nets = 0; -// //Go through all the blocks moved -// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { -// ClusterBlockId blk = blocks_affected.moved_blocks[iblk].block_num; -// -// //Go through all the pins in the moved block -// for (ClusterPinId blk_pin : cluster_ctx.clb_nlist.block_pins(blk)) { -// ClusterNetId net_id = cluster_ctx.clb_nlist.pin_net(blk_pin); -// VTR_ASSERT_SAFE_MSG(net_id, "Only valid nets should be found in compressed netlist block pins"); -// + //Go through all the blocks moved + for (const auto& b : blocks_affected) { + auto blk = b.first; + auto bel = b.second; + + //Go through all the pins in the moved block + for (const auto &port : blk->ports) { + if (port.second.net == nullptr) continue; + auto net = port.second.net; + VTR_ASSERT_SAFE_MSG(net, "Only valid nets should be found in compressed netlist block pins"); + // if (cluster_ctx.clb_nlist.net_is_global(net_id)) // continue; //Global nets are assumed to span the whole chip, and do not effect costs -// -// //Record effected nets -// record_affected_net(net_id, num_affected_nets); -// + + npnr_ctx->bindBel(bel, blk->name, STRENGTH_WEAK); + + //Record effected nets + record_affected_net(net, num_affected_nets); + + npnr_ctx->unbindBel(bel); + // //Update the net bounding boxes // // // //Do not update the net cost here since it should only be updated @@ -1631,34 +1652,33 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit // //Determine the change in timing costs if required // update_td_delta_costs(net_id, blk_pin, timing_delta_c, delay_delta_c); // } -// } -// } -// -// /* Now update the bounding box costs (since the net bounding boxes are up-to-date). -// * The cost is only updated once per net. -// */ -// for (int inet_affected = 0; inet_affected < num_affected_nets; inet_affected++) { -// ClusterNetId net_id = ts_nets_to_update[inet_affected]; -// -// temp_net_cost[net_id] = get_net_cost(net_id, &ts_bb_coord_new[net_id]); -// bb_delta_c += temp_net_cost[net_id] - net_cost[net_id]; -// } + } + } + + /* Now update the bounding box costs (since the net bounding boxes are up-to-date). + * The cost is only updated once per net. + */ + for (auto net : ts_nets_to_update) { + float temp_ns; + temp_net_cost[net] = get_net_wirelength(npnr_ctx, net, temp_ns); + bb_delta_c += temp_net_cost[net] - net_cost[net]; + } return num_affected_nets; } -// -//static void record_affected_net(const ClusterNetId net, int& num_affected_nets) { -// //Record effected nets -// if (temp_net_cost[net] < 0.) { -// //Net not marked yet. -// ts_nets_to_update[num_affected_nets] = net; -// num_affected_nets++; -// -// //Flag to say we've marked this net. -// temp_net_cost[net] = 1.; -// } -//} -// + +static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_affected_nets) { + //Record effected nets + if (temp_net_cost[net] < 0.) { + //Net not marked yet. + ts_nets_to_update.push_back(net); + num_affected_nets++; + + //Flag to say we've marked this net. + temp_net_cost[net] = 1.; + } +} + //static void update_net_bb(const ClusterNetId net, int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin) { // auto& cluster_ctx = g_vpr_ctx.clustering(); // @@ -1739,13 +1759,14 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, int num_tries; int active_area; bool is_legal; - int itype; +// int itype; // auto& grid = g_vpr_ctx.device().grid; // auto& place_ctx = g_vpr_ctx.placement(); // // auto grid_type = grid[x_from][y_from].type; // VTR_ASSERT(type == grid_type); + auto type = cell_from->type; int rlx = std::min(grid_width - 1, rlim); int rly = std::min(grid_height - 1, rlim); /* Added rly for aspect_ratio != 1 case. */ @@ -1770,7 +1791,7 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, // is_legal = true; // // /* Limit the number of tries when searching for an alternative position */ -// if(num_tries >= 2 * min(active_area / (type->width * type->height), num_legal_pos[itype]) + 10) { +// if(num_tries >= 2 * std::min(active_area /*/ (type->width * type->height)*/, legal_pos[type].size()) + 10) { // /* Tried randomly searching for a suitable position */ // return false; // } else { @@ -1844,37 +1865,37 @@ static void find_to_location(/*t_type_ptr type,*/ float rlim, // *py_to = (*py_to) - grid[*px_to][*py_to].height_offset; /* align it */ // } } -// -//static e_swap_result assess_swap(float delta_c, float t) { -// -// /* Returns: 1 -> move accepted, 0 -> rejected. */ -// -// e_swap_result accept; -// float prob_fac, fnum; -// -// if (delta_c <= 0) { -// -// /* Reduce variation in final solution due to round off */ -// fnum = vtr::frand(); -// -// accept = ACCEPTED; -// return (accept); -// } -// -// if (t == 0.) -// return (REJECTED); -// -// fnum = vtr::frand(); -// prob_fac = exp(-delta_c / t); -// if (prob_fac > fnum) { -// accept = ACCEPTED; -// } -// else { -// accept = REJECTED; -// } -// return (accept); -//} -// + +static e_swap_result assess_swap(float delta_c, float t) { + + /* Returns: 1 -> move accepted, 0 -> rejected. */ + + e_swap_result accept; + float prob_fac, fnum; + + if (delta_c <= 0) { + + /* Reduce variation in final solution due to round off */ + fnum = npnr_ctx->rng() / float(0x3fffffff); + + accept = ACCEPTED; + return (accept); + } + + if (t == 0.) + return (REJECTED); + + fnum = npnr_ctx->rng() / float(0x3fffffff); + prob_fac = exp(-delta_c / t); + if (prob_fac > fnum) { + accept = ACCEPTED; + } + else { + accept = REJECTED; + } + return (accept); +} + //static float recompute_bb_cost() { // /* Recomputes the cost to eliminate roundoff that may have accrued. * // * This routine does as little work as possible to compute this new * @@ -2128,13 +2149,13 @@ static void find_to_location(/*t_type_ptr type,*/ float rlim, // free_blk_pin_from_port_pin(); // //} -// -///* Allocates the major structures needed only by the placer, primarily for * -//* computing costs quickly and such. */ -//static void alloc_and_load_placement_structs( -// float place_cost_exp, t_placer_opts placer_opts, -// t_direct_inf *directs, int num_directs) { -// + +/* Allocates the major structures needed only by the placer, primarily for * +* computing costs quickly and such. */ +static void alloc_and_load_placement_structs( + /*float place_cost_exp, t_placer_opts placer_opts, + t_direct_inf *directs, int num_directs*/) { + // int max_pins_per_clb, i; // unsigned int ipin; // @@ -2146,7 +2167,7 @@ static void find_to_location(/*t_type_ptr type,*/ float rlim, // init_placement_context(); // // alloc_legal_placements(); -// load_legal_placements(); + load_legal_placements(); // // max_pins_per_clb = 0; // for (i = 0; i < device_ctx.num_block_types; i++) { @@ -2204,8 +2225,8 @@ static void find_to_location(/*t_type_ptr type,*/ float rlim, // alloc_and_load_try_swap_structs(); // // num_pl_macros = alloc_and_load_placement_macros(directs, num_directs, &pl_macros); -//} -// +} + ///* Allocates and loads net_pin_indices array, this array allows us to quickly * //* find what pin on the net a block pin corresponds to. Returns the pointer * //* to the 2D net_pin_indices array. */ @@ -2699,8 +2720,8 @@ static void find_to_location(/*t_type_ptr type,*/ float rlim, // legal_pos[i] = (t_legal_pos *) vtr::malloc(num_legal_pos[i] * sizeof(t_legal_pos)); // } //} -// -//static void load_legal_placements() { + +static void load_legal_placements() { // auto& device_ctx = g_vpr_ctx.device(); // auto& place_ctx = g_vpr_ctx.placement(); // @@ -2723,8 +2744,14 @@ static void find_to_location(/*t_type_ptr type,*/ float rlim, // } // } // free(index); -//} -// + + for (auto bel : npnr_ctx->getBels()) { + auto belType = npnr_ctx->getBelType(bel); + auto type = npnr_ctx->belTypeToId(belType); + legal_pos[type].push_back(bel); + } +} + //static void free_legal_placements() { // auto& device_ctx = g_vpr_ctx.device(); // @@ -2843,8 +2870,7 @@ static int try_place_macro(/*int itype, int ipos, int imacro*/ return true; } -static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_locations*/ - std::vector>& free_locations) { +static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_locations*/) { // int macro_placed; // int imacro, itype, itry, ipos; @@ -2924,8 +2950,7 @@ static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_l /* Place blocks that are NOT a part of any macro. * We'll randomly place each block in the clustered netlist, one by one. */ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type pad_loc_type*/ - std::vector>& free_locations, - const std::unordered_map &bel_types) { + std::unordered_map>& free_locations) { // int itype, ipos, x, y, z; // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); @@ -2960,7 +2985,7 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // } // BelId bel; - initial_placement_location(free_locations, cell, bel_types, bel); + initial_placement_location(free_locations, /*blk_id, &ipos, &x, &y, &z*/ cell, bel); // // // Make sure that the position is EMPTY_BLOCK before placing the block down // VTR_ASSERT(place_ctx.grid_blocks[x][y].blocks[z] == EMPTY_BLOCK_ID); @@ -2990,9 +3015,7 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; /* overwrite used block position */ // free_locations[itype]--; - auto type = npnr_ctx->belTypeFromId(cell->type); - auto itype = bel_types.at(type); - free_locations[itype].pop_back(); + free_locations[cell->type].pop_back(); // } } @@ -3000,9 +3023,8 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type static void initial_placement_location(/*int * free_locations, ClusterBlockId blk_id, int *pipos, int *px, int *py, int *pz*/ - const std::vector> &free_locations, + const std::unordered_map> &free_locations, CellInfo *cell, - const std::unordered_map &bel_types, BelId &bel) { // auto& cluster_ctx = g_vpr_ctx.clustering(); @@ -3014,15 +3036,11 @@ static void initial_placement_location(/*int * free_locations, ClusterBlockId bl // *py_to = legal_pos[itype][*pipos].y; // *pz_to = legal_pos[itype][*pipos].z; - auto type = npnr_ctx->belTypeFromId(cell->type); - auto itype = bel_types.at(type); - - bel = free_locations[itype].back(); + bel = free_locations.at(cell->type).back(); } static void initial_placement(/*enum e_pad_loc_type pad_loc_type, - const char *pad_loc_file*/ - const std::unordered_map &bel_types) { + const char *pad_loc_file*/) { // /* Randomly places the blocks to create an initial placement. We rely on // * the legal_pos array already being loaded. That legal_pos[itype] is an @@ -3044,14 +3062,7 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // free_locations[itype] = num_legal_pos[itype]; // } - std::vector> free_locations; - for (auto bel : npnr_ctx->getBels()) { - auto type = npnr_ctx->getBelType(bel); - int type_idx = bel_types.at(type); - if (int(free_locations.size()) < type_idx + 1) - free_locations.resize(type_idx + 1); - free_locations[type_idx].push_back(bel); - } + std::unordered_map> free_locations(legal_pos.begin(), legal_pos.end()); // /* We'll use the grid to record where everything goes. Initialize to the grid has no // * blocks placed anywhere. @@ -3075,7 +3086,7 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // place_ctx.block_locs[blk_id].z = OPEN; // } - initial_placement_pl_macros(/*MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY,*/ free_locations); + initial_placement_pl_macros(/*MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY, free_locations*/); // // All the macros are placed, update the legal_pos[][] array // for (itype = 0; itype < device_ctx.num_block_types; itype++) { @@ -3097,13 +3108,13 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // } // } // Finish updating the legal_pos[][] and free_locations[] array - for (auto itype = free_locations.begin(); itype != free_locations.end(); itype++) { - for (auto ipos = itype->begin(); ipos != itype->end(); ) { + for (auto it = free_locations.begin(); it != free_locations.end(); it++) { + for (auto ipos = it->second.begin(); ipos != it->second.end(); ) { auto cell_name = npnr_ctx->getBoundBelCell(*ipos); if (cell_name != IdString()) { auto cell = npnr_ctx->cells[cell_name].get(); if (cell->belStrength == STRENGTH_USER) { - ipos = itype->erase(ipos); + ipos = it->second.erase(ipos); continue; } } @@ -3111,7 +3122,7 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, } } - initial_placement_blocks(free_locations, bel_types); + initial_placement_blocks(free_locations); // if (pad_loc_type == USER) { // read_user_pad_loc(pad_loc_file); From de09bfd4b645b1e3898a110b9c20b85324c2ee5e Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 14:51:18 -0700 Subject: [PATCH 018/116] starting_t computation now working --- common/place_vpr.inc | 809 ++++++++++++++-------------- common/vpr_types.h | 1200 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1622 insertions(+), 387 deletions(-) create mode 100644 common/vpr_types.h diff --git a/common/place_vpr.inc b/common/place_vpr.inc index 5f118fb3cd..2532779311 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -10,7 +10,7 @@ //#include "vtr_random.h" //#include "vtr_matrix.h" // -//#include "vpr_types.h" +#include "vpr_types.h" //#include "vpr_error.h" //#include "vpr_utils.h" // @@ -36,12 +36,12 @@ //#include "tatum/echo_writer.hpp" // ///************** Types and defines local to place.c ***************************/ -// -///* Cut off for incremental bounding box updates. * -// * 4 is fastest -- I checked. */ -///* To turn off incremental bounding box updates, set this to a huge value */ -//#define SMALL_NET 4 -// + +/* Cut off for incremental bounding box updates. * + * 4 is fastest -- I checked. */ +/* To turn off incremental bounding box updates, set this to a huge value */ +#define SMALL_NET 4 + ///* This defines the error tolerance for floating points variables used in * // * cost computation. 0.01 means that there is a 1% error tolerance. */ //#define ERROR_TOL .01 @@ -129,12 +129,12 @@ static std::unordered_map> legal_pos; ///* placement. It is used to allow us to update individual connections on */ ///* each net */ //static vtr::vector> net_pin_indices; -// -///* [0..cluster_ctx.clb_nlist.nets().size()-1]. Store the bounding box coordinates and the number of * -// * blocks on each of a net's bounding box (to allow efficient updates), * -// * respectively. */ -// -//static vtr::vector bb_coords, bb_num_on_edges; + +/* [0..cluster_ctx.clb_nlist.nets().size()-1]. Store the bounding box coordinates and the number of * + * blocks on each of a net's bounding box (to allow efficient updates), * + * respectively. */ + +static std::unordered_map bb_coords, bb_num_on_edges; /* Store the information on the blocks to be moved in a swap during * * placement, in the form of array of structs instead of struct with * @@ -155,7 +155,7 @@ static std::vector> blocks_affected; // ///* The following arrays are used by the try_swap function for speed. */ ///* [0...cluster_ctx.clb_nlist.nets().size()-1] */ -//static vtr::vector ts_bb_coord_new, ts_bb_edge_new; +static std::unordered_map ts_bb_coord_new, ts_bb_edge_new; static std::vector ts_nets_to_update; ///* The pl_macros array stores all the carry chains placement macros. * @@ -171,18 +171,18 @@ static int num_swap_accepted = 0; static int num_swap_aborted = 0; static int num_ts_called = 0; -///* Expected crossing counts for nets with different #'s of pins. From * -// * ICCAD 94 pp. 690 - 695 (with linear interpolation applied by me). * -// * Multiplied to bounding box of a net to better estimate wire length * -// * for higher fanout nets. Each entry is the correction factor for the * -// * fanout index-1 */ -//static const float cross_count[50] = { /* [0..49] */1.0, 1.0, 1.0, 1.0828, 1.1536, 1.2206, 1.2823, 1.3385, 1.3991, 1.4493, 1.4974, -// 1.5455, 1.5937, 1.6418, 1.6899, 1.7304, 1.7709, 1.8114, 1.8519, 1.8924, -// 1.9288, 1.9652, 2.0015, 2.0379, 2.0743, 2.1061, 2.1379, 2.1698, 2.2016, -// 2.2334, 2.2646, 2.2958, 2.3271, 2.3583, 2.3895, 2.4187, 2.4479, 2.4772, -// 2.5064, 2.5356, 2.5610, 2.5864, 2.6117, 2.6371, 2.6625, 2.6887, 2.7148, -// 2.7410, 2.7671, 2.7933 }; -// +/* Expected crossing counts for nets with different #'s of pins. From * + * ICCAD 94 pp. 690 - 695 (with linear interpolation applied by me). * + * Multiplied to bounding box of a net to better estimate wire length * + * for higher fanout nets. Each entry is the correction factor for the * + * fanout index-1 */ +static const float cross_count[50] = { /* [0..49] */1.0, 1.0, 1.0, 1.0828, 1.1536, 1.2206, 1.2823, 1.3385, 1.3991, 1.4493, 1.4974, + 1.5455, 1.5937, 1.6418, 1.6899, 1.7304, 1.7709, 1.8114, 1.8519, 1.8924, + 1.9288, 1.9652, 2.0015, 2.0379, 2.0743, 2.1061, 2.1379, 2.1698, 2.2016, + 2.2334, 2.2646, 2.2958, 2.3271, 2.3583, 2.3895, 2.4187, 2.4479, 2.4772, + 2.5064, 2.5356, 2.5610, 2.5864, 2.6117, 2.6371, 2.6625, 2.6887, 2.7148, + 2.7410, 2.7671, 2.7933 }; + ///********************* Static subroutines local to place.c *******************/ //#ifdef VERBOSE // static void print_clb_placement(const char *fname); @@ -226,7 +226,7 @@ static void initial_placement_location(/*int * free_locations, ClusterBlockId bl static void initial_placement(/*enum e_pad_loc_type pad_loc_type, const char *pad_loc_file*/); -//static float comp_bb_cost(e_cost_methods method); +static float comp_bb_cost(/*e_cost_methods method*/); static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, int y_to, int z_to*/ BelId bel_to); @@ -281,28 +281,28 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, int x_from, int y_from, /*int *px_to, int *py_to, int *pz_to*/ CellInfo* cell_from, BelId& bel_to); -static void find_to_location(/*t_type_ptr type,*/ float rlim, - int x_from, int y_from/*, - int *px_to, int *py_to, int *pz_to*/); +static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, + int x_from, int y_from, + /*int *px_to, int *py_to, int *pz_to*/ + BelId &bel_to); + +static void get_non_updateable_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new); -//static void get_non_updateable_bb(ClusterNetId net_id, t_bb *bb_coord_new); -// //static void update_bb(ClusterNetId net_id, t_bb *bb_coord_new, // t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew); // -static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorithm,*/ float& bb_delta_c, float& timing_delta_c, float& delay_delta_c, - CellInfo* b_from, BelId bel_to); +static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorithm,*/ float& bb_delta_c, float& timing_delta_c, float& delay_delta_c); static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_affected_nets); //static void update_net_bb(const ClusterNetId net, int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin); //static void update_td_delta_costs(const ClusterNetId net, const ClusterPinId pin, float& delta_timing_cost, float& delta_delay_cost); -// -//static float get_net_cost(ClusterNetId net_id, t_bb *bb_ptr); -// -//static void get_bb_from_scratch(ClusterNetId net_id, t_bb *coords, -// t_bb *num_on_edges); -// + +static float get_net_cost(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_ptr); + +static void get_bb_from_scratch(/*ClusterNetId*/ NetInfo* net, t_bb *coords, + t_bb *num_on_edges); + //static double get_net_wirelength_estimate(ClusterNetId net_id, t_bb *bbptr); // //static void free_try_swap_arrays(); @@ -332,8 +332,11 @@ static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_af // SetupTimingInfo& timing_info); static Context* npnr_ctx = NULL; -static int grid_width = 0; -static int grid_height = 0; +static struct { + inline int width() { return w; } + inline int height() { return h; } + int w, h; +} grid; static struct { const float inner_num = 10; } annealing_sched; @@ -347,6 +350,10 @@ namespace vtr { inline void printf_warning(const char*, unsigned, const char* fmt, Args... args) { log_warning(fmt, std::forward(args)...); } + template + inline void printf_info(const char* fmt, Args... args) { + log_info(fmt, std::forward(args)...); + } } @@ -360,26 +367,26 @@ void try_place(/*t_placer_opts placer_opts, #endif t_direct_inf *directs, int num_directs*/ Context *npnr_ctx_, - const int grid_width_, - const int grid_height_) { + const int grid_width, + const int grid_height) { // /* Does almost all the work of placing a circuit. Width_fac gives the * // * width of the widest channel. Place_cost_exp says what exponent the * // * width should be taken to when calculating costs. This allows a * // * greater bias for anisotropic architectures. */ - int tot_iter, move_lim, moves_since_cost_recompute, width_fac, num_connections, - outer_crit_iter_count, inner_recompute_limit; - unsigned int ipin; - float t, success_rat, rlim, cost, timing_cost, bb_cost, new_bb_cost, new_timing_cost, - delay_cost, new_delay_cost, place_delay_value, inverse_prev_bb_cost, inverse_prev_timing_cost, - oldt, crit_exponent, - first_rlim, final_rlim, inverse_delta_rlim; + int tot_iter, move_lim, moves_since_cost_recompute, /*width_fac,*/ num_connections, + outer_crit_iter_count /*, inner_recompute_limit*/; + //unsigned int ipin; + float t, /*success_rat,*/ rlim, cost, timing_cost, bb_cost, /*new_bb_cost, new_timing_cost,*/ + delay_cost, /*new_delay_cost,*/ place_delay_value, inverse_prev_bb_cost, inverse_prev_timing_cost, + /*oldt,*/ crit_exponent, + first_rlim, final_rlim , inverse_delta_rlim; // tatum::TimingPathInfo critical_path; // float sTNS = NAN; // float sWNS = NAN; // - double std_dev; +// double std_dev; // char msg[vtr::bufsize]; // t_placer_statistics stats; //#ifdef ENABLE_CLASSIC_VPR_STA @@ -403,8 +410,8 @@ void try_place(/*t_placer_opts placer_opts, num_ts_called = 0; npnr_ctx = npnr_ctx_; - grid_width = grid_width_; - grid_height = grid_height_; + grid.w = grid_width; + grid.h = grid_height; // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE // || placer_opts.enable_timing_computations) { @@ -502,16 +509,16 @@ void try_place(/*t_placer_opts placer_opts, // /*bb_cost and timing_cost, the value of cost will be reset */ // /*to 1 at each temperature when *_TIMING_DRIVEN_PLACE is true */ // } else { /*BOUNDING_BOX_PLACE */ -// cost = bb_cost = comp_bb_cost(NORMAL); -// timing_cost = 0; -// delay_cost = 0; -// place_delay_value = 0; -// outer_crit_iter_count = 0; -// num_connections = 0; -// crit_exponent = 0; -// -// inverse_prev_timing_cost = 0; /*inverses not used */ -// inverse_prev_bb_cost = 0; + cost = bb_cost = comp_bb_cost(/*NORMAL*/); + timing_cost = 0; + delay_cost = 0; + place_delay_value = 0; + outer_crit_iter_count = 0; + num_connections = 0; + crit_exponent = 0; + + inverse_prev_timing_cost = 0; /*inverses not used */ + inverse_prev_bb_cost = 0; // } // // //Sanity check that initial placement is legal @@ -589,7 +596,7 @@ void try_place(/*t_placer_opts placer_opts, // inner_recompute_limit = move_lim + 1; // } - rlim = (float) std::max(grid_width, grid_height); + rlim = (float) std::max(grid.width(), grid.height()); first_rlim = rlim; /*used in timing-driven placement for exponent computation */ final_rlim = 1; @@ -603,11 +610,8 @@ void try_place(/*t_placer_opts placer_opts, tot_iter = 0; moves_since_cost_recompute = 0; - printf("starting_t = %f\n", starting_t); - return; - // /* Outer loop of the simmulated annealing begins */ // while (exit_crit(t, cost, annealing_sched) == 0) { // @@ -1168,7 +1172,7 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, /* Try one move per block. Set t high so essentially all accepted. */ for (i = 0; i < move_lim; i++) { - e_swap_result swap_result = try_swap(std::numeric_limits::max(), cost_ptr, bb_cost_ptr, timing_cost_ptr, rlim, + e_swap_result swap_result = try_swap(HUGE_POSITIVE_FLOAT, cost_ptr, bb_cost_ptr, timing_cost_ptr, rlim, /*place_algorithm, timing_tradeoff,*/ inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost_ptr); @@ -1196,11 +1200,9 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, "Starting t: %d of %d configurations accepted.\n", num_accepted, move_lim); } - log_info("%d %d\n", num_accepted, move_lim); - -#ifdef VERBOSE +//#ifdef VERBOSE vtr::printf_info("std_dev: %g, average cost: %g, starting temp: %g\n", std_dev, av, 20. * std_dev); -#endif +//#endif /* Set the initial temperature to 20 times the standard of deviation */ /* so that the initial temperature adjusts according to the circuit */ @@ -1213,8 +1215,8 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to /* Find all the blocks affected when b_from is swapped with b_to. * Returns abort_swap. */ - int imoved_blk, imacro; - int x_from, y_from, z_from; +// int imoved_blk, imacro; +// int x_from, y_from, z_from; /*ClusterBlockId*/ CellInfo* b_to; int abort_swap = false; @@ -1223,7 +1225,9 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to // x_from = place_ctx.block_locs[b_from].x; // y_from = place_ctx.block_locs[b_from].y; // z_from = place_ctx.block_locs[b_from].z; -// + + auto bel_from = b_from->bel; + // b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; auto b_to_id = npnr_ctx->getBoundBelCell(bel_to); b_to = (b_to_id == IdString() ? NULL : npnr_ctx->cells[b_to_id].get()); @@ -1235,7 +1239,9 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to // place_ctx.block_locs[b_from].x = x_to; // place_ctx.block_locs[b_from].y = y_to; // place_ctx.block_locs[b_from].z = z_to; -// + + npnr_ctx->bindBel(bel_to, b_from->name, STRENGTH_WEAK); + // // Sets up the blocks moved // imoved_blk = blocks_affected.num_moved_blocks; // blocks_affected.moved_blocks[imoved_blk].block_num = b_from; @@ -1249,7 +1255,7 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to // blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = true; // blocks_affected.num_moved_blocks ++; - blocks_affected.emplace_back(b_from, bel_to); + blocks_affected.emplace_back(b_from, bel_from); } else if (b_to != /*INVALID_BLOCK_ID*/ NULL) { @@ -1268,7 +1274,12 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to // place_ctx.block_locs[b_from].x = x_to; // place_ctx.block_locs[b_from].y = y_to; // place_ctx.block_locs[b_from].z = z_to; -// + + npnr_ctx->unbindBel(bel_to); + npnr_ctx->unbindBel(bel_from); + npnr_ctx->bindBel(bel_to, b_from->name, STRENGTH_WEAK); + npnr_ctx->bindBel(bel_from, b_to->name, STRENGTH_WEAK); + // // Sets up the blocks moved // imoved_blk = blocks_affected.num_moved_blocks; // blocks_affected.moved_blocks[imoved_blk].block_num = b_from; @@ -1294,8 +1305,8 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to // blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = false; // blocks_affected.num_moved_blocks ++; - blocks_affected.emplace_back(b_from, bel_to); - blocks_affected.emplace_back(b_to, b_from->bel); + blocks_affected.emplace_back(b_from, bel_from); + blocks_affected.emplace_back(b_to, bel_to); } // Finish swapping the blocks and setting up blocks_affected @@ -1308,10 +1319,10 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, /* Finds and set ups the affected_blocks array. * Returns abort_swap. */ - int imacro, imember; - int x_swap_offset, y_swap_offset, z_swap_offset, x_from, y_from, z_from; +// int imacro, imember; +// int x_swap_offset, y_swap_offset, z_swap_offset, x_from, y_from, z_from; // ClusterBlockId curr_b_from; - int curr_x_from, curr_y_from, curr_z_from, curr_x_to, curr_y_to, curr_z_to; +// int curr_x_from, curr_y_from, curr_z_from, curr_x_to, curr_y_to, curr_z_to; int abort_swap = false; // auto& place_ctx = g_vpr_ctx.placement(); @@ -1450,8 +1461,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin if (abort_swap == false) { // Find all the nets affected by this swap and update thier bounding box - int num_nets_affected = find_affected_nets_and_update_costs(/*place_algorithm,*/ bb_delta_c, timing_delta_c, delay_delta_c, - b_from, bel_to); + /*int num_nets_affected =*/ find_affected_nets_and_update_costs(/*place_algorithm,*/ bb_delta_c, timing_delta_c, delay_delta_c); // if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // /*in this case we redefine delta_c as a combination of timing and bb. * @@ -1481,25 +1491,23 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin // } /* update net cost functions and reset flags. */ - for (auto net : ts_nets_to_update) { -// bb_coords[net_id] = ts_bb_coord_new[net_id]; -// if (cluster_ctx.clb_nlist.net_sinks(net_id).size() >= SMALL_NET) -// bb_num_on_edges[net_id] = ts_bb_edge_new[net_id]; + for (auto net_id : ts_nets_to_update) { + bb_coords[net_id] = ts_bb_coord_new[net_id]; + if (net_id->users.size() >= SMALL_NET) + bb_num_on_edges[net_id] = ts_bb_edge_new[net_id]; - net_cost[net] = temp_net_cost[net]; + net_cost[net_id] = temp_net_cost[net_id]; /* negative temp_net_cost value is acting as a flag. */ - temp_net_cost[net] = -1; + temp_net_cost[net_id] = -1; // bb_updated_before[net_id] = NOT_UPDATED_YET; } // /* Update clb data structures since we kept the move. */ // /* Swap physical location */ - for (const auto& b : blocks_affected) { - auto blk = b.first; - auto bel = b.second; - +// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { +// // x_to = blocks_affected.moved_blocks[iblk].xnew; // y_to = blocks_affected.moved_blocks[iblk].ynew; // z_to = blocks_affected.moved_blocks[iblk].znew; @@ -1511,9 +1519,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin // b_from = blocks_affected.moved_blocks[iblk].block_num; // // place_ctx.grid_blocks[x_to][y_to].blocks[z_to] = b_from; - - npnr_ctx->bindBel(bel, blk->name, STRENGTH_WEAK); - +// // if (blocks_affected.moved_blocks[iblk].swapped_to_was_empty) { // place_ctx.grid_blocks[x_to][y_to].usage++; // } @@ -1521,8 +1527,8 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin // place_ctx.grid_blocks[x_from][y_from].usage--; // place_ctx.grid_blocks[x_from][y_from].blocks[z_from] = EMPTY_BLOCK_ID; // } - - } // Finish updating clb for all blocks +// +// } // Finish updating clb for all blocks } else { /* Move was rejected. */ @@ -1533,13 +1539,21 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin } // /* Restore the place_ctx.block_locs data structures to their state before the move. */ -// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { + for (const auto &b : blocks_affected) { + auto blk = b.first; + auto bel = b.second; + // b_from = blocks_affected.moved_blocks[iblk].block_num; // // place_ctx.block_locs[b_from].x = blocks_affected.moved_blocks[iblk].xold; // place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; // place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; -// } + + if (npnr_ctx->checkBelAvail(bel)) + npnr_ctx->unbindBel(bel); + npnr_ctx->unbindBel(blk->bel); + npnr_ctx->bindBel(bel, blk->name, STRENGTH_WEAK); + } } // /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ @@ -1612,8 +1626,7 @@ static /*ClusterBlockId*/ CellInfo* pick_from_block() { //and updates their bounding box. // //Returns the number of affected nets. -static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorithm,*/ float& bb_delta_c, float& timing_delta_c, float& delay_delta_c, - CellInfo* b_from, BelId bel_to) { +static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorithm,*/ float& bb_delta_c, float& timing_delta_c, float& delay_delta_c) { VTR_ASSERT_SAFE(bb_delta_c == 0.); VTR_ASSERT_SAFE(timing_delta_c == 0.); VTR_ASSERT_SAFE(delay_delta_c == 0.); @@ -1622,9 +1635,8 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit int num_affected_nets = 0; //Go through all the blocks moved - for (const auto& b : blocks_affected) { + for (const auto &b : blocks_affected) { auto blk = b.first; - auto bel = b.second; //Go through all the pins in the moved block for (const auto &port : blk->ports) { @@ -1635,13 +1647,9 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit // if (cluster_ctx.clb_nlist.net_is_global(net_id)) // continue; //Global nets are assumed to span the whole chip, and do not effect costs - npnr_ctx->bindBel(bel, blk->name, STRENGTH_WEAK); - //Record effected nets record_affected_net(net, num_affected_nets); - npnr_ctx->unbindBel(bel); - // //Update the net bounding boxes // // // //Do not update the net cost here since it should only be updated @@ -1658,10 +1666,9 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit /* Now update the bounding box costs (since the net bounding boxes are up-to-date). * The cost is only updated once per net. */ - for (auto net : ts_nets_to_update) { - float temp_ns; - temp_net_cost[net] = get_net_wirelength(npnr_ctx, net, temp_ns); - bb_delta_c += temp_net_cost[net] - net_cost[net]; + for (auto net_id : ts_nets_to_update) { + temp_net_cost[net_id] = get_net_cost(net_id, &ts_bb_coord_new[net_id]); + bb_delta_c += temp_net_cost[net_id] - net_cost[net_id]; } return num_affected_nets; @@ -1669,7 +1676,7 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_affected_nets) { //Record effected nets - if (temp_net_cost[net] < 0.) { + if (!temp_net_cost.count(net) || temp_net_cost[net] < 0.) { //Net not marked yet. ts_nets_to_update.push_back(net); num_affected_nets++; @@ -1768,102 +1775,122 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, // VTR_ASSERT(type == grid_type); auto type = cell_from->type; - int rlx = std::min(grid_width - 1, rlim); - int rly = std::min(grid_height - 1, rlim); /* Added rly for aspect_ratio != 1 case. */ + int rlx = std::min(grid.width() - 1, rlim); + int rly = std::min(grid.height() - 1, rlim); /* Added rly for aspect_ratio != 1 case. */ active_area = 4 * rlx * rly; min_x = std::max(0, x_from - rlx); - max_x = std::min(grid_width - 1, x_from + rlx); + max_x = std::min(grid.width() - 1, x_from + rlx); min_y = std::max(0, y_from - rly); - max_y = std::min(grid_height - 1, y_from + rly); + max_y = std::min(grid.height() - 1, y_from + rly); - if (rlx < 1 || rlx > int(grid_width - 1)) { + if (rlx < 1 || rlx > int(grid.width() - 1)) { vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in find_to: rlx = %d out of range\n", rlx); } - if (rly < 1 || rly > int(grid_height - 1)) { + if (rly < 1 || rly > int(grid.height() - 1)) { vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in find_to: rly = %d out of range\n", rly); } num_tries = 0; // itype = type->index; -// -// do { /* Until legal */ -// is_legal = true; -// -// /* Limit the number of tries when searching for an alternative position */ -// if(num_tries >= 2 * std::min(active_area /*/ (type->width * type->height)*/, legal_pos[type].size()) + 10) { -// /* Tried randomly searching for a suitable position */ -// return false; -// } else { -// num_tries++; -// } -// -// find_to_location(type, rlim, x_from, y_from/*, -// px_to, py_to, pz_to*/); -// -// if((x_from == *px_to) && (y_from == *py_to)) { -// is_legal = false; -// } else if(*px_to > max_x || *px_to < min_x || *py_to > max_y || *py_to < min_y) { -// is_legal = false; -// } else if(grid[*px_to][*py_to].type != grid[x_from][y_from].type) { -// is_legal = false; -// } else { -// /* Find z_to and test to validate that the "to" block is *not* fixed */ -// *pz_to = 0; -// if (grid[*px_to][*py_to].type->capacity > 1) { -// *pz_to = vtr::irand(grid[*px_to][*py_to].type->capacity - 1); -// } -// ClusterBlockId b_to = place_ctx.grid_blocks[*px_to][*py_to].blocks[*pz_to]; -// if ((b_to != EMPTY_BLOCK_ID) && (place_ctx.block_locs[b_to].is_fixed == true)) { -// is_legal = false; -// } -// } -// -// VTR_ASSERT(*px_to >= 0 && *px_to < int(grid.width())); -// VTR_ASSERT(*py_to >= 0 && *py_to < int(grid.height())); -// } while (is_legal == false); -// -// if (*px_to < 0 || *px_to > int(grid.width() - 1) || *py_to < 0 || *py_to > int(grid.height() - 1)) { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in routine find_to: (x_to,y_to) = (%d,%d)\n", *px_to, *py_to); -// } -// -// VTR_ASSERT(type == grid[*px_to][*py_to].type); + + int px_to, py_to; + + do { /* Until legal */ + is_legal = true; + + /* Limit the number of tries when searching for an alternative position */ + if(num_tries >= 2 * std::min(active_area /*/ (type->width * type->height)*/, legal_pos[type].size()) + 10) { + /* Tried randomly searching for a suitable position */ + return false; + } else { + num_tries++; + } + + find_to_location(type, rlim, x_from, y_from/*, + px_to, py_to, pz_to*/, + bel_to); + + bool gb; + npnr_ctx->estimatePosition(bel_to, px_to, py_to, gb); + + if((x_from == px_to) && (y_from == py_to)) { + is_legal = false; + } else if(px_to > max_x || px_to < min_x || py_to > max_y || py_to < min_y) { + is_legal = false; + } else if(type != npnr_ctx->belTypeToId(npnr_ctx->getBelType(bel_to))) { + is_legal = false; + } else { + /* Find z_to and test to validate that the "to" block is *not* fixed */ + //*pz_to = 0; + //if (grid[*px_to][*py_to].type->capacity > 1) { + // *pz_to = vtr::irand(grid[*px_to][*py_to].type->capacity - 1); + //} + //ClusterBlockId b_to = place_ctx.grid_blocks[*px_to][*py_to].blocks[*pz_to]; + //if ((b_to != EMPTY_BLOCK_ID) && (place_ctx.block_locs[b_to].is_fixed == true)) { + // is_legal = false; + //} + + if (!npnr_ctx->isValidBelForCell(cell_from, bel_to)) + is_legal = false; + + auto cell_name = npnr_ctx->getBoundBelCell(bel_to); + if (cell_name != IdString()) { + auto cell = npnr_ctx->cells[cell_name].get(); + if (cell->belStrength > STRENGTH_WEAK) + is_legal = false; + } + } + + VTR_ASSERT(px_to >= 0 && px_to < int(grid.width())); + VTR_ASSERT(py_to >= 0 && py_to < int(grid.height())); + } while (is_legal == false); + + if (px_to < 0 || px_to > int(grid.width() - 1) || py_to < 0 || py_to > int(grid.height() - 1)) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in routine find_to: (x_to,y_to) = (%d,%d)\n", px_to, py_to); + } + + VTR_ASSERT(type == npnr_ctx->belTypeToId(npnr_ctx->getBelType(bel_to))); return true; } -static void find_to_location(/*t_type_ptr type,*/ float rlim, +static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, int x_from, int y_from/*, - int *px_to, int *py_to, int *pz_to*/) { + int *px_to, int *py_to, int *pz_to*/, + BelId &bel_to) { // auto& device_ctx = g_vpr_ctx.device(); // auto& grid = device_ctx.grid; // // int itype = type->index; -// -// -// int rlx = min(grid.width() - 1, rlim); -// int rly = min(grid.height() - 1, rlim); /* Added rly for aspect_ratio != 1 case. */ -// int active_area = 4 * rlx * rly; -// -// int min_x = max(0, x_from - rlx); -// int max_x = min(grid.width() - 1, x_from + rlx); -// int min_y = max(0, y_from - rly); -// int max_y = min(grid.height() - 1, y_from + rly); -// + + + int rlx = std::min(grid.width() - 1, rlim); + int rly = std::min(grid.height() - 1, rlim); /* Added rly for aspect_ratio != 1 case. */ + int active_area = 4 * rlx * rly; + + int min_x = std::max(0, x_from - rlx); + int max_x = std::min(grid.width() - 1, x_from + rlx); + int min_y = std::max(0, y_from - rly); + int max_y = std::min(grid.height() - 1, y_from + rly); + // *pz_to = 0; -// if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || num_legal_pos[itype] < active_area) { -// int ipos = vtr::irand(num_legal_pos[itype] - 1); + if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || legal_pos[type].size() < active_area) { + int ipos = npnr_ctx->rng(legal_pos[type].size()); // *px_to = legal_pos[itype][ipos].x; // *py_to = legal_pos[itype][ipos].y; // *pz_to = legal_pos[itype][ipos].z; -// } else { -// int x_rel = vtr::irand(max(0, max_x - min_x)); -// int y_rel = vtr::irand(max(0, max_y - min_y)); + bel_to = legal_pos[type][ipos]; + } else { + int x_rel = npnr_ctx->rng(std::max(0, max_x - min_x)); + int y_rel = npnr_ctx->rng(std::max(0, max_y - min_y)); // *px_to = min_x + x_rel; // *py_to = min_y + y_rel; // *px_to = (*px_to) - grid[*px_to][*py_to].width_offset; /* align it */ // *py_to = (*py_to) - grid[*px_to][*py_to].height_offset; /* align it */ -// } + bel_to = BelId(); + throw; + } } static e_swap_result assess_swap(float delta_c, float t) { @@ -2057,48 +2084,49 @@ static e_swap_result assess_swap(float delta_c, float t) { // // *connection_delay_sum = loc_connection_delay_sum; //} -// -// -///* Finds the cost from scratch. Done only when the placement * -//* has been radically changed (i.e. after initial placement). * -//* Otherwise find the cost change incrementally. If method * -//* check is NORMAL, we find bounding boxes that are updateable * -//* for the larger nets. If method is CHECK, all bounding boxes * -//* are found via the non_updateable_bb routine, to provide a * -//* cost which can be used to check the correctness of the * -//* other routine. */ -//static float comp_bb_cost(e_cost_methods method) { -// float cost = 0; -// double expected_wirelength = 0.0; + + +/* Finds the cost from scratch. Done only when the placement * +* has been radically changed (i.e. after initial placement). * +* Otherwise find the cost change incrementally. If method * +* check is NORMAL, we find bounding boxes that are updateable * +* for the larger nets. If method is CHECK, all bounding boxes * +* are found via the non_updateable_bb routine, to provide a * +* cost which can be used to check the correctness of the * +* other routine. */ +static float comp_bb_cost(/*e_cost_methods method*/) { + float cost = 0; + double expected_wirelength = 0.0; // auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// for (auto net_id : cluster_ctx.clb_nlist.nets()) { /* for each net ... */ + + for (auto& n : npnr_ctx->nets) { /* for each net ... */ + auto net_id = n.second.get(); // if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ -// /* Small nets don't use incremental updating on their bounding boxes, * -// * so they can use a fast bounding box calculator. */ -// if (cluster_ctx.clb_nlist.net_sinks(net_id).size() >= SMALL_NET && method == NORMAL) { -// get_bb_from_scratch(net_id, &bb_coords[net_id], -// &bb_num_on_edges[net_id]); -// } -// else { -// get_non_updateable_bb(net_id, &bb_coords[net_id]); -// } -// -// net_cost[net_id] = get_net_cost(net_id, &bb_coords[net_id]); -// cost += net_cost[net_id]; + /* Small nets don't use incremental updating on their bounding boxes, * + * so they can use a fast bounding box calculator. */ + if (net_id->users.size() >= SMALL_NET /*&& method == NORMAL*/) { + get_bb_from_scratch(net_id, &bb_coords[net_id], + &bb_num_on_edges[net_id]); + } + else { + get_non_updateable_bb(net_id, &bb_coords[net_id]); + } + + net_cost[net_id] = get_net_cost(net_id, &bb_coords[net_id]); + cost += net_cost[net_id]; // if (method == CHECK) // expected_wirelength += get_net_wirelength_estimate(net_id, &bb_coords[net_id]); // } -// } -// + } + // if (method == CHECK) { // vtr::printf_info("\n"); // vtr::printf_info("BB estimate of min-dist (placement) wire length: %.0f\n", expected_wirelength); // } -// return cost; -//} -// -// + return cost; +} + + ///* Frees the major structures needed by the placer (and not needed * //* elsewhere). */ //static void free_placement_structs(t_placer_opts placer_opts) { @@ -2277,98 +2305,102 @@ static void alloc_and_load_placement_structs( // blocks_affected.num_moved_blocks = 0; // //} -// -///* This routine finds the bounding box of each net from scratch (i.e. * -//* from only the block location information). It updates both the * -//* coordinate and number of pins on each edge information. It * -//* should only be called when the bounding box information is not valid. */ -//static void get_bb_from_scratch(ClusterNetId net_id, t_bb *coords, -// t_bb *num_on_edges) { -// int pnum, x, y, xmin, xmax, ymin, ymax; -// int xmin_edge, xmax_edge, ymin_edge, ymax_edge; -// + +/* This routine finds the bounding box of each net from scratch (i.e. * +* from only the block location information). It updates both the * +* coordinate and number of pins on each edge information. It * +* should only be called when the bounding box information is not valid. */ +static void get_bb_from_scratch(/*ClusterNetId*/ NetInfo *net_id, t_bb *coords, + t_bb *num_on_edges) { + int /*pnum,*/ x, y, xmin, xmax, ymin, ymax; + int xmin_edge, xmax_edge, ymin_edge, ymax_edge; + // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.placement(); // auto& device_ctx = g_vpr_ctx.device(); // auto& grid = device_ctx.grid; -// + // ClusterBlockId bnum = cluster_ctx.clb_nlist.net_driver_block(net_id); // pnum = cluster_ctx.clb_nlist.net_pin_physical_index(net_id, 0); // x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; // y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; -// -// x = max(min(x, grid.width() - 2), 1); -// y = max(min(y, grid.height() - 2), 1); -// -// xmin = x; -// ymin = y; -// xmax = x; -// ymax = y; -// xmin_edge = 1; -// ymin_edge = 1; -// xmax_edge = 1; -// ymax_edge = 1; -// -// for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) { -// bnum = cluster_ctx.clb_nlist.pin_block(pin_id); -// pnum = cluster_ctx.clb_nlist.pin_physical_index(pin_id); -// x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; -// y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; -// -// /* Code below counts IO blocks as being within the 1..grid.width()-2, 1..grid.height()-2 clb array. * -// * This is because channels do not go out of the 0..grid.width()-2, 0..grid.height()-2 range, and * -// * I always take all channels impinging on the bounding box to be within * -// * that bounding box. Hence, this "movement" of IO blocks does not affect * -// * the which channels are included within the bounding box, and it * -// * simplifies the code a lot. */ -// -// x = max(min(x, grid.width() - 2), 1); //-2 for no perim channels -// y = max(min(y, grid.height() - 2), 1); //-2 for no perim channels -// -// if (x == xmin) { -// xmin_edge++; -// } -// if (x == xmax) { /* Recall that xmin could equal xmax -- don't use else */ -// xmax_edge++; -// } -// else if (x < xmin) { -// xmin = x; -// xmin_edge = 1; -// } -// else if (x > xmax) { -// xmax = x; -// xmax_edge = 1; -// } -// -// if (y == ymin) { -// ymin_edge++; -// } -// if (y == ymax) { -// ymax_edge++; -// } -// else if (y < ymin) { -// ymin = y; -// ymin_edge = 1; -// } -// else if (y > ymax) { -// ymax = y; -// ymax_edge = 1; -// } -// } -// -// /* Copy the coordinates and number on edges information into the proper * -// * structures. */ -// coords->xmin = xmin; -// coords->xmax = xmax; -// coords->ymin = ymin; -// coords->ymax = ymax; -// -// num_on_edges->xmin = xmin_edge; -// num_on_edges->xmax = xmax_edge; -// num_on_edges->ymin = ymin_edge; -// num_on_edges->ymax = ymax_edge; -//} -// + auto bnum = net_id->driver.cell; + bool gb; + npnr_ctx->estimatePosition(bnum->bel, x, y, gb); + + x = std::max(std::min(x, grid.width() - 2), 1); + y = std::max(std::min(y, grid.height() - 2), 1); + + xmin = x; + ymin = y; + xmax = x; + ymax = y; + xmin_edge = 1; + ymin_edge = 1; + xmax_edge = 1; + ymax_edge = 1; + + for (auto pin_id : net_id->users) { + bnum = pin_id.cell; + //pnum = cluster_ctx.clb_nlist.pin_physical_index(pin_id); + //x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; + //y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; + npnr_ctx->estimatePosition(bnum->bel, x, y, gb); + + /* Code below counts IO blocks as being within the 1..grid.width()-2, 1..grid.height()-2 clb array. * + * This is because channels do not go out of the 0..grid.width()-2, 0..grid.height()-2 range, and * + * I always take all channels impinging on the bounding box to be within * + * that bounding box. Hence, this "movement" of IO blocks does not affect * + * the which channels are included within the bounding box, and it * + * simplifies the code a lot. */ + + x = std::max(std::min(x, grid.width() - 2), 1); //-2 for no perim channels + y = std::max(std::min(y, grid.height() - 2), 1); //-2 for no perim channels + + if (x == xmin) { + xmin_edge++; + } + if (x == xmax) { /* Recall that xmin could equal xmax -- don't use else */ + xmax_edge++; + } + else if (x < xmin) { + xmin = x; + xmin_edge = 1; + } + else if (x > xmax) { + xmax = x; + xmax_edge = 1; + } + + if (y == ymin) { + ymin_edge++; + } + if (y == ymax) { + ymax_edge++; + } + else if (y < ymin) { + ymin = y; + ymin_edge = 1; + } + else if (y > ymax) { + ymax = y; + ymax_edge = 1; + } + } + + /* Copy the coordinates and number on edges information into the proper * + * structures. */ + coords->xmin = xmin; + coords->xmax = xmax; + coords->ymin = ymin; + coords->ymax = ymax; + + num_on_edges->xmin = xmin_edge; + num_on_edges->xmax = xmax_edge; + num_on_edges->ymin = ymin_edge; + num_on_edges->ymax = ymax_edge; +} + //static double get_net_wirelength_estimate(ClusterNetId net_id, t_bb *bbptr) { // // /* WMF: Finds the estimate of wirelength due to one net by looking at * @@ -2404,101 +2436,104 @@ static void alloc_and_load_placement_structs( // // return (ncost); //} -// -//static float get_net_cost(ClusterNetId net_id, t_bb *bbptr) { -// -// /* Finds the cost due to one net by looking at its coordinate bounding * -// * box. */ -// -// float ncost, crossing; + +static float get_net_cost(/*ClusterNetId*/ NetInfo* net_id, t_bb *bbptr) { + + /* Finds the cost due to one net by looking at its coordinate bounding * + * box. */ + + float ncost, crossing; // auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// /* Get the expected "crossing count" of a net, based on its number * -// * of pins. Extrapolate for very large nets. */ -// -// if ((cluster_ctx.clb_nlist.net_pins(net_id).size()) > 50) { -// crossing = 2.7933 + 0.02616 * ((cluster_ctx.clb_nlist.net_pins(net_id).size()) - 50); -// /* crossing = 3.0; Old value */ -// } else { -// crossing = cross_count[(cluster_ctx.clb_nlist.net_pins(net_id).size()) - 1]; -// } -// -// /* Could insert a check for xmin == xmax. In that case, assume * -// * connection will be made with no bends and hence no x-cost. * -// * Same thing for y-cost. */ -// -// /* Cost = wire length along channel * cross_count / average * -// * channel capacity. Do this for x, then y direction and add. */ -// -// ncost = (bbptr->xmax - bbptr->xmin + 1) * crossing -// * chanx_place_cost_fac[bbptr->ymax][bbptr->ymin - 1]; -// -// ncost += (bbptr->ymax - bbptr->ymin + 1) * crossing -// * chany_place_cost_fac[bbptr->xmax][bbptr->xmin - 1]; -// -// return (ncost); -//} -// -///* Finds the bounding box of a net and stores its coordinates in the * -//* bb_coord_new data structure. This routine should only be called * -//* for small nets, since it does not determine enough information for * -//* the bounding box to be updated incrementally later. * -//* Currently assumes channels on both sides of the CLBs forming the * -//* edges of the bounding box can be used. Essentially, I am assuming * -//* the pins always lie on the outside of the bounding box. */ -//static void get_non_updateable_bb(ClusterNetId net_id, t_bb *bb_coord_new) { -// //TODO: account for multiple physical pin instances per logical pin -// -// int xmax, ymax, xmin, ymin, x, y; + + /* Get the expected "crossing count" of a net, based on its number * + * of pins. Extrapolate for very large nets. */ + + if (net_id->users.size() > 50) { + crossing = 2.7933 + 0.02616 * (net_id->users.size() - 50); + /* crossing = 3.0; Old value */ + } else { + crossing = cross_count[net_id->users.size() - 1]; + } + + /* Could insert a check for xmin == xmax. In that case, assume * + * connection will be made with no bends and hence no x-cost. * + * Same thing for y-cost. */ + + /* Cost = wire length along channel * cross_count / average * + * channel capacity. Do this for x, then y direction and add. */ + + ncost = (bbptr->xmax - bbptr->xmin + 1) * crossing + /** chanx_place_cost_fac[bbptr->ymax][bbptr->ymin - 1]*/; + + ncost += (bbptr->ymax - bbptr->ymin + 1) * crossing + /** chany_place_cost_fac[bbptr->xmax][bbptr->xmin - 1]*/; + + return (ncost); +} + +/* Finds the bounding box of a net and stores its coordinates in the * +* bb_coord_new data structure. This routine should only be called * +* for small nets, since it does not determine enough information for * +* the bounding box to be updated incrementally later. * +* Currently assumes channels on both sides of the CLBs forming the * +* edges of the bounding box can be used. Essentially, I am assuming * +* the pins always lie on the outside of the bounding box. */ +static void get_non_updateable_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new) { + //TODO: account for multiple physical pin instances per logical pin + + int xmax, ymax, xmin, ymin, x, y; // int pnum; -// + // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.placement(); // auto& device_ctx = g_vpr_ctx.device(); -// -// ClusterBlockId bnum = cluster_ctx.clb_nlist.net_driver_block(net_id); + + auto bnum = net_id->driver.cell; // pnum = cluster_ctx.clb_nlist.net_pin_physical_index(net_id, 0); // x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; // y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; -// -// xmin = x; -// ymin = y; -// xmax = x; -// ymax = y; -// -// for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) { -// bnum = cluster_ctx.clb_nlist.pin_block(pin_id); -// pnum = cluster_ctx.clb_nlist.pin_physical_index(pin_id); -// x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; -// y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; -// -// if (x < xmin) { -// xmin = x; -// } else if (x > xmax) { -// xmax = x; -// } -// -// if (y < ymin) { -// ymin = y; -// } else if (y > ymax) { -// ymax = y; -// } -// } -// -// /* Now I've found the coordinates of the bounding box. There are no * -// * channels beyond device_ctx.grid.width()-2 and * -// * device_ctx.grid.height() - 2, so I want to clip to that. As well,* -// * since I'll always include the channel immediately below and the * -// * channel immediately to the left of the bounding box, I want to * -// * clip to 1 in both directions as well (since minimum channel index * -// * is 0). See route_common.cpp for a channel diagram. */ -// -// bb_coord_new->xmin = max(min(xmin, device_ctx.grid.width() - 2), 1); //-2 for no perim channels -// bb_coord_new->ymin = max(min(ymin, device_ctx.grid.height() - 2), 1); //-2 for no perim channels -// bb_coord_new->xmax = max(min(xmax, device_ctx.grid.width() - 2), 1); //-2 for no perim channels -// bb_coord_new->ymax = max(min(ymax, device_ctx.grid.height() - 2), 1); //-2 for no perim channels -//} -// + bool gb; + npnr_ctx->estimatePosition(bnum->bel, x, y, gb); + + xmin = x; + ymin = y; + xmax = x; + ymax = y; + + for (auto pin_id : net_id->users) { + bnum = pin_id.cell; + //pnum = cluster_ctx.clb_nlist.pin_physical_index(pin_id); + //x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; + //y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; + npnr_ctx->estimatePosition(bnum->bel, x, y, gb); + + if (x < xmin) { + xmin = x; + } else if (x > xmax) { + xmax = x; + } + + if (y < ymin) { + ymin = y; + } else if (y > ymax) { + ymax = y; + } + } + + /* Now I've found the coordinates of the bounding box. There are no * + * channels beyond device_ctx.grid.width()-2 and * + * device_ctx.grid.height() - 2, so I want to clip to that. As well,* + * since I'll always include the channel immediately below and the * + * channel immediately to the left of the bounding box, I want to * + * clip to 1 in both directions as well (since minimum channel index * + * is 0). See route_common.cpp for a channel diagram. */ + + bb_coord_new->xmin = std::max(std::min(xmin, grid.width() - 2), 1); //-2 for no perim channels + bb_coord_new->ymin = std::max(std::min(ymin, grid.height() - 2), 1); //-2 for no perim channels + bb_coord_new->xmax = std::max(std::min(xmax, grid.width() - 2), 1); //-2 for no perim channels + bb_coord_new->ymax = std::max(std::min(ymax, grid.height() - 2), 1); //-2 for no perim channels +} + //static void update_bb(ClusterNetId net_id, t_bb *bb_coord_new, // t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew) { // diff --git a/common/vpr_types.h b/common/vpr_types.h new file mode 100644 index 0000000000..246863b5e3 --- /dev/null +++ b/common/vpr_types.h @@ -0,0 +1,1200 @@ +/* This is a core file that defines the major data types used by VPR + + This file is divided into generally 4 major sections: + + 1. Global data types and constants + 2. Packing specific data types + 3. Placement specific data types + 4. Routing specific data types + + Key background file: + + An understanding of libarchfpga/physical_types.h is crucial to understanding this file. physical_types.h contains information about the architecture described in the architecture description language + + Key data structures: + t_rr_node - The basic building block of the interconnect in the FPGA architecture + + Cluster-specific main data structure: + t_pb: Stores the mapping between the user netlist and the logic blocks on the FPGA architecture. For example, if a user design has 10 clusters of 5 LUTs each, you will have 10 t_pb instances of type cluster and within each of those clusters another 5 t_pb instances of type LUT. + The t_pb hierarchy follows what is described by t_pb_graph_node + + */ + +#ifndef VPR_TYPES_H +#define VPR_TYPES_H + +//#include +//#include +//#include "arch_types.h" +//#include "atom_netlist_fwd.h" +//#include "clustered_netlist_fwd.h" +//#include "constant_nets.h" +//#include "clock_modeling.h" +// +//#include "vtr_assert.h" +//#include "vtr_ndmatrix.h" +//#include "vtr_vector_map.h" +// +///******************************************************************************* +// * Global data types and constants +// ******************************************************************************/ +///*#define CREATE_ECHO_FILES*//* prints echo files */ +///*#define PRINT_SINK_DELAYS*//*prints the sink delays to files */ +///*#define PRINT_SLACKS*//*prints out all slacks in the circuit */ +///*#define PRINT_PLACE_CRIT_PATH*//*prints out placement estimated critical path */ +///*#define PRINT_NET_DELAYS*//*prints out delays for all connections */ +///*#define PRINT_TIMING_GRAPH*//*prints out the timing graph */ +///*#define DUMP_BLIF_ECHO*//*dump blif of internal representation of user circuit. Useful for ensuring functional correctness via logical equivalence with input blif*/ +// +////#define ROUTER_DEBUG //Prints out very detailed routing progress information if defined +// +//#define TOKENS " \t\n" /* Input file parsing. */ +// +////#define VERBOSE //Prints additional intermediate data +// +///* For update_screen. Denotes importance of update. +// * By default MINOR only updates the screen, while MAJOR +// * pauses graphics for the user to interact */ +//enum class ScreenUpdatePriority { +// MINOR = 0, +// MAJOR = 1 +//}; +// +//#define MAX_SHORT 32767 + +/* Values large enough to be way out of range for any data, but small enough + to allow a small number to be added to them without going out of range. */ +#define HUGE_POSITIVE_FLOAT 1.e30 +//#define HUGE_NEGATIVE_FLOAT -1.e30 +// +///* Used to avoid floating-point errors when comparing values close to 0 */ +//#define EPSILON 1.e-15 +//#define NEGATIVE_EPSILON -1.e-15 +// +//#define HIGH_FANOUT_NET_LIM 64 /* All nets with this number of sinks or more are considered high fanout nets */ +// +//#define FIRST_ITER_WIRELENTH_LIMIT 0.85 /* If used wirelength exceeds this value in first iteration of routing, do not route */ +// +///* Defining macros for the placement_ctx t_grid_blocks. Assumes that ClusterBlockId's won't exceed positive 32-bit integers */ +//constexpr auto EMPTY_BLOCK_ID = ClusterBlockId(-1); +//constexpr auto INVALID_BLOCK_ID = ClusterBlockId(-2); +// +//constexpr const char* EMPTY_BLOCK_NAME = "EMPTY"; +// +///* +// * Files +// */ +///******************************************************************************* +// * Packing specific data types and constants +// * Packing takes the circuit described in the technology mapped user netlist +// * and maps it to the complex logic blocks found in the arhictecture +// ******************************************************************************/ +// +//#define NO_CLUSTER -1 +//#define NEVER_CLUSTER -2 +//#define NOT_VALID -10000 /* Marks gains that aren't valid */ +///* Ensure no gain can ever be this negative! */ +//#ifndef UNDEFINED +//#define UNDEFINED -1 +//#endif +// +///* Selection algorithm for selecting next seed */ +//enum e_cluster_seed { +// VPACK_TIMING, VPACK_MAX_INPUTS, VPACK_BLEND +//}; +// +//enum e_block_pack_status { +// BLK_PASSED, +// BLK_FAILED_FEASIBLE, +// BLK_FAILED_ROUTE, +// BLK_STATUS_UNDEFINED +//}; +// +//struct t_ext_pin_util { +// t_ext_pin_util() = default; +// t_ext_pin_util(float in, float out) +// : input_pin_util(in), output_pin_util(out) {} +// +// float input_pin_util = 1.; +// float output_pin_util = 1.; +//}; +// +////Specifies the utilization of external input/output pins +////during packing +//class t_ext_pin_util_targets { +//public: +// t_ext_pin_util_targets() = default; +// t_ext_pin_util_targets(float default_in_util, float default_out_util); +// +// //Returns the input pin util of the specified block (or default if unspecified) +// t_ext_pin_util get_pin_util(std::string block_type_name) const; +// +//public: +// //Sets the pin util for the specified block type +// //Returns true if non-default was previously set +// void set_block_pin_util(std::string block_type_name, t_ext_pin_util target); +// +// //Sets the default pin util +// //Returns true if a default was previously set +// void set_default_pin_util(t_ext_pin_util target); +//private: +// t_ext_pin_util defaults_; +// std::map overrides_; +//}; +// +///* these are defined later, but need to declare here because it is used */ +//class t_rr_node; +//struct t_pack_molecule; +//struct t_pb_stats; +//struct t_pb_route; +// +///* A t_pb represents an instance of a clustered block, which may be: +// * 1) A top level clustered block which is placeable at a location in FPGA device +// * grid location (e.g. a Logic block, RAM block, DSP block), or +// * 2) An internal 'block' representing an intermediate level of hierarchy inside a top level +// * block (e.g. a BLE), or +// * 3) A leaf (i.e. atom or primitive) block representing an element of netlist (e.g. LUT, +// * flip-lop, memory slice etc.) +// * +// * t_pb (in combination with t_pb_route) implement the mapping from the netlist elements to architectural +// * instances. +// */ +//struct t_pb { +// char *name = nullptr; /* Name of this physical block */ +// t_pb_graph_node *pb_graph_node = nullptr; /* pointer to pb_graph_node this pb corresponds to */ +// +// int mode = 0; /* mode that this pb is set to */ +// +// t_pb **child_pbs = nullptr; /* children pbs attached to this pb [0..num_child_pb_types - 1][0..child_type->num_pb - 1] */ +// t_pb *parent_pb = nullptr; /* pointer to parent node */ +// +// t_pb_stats *pb_stats = nullptr; /* statistics for current pb */ +// +// /* Representation of intra-logic block routing, t_pb_route describes all internal hierarchy routing. +// * t_pb_route is an array of size [t_pb->pb_graph_node->total_pb_pins] +// * Only valid for the top-level t_pb (parent_pb == nullptr). On any child pb, t_pb_route will be nullptr. */ +// t_pb_route *pb_route = nullptr; +// +// int clock_net = 0; /* Records clock net driving a flip-flop, valid only for lowest-level, flip-flop PBs */ +// +// +// int get_num_child_types() const { +// if (child_pbs != nullptr && has_modes()) { +// return pb_graph_node->pb_type->modes[mode].num_pb_type_children; +// } else { +// return 0; +// } +// } +// +// int get_num_children_of_type(int type_index) const { +// t_mode* mode_ptr = get_mode(); +// if (mode_ptr) { +// return mode_ptr->pb_type_children[type_index].num_pb; +// } +// return 0; //No mode +// } +// +// t_mode* get_mode() const { +// if (has_modes()) { +// return &pb_graph_node->pb_type->modes[mode]; +// } else { +// return nullptr; +// } +// } +// +// bool has_modes() const { +// return pb_graph_node->pb_type->num_modes > 0; +// } +// +// //Returns the t_pb associated with the specified gnode which is contained +// //within the current pb +// const t_pb* find_pb(const t_pb_graph_node* gnode) const { +// //Base case +// if(pb_graph_node == gnode) { +// return this; +// } +// +// //Search recursively +// for(int ichild_type = 0; ichild_type < get_num_child_types(); ++ichild_type) { +// +// if(child_pbs[ichild_type] == nullptr) continue; +// +// for(int ipb = 0; ipb < get_num_children_of_type(ichild_type); ++ipb) { +// +// const t_pb* child_pb = &child_pbs[ichild_type][ipb]; +// +// const t_pb* found_pb = child_pb->find_pb(gnode); +// if(found_pb != nullptr) { +// VTR_ASSERT(found_pb->pb_graph_node == gnode); +// return found_pb; //Found +// } +// } +// } +// return nullptr; //Not found +// } +// +// const t_pb* find_pb_for_model(const std::string& blif_model) const { +// //Base case +// const t_model* model = pb_graph_node->pb_type->model; +// if (model && model->name == blif_model) { +// return this; +// } +// +// //Search recursively +// for(int ichild_type = 0; ichild_type < get_num_child_types(); ++ichild_type) { +// +// if(child_pbs[ichild_type] == nullptr) continue; +// +// for(int ipb = 0; ipb < get_num_children_of_type(ichild_type); ++ipb) { +// +// const t_pb* child_pb = &child_pbs[ichild_type][ipb]; +// +// const t_pb* matching_pb = child_pb->find_pb_for_model(blif_model); +// if (matching_pb) { +// return this; +// } +// } +// } +// return nullptr; //Not found +// } +// +// //Returns the root pb containing this pb +// const t_pb* root_pb() const { +// +// const t_pb* curr_pb = this; +// while(!is_root()) { +// curr_pb = curr_pb->parent_pb; +// } +// +// VTR_ASSERT(curr_pb->parent_pb == nullptr); +// return curr_pb; +// } +// +// bool is_root() const { +// return parent_pb == nullptr; +// } +// +// //Returns true if this pb corresponds to a primitive block (i.e. in the AtomNetlist) +// bool is_primitive() const { +// return child_pbs == nullptr; +// } +// +// //Returns the bit index into the AtomPort for the specified primitive +// //pb_graph_pin, considering any pin rotations which have been applied to logically +// //equivalent pins +// BitIndex atom_pin_bit_index(const t_pb_graph_pin* gpin) const { +// VTR_ASSERT_MSG(is_primitive(), "Atom pin indicies can only be looked up from primitives"); +// +// auto iter = pin_rotations_.find(gpin); +// +// if(iter != pin_rotations_.end()) { +// //Return the original atom pin index +// return iter->second; +// } else { +// //No re-mapping, return the index directly +// return gpin->pin_number; +// } +// } +// +// //For a given gpin, sets the mapping to the original atom netlist pin's bit index in +// //it's AtomPort. This is used to record any pin rotations which have been applied to +// //logically equivalent pins +// void set_atom_pin_bit_index(const t_pb_graph_pin* gpin, BitIndex atom_pin_bit_idx) { +// pin_rotations_[gpin] = atom_pin_bit_idx; +// } +// +//private: +// std::map pin_rotations_; //Contains the atom netlist port bit index associated +// //with any primitive pins which have been rotated during clustering +// +//}; +// +///* Representation of intra-logic block routing */ +//struct t_pb_route { +// AtomNetId atom_net_id; /* which net in the atom netlist uses this pin */ +// int driver_pb_pin_id; /* The pb_pin id of the pb_pin that drives this pin */ +// std::vector sink_pb_pin_ids; /* The pb_pin id's of the pb_pins driven by this node */ +// const t_pb_graph_pin* pb_graph_pin = nullptr; /* The graph pin associated with this node */ +// +// t_pb_route() { +// atom_net_id = AtomNetId::INVALID(); +// driver_pb_pin_id = OPEN; +// } +//}; +// +//enum e_pack_pattern_molecule_type { +// MOLECULE_SINGLE_ATOM, MOLECULE_FORCED_PACK +//}; +// +///* Represents a grouping of atom blocks that match a pack_pattern, these groups are intended to be placed as a single unit during packing +// * Store in linked list +// * +// * A chain is a special type of pack pattern. A chain can extend across multiple logic blocks. +// * Must segment the chain to fit in a logic block by identifying the actual atom that forms the root of the new chain. +// * Assumes that the root of a chain is the primitive that starts the chain or is driven from outside the logic block +// */ +//struct t_pack_molecule { +// enum e_pack_pattern_molecule_type type; /* what kind of molecule is this? */ +// t_pack_patterns *pack_pattern; /* If this is a forced_pack molecule, pattern this molecule matches */ +// t_model_chain_pattern *chain_pattern; /* If this is a chain molecule, chain that this molecule matches */ +// std::vector atom_block_ids; /* [0..num_blocks-1] IDs of atom blocks that implements this molecule, +// index on pack_pattern_block->index of pack pattern */ +// bool valid; /* Whether or not this molecule is still valid */ +// +// int num_blocks; /* number of atom blocks of molecule */ +// int root; /* root index of molecule, atom_block_ids[root] is the root atom block */ +// +// float base_gain; /* Intrinsic "goodness" score for molecule independant of rest of netlist */ +// +// int num_ext_inputs; /* number of input pins used by molecule that are not self-contained by pattern molecule matches */ +// t_pack_molecule *next; +//}; +// +///* Stats keeper for placement information during packing +// * Contains linked lists to placement locations based on status of primitive +// */ +//struct t_cluster_placement_stats { +// int num_pb_types; /* num primitive pb_types inside complex block */ +// const t_pack_molecule *curr_molecule; /* current molecule being considered for packing */ +// t_cluster_placement_primitive **valid_primitives; /* [0..num_pb_types-1] ptrs to linked list of valid primitives, for convenience, each linked list head is empty */ +// t_cluster_placement_primitive *in_flight; /* ptrs to primitives currently being considered */ +// t_cluster_placement_primitive *tried; /* ptrs to primitives that are open but current logic block unable to pack to */ +// t_cluster_placement_primitive *invalid; /* ptrs to primitives that are invalid */ +//}; +// +///****************************************************************** +// * Timing data types +// *******************************************************************/ +// +////Enable the legacy STA engine to run along side the +////new STA engine +////#define ENABLE_CLASSIC_VPR_STA +// +//// #define PATH_COUNTING 'P' +///* Uncomment this to turn on path counting. Its value determines how path criticality +// is calculated from forward and backward weights. Possible values: +// 'S' - sum of forward and backward weights +// 'P' - product of forward and backward weights +// 'L' - natural log of the product of forward and backward weights +// 'R' - product of the natural logs of forward and backward weights +// See path_delay.h for further path-counting options. */ +// +///* Timing graph information */ +//struct t_tedge { +// /* Edge in the timing graph. */ +// int to_node; /* index of node at the sink end of this edge */ +// float Tdel; /* delay to go to to_node along this edge */ +//}; +// +//enum e_tnode_type { +// /* Types of tnodes (timing graph nodes). */ +// TN_INPAD_SOURCE, /* input to an input I/O pad */ +// TN_INPAD_OPIN, /* output from an input I/O pad */ +// TN_OUTPAD_IPIN, /* input to an output I/O pad */ +// TN_OUTPAD_SINK, /* output from an output I/O pad */ +// TN_CB_IPIN, /* input pin to complex block */ +// TN_CB_OPIN, /* output pin from complex block */ +// TN_INTERMEDIATE_NODE, /* Used in post-packed timing graph only: +// connection between intra-cluster pins. */ +// TN_PRIMITIVE_IPIN, /* input pin to a primitive (e.g. a LUT) */ +// TN_PRIMITIVE_OPIN, /* output pin from a primitive (e.g. a LUT) */ +// TN_FF_IPIN, /* input pin to a flip-flop - goes to TN_FF_SINK */ +// TN_FF_OPIN, /* output pin from a flip-flop - comes from TN_FF_SOURCE */ +// TN_FF_SINK, /* sink (D) pin of flip-flop */ +// TN_FF_SOURCE, /* source (Q) pin of flip-flop */ +// TN_FF_CLOCK, /* clock pin of flip-flop */ +// TN_CLOCK_SOURCE, /* An on-chip clock generator such as a pll */ +// TN_CLOCK_OPIN, /* Output pin from an on-chip clock source - comes from TN_CLOCK_SOURCE */ +// TN_CONSTANT_GEN_SOURCE /* source of a constant logic 1 or 0 */ +//}; +// +//struct t_prepacked_tnode_data { +// /* Data only used by prepacked tnodes. Stored separately so it +// doesn't need to be allocated in the post-packed netlist. */ +// int model_port, model_pin; /* technology mapped model pin */ +// t_model_ports *model_port_ptr; +//#ifndef PATH_COUNTING +// long num_critical_input_paths, num_critical_output_paths; /* count of critical paths fanning into/out of this tnode */ +// float normalized_slack; /* slack (normalized with respect to max slack) */ +// float normalized_total_critical_paths; /* critical path count (normalized with respect to max count) */ +// float normalized_T_arr; /* arrival time (normalized with respect to max time) */ +//#endif +//}; +// +//struct t_tnode { +// /* Node in the timing graph. Note: we combine 2 members into a bit field. */ +// e_tnode_type type; /* see the above enum */ +// t_tedge *out_edges; /* [0..num_edges - 1] array of edges fanning out from this tnode. +// Note: there is a correspondence in indexing between out_edges and the +// net data structure: out_edges[iedge] = net[inet].node_block[iedge + 1] +// There is an offset of 1 because net[inet].node_block includes the driver +// node at index 0, while out_edges is part of the driver node and does +// not bother to refer to itself. */ +// int num_edges; +// float T_arr; /* Arrival time of the last input signal to this node. */ +// float T_req; /* Required arrival time of the last input signal to this node +// if the critical path is not to be lengthened. */ +// ClusterBlockId block; /* atom block primitive which this tnode is part of */ +// +//#ifdef PATH_COUNTING +// float forward_weight, backward_weight; /* Weightings of the importance of paths +// fanning into and out of this node, respectively. */ +//#endif +// +// /* Valid values for TN_FF_SINK, TN_FF_SOURCE, TN_FF_CLOCK, TN_INPAD_SOURCE, and TN_OUTPAD_SINK only: */ +// int clock_domain; /* Index of the clock in timing_ctx.sdc->constrained_clocks which this flip-flop or I/O is constrained on. */ +// float clock_delay; /* The time taken for a clock signal to get to the flip-flop or I/O (assumed 0 for I/Os). */ +// +// /* Used in post-packing timing graph only: */ +// t_pb_graph_pin *pb_graph_pin; /* pb_graph_pin that this block is connected to */ +// +// /* Used in pre-packing timing graph only: */ +// t_prepacked_tnode_data * prepacked_data; +// +// unsigned int is_comb_loop_breakpoint : 1; /* Indicates that this tnode had input edges purposely +// disconnected to break a combinational loop */ +//}; +// +///* Other structures storing timing information */ +//struct t_clock { +// /* Stores information on clocks given timing constraints. +// Used in SDC parsing and timing analysis. */ +// char * name; +// bool is_netlist_clock; /* Is this a netlist or virtual (external) clock? */ +// int fanout; +//}; +// +//struct t_io { +// /* Stores information on I/Os given timing constraints. +// Used in SDC parsing and timing analysis. */ +// char * name; /* I/O port name with an SDC constraint */ +// char * clock_name; /* Clock it was constrained on */ +// float delay; /* Delay through the I/O in this constraint */ +// int file_line_number; /* line in the SDC file I/O was constrained on - used for error reporting */ +//}; +// +//struct t_timing_stats { +// /* Timing statistics for final reporting for each constraint +// (pair of constrained source and sink clock domains). +// +// cpd holds the critical path delay, the longest path between the +// pair of domains, or equivalently the path with the least slack. +// +// least_slack holds the slack of the connection with the least slack +// over all paths in this constraint, even if this connection is part +// of another constraint and has a lower slack from that constraint. +// +// The "critical path" of the entire design is the path with the least +// slack in the constraint with the least slack +// (see get_critical_path_delay()). */ +// +// float ** cpd; +// float ** least_slack; +//}; +// +//struct t_slack { +// /* Matrices storing slacks and criticalities of each sink pin on each net +// [0..net.size()-1][1..num_pins-1] for post-packed netlists. */ +// float ** slack; +// float ** timing_criticality; +//}; +// +//struct t_override_constraint { +// /* A special-case constraint to override the default, calculated, timing constraint. Holds data from +// set_clock_groups, set_false_path, set_max_delay, and set_multicycle_path commands. Can hold data for +// clock-to-clock, clock-to-flip-flop, flip-flop-to-clock or flip-flop-to-flip-flop constraints, each of +// which has its own array (timing_ctx.sdc->cc_constraints, timing_ctx.sdc->cf_constraints, timing_ctx.sdc->fc_constraints, and timing_ctx.sdc->ff_constraints). */ +// char ** source_list; /* Array of net names of flip-flops or clocks */ +// char ** sink_list; +// int num_source; +// int num_sink; +// float constraint; +// int num_multicycles; +// int file_line_number; /* line in the SDC file clock was constrained on - used for error reporting */ +//}; +// +//struct t_timing_constraints { /* Container structure for all SDC timing constraints. +// See top-level comment to read_sdc.c for details on members. */ +// int num_constrained_clocks; /* number of clocks with timing constraints */ +// t_clock * constrained_clocks; /* [0..timing_ctx.sdc->num_constrained_clocks - 1] array of clocks with timing constraints */ +// +// vtr::Matrix domain_constraint; /* [0..num_constrained_clocks - 1 (source)][0..num_constrained_clocks - 1 (destination)] */ +// +// int num_constrained_inputs; /* number of inputs with timing constraints */ +// t_io * constrained_inputs; /* [0..num_constrained_inputs - 1] array of inputs with timing constraints */ +// +// int num_constrained_outputs; /* number of outputs with timing constraints */ +// t_io * constrained_outputs; /* [0..num_constrained_outputs - 1] array of outputs with timing constraints */ +// +// int num_cc_constraints; /* number of special-case clock-to-clock constraints overriding default, calculated, timing constraints */ +// t_override_constraint * cc_constraints; /* [0..num_cc_constraints - 1] array of such constraints */ +// +// int num_cf_constraints; /* number of special-case clock-to-flipflop constraints */ +// t_override_constraint * cf_constraints; /* [0..num_cf_constraints - 1] array of such constraints */ +// +// int num_fc_constraints; /* number of special-case flipflop-to-clock constraints */ +// t_override_constraint * fc_constraints; /* [0..num_fc_constraints - 1] */ +// +// int num_ff_constraints; /* number of special-case flipflop-to-flipflop constraints */ +// t_override_constraint * ff_constraints; /* [0..num_ff_constraints - 1] array of such constraints */ +//}; +// +///* Cluster timing delays: +// * C_ipin_cblock: Capacitance added to a routing track by the isolation * +// * buffer between a track and the Cblocks at an (i,j) loc. * +// * T_ipin_cblock: Delay through an input pin connection box (from a * +// * routing track to a logic block input pin). */ +//struct t_timing_inf { +// bool timing_analysis_enabled; +// float C_ipin_cblock; +// float T_ipin_cblock; +// std::string SDCFile; +// std::string slack_definition; +//}; +// +///*************************************************************************** +// * Placement and routing data types +// ****************************************************************************/ +// +///* Timing data structures end */ +//enum sched_type { +// AUTO_SCHED, USER_SCHED +//}; +///* Annealing schedule */ +// +//enum pic_type { +// NO_PICTURE, PLACEMENT, ROUTING +//}; +///* What's on screen? */ +// +//enum pfreq { +// PLACE_NEVER, PLACE_ONCE, PLACE_ALWAYS +//}; +// +///* Are the pads free to be moved, locked in a random configuration, or +// * locked in user-specified positions? */ +//enum e_pad_loc_type { +// FREE, RANDOM, USER +//}; +// +///* Power data for t_netlist structure */ +//struct t_net_power { +// /* Signal probability - long term probability that signal is logic-high*/ +// float probability; +// +// /* Transistion density - average # of transitions per clock cycle +// * For example, a clock would have density = 2 +// */ +// float density; +//}; +// +///* s_grid_tile is the minimum tile of the fpga +// * type: Pointer to type descriptor, NULL for illegal +// * width_offset: Number of grid tiles reserved based on width (right) of a block +// * height_offset: Number of grid tiles reserved based on height (top) of a block */ +//struct t_grid_tile { +// t_type_ptr type = nullptr; +// int width_offset = 0; +// int height_offset = 0; +//}; + +/* Stores the bounding box of a net in terms of the minimum and * + * maximum coordinates of the blocks forming the net, clipped to * + * the region: * + * (1..device_ctx.grid.width()-2, 1..device_ctx.grid.height()-1) */ +struct t_bb { + int xmin; + int xmax; + int ymin; + int ymax; +}; + +///* capacity: Capacity of this region, in tracks. * +// * occupancy: Expected number of tracks that will be occupied. * +// * cost: Current cost of this usage. */ +//struct t_place_region { +// float capacity; +// float inv_capacity; +// float occupancy; +// float cost; +//}; +// +///* Stores the information of the move for a block that is * +// * moved during placement * +// * block_num: the index of the moved block * +// * xold: the x_coord that the block is moved from * +// * xnew: the x_coord that the block is moved to * +// * yold: the y_coord that the block is moved from * +// * xnew: the x_coord that the block is moved to */ +//struct t_pl_moved_block { +// ClusterBlockId block_num; +// int xold; +// int xnew; +// int yold; +// int ynew; +// int zold; +// int znew; +// int swapped_to_was_empty; +// int swapped_from_is_empty; +//}; +// +///* Stores the list of blocks to be moved in a swap during * +// * placement. * +// * num_moved_blocks: total number of blocks moved when * +// * swapping two blocks. * +// * moved blocks: a list of moved blocks data structure with * +// * information on the move. * +// * [0...num_moved_blocks-1] */ +//struct t_pl_blocks_to_be_moved { +// int num_moved_blocks; +// t_pl_moved_block * moved_blocks; +//}; +// +///* legal positions for type */ +//struct t_legal_pos { +// int x; +// int y; +// int z; +//}; +// +///* Represents the placement location of a clustered block +// * x: x-coordinate +// * y: y-coordinate +// * z: occupancy coordinate +// * is_fixed: true if this block's position is fixed by the user and shouldn't be moved during annealing +// * nets_and_pins_synced_to_z_coordinate: true if the associated clb's pins have been synced to the z location (i.e. after placement) */ +//struct t_block_loc { +// int x = OPEN; +// int y = OPEN; +// int z = OPEN; +// +// bool is_fixed = false; +// bool nets_and_pins_synced_to_z_coordinate = false; +//}; +// +///* Stores the clustered blocks placed at a particular grid location */ +//struct t_grid_blocks { +// //How many valid blocks are in use at this location +// int usage; +// +// //The clustered blocks associated with this grid location +// std::vector blocks; +//}; +// +///* Names of various files */ +//struct t_file_name_opts { +// std::string ArchFile; +// std::string CircuitName; +// std::string BlifFile; +// std::string NetFile; +// std::string PlaceFile; +// std::string RouteFile; +// std::string ActFile; +// std::string PowerFile; +// std::string CmosTechFile; +// std::string out_file_prefix; +// bool verify_file_digests; +//}; +// +///* Options for netlist loading */ +//struct t_netlist_opts { +// bool absorb_buffer_luts = true; +// bool sweep_dangling_primary_ios = true; +// bool sweep_dangling_blocks = true; +// bool sweep_dangling_nets = true; +// bool sweep_constant_primary_outputs = false; +// +// bool verbose_sweep = false; //Verbose output during netlist cleaning +//}; +// +////Should a stage in the CAD flow be skipped, loaded from a file, or performed +//enum e_stage_action { +// STAGE_SKIP = 0, +// STAGE_LOAD, +// STAGE_DO +//}; +// +///* Options for packing +// * TODO: document each packing parameter */ +//enum e_packer_algorithm { +// PACK_GREEDY, PACK_BRUTE_FORCE +//}; +// +//struct t_packer_opts { +// std::string blif_file_name; +// std::string sdc_file_name; +// std::string output_file; +// bool global_clocks; +// bool hill_climbing_flag; +// bool timing_driven; +// enum e_cluster_seed cluster_seed_type; +// float alpha; +// float beta; +// float inter_cluster_net_delay; +// float target_device_utilization; +// bool auto_compute_inter_cluster_net_delay; +// bool allow_unrelated_clustering; +// bool connection_driven; +// bool debug_clustering; +// bool enable_pin_feasibility_filter; +// t_ext_pin_util_targets target_external_pin_util; +// e_stage_action doPacking; +// enum e_packer_algorithm packer_algorithm; +// std::string device_layout; +// std::string hmetis_input_file; +//}; +// +///* Annealing schedule information for the placer. The schedule type * +// * is either USER_SCHED or AUTO_SCHED. Inner_num is multiplied by * +// * num_blocks^4/3 to find the number of moves per temperature. The * +// * remaining information is used only for USER_SCHED, and have the * +// * obvious meanings. */ +//struct t_annealing_sched { +// enum sched_type type; +// float inner_num; +// float init_t; +// float alpha_t; +// float exit_t; +//}; +// +///* Various options for the placer. * +// * place_algorithm: BOUNDING_BOX_PLACE or PATH_TIMING_DRIVEN_PLACE * +// * timing_tradeoff: When TIMING_DRIVEN_PLACE mode, what is the tradeoff * +// * timing driven and BOUNDING_BOX_PLACE. * +// * place_cost_exp: Power to which denominator is raised for linear_cong. * +// * place_chan_width: The channel width assumed if only one placement is * +// * performed. * +// * pad_loc_type: Are pins FREE, fixed randomly, or fixed from a file. * +// * pad_loc_file: File to read pin locations form if pad_loc_type * +// * is USER. * +// * place_freq: Should the placement be skipped, done once, or done for each * +// * channel width in the binary search. * +// * recompute_crit_iter: how many temperature stages pass before we recompute * +// * criticalities based on average point to point delay * +// * enable_timing_computations: in bounding_box mode, normally, timing * +// * information is not produced, this causes the information * +// * to be computed. in *_TIMING_DRIVEN modes, this has no effect* +// * inner_loop_crit_divider: (move_lim/inner_loop_crit_divider) determines how* +// * many inner_loop iterations pass before a recompute of * +// * criticalities is done. * +// * td_place_exp_first: exponent that is used on the timing_driven criticlity * +// * it is the value that the exponent starts at. * +// * td_place_exp_last: value that the criticality exponent will be at the end * +// * doPlacement: true if placement is supposed to be done in the CAD flow, false otherwise */ +//enum e_place_algorithm { +// BOUNDING_BOX_PLACE, PATH_TIMING_DRIVEN_PLACE +//}; +// +//struct t_placer_opts { +// enum e_place_algorithm place_algorithm; +// float timing_tradeoff; +// float place_cost_exp; +// int place_chan_width; +// enum e_pad_loc_type pad_loc_type; +// std::string pad_loc_file; +// enum pfreq place_freq; +// int recompute_crit_iter; +// bool enable_timing_computations; +// int inner_loop_recompute_divider; +// float td_place_exp_first; +// int seed; +// float td_place_exp_last; +// e_stage_action doPlacement; +//}; +// +///* All the parameters controlling the router's operation are in this * +// * structure. * +// * first_iter_pres_fac: Present sharing penalty factor used for the * +// * very first (congestion mapping) Pathfinder iteration. * +// * initial_pres_fac: Initial present sharing penalty factor for * +// * Pathfinder; used to set pres_fac on 2nd iteration. * +// * pres_fac_mult: Amount by which pres_fac is multiplied each * +// * routing iteration. * +// * acc_fac: Historical congestion cost multiplier. Used unchanged * +// * for all iterations. * +// * bend_cost: Cost of a bend (usually non-zero only for global routing). * +// * max_router_iterations: Maximum number of iterations before giving * +// * up. * +// * min_incremental_reroute_fanout: Minimum fanout a net needs to have * +// * for incremental reroute to be applied to it through route * +// * tree pruning. Larger circuits should get larger thresholds * +// * bb_factor: Linear distance a route can go outside the net bounding * +// * box. * +// * route_type: GLOBAL or DETAILED. * +// * fixed_channel_width: Only attempt to route the design once, with the * +// * channel width given. If this variable is * +// * == NO_FIXED_CHANNEL_WIDTH, do a binary search * +// * on channel width. * +// * router_algorithm: BREADTH_FIRST or TIMING_DRIVEN. Selects the desired * +// * routing algorithm. * +// * base_cost_type: Specifies how to compute the base cost of each type of * +// * rr_node. DELAY_NORMALIZED -> base_cost = "demand" * +// * x average delay to route past 1 CLB. DEMAND_ONLY -> * +// * expected demand of this node (old breadth-first costs). * +// * * +// * The following parameters are used only by the timing-driven router. * +// * * +// * astar_fac: Factor (alpha) used to weight expected future costs to * +// * target in the timing_driven router. astar_fac = 0 leads to * +// * an essentially breadth-first search, astar_fac = 1 is near * +// * the usual astar algorithm and astar_fac > 1 are more * +// * aggressive. * +// * max_criticality: The maximum criticality factor (from 0 to 1) any sink * +// * will ever have (i.e. clip criticality to this number). * +// * criticality_exp: Set criticality to (path_length(sink) / longest_path) ^ * +// * criticality_exp (then clip to max_criticality). * +// * doRouting: true if routing is supposed to be done, false otherwise * +// * routing_failure_predictor: sets the configuration to be used by the * +// * routing failure predictor, how aggressive the threshold used to judge * +// * and abort routings deemed unroutable * +// * write_rr_graph_name: stores the file name of the output rr graph * +// * read_rr_graph_name: stores the file name of the rr graph to be read by vpr */ +//enum e_route_type { +// GLOBAL, DETAILED +//}; +//enum e_router_algorithm { +// BREADTH_FIRST, TIMING_DRIVEN, NO_TIMING +//}; +//enum e_base_cost_type { +// DELAY_NORMALIZED, DEMAND_ONLY +//}; +//enum e_routing_failure_predictor { +// OFF, SAFE, AGGRESSIVE +//}; +//enum e_routing_budgets_algorithm { +// MINIMAX, SCALE_DELAY, DISABLE +//}; +// +//enum class e_timing_report_detail { +// NETLIST, //Only show netlist elements +// AGGREGATED, //Show aggregated intra-block and inter-block delays +// //DETAILED_ROUTING, //Show inter-block routing resources used +//}; +// +//constexpr int NO_FIXED_CHANNEL_WIDTH = -1; +// +//struct t_router_opts { +// float first_iter_pres_fac; +// float initial_pres_fac; +// float pres_fac_mult; +// float acc_fac; +// float bend_cost; +// int max_router_iterations; +// int min_incremental_reroute_fanout; +// int bb_factor; +// enum e_route_type route_type; +// int fixed_channel_width; +// int min_channel_width_hint; //Hint to binary search of what the minimum channel width is +// bool trim_empty_channels; +// bool trim_obs_channels; +// enum e_router_algorithm router_algorithm; +// enum e_base_cost_type base_cost_type; +// float astar_fac; +// float max_criticality; +// float criticality_exp; +// bool verify_binary_search; +// bool full_stats; +// bool congestion_analysis; +// bool fanout_analysis; +// bool switch_usage_analysis; +// e_stage_action doRouting; +// enum e_routing_failure_predictor routing_failure_predictor; +// enum e_routing_budgets_algorithm routing_budgets_algorithm; +//}; +// +//struct t_analysis_opts { +// e_stage_action doAnalysis; +// +// bool gen_post_synthesis_netlist; +// +// int timing_report_npaths; +// e_timing_report_detail timing_report_detail; +// bool timing_report_skew; +//}; +// +///* Defines the detailed routing architecture of the FPGA. Only important * +// * if the route_type is DETAILED. * +// * (UDSD by AY) directionality: Should the tracks be uni-directional or * +// * bi-directional? * +// * switch_block_type: Pattern of switches at each switch block. I * +// * assume Fs is always 3. If the type is SUBSET, I use a * +// * Xilinx-like switch block where track i in one channel always * +// * connects to track i in other channels. If type is WILTON, * +// * I use a switch block where track i does not always connect * +// * to track i in other channels. See Steve Wilton, Phd Thesis, * +// * University of Toronto, 1996. The UNIVERSAL switch block is * +// * from Y. W. Chang et al, TODAES, Jan. 1996, pp. 80 - 101. * +// * A CUSTOM switch block has also been added which allows a user * +// * to describe custom permutation functions and connection * +// * patterns. See comment at top of SRC/route/build_switchblocks.c * +// * switchblocks: A vector of custom switch block descriptions that is * +// * used with the CUSTOM switch block type. See comment at top of * +// * SRC/route/build_switchblocks.c * +// * num_segment: Number of distinct segment types in the FPGA. * +// * delayless_switch: Index of a zero delay switch (used to connect things * +// * that should have no delay). * +// * wire_to_arch_ipin_switch: keeps track of the type of architecture switch * +// * that connects wires to ipins * +// * wire_to_rr_ipin_switch: keeps track of the type of RR graph switch that * +// * connects wires to ipins in the RR graph * +// * R_minW_nmos: Resistance (in Ohms) of a minimum width nmos transistor. * +// * Used only in the FPGA area model. * +// * R_minW_pmos: Resistance (in Ohms) of a minimum width pmos transistor. * +// * * +// * read_rr_graph_filename: File to read the RR graph from (overrides * +// * architecture) * +// * write_rr_graph_filename: File to write the RR graph to after generation * +// * */ +// +//struct t_det_routing_arch { +// enum e_directionality directionality; /* UDSD by AY */ +// int Fs; +// enum e_switch_block_type switch_block_type; +// std::vector switchblocks; +// int num_segment; +// +// short global_route_switch; +// short delayless_switch; +// int wire_to_arch_ipin_switch; +// int wire_to_rr_ipin_switch; +// float R_minW_nmos; +// float R_minW_pmos; +// +// std::string read_rr_graph_filename; +// std::string write_rr_graph_filename; +//}; +// +//enum e_direction : unsigned char { +// INC_DIRECTION = 0, +// DEC_DIRECTION = 1, +// BI_DIRECTION = 2, +// NO_DIRECTION = 3, +// NUM_DIRECTIONS +//}; +// +//constexpr std::array DIRECTIONS_STRING = { {"INC_DIRECTION", "DEC_DIRECTION", "BI_DIRECTION", "NO_DIRECTION"} }; +// +///* Lists detailed information about segmentation. [0 .. W-1]. * +// * length: length of segment. * +// * start: index at which a segment starts in channel 0. * +// * longline: true if this segment spans the entire channel. * +// * sb: [0..length]: true for every channel intersection, relative to the * +// * segment start, at which there is a switch box. * +// * cb: [0..length-1]: true for every logic block along the segment at * +// * which there is a connection box. * +// * arch_wire_switch: Index of the switch type that connects other wires * +// * *to* this segment. Note that this index is in relation * +// * to the switches from the architecture file, not the * +// * expanded list of switches that is built at the end of * +// * build_rr_graph. * +// * arch_opin_switch: Index of the switch type that connects output pins * +// * (OPINs) *to* this segment. Note that this index is in * +// * relation to the switches from the architecture file, * +// * not the expanded list of switches that is is built * +// * at the end of build_rr_graph * +// * Cmetal: Capacitance of a routing track, per unit logic block length. * +// * Rmetal: Resistance of a routing track, per unit logic block length. * +// * direction: The direction of a routing track. * +// * index: index of the segment type used for this track. * +// * type_name_ptr: pointer to name of the segment type this track belongs * +// * to. points to the appropriate name in s_segment_inf */ +//struct t_seg_details { +// int length; +// int start; +// bool longline; +// bool *sb; +// bool *cb; +// short arch_wire_switch; +// short arch_opin_switch; +// float Rmetal; +// float Cmetal; +// bool twisted; +// enum e_direction direction; +// int group_start; +// int group_size; +// int seg_start; +// int seg_end; +// int index; +// float Cmetal_per_m; /* Used for power */ +// const char *type_name_ptr; +//}; +// +///* Defines a 2-D array of t_seg_details data structures (one per channel) */ +//typedef vtr::Matrix t_chan_details; +// +///* A linked list of float pointers. Used for keeping track of * +// * which pathcosts in the router have been changed. */ +// +//struct t_linked_f_pointer { +// t_linked_f_pointer *next; +// float *fptr; +//}; +// +//typedef std::vector>>>> t_rr_node_indices; //[0..num_rr_types-1][0..grid_width-1][0..grid_height-1][0..NUM_SIDES-1][0..max_ptc-1] +// +///* Uncomment lines below to save some memory, at the cost of debugging ease. */ +///*enum e_rr_type {SOURCE, SINK, IPIN, OPIN, CHANX, CHANY}; */ +///* typedef short t_rr_type */ +// +///* Type of a routing resource node. x-directed channel segment, * +// * y-directed channel segment, input pin to a clb to pad, output * +// * from a clb or pad (i.e. output pin of a net) and: * +// * SOURCE: A dummy node that is a logical output within a block * +// * -- i.e., the gate that generates a signal. * +// * SINK: A dummy node that is a logical input within a block * +// * -- i.e. the gate that needs a signal. */ +//typedef enum e_rr_type : unsigned char { +// SOURCE = 0, SINK, IPIN, OPIN, CHANX, CHANY, INTRA_CLUSTER_EDGE, NUM_RR_TYPES +//} t_rr_type; +// +//constexpr std::array RR_TYPES = { { +// SOURCE, SINK, IPIN, OPIN, CHANX, CHANY, INTRA_CLUSTER_EDGE +//} }; +//constexpr std::array rr_node_typename { { +// "SOURCE", "SINK", "IPIN", "OPIN", "CHANX", "CHANY", "INTRA_CLUSTER_EDGE" +//} }; +// +///* Basic element used to store the traceback (routing) of each net. * +// * index: Array index (ID) of this routing resource node. * +// * iswitch: Index of the switch type used to go from this rr_node to * +// * the next one in the routing. OPEN if there is no next node * +// * (i.e. this node is the last one (a SINK) in a branch of the * +// * net's routing). * +// * next: Pointer to the next traceback element in this route. */ +//struct t_trace { +// t_trace *next; +// int index; +// short iswitch; +//}; +// +///* Extra information about each rr_node needed only during routing (i.e. * +// * during the maze expansion). * +// * * +// * prev_node: Index of the previous node (on the lowest cost path known to * +// * reach this node); used to generate the traceback. If there * +// * is no predecessor, prev_node = NO_PREVIOUS. * +// * prev_edge: Index of the edge (from 0 to num_edges-1 of prev_node) that * +// * was used to reach this node from the previous node. If * +// * there is no predecessor, prev_edge = NO_PREVIOUS. * +// * pres_cost: Present congestion cost term for this node. * +// * acc_cost: Accumulated cost term from previous Pathfinder iterations. * +// * path_cost: Total cost of the path up to and including this node + * +// * the expected cost to the target if the timing_driven router * +// * is being used. * +// * backward_path_cost: Total cost of the path up to and including this * +// * node. Not used by breadth-first router. * +// * target_flag: Is this node a target (sink) for the current routing? * +// * Number of times this node must be reached to fully route. * +// * occ: The current occupancy of the associated rr node */ +//struct t_rr_node_route_inf { +// int prev_node; +// short prev_edge; +// +// float pres_cost; +// float acc_cost; +// float path_cost; +// float backward_path_cost; +// +// short target_flag; +// +// public: //Accessors +// short occ() const { return occ_; } +// +// public: //Mutators +// void set_occ(int new_occ) { occ_ = new_occ; } +// +// private: //Data +// short occ_ = 0; +//}; +// +////Information about the current status of a particular net as pertains to routing +//struct t_net_routing_status { +// bool is_routed = false; //Whether the net has been legally routed +// bool is_fixed = false; //Whether the net is fixed (i.e. not to be re-routed) +//}; +// +// +// +//#define NO_PREVIOUS -1 +// +///* Index of the SOURCE, SINK, OPIN, IPIN, etc. member of device_ctx.rr_indexed_data. */ +//enum e_cost_indices { +// SOURCE_COST_INDEX = 0, +// SINK_COST_INDEX, +// OPIN_COST_INDEX, +// IPIN_COST_INDEX, +// CHANX_COST_INDEX_START +//}; +// +///* Power estimation options */ +//struct t_power_opts { +// bool do_power; /* Perform power estimation? */ +//}; +// +///* Channel width data */ +//struct t_chan_width { +// int max; +// int x_max; +// int y_max; +// int x_min; +// int y_min; +// int *x_list; +// int *y_list; +//}; +// +///* Type to store our list of token to enum pairings */ +//struct t_TokenPair { +// const char *Str; +// int Enum; +//}; +// +//struct t_lb_type_rr_node; /* Defined in pack_types.h */ +// +///* Store settings for VPR */ +//struct t_vpr_setup { +// bool TimingEnabled; /* Is VPR timing enabled */ +// t_file_name_opts FileNameOpts; /* File names */ +// t_model * user_models; /* blif models defined by the user */ +// t_model * library_models; /* blif models in VPR */ +// t_netlist_opts NetlistOpts; /* Options for packer */ +// t_packer_opts PackerOpts; /* Options for packer */ +// t_placer_opts PlacerOpts; /* Options for placer */ +// t_annealing_sched AnnealSched; /* Placement option annealing schedule */ +// t_router_opts RouterOpts; /* router options */ +// t_analysis_opts AnalysisOpts; /* Analysis options */ +// t_det_routing_arch RoutingArch; /* routing architecture */ +// std::vector *PackerRRGraph; +// t_segment_inf * Segments; /* wires in routing architecture */ +// t_timing_inf Timing; /* timing information */ +// float constant_net_delay; /* timing information when place and route not run */ +// bool ShowGraphics; /* option to show graphics */ +// bool gen_netlist_as_blif; /* option to print out post-pack/pre-place netlist as blif */ +// int GraphPause; /* user interactiveness graphics option */ +// t_power_opts PowerOpts; +// std::string device_layout; +// e_constant_net_method constant_net_method; //How constant nets should be handled +// e_clock_modeling clock_modeling; //How clocks should be handled +//}; +// +//class RouteStatus { +// public: +// RouteStatus() = default; +// RouteStatus(bool status_val, int chan_width_val) +// : success_(status_val) +// , chan_width_(chan_width_val) {} +// +// //Was routing successful? +// operator bool() { return success(); } +// bool success() { return success_; } +// +// //What was the channel width? +// int chan_width() { return chan_width_; } +// +// private: +// bool success_ = false; +// int chan_width_ = -1; +//}; +// +//typedef vtr::vector_map>> t_clb_opins_used; //[0..num_blocks-1][0..class-1][0..used_pins-1] + +#endif From fc8c0e75dfd19c3f61484a3279ec163131372069 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 15:45:19 -0700 Subject: [PATCH 019/116] WIP --- .gitignore | 1 + common/place_vpr.inc | 612 +++++++++++++++++++++---------------------- 2 files changed, 307 insertions(+), 306 deletions(-) diff --git a/.gitignore b/.gitignore index f308b34ada..cf07c3d4b3 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ build/ /Testing/* CTestTestfile.cmake install_manifest.txt +*.gdb_history diff --git a/common/place_vpr.inc b/common/place_vpr.inc index 2532779311..10f79259fe 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -42,15 +42,15 @@ /* To turn off incremental bounding box updates, set this to a huge value */ #define SMALL_NET 4 -///* This defines the error tolerance for floating points variables used in * -// * cost computation. 0.01 means that there is a 1% error tolerance. */ -//#define ERROR_TOL .01 -// -///* This defines the maximum number of swap attempts before invoking the * -// * once-in-a-while placement legality check as well as floating point * -// * variables round-offs check. */ -//#define MAX_MOVES_BEFORE_RECOMPUTE 50000 -// +/* This defines the error tolerance for floating points variables used in * + * cost computation. 0.01 means that there is a 1% error tolerance. */ +#define ERROR_TOL .01 + +/* This defines the maximum number of swap attempts before invoking the * + * once-in-a-while placement legality check as well as floating point * + * variables round-offs check. */ +#define MAX_MOVES_BEFORE_RECOMPUTE 50000 + ///* The maximum number of tries when trying to place a carry chain at a * // * random location before trying exhaustive placement - find the fist * // * legal position and place it during initial placement. */ @@ -77,12 +77,12 @@ enum e_swap_result { REJECTED, ACCEPTED, ABORTED }; -//struct t_placer_statistics { -// double av_cost, av_bb_cost, av_timing_cost, -// sum_of_squares, av_delay_cost; -// int success_sum; -//}; -// +struct t_placer_statistics { + double av_cost, av_bb_cost, av_timing_cost, + sum_of_squares, av_delay_cost; + int success_sum; +}; + //#define MAX_INV_TIMING_COST 1.e9 ///* Stops inverse timing cost from going to infinity with very lax timing constraints, //which avoids multiplying by a gigantic inverse_prev_timing_cost when auto-normalizing. @@ -251,20 +251,20 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, float inverse_prev_bb_cost, float inverse_prev_timing_cost, float *delay_cost_ptr); -//static void update_t(float *t, float rlim, float success_rat, -// t_annealing_sched annealing_sched); -// -//static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid); -// -//static int exit_crit(float t, float cost, -// t_annealing_sched annealing_sched); -// +static void update_t(float *t, float rlim, float success_rat/*, + t_annealing_sched annealing_sched*/); + +static void update_rlim(float *rlim, float success_rat /*, const DeviceGrid& grid*/); + +static int exit_crit(float t, float cost /*, + t_annealing_sched annealing_sched*/); + //static int count_connections(); static double get_std_dev(int n, double sum_x_squared, double av_x); -//static float recompute_bb_cost(); -// +static float recompute_bb_cost(); + //static float comp_td_point_to_point_delay(ClusterNetId net_id, int ipin); // //static void comp_td_point_to_point_delays(); @@ -306,7 +306,7 @@ static void get_bb_from_scratch(/*ClusterNetId*/ NetInfo* net, t_bb *coords, //static double get_net_wirelength_estimate(ClusterNetId net_id, t_bb *bbptr); // //static void free_try_swap_arrays(); -// + //static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, // int num_connections, float crit_exponent, float bb_cost, // float * place_delay_value, float * timing_cost, float * delay_cost, @@ -318,18 +318,18 @@ static void get_bb_from_scratch(/*ClusterNetId*/ NetInfo* net, t_bb *coords, // t_timing_inf timing_inf, //#endif // SetupTimingInfo& timing_info); -// -//static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, -// float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, -// float crit_exponent, int inner_recompute_limit, -// t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, -// float * delay_cost, -//#ifdef ENABLE_CLASSIC_VPR_STA -// t_slack* slacks, -// t_timing_inf timing_inf, -//#endif -// const ClusteredPinAtomPinsLookup& netlist_pin_lookup, -// SetupTimingInfo& timing_info); + +static void placement_inner_loop(float t, float rlim, /*t_placer_opts placer_opts,*/ + float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, + float crit_exponent, int inner_recompute_limit, + t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, + float * delay_cost/*, +#ifdef ENABLE_CLASSIC_VPR_STA + t_slack* slacks, + t_timing_inf timing_inf, +#endif + const ClusteredPinAtomPinsLookup& netlist_pin_lookup, + SetupTimingInfo& timing_info*/); static Context* npnr_ctx = NULL; static struct { @@ -346,6 +346,7 @@ static struct { #define VTR_ASSERT_SAFE_MSG NPNR_ASSERT_MSG #define vpr_throw(__a, __b, __c, ...) log_error(__VA_ARGS__) namespace vtr { + constexpr size_t bufsize = 32768; template inline void printf_warning(const char*, unsigned, const char* fmt, Args... args) { log_warning(fmt, std::forward(args)...); @@ -376,19 +377,19 @@ void try_place(/*t_placer_opts placer_opts, // * greater bias for anisotropic architectures. */ int tot_iter, move_lim, moves_since_cost_recompute, /*width_fac,*/ num_connections, - outer_crit_iter_count /*, inner_recompute_limit*/; + outer_crit_iter_count, inner_recompute_limit; //unsigned int ipin; - float t, /*success_rat,*/ rlim, cost, timing_cost, bb_cost, /*new_bb_cost, new_timing_cost,*/ + float t, success_rat, rlim, cost, timing_cost, bb_cost, new_bb_cost, /*new_timing_cost,*/ delay_cost, /*new_delay_cost,*/ place_delay_value, inverse_prev_bb_cost, inverse_prev_timing_cost, - /*oldt,*/ crit_exponent, + oldt, crit_exponent, first_rlim, final_rlim , inverse_delta_rlim; // tatum::TimingPathInfo critical_path; // float sTNS = NAN; // float sWNS = NAN; -// -// double std_dev; -// char msg[vtr::bufsize]; -// t_placer_statistics stats; + + double std_dev; + char msg[vtr::bufsize]; + t_placer_statistics stats; //#ifdef ENABLE_CLASSIC_VPR_STA // t_slack * slacks = NULL; //#endif @@ -524,9 +525,9 @@ void try_place(/*t_placer_opts placer_opts, // //Sanity check that initial placement is legal // check_place(bb_cost, timing_cost, placer_opts.place_algorithm, delay_cost); // -// //Initial pacement statistics -// vtr::printf_info("Initial placement cost: %g bb_cost: %g td_cost: %g delay_cost: %g\n", -// cost, bb_cost, timing_cost, delay_cost); + //Initial pacement statistics + vtr::printf_info("Initial placement cost: %g bb_cost: %g td_cost: %g delay_cost: %g\n", + cost, bb_cost, timing_cost, delay_cost); // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // vtr::printf_info("Initial placement estimated Critical Path Delay (CPD): %g ns\n", // 1e9*critical_path.delay()); @@ -539,43 +540,43 @@ void try_place(/*t_placer_opts placer_opts, // vtr::printf_info("Initial placement estimated setup slack histogram:\n"); // print_histogram(create_setup_slack_histogram(*timing_info->setup_analyzer())); // } -// vtr::printf_info("\n"); -// -// //Table header -// vtr::printf_info("%7s " -// "%7s %10s %10s %10s " -// "%10s %7s %10s %8s " -// "%7s %7s %7s %6s " -// "%9s %6s\n", -// "-------", -// "-------", "----------", "----------", "----------", -// "----------", "-------", "----------", "--------", -// "-------", "-------", "-------", "------", -// "---------", "------"); -// vtr::printf_info("%7s " -// "%7s %10s %10s %10s " -// "%10s %7s %10s %8s " -// "%7s %7s %7s %6s " -// "%9s %6s\n", -// "T", -// "Cost", "Av BB Cost", "Av TD Cost", "Av Tot Del", -// "P to P Del", "CPD", "sTNS", "sWNS", -// "Ac Rate", "Std Dev", "R limit", "Exp", -// "Tot Moves", "Alpha"); -// vtr::printf_info("%7s " -// "%7s %10s %10s %10s " -// "%10s %7s %10s %8s " -// "%7s %7s %7s %6s " -// "%9s %6s\n", -// "-------", -// "-------", "----------", "----------", "----------", -// "----------", "-------", "----------", "--------", -// "-------", "-------", "-------", "------", -// "---------", "------"); -// -// sprintf(msg, "Initial Placement. Cost: %g BB Cost: %g TD Cost %g Delay Cost: %g \t Channel Factor: %d", -// cost, bb_cost, timing_cost, delay_cost, width_fac); -// + vtr::printf_info("\n"); + + //Table header + vtr::printf_info("%7s " + "%7s %10s %10s %10s " + "%10s %7s %10s %8s " + "%7s %7s %7s %6s " + "%9s %6s\n", + "-------", + "-------", "----------", "----------", "----------", + "----------", "-------", "----------", "--------", + "-------", "-------", "-------", "------", + "---------", "------"); + vtr::printf_info("%7s " + "%7s %10s %10s %10s " + "%10s %7s %10s %8s " + "%7s %7s %7s %6s " + "%9s %6s\n", + "T", + "Cost", "Av BB Cost", "Av TD Cost", "Av Tot Del", + "P to P Del", "CPD", "sTNS", "sWNS", + "Ac Rate", "Std Dev", "R limit", "Exp", + "Tot Moves", "Alpha"); + vtr::printf_info("%7s " + "%7s %10s %10s %10s " + "%10s %7s %10s %8s " + "%7s %7s %7s %6s " + "%9s %6s\n", + "-------", + "-------", "----------", "----------", "----------", + "----------", "-------", "----------", "--------", + "-------", "-------", "-------", "------", + "---------", "------"); + + sprintf(msg, "Initial Placement. Cost: %g BB Cost: %g TD Cost %g Delay Cost: %g \t Channel Factor: %d", + cost, bb_cost, timing_cost, delay_cost, /*width_fac*/ -1); + // //Draw the initial placement // update_screen(ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info); @@ -592,8 +593,8 @@ void try_place(/*t_placer_opts placer_opts, // inner_recompute_limit = (int) // (0.5 + (float) move_lim / (float) placer_opts.inner_loop_recompute_divider); // } else { -// /*don't do an inner recompute */ -// inner_recompute_limit = move_lim + 1; + /*don't do an inner recompute */ + inner_recompute_limit = move_lim + 1; // } rlim = (float) std::max(grid.width(), grid.height()); @@ -610,15 +611,13 @@ void try_place(/*t_placer_opts placer_opts, tot_iter = 0; moves_since_cost_recompute = 0; - return; + /* Outer loop of the simmulated annealing begins */ + while (exit_crit(t, cost /*, annealing_sched*/) == 0) { -// /* Outer loop of the simmulated annealing begins */ -// while (exit_crit(t, cost, annealing_sched) == 0) { -// // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // cost = 1; // } -// + // outer_loop_recompute_criticalities(placer_opts, num_connections, // crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, // &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, @@ -628,32 +627,32 @@ void try_place(/*t_placer_opts placer_opts, // timing_inf, //#endif // *timing_info); -// -// placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, -// move_lim, crit_exponent, inner_recompute_limit, &stats, -// &cost, &bb_cost, &timing_cost, &delay_cost, -//#ifdef ENABLE_CLASSIC_VPR_STA -// slacks, -// timing_inf, -//#endif -// netlist_pin_lookup, -// *timing_info); -// -// /* Lines below prevent too much round-off error from accumulating * -// * in the cost over many iterations. This round-off can lead to * -// * error checks failing because the cost is different from what * -// * you get when you recompute from scratch. */ -// -// moves_since_cost_recompute += move_lim; -// if (moves_since_cost_recompute > MAX_MOVES_BEFORE_RECOMPUTE) { -// new_bb_cost = recompute_bb_cost(); -// if (fabs(new_bb_cost - bb_cost) > bb_cost * ERROR_TOL) { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "in try_place: new_bb_cost = %g, old bb_cost = %g\n", -// new_bb_cost, bb_cost); -// } -// bb_cost = new_bb_cost; -// + + placement_inner_loop(t, rlim, /*placer_opts,*/ inverse_prev_bb_cost, inverse_prev_timing_cost, + move_lim, crit_exponent, inner_recompute_limit, &stats, + &cost, &bb_cost, &timing_cost, &delay_cost/*, +#ifdef ENABLE_CLASSIC_VPR_STA + slacks, + timing_inf, +#endif + netlist_pin_lookup, + *timing_info*/); + + /* Lines below prevent too much round-off error from accumulating * + * in the cost over many iterations. This round-off can lead to * + * error checks failing because the cost is different from what * + * you get when you recompute from scratch. */ + + moves_since_cost_recompute += move_lim; + if (moves_since_cost_recompute > MAX_MOVES_BEFORE_RECOMPUTE) { + new_bb_cost = recompute_bb_cost(); + if (fabs(new_bb_cost - bb_cost) > bb_cost * ERROR_TOL) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "in try_place: new_bb_cost = %g, old bb_cost = %g\n", + new_bb_cost, bb_cost); + } + bb_cost = new_bb_cost; + // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // comp_td_costs(&new_timing_cost, &new_delay_cost); // if (fabs(new_timing_cost - timing_cost) > timing_cost * ERROR_TOL) { @@ -670,75 +669,75 @@ void try_place(/*t_placer_opts placer_opts, // } // // if (placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { -// cost = new_bb_cost; + cost = new_bb_cost; // } -// moves_since_cost_recompute = 0; -// } -// -// tot_iter += move_lim; -// success_rat = ((float) stats.success_sum) / move_lim; -// if (stats.success_sum == 0) { -// stats.av_cost = cost; -// stats.av_bb_cost = bb_cost; -// stats.av_timing_cost = timing_cost; -// stats.av_delay_cost = delay_cost; -// } else { -// stats.av_cost /= stats.success_sum; -// stats.av_bb_cost /= stats.success_sum; -// stats.av_timing_cost /= stats.success_sum; -// stats.av_delay_cost /= stats.success_sum; -// } -// std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); -// -// oldt = t; /* for finding and printing alpha. */ -// update_t(&t, rlim, success_rat, annealing_sched); -// + moves_since_cost_recompute = 0; + } + + tot_iter += move_lim; + success_rat = ((float) stats.success_sum) / move_lim; + if (stats.success_sum == 0) { + stats.av_cost = cost; + stats.av_bb_cost = bb_cost; + stats.av_timing_cost = timing_cost; + stats.av_delay_cost = delay_cost; + } else { + stats.av_cost /= stats.success_sum; + stats.av_bb_cost /= stats.success_sum; + stats.av_timing_cost /= stats.success_sum; + stats.av_delay_cost /= stats.success_sum; + } + std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); + + oldt = t; /* for finding and printing alpha. */ + update_t(&t, rlim, success_rat /*, annealing_sched*/); + // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // critical_path = timing_info->least_slack_critical_path(); // sTNS = timing_info->setup_total_negative_slack(); // sWNS = timing_info->setup_worst_negative_slack(); // } -// -// vtr::printf_info("%7.3f " -// "%7.4f %10.4f %-10.5g %-10.5g " -// "%-10.5g %7.3f % 10.3g % 8.3f " -// "%7.4f %7.4f %7.4f %6.3f" -// "%9d %6.3f\n", -// oldt, -// stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, -// place_delay_value, 1e9*critical_path.delay(), 1e9*sTNS, 1e9*sWNS, -// success_rat, std_dev, rlim, crit_exponent, -// tot_iter, t / oldt); -// -//#ifdef ENABLE_CLASSIC_VPR_STA -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); -// if(cpd_diff_ns > ERROR_TOL) { -// print_classic_cpds(); -// print_tatum_cpds(timing_info->critical_paths()); -// -// vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); -// } -// } -//#endif -// -// sprintf(msg, "Cost: %g BB Cost %g TD Cost %g Temperature: %g", -// cost, bb_cost, timing_cost, t); + + vtr::printf_info("%7.3f " + "%7.4f %10.4f %-10.5g %-10.5g " + "%-10.5g %7.3f % 10.3g % 8.3f " + "%7.4f %7.4f %7.4f %6.3f" + "%9d %6.3f\n", + oldt, + stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, + place_delay_value, /*1e9*critical_path.delay()*/ -1, /*1e9*sTNS*/ -1, /*1e9*sWNS*/ -1, + success_rat, std_dev, rlim, crit_exponent, + tot_iter, t / oldt); + +#ifdef ENABLE_CLASSIC_VPR_STA + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); + if(cpd_diff_ns > ERROR_TOL) { + print_classic_cpds(); + print_tatum_cpds(timing_info->critical_paths()); + + vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); + } + } +#endif + + sprintf(msg, "Cost: %g BB Cost %g TD Cost %g Temperature: %g", + cost, bb_cost, timing_cost, t); // update_screen(ScreenUpdatePriority::MINOR, msg, PLACEMENT, timing_info); -// update_rlim(&rlim, success_rat, device_ctx.grid); -// + update_rlim(&rlim, success_rat /*, device_ctx.grid*/); + // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // crit_exponent = (1 - (rlim - final_rlim) * inverse_delta_rlim) // * (placer_opts.td_place_exp_last - placer_opts.td_place_exp_first) // + placer_opts.td_place_exp_first; // } -//#ifdef VERBOSE -// if (getEchoEnabled()) { -// print_clb_placement("first_iteration_clb_placement.echo"); -// } -//#endif -// } -// /* Outer loop of the simmulated annealing ends */ +#ifdef VERBOSE + if (getEchoEnabled()) { + print_clb_placement("first_iteration_clb_placement.echo"); + } +#endif + } + /* Outer loop of the simmulated annealing ends */ // // // outer_loop_recompute_criticalities(placer_opts, num_connections, @@ -958,55 +957,55 @@ void try_place(/*t_placer_opts placer_opts, ///*Prevent inverse timing cost from going to infinity */ //*inverse_prev_timing_cost = min(1 / (*timing_cost), (float)MAX_INV_TIMING_COST); //} -// -///* Function which contains the inner loop of the simulated annealing */ -//static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, -// float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, -// float crit_exponent, int inner_recompute_limit, -// t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, -// float * delay_cost, -//#ifdef ENABLE_CLASSIC_VPR_STA -// t_slack* slacks, -// t_timing_inf timing_inf, -//#endif -// const ClusteredPinAtomPinsLookup& netlist_pin_lookup, -// SetupTimingInfo& timing_info) { -// -// int inner_crit_iter_count, inner_iter; -// -// stats->av_cost = 0.; -// stats->av_bb_cost = 0.; -// stats->av_delay_cost = 0.; -// stats->av_timing_cost = 0.; -// stats->sum_of_squares = 0.; -// stats->success_sum = 0; -// -// inner_crit_iter_count = 1; -// -// /* Inner loop begins */ -// for (inner_iter = 0; inner_iter < move_lim; inner_iter++) { -// e_swap_result swap_result = try_swap(t, cost, bb_cost, timing_cost, rlim, -// placer_opts.place_algorithm, placer_opts.timing_tradeoff, -// inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost); -// -// if (swap_result == ACCEPTED) { -// /* Move was accepted. Update statistics that are useful for the annealing schedule. */ -// stats->success_sum++; -// stats->av_cost += *cost; -// stats->av_bb_cost += *bb_cost; -// stats->av_timing_cost += *timing_cost; -// stats->av_delay_cost += *delay_cost; -// stats->sum_of_squares += (*cost) * (*cost); -// num_swap_accepted++; -// } -// else if (swap_result == ABORTED) { -// num_swap_aborted++; -// } -// else { // swap_result == REJECTED -// num_swap_rejected++; -// } -// -// + +/* Function which contains the inner loop of the simulated annealing */ +static void placement_inner_loop(float t, float rlim, /*t_placer_opts placer_opts,*/ + float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, + float crit_exponent, int inner_recompute_limit, + t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, + float * delay_cost /*, +#ifdef ENABLE_CLASSIC_VPR_STA + t_slack* slacks, + t_timing_inf timing_inf, +#endif + const ClusteredPinAtomPinsLookup& netlist_pin_lookup, + SetupTimingInfo& timing_info*/) { + + int inner_crit_iter_count, inner_iter; + + stats->av_cost = 0.; + stats->av_bb_cost = 0.; + stats->av_delay_cost = 0.; + stats->av_timing_cost = 0.; + stats->sum_of_squares = 0.; + stats->success_sum = 0; + + inner_crit_iter_count = 1; + + /* Inner loop begins */ + for (inner_iter = 0; inner_iter < move_lim; inner_iter++) { + e_swap_result swap_result = try_swap(t, cost, bb_cost, timing_cost, rlim, + /*placer_opts.place_algorithm, placer_opts.timing_tradeoff,*/ + inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost); + + if (swap_result == ACCEPTED) { + /* Move was accepted. Update statistics that are useful for the annealing schedule. */ + stats->success_sum++; + stats->av_cost += *cost; + stats->av_bb_cost += *bb_cost; + stats->av_timing_cost += *timing_cost; + stats->av_delay_cost += *delay_cost; + stats->sum_of_squares += (*cost) * (*cost); + num_swap_accepted++; + } + else if (swap_result == ABORTED) { + num_swap_aborted++; + } + else { // swap_result == REJECTED + num_swap_rejected++; + } + + // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // // /* Do we want to re-timing analyze the circuit to get updated slack and criticality values? @@ -1035,17 +1034,17 @@ void try_place(/*t_placer_opts placer_opts, // } // inner_crit_iter_count++; // } -//#ifdef VERBOSE -// vtr::printf("t = %g cost = %g bb_cost = %g timing_cost = %g move = %d dmax = %g\n", -// t, *cost, *bb_cost, *timing_cost, inner_iter, *delay_cost); -// if (fabs((*bb_cost) - comp_bb_cost(CHECK)) > (*bb_cost) * ERROR_TOL) -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "fabs((*bb_cost) - comp_bb_cost(CHECK)) > (*bb_cost) * ERROR_TOL"); -//#endif -// } -// /* Inner loop ends */ -//} -// +#ifdef VERBOSE + vtr::printf("t = %g cost = %g bb_cost = %g timing_cost = %g move = %d dmax = %g\n", + t, *cost, *bb_cost, *timing_cost, inner_iter, *delay_cost); + if (fabs((*bb_cost) - comp_bb_cost(CHECK)) > (*bb_cost) * ERROR_TOL) + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "fabs((*bb_cost) - comp_bb_cost(CHECK)) > (*bb_cost) * ERROR_TOL"); +#endif + } + /* Inner loop ends */ +} + ///*only count non-global connections */ //static int count_connections() { // @@ -1084,45 +1083,45 @@ static double get_std_dev(int n, double sum_x_squared, double av_x) { return (std_dev); } -//static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid) { -// -// /* Update the range limited to keep acceptance prob. near 0.44. Use * -// * a floating point rlim to allow gradual transitions at low temps. */ -// -// float upper_lim; -// -// *rlim = (*rlim) * (1. - 0.44 + success_rat); -// upper_lim = max(grid.width() - 1, grid.height() - 1); -// *rlim = min(*rlim, upper_lim); -// *rlim = max(*rlim, (float)1.); -//} -// -///* Update the temperature according to the annealing schedule selected. */ -//static void update_t(float *t, float rlim, float success_rat, -// t_annealing_sched annealing_sched) { -// -// /* float fac; */ -// +static void update_rlim(float *rlim, float success_rat /*, const DeviceGrid& grid*/) { + + /* Update the range limited to keep acceptance prob. near 0.44. Use * + * a floating point rlim to allow gradual transitions at low temps. */ + + float upper_lim; + + *rlim = (*rlim) * (1. - 0.44 + success_rat); + upper_lim = std::max(grid.width() - 1, grid.height() - 1); + *rlim = std::min(*rlim, upper_lim); + *rlim = std::max(*rlim, (float)1.); +} + +/* Update the temperature according to the annealing schedule selected. */ +static void update_t(float *t, float rlim, float success_rat /*, + t_annealing_sched annealing_sched*/) { + + /* float fac; */ + // if (annealing_sched.type == USER_SCHED) { // *t = annealing_sched.alpha_t * (*t); // } else { /* AUTO_SCHED */ -// if (success_rat > 0.96) { -// *t = (*t) * 0.5; -// } else if (success_rat > 0.8) { -// *t = (*t) * 0.9; -// } else if (success_rat > 0.15 || rlim > 1.) { -// *t = (*t) * 0.95; -// } else { -// *t = (*t) * 0.8; -// } + if (success_rat > 0.96) { + *t = (*t) * 0.5; + } else if (success_rat > 0.8) { + *t = (*t) * 0.9; + } else if (success_rat > 0.15 || rlim > 1.) { + *t = (*t) * 0.95; + } else { + *t = (*t) * 0.8; + } // } -//} -// -//static int exit_crit(float t, float cost, -// t_annealing_sched annealing_sched) { -// -// /* Return 1 when the exit criterion is met. */ -// +} + +static int exit_crit(float t, float cost/*, + t_annealing_sched annealing_sched*/) { + + /* Return 1 when the exit criterion is met. */ + // if (annealing_sched.type == USER_SCHED) { // if (t < annealing_sched.exit_t) { // return (1); @@ -1132,19 +1131,19 @@ static double get_std_dev(int n, double sum_x_squared, double av_x) { // } // // auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// /* Automatic annealing schedule */ -// float t_exit = 0.005 * cost / cluster_ctx.clb_nlist.nets().size(); -// -// if (t < t_exit) { -// return (1); -// } else if (std::isnan(t_exit)) { -// //May get nan if there are no nets -// return (1); -// } else { -// return (0); -// } -//} + + /* Automatic annealing schedule */ + float t_exit = 0.005 * cost / npnr_ctx->nets.size(); + + if (t < t_exit) { + return (1); + } else if (std::isnan(t_exit)) { + //May get nan if there are no nets + return (1); + } else { + return (0); + } +} static float starting_t(float *cost_ptr, float *bb_cost_ptr, float *timing_cost_ptr, @@ -1200,9 +1199,9 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, "Starting t: %d of %d configurations accepted.\n", num_accepted, move_lim); } -//#ifdef VERBOSE +#ifdef VERBOSE vtr::printf_info("std_dev: %g, average cost: %g, starting temp: %g\n", std_dev, av, 20. * std_dev); -//#endif +#endif /* Set the initial temperature to 20 times the standard of deviation */ /* so that the initial temperature adjusts according to the circuit */ @@ -1533,8 +1532,8 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin } else { /* Move was rejected. */ /* Reset the net cost function flags first. */ - for (auto net : ts_nets_to_update) { - temp_net_cost[net] = -1; + for (auto net_id : ts_nets_to_update) { + temp_net_cost[net_id] = -1; // bb_updated_before[net_id] = NOT_UPDATED_YET; } @@ -1884,8 +1883,8 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, } else { int x_rel = npnr_ctx->rng(std::max(0, max_x - min_x)); int y_rel = npnr_ctx->rng(std::max(0, max_y - min_y)); -// *px_to = min_x + x_rel; -// *py_to = min_y + y_rel; + int px_to = min_x + x_rel; + int py_to = min_y + y_rel; // *px_to = (*px_to) - grid[*px_to][*py_to].width_offset; /* align it */ // *py_to = (*py_to) - grid[*px_to][*py_to].height_offset; /* align it */ bel_to = BelId(); @@ -1923,25 +1922,26 @@ static e_swap_result assess_swap(float delta_c, float t) { return (accept); } -//static float recompute_bb_cost() { -// /* Recomputes the cost to eliminate roundoff that may have accrued. * -// * This routine does as little work as possible to compute this new * -// * cost. */ -// -// float cost = 0; -// +static float recompute_bb_cost() { + /* Recomputes the cost to eliminate roundoff that may have accrued. * + * This routine does as little work as possible to compute this new * + * cost. */ + + float cost = 0; + // auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// for (auto net_id : cluster_ctx.clb_nlist.nets()) { /* for each net ... */ + + for (auto& n : npnr_ctx->nets) { /* for each net ... */ + auto net_id = n.second.get(); // if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ -// /* Bounding boxes don't have to be recomputed; they're correct. */ -// cost += net_cost[net_id]; + /* Bounding boxes don't have to be recomputed; they're correct. */ + cost += net_cost[net_id]; // } -// } -// -// return (cost); -//} -// + } + + return (cost); +} + ///*returns the delay of one point to point connection */ //static float comp_td_point_to_point_delay(ClusterNetId net_id, int ipin) { // auto& cluster_ctx = g_vpr_ctx.clustering(); @@ -2096,7 +2096,7 @@ static e_swap_result assess_swap(float delta_c, float t) { * other routine. */ static float comp_bb_cost(/*e_cost_methods method*/) { float cost = 0; - double expected_wirelength = 0.0; +// double expected_wirelength = 0.0; // auto& cluster_ctx = g_vpr_ctx.clustering(); for (auto& n : npnr_ctx->nets) { /* for each net ... */ From aab5105419256bbae771a9ba337195905521ed7a Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 17:03:07 -0700 Subject: [PATCH 020/116] Update bounding boxes --- common/place_vpr.inc | 687 ++++++++++++++++++++++--------------------- 1 file changed, 351 insertions(+), 336 deletions(-) diff --git a/common/place_vpr.inc b/common/place_vpr.inc index 10f79259fe..dfbcd2265d 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -55,13 +55,13 @@ // * random location before trying exhaustive placement - find the fist * // * legal position and place it during initial placement. */ //#define MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY 4 -// -///* Flags for the states of the bounding box. * -// * Stored as char for memory efficiency. */ -//#define NOT_UPDATED_YET 'N' -//#define UPDATED_ONCE 'U' -//#define GOT_FROM_SCRATCH 'S' -// + +/* Flags for the states of the bounding box. * + * Stored as char for memory efficiency. */ +#define NOT_UPDATED_YET 'N' +#define UPDATED_ONCE 'U' +#define GOT_FROM_SCRATCH 'S' + ///* For comp_cost. NORMAL means use the method that generates updateable * // * bounding boxes for speed. CHECK means compute all bounding boxes from * // * scratch using a very simple routine to allow checks of the other * @@ -97,21 +97,21 @@ static std::unordered_map net_cost, temp_net_cost; //static t_legal_pos **legal_pos = nullptr; /* [0..device_ctx.num_block_types-1][0..type_tsize - 1] */ static std::unordered_map> legal_pos; //static int *num_legal_pos = nullptr; /* [0..num_legal_pos-1] */ -// -///* [0...cluster_ctx.clb_nlist.nets().size()-1] * -// * A flag array to indicate whether the specific bounding box has been updated * -// * in this particular swap or not. If it has been updated before, the code * -// * must use the updated data, instead of the out-of-date data passed into the * -// * subroutine, particularly used in try_swap(). The value NOT_UPDATED_YET * -// * indicates that the net has not been updated before, UPDATED_ONCE indicated * -// * that the net has been updated once, if it is going to be updated again, the * -// * values from the previous update must be used. GOT_FROM_SCRATCH is only * -// * applicable for nets larger than SMALL_NETS and it indicates that the * -// * particular bounding box cannot be updated incrementally before, hence the * -// * bounding box is got from scratch, so the bounding box would definitely be * -// * right, DO NOT update again. */ -//static vtr::vector bb_updated_before; -// + +/* [0...cluster_ctx.clb_nlist.nets().size()-1] * + * A flag array to indicate whether the specific bounding box has been updated * + * in this particular swap or not. If it has been updated before, the code * + * must use the updated data, instead of the out-of-date data passed into the * + * subroutine, particularly used in try_swap(). The value NOT_UPDATED_YET * + * indicates that the net has not been updated before, UPDATED_ONCE indicated * + * that the net has been updated once, if it is going to be updated again, the * + * values from the previous update must be used. GOT_FROM_SCRATCH is only * + * applicable for nets larger than SMALL_NETS and it indicates that the * + * particular bounding box cannot be updated incrementally before, hence the * + * bounding box is got from scratch, so the bounding box would definitely be * + * right, DO NOT update again. */ +static std::unordered_map bb_updated_before; + ///* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1]. What is the value of the timing */ ///* driven portion of the cost function. These arrays will be set to */ ///* (criticality * delay) for each point to point connection. */ @@ -288,14 +288,15 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, static void get_non_updateable_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new); -//static void update_bb(ClusterNetId net_id, t_bb *bb_coord_new, -// t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew); -// +static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, + t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew); + static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorithm,*/ float& bb_delta_c, float& timing_delta_c, float& delay_delta_c); static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_affected_nets); -//static void update_net_bb(const ClusterNetId net, int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin); +static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin*/ + CellInfo* blk, BelId bel_from); //static void update_td_delta_costs(const ClusterNetId net, const ClusterPinId pin, float& delta_timing_cost, float& delta_delay_cost); static float get_net_cost(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_ptr); @@ -705,7 +706,7 @@ void try_place(/*t_placer_opts placer_opts, "%9d %6.3f\n", oldt, stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, - place_delay_value, /*1e9*critical_path.delay()*/ -1, /*1e9*sTNS*/ -1, /*1e9*sWNS*/ -1, + place_delay_value, /*1e9*critical_path.delay()*/ 0., /*1e9*sTNS*/ 0., /*1e9*sWNS*/ 0., success_rat, std_dev, rlim, crit_exponent, tot_iter, t / oldt); @@ -738,8 +739,8 @@ void try_place(/*t_placer_opts placer_opts, #endif } /* Outer loop of the simmulated annealing ends */ -// -// + + // outer_loop_recompute_criticalities(placer_opts, num_connections, // crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, // &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, @@ -749,70 +750,70 @@ void try_place(/*t_placer_opts placer_opts, // timing_inf, //#endif // *timing_info); -// -// t = 0; /* freeze out */ -// -// /* Run inner loop again with temperature = 0 so as to accept only swaps -// * which reduce the cost of the placement */ -// placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, -// move_lim, crit_exponent, inner_recompute_limit, &stats, -// &cost, &bb_cost, &timing_cost, &delay_cost, -//#ifdef ENABLE_CLASSIC_VPR_STA -// slacks, -// timing_inf, -//#endif -// netlist_pin_lookup, -// *timing_info); -// -// tot_iter += move_lim; -// success_rat = ((float) stats.success_sum) / move_lim; -// if (stats.success_sum == 0) { -// stats.av_cost = cost; -// stats.av_bb_cost = bb_cost; -// stats.av_delay_cost = delay_cost; -// stats.av_timing_cost = timing_cost; -// } else { -// stats.av_cost /= stats.success_sum; -// stats.av_bb_cost /= stats.success_sum; -// stats.av_delay_cost /= stats.success_sum; -// stats.av_timing_cost /= stats.success_sum; -// } -// -// std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); -// + + t = 0; /* freeze out */ + + /* Run inner loop again with temperature = 0 so as to accept only swaps + * which reduce the cost of the placement */ + placement_inner_loop(t, rlim, /*placer_opts,*/ inverse_prev_bb_cost, inverse_prev_timing_cost, + move_lim, crit_exponent, inner_recompute_limit, &stats, + &cost, &bb_cost, &timing_cost, &delay_cost/*, +#ifdef ENABLE_CLASSIC_VPR_STA + slacks, + timing_inf, +#endif + netlist_pin_lookup, + *timing_info*/); + + tot_iter += move_lim; + success_rat = ((float) stats.success_sum) / move_lim; + if (stats.success_sum == 0) { + stats.av_cost = cost; + stats.av_bb_cost = bb_cost; + stats.av_delay_cost = delay_cost; + stats.av_timing_cost = timing_cost; + } else { + stats.av_cost /= stats.success_sum; + stats.av_bb_cost /= stats.success_sum; + stats.av_delay_cost /= stats.success_sum; + stats.av_timing_cost /= stats.success_sum; + } + + std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); + // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // critical_path = timing_info->least_slack_critical_path(); // sTNS = timing_info->setup_total_negative_slack(); // sWNS = timing_info->setup_worst_negative_slack(); // } -// -// vtr::printf_info("%7.3f " -// "%7.4f %10.4f %-10.5g %-10.5g " -// "%-10.5g %7.3f % 10.3g % 8.3f " -// "%7.4f %7.4f %7.4f %6.3f" -// "%9d %6.3f\n", -// t, -// stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, -// place_delay_value, 1e9*critical_path.delay(), 1e9*sTNS, 1e9*sWNS, -// success_rat, std_dev, rlim, crit_exponent, -// tot_iter, 0.); -// -// // TODO: -// // 1. print a message about number of aborted moves. -// // 2. add some subroutine hierarchy! Too big! -// -//#ifdef VERBOSE -// if (getEchoEnabled() && isEchoFileEnabled(E_ECHO_END_CLB_PLACEMENT)) { -// print_clb_placement(getEchoFileName(E_ECHO_END_CLB_PLACEMENT)); -// } -//#endif -// + + vtr::printf_info("%7.3f " + "%7.4f %10.4f %-10.5g %-10.5g " + "%-10.5g %7.3f % 10.3g % 8.3f " + "%7.4f %7.4f %7.4f %6.3f" + "%9d %6.3f\n", + t, + stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, + place_delay_value, /*1e9*critical_path.delay()*/0., /*1e9*sTNS*/0., /*1e9*sWNS*/0., + success_rat, std_dev, rlim, crit_exponent, + tot_iter, 0.); + + // TODO: + // 1. print a message about number of aborted moves. + // 2. add some subroutine hierarchy! Too big! + +#ifdef VERBOSE + if (getEchoEnabled() && isEchoFileEnabled(E_ECHO_END_CLB_PLACEMENT)) { + print_clb_placement(getEchoFileName(E_ECHO_END_CLB_PLACEMENT)); + } +#endif + // check_place(bb_cost, timing_cost, placer_opts.place_algorithm, delay_cost); -// -// //Some stats -// vtr::printf_info("\n"); -// vtr::printf_info("Swaps called: %d\n", num_ts_called); -// + + //Some stats + vtr::printf_info("\n"); + vtr::printf_info("Swaps called: %d\n", num_ts_called); + // if (placer_opts.enable_timing_computations // && placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { // /*need this done since the timing data has not been kept up to date* @@ -874,26 +875,26 @@ void try_place(/*t_placer_opts placer_opts, // } //#endif // } -// -// sprintf(msg, "Placement. Cost: %g bb_cost: %g td_cost: %g Channel Factor: %d", -// cost, bb_cost, timing_cost, width_fac); -// vtr::printf_info("Placement cost: %g, bb_cost: %g, td_cost: %g, delay_cost: %g\n", -// cost, bb_cost, timing_cost, delay_cost); + + sprintf(msg, "Placement. Cost: %g bb_cost: %g td_cost: %g Channel Factor: %d", + cost, bb_cost, timing_cost, /*width_fac*/ -1); + vtr::printf_info("Placement cost: %g, bb_cost: %g, td_cost: %g, delay_cost: %g\n", + cost, bb_cost, timing_cost, delay_cost); // update_screen(ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info); -// -// // Print out swap statistics -// size_t total_swap_attempts = num_swap_rejected + num_swap_accepted + num_swap_aborted; -// VTR_ASSERT(total_swap_attempts > 0); -// -// size_t num_swap_print_digits = ceil(log10(total_swap_attempts)); -// float reject_rate = (float) num_swap_rejected / total_swap_attempts; -// float accept_rate = (float) num_swap_accepted / total_swap_attempts; -// float abort_rate = (float) num_swap_aborted / total_swap_attempts; -// vtr::printf_info("Placement total # of swap attempts: %*d\n", num_swap_print_digits, total_swap_attempts); -// vtr::printf_info("\tSwaps accepted: %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_accepted, 100*accept_rate); -// vtr::printf_info("\tSwaps rejected: %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_rejected, 100*reject_rate); -// vtr::printf_info("\tSwaps aborted : %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_aborted, 100*abort_rate); -// + + // Print out swap statistics + size_t total_swap_attempts = num_swap_rejected + num_swap_accepted + num_swap_aborted; + VTR_ASSERT(total_swap_attempts > 0); + + size_t num_swap_print_digits = ceil(log10(total_swap_attempts)); + float reject_rate = (float) num_swap_rejected / total_swap_attempts; + float accept_rate = (float) num_swap_accepted / total_swap_attempts; + float abort_rate = (float) num_swap_aborted / total_swap_attempts; + vtr::printf_info("Placement total # of swap attempts: %*d\n", num_swap_print_digits, total_swap_attempts); + vtr::printf_info("\tSwaps accepted: %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_accepted, 100*accept_rate); + vtr::printf_info("\tSwaps rejected: %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_rejected, 100*reject_rate); + vtr::printf_info("\tSwaps aborted : %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_aborted, 100*abort_rate); + // free_placement_structs(placer_opts); // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE // || placer_opts.enable_timing_computations) { @@ -1239,6 +1240,7 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to // place_ctx.block_locs[b_from].y = y_to; // place_ctx.block_locs[b_from].z = z_to; + npnr_ctx->unbindBel(bel_from); npnr_ctx->bindBel(bel_to, b_from->name, STRENGTH_WEAK); // // Sets up the blocks moved @@ -1479,7 +1481,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin if (keep_switch == ACCEPTED) { *cost = *cost + delta_c; *bb_cost = *bb_cost + bb_delta_c; -// + // if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // /*update the point_to_point_timing_cost and point_to_point_delay_cost // * values from the temporary values */ @@ -1499,7 +1501,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin /* negative temp_net_cost value is acting as a flag. */ temp_net_cost[net_id] = -1; -// bb_updated_before[net_id] = NOT_UPDATED_YET; + bb_updated_before[net_id] = NOT_UPDATED_YET; } @@ -1534,7 +1536,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin /* Reset the net cost function flags first. */ for (auto net_id : ts_nets_to_update) { temp_net_cost[net_id] = -1; -// bb_updated_before[net_id] = NOT_UPDATED_YET; + bb_updated_before[net_id] = NOT_UPDATED_YET; } // /* Restore the place_ctx.block_locs data structures to their state before the move. */ @@ -1636,25 +1638,26 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit //Go through all the blocks moved for (const auto &b : blocks_affected) { auto blk = b.first; + auto bel = b.second; //Go through all the pins in the moved block for (const auto &port : blk->ports) { - if (port.second.net == nullptr) continue; - auto net = port.second.net; - VTR_ASSERT_SAFE_MSG(net, "Only valid nets should be found in compressed netlist block pins"); + auto net_id = port.second.net; + if (!net_id) continue; + VTR_ASSERT_SAFE_MSG(net_id, "Only valid nets should be found in compressed netlist block pins"); // if (cluster_ctx.clb_nlist.net_is_global(net_id)) // continue; //Global nets are assumed to span the whole chip, and do not effect costs //Record effected nets - record_affected_net(net, num_affected_nets); + record_affected_net(net_id, num_affected_nets); + + //Update the net bounding boxes + // + //Do not update the net cost here since it should only be updated + //once per net, not once per pin. + update_net_bb(net_id, /*iblk, blk, blk_pin,*/ blk, bel); -// //Update the net bounding boxes -// // -// //Do not update the net cost here since it should only be updated -// //once per net, not once per pin. -// update_net_bb(net_id, iblk, blk, blk_pin); -// // if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // //Determine the change in timing costs if required // update_td_delta_costs(net_id, blk_pin, timing_delta_c, delay_delta_c); @@ -1675,7 +1678,7 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_affected_nets) { //Record effected nets - if (!temp_net_cost.count(net) || temp_net_cost[net] < 0.) { + if (temp_net_cost[net] < 0.) { //Net not marked yet. ts_nets_to_update.push_back(net); num_affected_nets++; @@ -1685,34 +1688,41 @@ static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_af } } -//static void update_net_bb(const ClusterNetId net, int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin) { +static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin*/ + CellInfo* blk, BelId bel_from) { // auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// if (cluster_ctx.clb_nlist.net_sinks(net).size() < SMALL_NET) { -// //For small nets brute-force bounding box update is faster -// -// if(bb_updated_before[net] == NOT_UPDATED_YET) { //Only once per-net -// get_non_updateable_bb(net, &ts_bb_coord_new[net]); -// } -// } else { -// //For large nets, update bounding box incrementally + + if (net->users.size() < SMALL_NET) { + //For small nets brute-force bounding box update is faster + + if(bb_updated_before[net] == NOT_UPDATED_YET) { //Only once per-net + get_non_updateable_bb(net, &ts_bb_coord_new[net]); + } + } else { + //For large nets, update bounding box incrementally // int iblk_pin = cluster_ctx.clb_nlist.pin_physical_index(blk_pin); // // t_type_ptr blk_type = cluster_ctx.clb_nlist.block_type(blk); // int pin_width_offset = blk_type->pin_width_offset[iblk_pin]; // int pin_height_offset = blk_type->pin_height_offset[iblk_pin]; -// -// //Incremental bounding box update -// update_bb(net, &ts_bb_coord_new[net], -// &ts_bb_edge_new[net], -// blocks_affected.moved_blocks[iblk].xold + pin_width_offset, -// blocks_affected.moved_blocks[iblk].yold + pin_height_offset, -// blocks_affected.moved_blocks[iblk].xnew + pin_width_offset, -// blocks_affected.moved_blocks[iblk].ynew + pin_height_offset); -// } -// -//} -// + + int xold, yold; + int xnew, ynew; + bool gb; + npnr_ctx->estimatePosition(bel_from, xold, yold, gb); + npnr_ctx->estimatePosition(blk->bel, xnew, ynew, gb); + + //Incremental bounding box update + update_bb(net, &ts_bb_coord_new[net], + &ts_bb_edge_new[net], + /*blocks_affected.moved_blocks[iblk].xold + pin_width_offset*/ xold, + /*blocks_affected.moved_blocks[iblk].yold + pin_height_offset*/ yold, + /*blocks_affected.moved_blocks[iblk].xnew + pin_width_offset*/ xnew, + /*blocks_affected.moved_blocks[iblk].ynew + pin_height_offset*/ ynew); + } + +} + //static void update_td_delta_costs(const ClusterNetId net, const ClusterPinId pin, float& delta_timing_cost, float& delta_delay_cost) { // auto& cluster_ctx = g_vpr_ctx.clustering(); // @@ -2235,9 +2245,14 @@ static void alloc_and_load_placement_structs( // } // } // } -// -// net_cost.resize(num_nets, -1.); -// temp_net_cost.resize(num_nets, -1.); + + for (auto& n : npnr_ctx->nets) { + auto net_id = n.second.get(); + net_cost.emplace(net_id, -1); + temp_net_cost.emplace(net_id, -1); + bb_updated_before.emplace(net_id, NOT_UPDATED_YET); + } + // bb_coords.resize(num_nets, t_bb()); // bb_num_on_edges.resize(num_nets, t_bb()); // @@ -2534,198 +2549,198 @@ static void get_non_updateable_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coo bb_coord_new->ymax = std::max(std::min(ymax, grid.height() - 2), 1); //-2 for no perim channels } -//static void update_bb(ClusterNetId net_id, t_bb *bb_coord_new, -// t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew) { -// -// /* Updates the bounding box of a net by storing its coordinates in * -// * the bb_coord_new data structure and the number of blocks on each * -// * edge in the bb_edge_new data structure. This routine should only * -// * be called for large nets, since it has some overhead relative to * -// * just doing a brute force bounding box calculation. The bounding * -// * box coordinate and edge information for inet must be valid before * -// * this routine is called. * -// * Currently assumes channels on both sides of the CLBs forming the * -// * edges of the bounding box can be used. Essentially, I am assuming * -// * the pins always lie on the outside of the bounding box. * -// * The x and y coordinates are the pin's x and y coordinates. */ -// /* IO blocks are considered to be one cell in for simplicity. */ -// //TODO: account for multiple physical pin instances per logical pin -// -// t_bb *curr_bb_edge, *curr_bb_coord; -// +static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, + t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew) { + + /* Updates the bounding box of a net by storing its coordinates in * + * the bb_coord_new data structure and the number of blocks on each * + * edge in the bb_edge_new data structure. This routine should only * + * be called for large nets, since it has some overhead relative to * + * just doing a brute force bounding box calculation. The bounding * + * box coordinate and edge information for inet must be valid before * + * this routine is called. * + * Currently assumes channels on both sides of the CLBs forming the * + * edges of the bounding box can be used. Essentially, I am assuming * + * the pins always lie on the outside of the bounding box. * + * The x and y coordinates are the pin's x and y coordinates. */ + /* IO blocks are considered to be one cell in for simplicity. */ + //TODO: account for multiple physical pin instances per logical pin + + t_bb *curr_bb_edge, *curr_bb_coord; + // auto& device_ctx = g_vpr_ctx.device(); -// -// xnew = max(min(xnew, device_ctx.grid.width() - 2), 1); //-2 for no perim channels -// ynew = max(min(ynew, device_ctx.grid.height() - 2), 1); //-2 for no perim channels -// xold = max(min(xold, device_ctx.grid.width() - 2), 1); //-2 for no perim channels -// yold = max(min(yold, device_ctx.grid.height() - 2), 1); //-2 for no perim channels -// -// /* Check if the net had been updated before. */ -// if (bb_updated_before[net_id] == GOT_FROM_SCRATCH) { -// /* The net had been updated from scratch, DO NOT update again! */ -// return; -// } else if (bb_updated_before[net_id] == NOT_UPDATED_YET) { -// /* The net had NOT been updated before, could use the old values */ -// curr_bb_coord = &bb_coords[net_id]; -// curr_bb_edge = &bb_num_on_edges[net_id]; -// bb_updated_before[net_id] = UPDATED_ONCE; -// } else { -// /* The net had been updated before, must use the new values */ -// curr_bb_coord = bb_coord_new; -// curr_bb_edge = bb_edge_new; -// } -// -// /* Check if I can update the bounding box incrementally. */ -// -// if (xnew < xold) { /* Move to left. */ -// -// /* Update the xmax fields for coordinates and number of edges first. */ -// -// if (xold == curr_bb_coord->xmax) { /* Old position at xmax. */ -// if (curr_bb_edge->xmax == 1) { -// get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); -// bb_updated_before[net_id] = GOT_FROM_SCRATCH; -// return; -// } else { -// bb_edge_new->xmax = curr_bb_edge->xmax - 1; -// bb_coord_new->xmax = curr_bb_coord->xmax; -// } -// } else { /* Move to left, old postion was not at xmax. */ -// bb_coord_new->xmax = curr_bb_coord->xmax; -// bb_edge_new->xmax = curr_bb_edge->xmax; -// } -// -// /* Now do the xmin fields for coordinates and number of edges. */ -// -// if (xnew < curr_bb_coord->xmin) { /* Moved past xmin */ -// bb_coord_new->xmin = xnew; -// bb_edge_new->xmin = 1; -// } else if (xnew == curr_bb_coord->xmin) { /* Moved to xmin */ -// bb_coord_new->xmin = xnew; -// bb_edge_new->xmin = curr_bb_edge->xmin + 1; -// } else { /* Xmin unchanged. */ -// bb_coord_new->xmin = curr_bb_coord->xmin; -// bb_edge_new->xmin = curr_bb_edge->xmin; -// } -// /* End of move to left case. */ -// -// } else if (xnew > xold) { /* Move to right. */ -// -// /* Update the xmin fields for coordinates and number of edges first. */ -// -// if (xold == curr_bb_coord->xmin) { /* Old position at xmin. */ -// if (curr_bb_edge->xmin == 1) { -// get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); -// bb_updated_before[net_id] = GOT_FROM_SCRATCH; -// return; -// } else { -// bb_edge_new->xmin = curr_bb_edge->xmin - 1; -// bb_coord_new->xmin = curr_bb_coord->xmin; -// } -// } else { /* Move to right, old position was not at xmin. */ -// bb_coord_new->xmin = curr_bb_coord->xmin; -// bb_edge_new->xmin = curr_bb_edge->xmin; -// } -// -// /* Now do the xmax fields for coordinates and number of edges. */ -// -// if (xnew > curr_bb_coord->xmax) { /* Moved past xmax. */ -// bb_coord_new->xmax = xnew; -// bb_edge_new->xmax = 1; -// } else if (xnew == curr_bb_coord->xmax) { /* Moved to xmax */ -// bb_coord_new->xmax = xnew; -// bb_edge_new->xmax = curr_bb_edge->xmax + 1; -// } else { /* Xmax unchanged. */ -// bb_coord_new->xmax = curr_bb_coord->xmax; -// bb_edge_new->xmax = curr_bb_edge->xmax; -// } -// /* End of move to right case. */ -// -// } else { /* xnew == xold -- no x motion. */ -// bb_coord_new->xmin = curr_bb_coord->xmin; -// bb_coord_new->xmax = curr_bb_coord->xmax; -// bb_edge_new->xmin = curr_bb_edge->xmin; -// bb_edge_new->xmax = curr_bb_edge->xmax; -// } -// -// /* Now account for the y-direction motion. */ -// -// if (ynew < yold) { /* Move down. */ -// -// /* Update the ymax fields for coordinates and number of edges first. */ -// -// if (yold == curr_bb_coord->ymax) { /* Old position at ymax. */ -// if (curr_bb_edge->ymax == 1) { -// get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); -// bb_updated_before[net_id] = GOT_FROM_SCRATCH; -// return; -// } else { -// bb_edge_new->ymax = curr_bb_edge->ymax - 1; -// bb_coord_new->ymax = curr_bb_coord->ymax; -// } -// } else { /* Move down, old postion was not at ymax. */ -// bb_coord_new->ymax = curr_bb_coord->ymax; -// bb_edge_new->ymax = curr_bb_edge->ymax; -// } -// -// /* Now do the ymin fields for coordinates and number of edges. */ -// -// if (ynew < curr_bb_coord->ymin) { /* Moved past ymin */ -// bb_coord_new->ymin = ynew; -// bb_edge_new->ymin = 1; -// } else if (ynew == curr_bb_coord->ymin) { /* Moved to ymin */ -// bb_coord_new->ymin = ynew; -// bb_edge_new->ymin = curr_bb_edge->ymin + 1; -// } else { /* ymin unchanged. */ -// bb_coord_new->ymin = curr_bb_coord->ymin; -// bb_edge_new->ymin = curr_bb_edge->ymin; -// } -// /* End of move down case. */ -// -// } else if (ynew > yold) { /* Moved up. */ -// -// /* Update the ymin fields for coordinates and number of edges first. */ -// -// if (yold == curr_bb_coord->ymin) { /* Old position at ymin. */ -// if (curr_bb_edge->ymin == 1) { -// get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); -// bb_updated_before[net_id] = GOT_FROM_SCRATCH; -// return; -// } else { -// bb_edge_new->ymin = curr_bb_edge->ymin - 1; -// bb_coord_new->ymin = curr_bb_coord->ymin; -// } -// } else { /* Moved up, old position was not at ymin. */ -// bb_coord_new->ymin = curr_bb_coord->ymin; -// bb_edge_new->ymin = curr_bb_edge->ymin; -// } -// -// /* Now do the ymax fields for coordinates and number of edges. */ -// -// if (ynew > curr_bb_coord->ymax) { /* Moved past ymax. */ -// bb_coord_new->ymax = ynew; -// bb_edge_new->ymax = 1; -// } else if (ynew == curr_bb_coord->ymax) { /* Moved to ymax */ -// bb_coord_new->ymax = ynew; -// bb_edge_new->ymax = curr_bb_edge->ymax + 1; -// } else { /* ymax unchanged. */ -// bb_coord_new->ymax = curr_bb_coord->ymax; -// bb_edge_new->ymax = curr_bb_edge->ymax; -// } -// /* End of move up case. */ -// -// } else { /* ynew == yold -- no y motion. */ -// bb_coord_new->ymin = curr_bb_coord->ymin; -// bb_coord_new->ymax = curr_bb_coord->ymax; -// bb_edge_new->ymin = curr_bb_edge->ymin; -// bb_edge_new->ymax = curr_bb_edge->ymax; -// } -// -// if (bb_updated_before[net_id] == NOT_UPDATED_YET) { -// bb_updated_before[net_id] = UPDATED_ONCE; -// } -//} -// + + xnew = std::max(std::min(xnew, /*device_ctx.*/grid.width() - 2), 1); //-2 for no perim channels + ynew = std::max(std::min(ynew, /*device_ctx.*/grid.height() - 2), 1); //-2 for no perim channels + xold = std::max(std::min(xold, /*device_ctx.*/grid.width() - 2), 1); //-2 for no perim channels + yold = std::max(std::min(yold, /*device_ctx.*/grid.height() - 2), 1); //-2 for no perim channels + + /* Check if the net had been updated before. */ + if (bb_updated_before[net_id] == GOT_FROM_SCRATCH) { + /* The net had been updated from scratch, DO NOT update again! */ + return; + } else if (bb_updated_before[net_id] == NOT_UPDATED_YET) { + /* The net had NOT been updated before, could use the old values */ + curr_bb_coord = &bb_coords[net_id]; + curr_bb_edge = &bb_num_on_edges[net_id]; + bb_updated_before[net_id] = UPDATED_ONCE; + } else { + /* The net had been updated before, must use the new values */ + curr_bb_coord = bb_coord_new; + curr_bb_edge = bb_edge_new; + } + + /* Check if I can update the bounding box incrementally. */ + + if (xnew < xold) { /* Move to left. */ + + /* Update the xmax fields for coordinates and number of edges first. */ + + if (xold == curr_bb_coord->xmax) { /* Old position at xmax. */ + if (curr_bb_edge->xmax == 1) { + get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); + bb_updated_before[net_id] = GOT_FROM_SCRATCH; + return; + } else { + bb_edge_new->xmax = curr_bb_edge->xmax - 1; + bb_coord_new->xmax = curr_bb_coord->xmax; + } + } else { /* Move to left, old postion was not at xmax. */ + bb_coord_new->xmax = curr_bb_coord->xmax; + bb_edge_new->xmax = curr_bb_edge->xmax; + } + + /* Now do the xmin fields for coordinates and number of edges. */ + + if (xnew < curr_bb_coord->xmin) { /* Moved past xmin */ + bb_coord_new->xmin = xnew; + bb_edge_new->xmin = 1; + } else if (xnew == curr_bb_coord->xmin) { /* Moved to xmin */ + bb_coord_new->xmin = xnew; + bb_edge_new->xmin = curr_bb_edge->xmin + 1; + } else { /* Xmin unchanged. */ + bb_coord_new->xmin = curr_bb_coord->xmin; + bb_edge_new->xmin = curr_bb_edge->xmin; + } + /* End of move to left case. */ + + } else if (xnew > xold) { /* Move to right. */ + + /* Update the xmin fields for coordinates and number of edges first. */ + + if (xold == curr_bb_coord->xmin) { /* Old position at xmin. */ + if (curr_bb_edge->xmin == 1) { + get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); + bb_updated_before[net_id] = GOT_FROM_SCRATCH; + return; + } else { + bb_edge_new->xmin = curr_bb_edge->xmin - 1; + bb_coord_new->xmin = curr_bb_coord->xmin; + } + } else { /* Move to right, old position was not at xmin. */ + bb_coord_new->xmin = curr_bb_coord->xmin; + bb_edge_new->xmin = curr_bb_edge->xmin; + } + + /* Now do the xmax fields for coordinates and number of edges. */ + + if (xnew > curr_bb_coord->xmax) { /* Moved past xmax. */ + bb_coord_new->xmax = xnew; + bb_edge_new->xmax = 1; + } else if (xnew == curr_bb_coord->xmax) { /* Moved to xmax */ + bb_coord_new->xmax = xnew; + bb_edge_new->xmax = curr_bb_edge->xmax + 1; + } else { /* Xmax unchanged. */ + bb_coord_new->xmax = curr_bb_coord->xmax; + bb_edge_new->xmax = curr_bb_edge->xmax; + } + /* End of move to right case. */ + + } else { /* xnew == xold -- no x motion. */ + bb_coord_new->xmin = curr_bb_coord->xmin; + bb_coord_new->xmax = curr_bb_coord->xmax; + bb_edge_new->xmin = curr_bb_edge->xmin; + bb_edge_new->xmax = curr_bb_edge->xmax; + } + + /* Now account for the y-direction motion. */ + + if (ynew < yold) { /* Move down. */ + + /* Update the ymax fields for coordinates and number of edges first. */ + + if (yold == curr_bb_coord->ymax) { /* Old position at ymax. */ + if (curr_bb_edge->ymax == 1) { + get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); + bb_updated_before[net_id] = GOT_FROM_SCRATCH; + return; + } else { + bb_edge_new->ymax = curr_bb_edge->ymax - 1; + bb_coord_new->ymax = curr_bb_coord->ymax; + } + } else { /* Move down, old postion was not at ymax. */ + bb_coord_new->ymax = curr_bb_coord->ymax; + bb_edge_new->ymax = curr_bb_edge->ymax; + } + + /* Now do the ymin fields for coordinates and number of edges. */ + + if (ynew < curr_bb_coord->ymin) { /* Moved past ymin */ + bb_coord_new->ymin = ynew; + bb_edge_new->ymin = 1; + } else if (ynew == curr_bb_coord->ymin) { /* Moved to ymin */ + bb_coord_new->ymin = ynew; + bb_edge_new->ymin = curr_bb_edge->ymin + 1; + } else { /* ymin unchanged. */ + bb_coord_new->ymin = curr_bb_coord->ymin; + bb_edge_new->ymin = curr_bb_edge->ymin; + } + /* End of move down case. */ + + } else if (ynew > yold) { /* Moved up. */ + + /* Update the ymin fields for coordinates and number of edges first. */ + + if (yold == curr_bb_coord->ymin) { /* Old position at ymin. */ + if (curr_bb_edge->ymin == 1) { + get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); + bb_updated_before[net_id] = GOT_FROM_SCRATCH; + return; + } else { + bb_edge_new->ymin = curr_bb_edge->ymin - 1; + bb_coord_new->ymin = curr_bb_coord->ymin; + } + } else { /* Moved up, old position was not at ymin. */ + bb_coord_new->ymin = curr_bb_coord->ymin; + bb_edge_new->ymin = curr_bb_edge->ymin; + } + + /* Now do the ymax fields for coordinates and number of edges. */ + + if (ynew > curr_bb_coord->ymax) { /* Moved past ymax. */ + bb_coord_new->ymax = ynew; + bb_edge_new->ymax = 1; + } else if (ynew == curr_bb_coord->ymax) { /* Moved to ymax */ + bb_coord_new->ymax = ynew; + bb_edge_new->ymax = curr_bb_edge->ymax + 1; + } else { /* ymax unchanged. */ + bb_coord_new->ymax = curr_bb_coord->ymax; + bb_edge_new->ymax = curr_bb_edge->ymax; + } + /* End of move up case. */ + + } else { /* ynew == yold -- no y motion. */ + bb_coord_new->ymin = curr_bb_coord->ymin; + bb_coord_new->ymax = curr_bb_coord->ymax; + bb_edge_new->ymin = curr_bb_edge->ymin; + bb_edge_new->ymax = curr_bb_edge->ymax; + } + + if (bb_updated_before[net_id] == NOT_UPDATED_YET) { + bb_updated_before[net_id] = UPDATED_ONCE; + } +} + //static void alloc_legal_placements() { // auto& device_ctx = g_vpr_ctx.device(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); From dbfb518e36bc00bc4fab5f286fb2d7fdf08b8649 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 17:25:56 -0700 Subject: [PATCH 021/116] Unbind all affected blocks before binding new ones --- common/place_vpr.inc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/place_vpr.inc b/common/place_vpr.inc index dfbcd2265d..4eb820f9ba 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -1540,6 +1540,10 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin } // /* Restore the place_ctx.block_locs data structures to their state before the move. */ + for (const auto &b : blocks_affected) { + auto blk = b.first; + npnr_ctx->unbindBel(blk->bel); + } for (const auto &b : blocks_affected) { auto blk = b.first; auto bel = b.second; @@ -1550,9 +1554,6 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin // place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; // place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; - if (npnr_ctx->checkBelAvail(bel)) - npnr_ctx->unbindBel(bel); - npnr_ctx->unbindBel(blk->bel); npnr_ctx->bindBel(bel, blk->name, STRENGTH_WEAK); } } @@ -1897,7 +1898,6 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, int py_to = min_y + y_rel; // *px_to = (*px_to) - grid[*px_to][*py_to].width_offset; /* align it */ // *py_to = (*py_to) - grid[*px_to][*py_to].height_offset; /* align it */ - bel_to = BelId(); throw; } } From 48e4cf391301555fe9ddb80dccf89a1d8d1dc6ed Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 18:35:42 -0700 Subject: [PATCH 022/116] Refactor --- common/place_vpr.cc | 847 ++----------------------------------------- common/place_vpr.inc | 27 -- 2 files changed, 27 insertions(+), 847 deletions(-) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index d336a6b9ed..4b761efb90 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -44,6 +44,33 @@ namespace vpr { using namespace NEXTPNR_NAMESPACE; + + static Context* npnr_ctx = NULL; + static struct { + inline int width() { return w; } + inline int height() { return h; } + int w, h; + } grid; + static struct { + const float inner_num = 10; + } annealing_sched; + + #define VTR_ASSERT NPNR_ASSERT + #define VTR_ASSERT_SAFE NPNR_ASSERT + #define VTR_ASSERT_SAFE_MSG NPNR_ASSERT_MSG + #define vpr_throw(__a, __b, __c, ...) log_error(__VA_ARGS__) + namespace vtr { + constexpr size_t bufsize = 32768; + template + inline void printf_warning(const char*, unsigned, const char* fmt, Args... args) { + log_warning(fmt, std::forward(args)...); + } + template + inline void printf_info(const char* fmt, Args... args) { + log_info(fmt, std::forward(args)...); + } + } + #include "place_vpr.inc" } @@ -345,826 +372,6 @@ class VPRPlacer } #endif - void vpr_initial_placement(size_t& placed_cells, int constr_placed_cells) { - - /* Randomly places the blocks to create an initial placement. We rely on - * the legal_pos array already being loaded. That legal_pos[itype] is an - * array that gives every legal value of (x,y,z) that can accomodate a block. - * The number of such locations is given by num_legal_pos[itype]. - */ -// int itype, x, y, z, ipos; -// int *free_locations; /* [0..device_ctx.num_block_types-1]. -// * Stores how many locations there are for this type that *might* still be free. -// * That is, this stores the number of entries in legal_pos[itype] that are worth considering -// * as you look for a free location. -// */ -// auto& device_ctx = g_vpr_ctx.device(); -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// auto& place_ctx = g_vpr_ctx.mutable_placement(); - - // free_locations is populated in constructor - -// initial_placement_pl_macros(MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY, free_locations); -// -// // All the macros are placed, update the legal_pos[][] array -// for (itype = 0; itype < device_ctx.num_block_types; itype++) { -// VTR_ASSERT(free_locations[itype] >= 0); -// for (ipos = 0; ipos < free_locations[itype]; ipos++) { -// x = legal_pos[itype][ipos].x; -// y = legal_pos[itype][ipos].y; -// z = legal_pos[itype][ipos].z; -// -// // Check if that location is occupied. If it is, remove from legal_pos -// if (place_ctx.grid_blocks[x][y].blocks[z] != EMPTY_BLOCK_ID && place_ctx.grid_blocks[x][y].blocks[z] != INVALID_BLOCK_ID) { -// legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; -// free_locations[itype]--; -// -// // After the move, I need to check this particular entry again -// ipos--; -// continue; -// } -// } -// } // Finish updating the legal_pos[][] and free_locations[] array - - vpr_initial_placement_blocks(placed_cells, constr_placed_cells); - -// if (pad_loc_type == USER) { -// read_user_pad_loc(pad_loc_file); -// } -// -// /* Restore legal_pos */ -// load_legal_placements(); -// -// #ifdef VERBOSE -// vtr::printf_info("At end of initial_placement.\n"); -// if (getEchoEnabled() && isEchoFileEnabled(E_ECHO_INITIAL_CLB_PLACEMENT)) { -// print_clb_placement(getEchoFileName(E_ECHO_INITIAL_CLB_PLACEMENT)); -// } -// #endif -// free(free_locations); - } - - /* Place blocks that are NOT a part of any macro. - * We'll randomly place each block in the clustered netlist, one by one. */ - void vpr_initial_placement_blocks(size_t &placed_cells, int& constr_placed_cells) { -// int itype, ipos, x, y, z; -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// auto& place_ctx = g_vpr_ctx.mutable_placement(); -// auto& device_ctx = g_vpr_ctx.device(); - - size_t itype; - BelId bel; - - // Shuffle all free locations once here, rather than picking a block at random - free_locations_front.reserve(free_locations.size()); - for (auto& free_locations_type : free_locations) { - ctx->shuffle(free_locations_type); - free_locations_front.push_back(free_locations_type.begin()); - } - - for (auto& cell : autoplaced) { -// if (place_ctx.block_locs[blk_id].x != -1) { // -1 is a sentinel for an empty block -// // block placed. -// continue; -// } - -// /* Don't do IOs if the user specifies IOs; we'll read those locations later. */ -// if (!(is_io_type(cluster_ctx.clb_nlist.block_type(blk_id)) && pad_loc_type == USER)) { -// -// /* Randomly select a free location of the appropriate type for blk_id. -// * We have a linearized list of all the free locations that can -// * accomodate a block of that type in free_locations[itype]. -// * Choose one randomly and put blk_id there. Then we don't want to pick -// * that location again, so remove it from the free_locations array. -// */ -// itype = cluster_ctx.clb_nlist.block_type(blk_id)->index; -// if (free_locations[itype] <= 0) { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "Initial placement failed.\n" -// "Could not place block %s (#%zu); no free locations of type %s (#%d).\n", -// cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id), device_ctx.block_types[itype].name, itype); -// } - - vpr_initial_placement_location(cell, itype, bel); - - NPNR_ASSERT(ctx->checkBelAvail(bel)); - ctx->bindBel(bel, cell->name, STRENGTH_WEAK); - -// //Mark IOs as fixed if specifying a (fixed) random placement -// if(is_io_type(cluster_ctx.clb_nlist.block_type(blk_idctx->belTypeFromId(cell->type))) && pad_loc_type == RANDOM) { -// place_ctx.block_locs[blk_id].is_fixed = true; -// } - - if (ctx->isIO(cell)) { - // TODO: Add method to change bind strength without unbinding and re-binding - ctx->unbindBel(bel); - ctx->bindBel(bel, cell->name, STRENGTH_LOCKED); - cell = nullptr; - } - -// /* Ensure randomizer doesn't pick this location again, since it's occupied. Could shift all the -// * legal positions in legal_pos to remove the entry (choice) we just used, but faster to -// * just move the last entry in legal_pos to the spot we just used and decrement the -// * count of free_locations. */ -// legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; /* overwrite used block position */ -// free_locations[itype]--; - - ++free_locations_front[itype]; - -// } - - ++placed_cells; - if ((placed_cells - constr_placed_cells) % 500 == 0) - log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), - int(autoplaced.size())); - } - - // Linear complexity removal of all IOs cells that were set to nullptr - autoplaced.erase(std::remove(autoplaced.begin(), autoplaced.end(), nullptr), autoplaced.end()); - - //if ((placed_cells - constr_placed_cells) % 500 != 0) - log_info(" initial placement finished with %d unconstrained cells\n", int(autoplaced.size())); - - } - - void vpr_initial_placement_location(CellInfo *cell_from, size_t& itype, BelId& bel_to) { -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// - auto type = ctx->belTypeFromId(cell_from->type); - itype = bel_types.at(type); - - for (auto it = free_locations_front[itype]; it != free_locations[itype].end(); ++it) { - bel_to = *it; - ctx->bindBel(bel_to, cell_from->name, STRENGTH_WEAK); - if (!ctx->isBelLocationValid(bel_to)) { - ctx->unbindBel(bel_to); - continue; - } - ctx->unbindBel(bel_to); - std::iter_swap(it, free_locations_front[itype]); - return; - } - log_error(" initial placement failed; unable to find location for '%s'\n", cell_from->name.c_str(ctx)); - } - - float vpr_starting_t(int max_moves) { - - /* Finds the starting temperature (hot condition). */ - - int i, num_accepted, move_lim; - double std_dev, av, sum_of_squares; /* Double important to avoid round off */ - -// if (annealing_sched.type == USER_SCHED) -// return (annealing_sched.init_t); -// -// auto& cluster_ctx = g_vpr_ctx.clustering(); - - move_lim = std::min(max_moves, (int) autoplaced.size()); - - num_accepted = 0; - av = 0.; - sum_of_squares = 0.; - - /* Try one move per block. Set t high so essentially all accepted. */ - - for (i = 0; i < move_lim; i++) { - auto swap_result = vpr_try_swap(std::numeric_limits::max()/*, cost_ptr, bb_cost_ptr, timing_cost_ptr, rlim, - place_algorithm, timing_tradeoff, - inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost_ptr*/); - - if (swap_result/* == ACCEPTED*/) { - num_accepted++; - av += cost; - sum_of_squares += cost * cost; - num_swap_accepted++; - //} else if (swap_result == ABORTED) { - // num_swap_aborted++; - } else { - num_swap_rejected++; - } - } - - if (num_accepted != 0) - av /= num_accepted; - else - av = 0.; - - std_dev = get_std_dev(num_accepted, sum_of_squares, av); - - if (num_accepted != move_lim) { - log_warning("Starting t: %d of %d configurations accepted.\n", num_accepted, move_lim); - } - -// #ifdef VERBOSE - log_info("std_dev: %g, average cost: %g, starting temp: %g\n", std_dev, av, 20. * std_dev); -// #endif - - /* Set the initial temperature to 20 times the standard of deviation */ - /* so that the initial temperature adjusts according to the circuit */ - return (20. * std_dev); - } - - bool vpr_try_swap(float t /*, float *cost, float *bb_cost, float *timing_cost, - float rlim, - enum e_place_algorithm place_algorithm, float timing_tradeoff, - float inverse_prev_bb_cost, float inverse_prev_timing_cost, - float *delay_cost*/) { - -// /* Picks some block and moves it to another spot. If this spot is * -// * occupied, switch the blocks. Assess the change in cost function. * -// * rlim is the range limiter. * -// * Returns whether the swap is accepted, rejected or aborted. * -// * Passes back the new value of the cost functions. */ -// -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// auto& place_ctx = g_vpr_ctx.mutable_placement(); -// -// num_ts_called ++; - - /* I'm using negative values of temp_net_cost as a flag, so DO NOT * - * use cost functions that can go negative. */ - - /*float*/ delta_c = 0; /* Change in cost due to this swap. */ - /*float*/ bb_delta_c = 0; -// float timing_delta_c = 0; -// float delay_delta_c = 0.0; - - /* Pick a random block to be swapped with another random block. */ - auto cell_from = vpr_pick_from_block(); - if (!cell_from) { - return false/*ABORTED*/; //No movable block found - } - -// int x_from = place_ctx.block_locs[b_from].x; -// int y_from = place_ctx.block_locs[b_from].y; -// int z_from = place_ctx.block_locs[b_from].z; -// -// int x_to = OPEN; -// int y_to = OPEN; -// int z_to = OPEN; - - BelId bel_to; - -// auto cluster_from_type = cluster_ctx.clb_nlist.block_type(b_from); -// auto grid_from_type = g_vpr_ctx.device().grid[x_from][y_from].type; -// VTR_ASSERT(cluster_from_type == grid_from_type); - - if (!vpr_find_to(cell_from, bel_to /*cluster_ctx.clb_nlist.block_type(b_from), rlim, x_from, y_from, &x_to, &y_to, &z_to*/)) - return false/*REJECTED*/; - - #if 0 - auto& grid = g_vpr_ctx.device().grid; - int b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; - vtr::printf_info( "swap [%d][%d][%d] %s \"%s\" <=> [%d][%d][%d] %s \"%s\"\n", - x_from, y_from, z_from, grid[x_from][y_from].type->name, (b_from != -1 ? cluster_ctx.blocks[b_from].name : ""), - x_to, y_to, z_to, grid[x_to][y_to].type->name, (b_to != -1 ? cluster_ctx.blocks[b_to].name : "")); - #endif - -// /* Make the switch in order to make computing the new bounding * -// * box simpler. If the cost increase is too high, switch them * -// * back. (place_ctx.block_locs data structures switched, clbs not switched * -// * until success of move is determined.) * -// * Also check that whether those are the only 2 blocks * -// * to be moved - check for carry chains and other placement * -// * macros. */ -// -// /* Check whether the from_block is part of a macro first. * -// * If it is, the whole macro has to be moved. Calculate the * -// * x, y, z offsets of the swap to maintain relative placements * -// * of the blocks. Abort the swap if the to_block is part of a * -// * macro (not supported yet). */ -// -// bool abort_swap = find_affected_blocks(b_from, x_to, y_to, z_to); -// -// if (abort_swap == false) { - - BelId bel_from = cell_from->bel; - IdString other = ctx->getBoundBelCell(bel_to); - CellInfo *cell_to = nullptr; - if (other != IdString()) { - cell_to = ctx->cells[other].get(); - if (cell_to->belStrength > STRENGTH_WEAK) - return false; - } - ctx->unbindBel(bel_from); - if (other != IdString()) { - ctx->unbindBel(bel_to); - } - - for (const auto &port : cell_from->ports) - if (port.second.net != nullptr) - affected_nets.insert(port.second.net); - - if (other != IdString()) { - for (const auto &port : cell_to->ports) - if (port.second.net != nullptr) - affected_nets.insert(port.second.net); - } - - // Find all the nets affected by this swap and update thier bounding box - /*int num_nets_affected =*/ vpr_find_affected_nets_and_update_costs(cell_from, cell_to, bel_from, bel_to, /*place_algorithm,*/ bb_delta_c /*, timing_delta_c, delay_delta_c*/); - - -// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// /*in this case we redefine delta_c as a combination of timing and bb. * -// *additionally, we normalize all values, therefore delta_c is in * -// *relation to 1*/ -// -// delta_c = (1 - timing_tradeoff) * bb_delta_c * inverse_prev_bb_cost -// + timing_tradeoff * timing_delta_c * inverse_prev_timing_cost; -// } else - { - delta_c = bb_delta_c; - } - - /* 1 -> move accepted, 0 -> rejected. */ - auto keep_switch = vpr_assess_swap(t); - - if (keep_switch) { - cost += delta_c; -// bb_cost += bb_delta_c; -// -// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// /*update the point_to_point_timing_cost and point_to_point_delay_cost -// * values from the temporary values */ -// *timing_cost = *timing_cost + timing_delta_c; -// *delay_cost = *delay_cost + delay_delta_c; -// -// update_td_cost(); -// } - -// /* update net cost functions and reset flags. */ -// for (int inet_affected = 0; inet_affected < num_nets_affected; inet_affected++) { -// ClusterNetId net_id = ts_nets_to_update[inet_affected]; -// -// bb_coords[net_id] = ts_bb_coord_new[net_id]; -// if (cluster_ctx.clb_nlist.net_sinks(net_id).size() >= SMALL_NET) -// bb_num_on_edges[net_id] = ts_bb_edge_new[net_id]; -// -// net_cost[net_id] = temp_net_cost[net_id]; -// -// /* negative temp_net_cost value is acting as a flag. */ -// temp_net_cost[net_id] = -1; -// bb_updated_before[net_id] = NOT_UPDATED_YET; -// } -// -// /* Update clb data structures since we kept the move. */ -// /* Swap physical location */ -// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { -// -// x_to = blocks_affected.moved_blocks[iblk].xnew; -// y_to = blocks_affected.moved_blocks[iblk].ynew; -// z_to = blocks_affected.moved_blocks[iblk].znew; -// -// x_from = blocks_affected.moved_blocks[iblk].xold; -// y_from = blocks_affected.moved_blocks[iblk].yold; -// z_from = blocks_affected.moved_blocks[iblk].zold; -// -// b_from = blocks_affected.moved_blocks[iblk].block_num; -// -// place_ctx.grid_blocks[x_to][y_to].blocks[z_to] = b_from; -// -// if (blocks_affected.moved_blocks[iblk].swapped_to_was_empty) { -// place_ctx.grid_blocks[x_to][y_to].usage++; -// } -// if (blocks_affected.moved_blocks[iblk].swapped_from_is_empty) { -// place_ctx.grid_blocks[x_from][y_from].usage--; -// place_ctx.grid_blocks[x_from][y_from].blocks[z_from] = EMPTY_BLOCK_ID; -// } -// -// } // Finish updating clb for all blocks - - for (auto new_wl : new_lengths) - wirelengths.at(new_wl.first) = new_wl.second; - - - } else { /* Move was rejected. */ - -// /* Reset the net cost function flags first. */ -// for (int inet_affected = 0; inet_affected < num_nets_affected; inet_affected++) { -// ClusterNetId net_id = ts_nets_to_update[inet_affected]; -// temp_net_cost[net_id] = -1; -// bb_updated_before[net_id] = NOT_UPDATED_YET; -// } -// -// /* Restore the place_ctx.block_locs data structures to their state before the move. */ -// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { -// b_from = blocks_affected.moved_blocks[iblk].block_num; -// -// place_ctx.block_locs[b_from].x = blocks_affected.moved_blocks[iblk].xold; -// place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; -// place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; -// } - - if (other != IdString()) - ctx->unbindBel(bel_from); - ctx->unbindBel(bel_to); - ctx->bindBel(bel_from, cell_from->name, STRENGTH_WEAK); - if (other != IdString()) - ctx->bindBel(bel_to, other, STRENGTH_WEAK); - return false; - } - -// /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ -// blocks_affected.num_moved_blocks = 0; -// -// #if 0 -// //Check that each accepted swap yields a valid placement -// check_place(*bb_cost, *timing_cost, place_algorithm, *delay_cost); -// #endif - - return (keep_switch); -// } else { -// -// /* Restore the place_ctx.block_locs data structures to their state before the move. */ -// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { -// b_from = blocks_affected.moved_blocks[iblk].block_num; -// -// place_ctx.block_locs[b_from].x = blocks_affected.moved_blocks[iblk].xold; -// place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; -// place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; -// } -// -// /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ -// blocks_affected.num_moved_blocks = 0; -// -// return ABORTED; -// } - } - - //Pick a random block to be swapped with another random block. - //If none is found return ClusterBlockId::INVALID() - CellInfo* vpr_pick_from_block() { -// /* Some blocks may be fixed, and should never be moved from their * -// * initial positions. If we randomly selected such a block try * -// * another random block. * -// * * -// * We need to track the blocks we have tried to avoid an infinite * -// * loop if all blocks are fixed. */ -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// auto& place_ctx = g_vpr_ctx.mutable_placement(); -// -// std::unordered_set tried_from_blocks; -// -// //So long as untried blocks remain -// while (tried_from_blocks.size() < cluster_ctx.clb_nlist.blocks().size()) { -// -// //Pick a block at random -// ClusterBlockId b_from = ClusterBlockId(vtr::irand((int) cluster_ctx.clb_nlist.blocks().size() - 1)); -// -// //Record it as tried -// tried_from_blocks.insert(b_from); -// -// if (place_ctx.block_locs[b_from].is_fixed) { -// continue; //Fixed location, try again -// } -// -// //Found a movable block -// return b_from; -// } -// -// //No movable blocks found -// return ClusterBlockId::INVALID(); - - // Assume that autoplaced only contains movable blocks - return autoplaced.at(ctx->rng(int(autoplaced.size()))); - } - - bool vpr_find_to(CellInfo* cell_from, BelId& bel_to) { - - /* Returns the point to which I want to swap, properly range limited. - * rlim must always be between 1 and device_ctx.grid.width() - 2 (inclusive) for this routine - * to work. Note -2 for no perim channels - */ - - int min_x, max_x, min_y, max_y; - int num_tries; - int active_area; - bool is_legal; -// int itype; -// -// auto& grid = g_vpr_ctx.device().grid; -// auto& place_ctx = g_vpr_ctx.placement(); -// -// auto grid_type = grid[x_from][y_from].type; -// VTR_ASSERT(type == grid_type); - - int rlx = std::min(this->max_x, rlim); - int rly = std::min(this->max_y, rlim); /* Added rly for aspect_ratio != 1 case. */ - active_area = 4 * rlx * rly; - - int x_from, y_from; - bool gb; - ctx->estimatePosition(cell_from->bel, x_from, y_from, gb); - - min_x = std::max(0, x_from - rlx); - max_x = std::min(this->max_x, x_from + rlx); - min_y = std::max(0, y_from - rly); - max_y = std::min(this->max_y, y_from + rly); - - if (rlx < 1 || rlx > int(this->max_x)) { - log_error("in find_to: rlx = %d out of range\n", rlx); - } - if (rly < 1 || rly > int(this->max_y)) { - log_error("in find_to: rly = %d out of range\n", rly); - } - - num_tries = 0; -// itype = type->index; - auto type = ctx->belTypeFromId(cell_from->type); - auto itype = bel_types.at(type); - - int px_to, py_to; - - do { /* Until legal */ - is_legal = true; - - /* Limit the number of tries when searching for an alternative position */ - if(num_tries >= 2 * std::min(active_area /*/ (type->width * type->height)*/, free_locations[itype].size()) + 10) { - /* Tried randomly searching for a suitable position */ - return false; - } else { - num_tries++; - } - - vpr_find_to_location(cell_from, bel_to); - ctx->estimatePosition(bel_to, px_to, py_to, gb); - - if((x_from == px_to) && (y_from == py_to)) { - is_legal = false; - } else if(px_to > max_x || px_to < min_x || py_to > max_y || py_to < min_y) { - is_legal = false; - } else if(type != ctx->getBelType(bel_to)) { - is_legal = false; - } else { - auto bel_from = cell_from->bel; - IdString other = ctx->getBoundBelCell(bel_to); - if (other != IdString()) { - auto cell_to = ctx->cells[other].get(); - if (cell_to->belStrength > STRENGTH_WEAK) - is_legal = false; - } - - if (is_legal) { - ctx->unbindBel(bel_from); - if (other != IdString()) - ctx->unbindBel(bel_to); - ctx->bindBel(bel_to, cell_from->name, STRENGTH_WEAK); - if (other != IdString()) - ctx->bindBel(bel_from, other, STRENGTH_WEAK); - if (!ctx->isBelLocationValid(bel_to) || ((other != IdString() && !ctx->isBelLocationValid(bel_from)))) { - is_legal = false; - } - ctx->unbindBel(bel_to); - if (other != IdString()) - ctx->unbindBel(bel_from); - ctx->bindBel(bel_from, cell_from->name, STRENGTH_WEAK); - if (other != IdString()) { - ctx->bindBel(bel_to, other, STRENGTH_WEAK); - } - } - -// /* Find z_to and test to validate that the "to" block is *not* fixed */ -// *pz_to = 0; -// if (grid[*px_to][*py_to].type->capacity > 1) { -// *pz_to = vtr::irand(grid[*px_to][*py_to].type->capacity - 1); -// } -// ClusterBlockId b_to = place_ctx.grid_blocks[*px_to][*py_to].blocks[*pz_to]; -// if ((b_to != EMPTY_BLOCK_ID) && (place_ctx.block_locs[b_to].is_fixed == true)) { -// is_legal = false; -// } - } - - NPNR_ASSERT(px_to >= 0 && px_to <= int(this->max_x)); - NPNR_ASSERT(py_to >= 0 && py_to <= int(this->max_y)); - } while (is_legal == false); - - if (px_to < 0 || px_to > int(this->max_x) || py_to < 0 || py_to > int(this->max_y)) { - log_error("in routine find_to: (x_to,y_to) = (%d,%d)\n", px_to, py_to); - } - - NPNR_ASSERT(type == ctx->getBelType(bel_to)); - return true; - } - - void vpr_find_to_location(CellInfo* cell, BelId& bel/*t_type_ptr type, float rlim, - int x_from, int y_from, - int *px_to, int *py_to, int *pz_to*/) { - -// auto& device_ctx = g_vpr_ctx.device(); -// auto& grid = device_ctx.grid; -// -// int itype = type->index; - auto type = ctx->belTypeFromId(cell->type); - auto itype = bel_types.at(type); - - int x_from, y_from; - bool gb; - ctx->estimatePosition(cell->bel, x_from, y_from, gb); - - int rlx = std::min(this->max_x, rlim); - int rly = std::min(this->max_y, rlim); /* Added rly for aspect_ratio != 1 case. */ - unsigned active_area = 4 * rlx * rly; - - int min_x = std::max(0, x_from - rlx); - int max_x = std::min(this->max_x, x_from + rlx); - int min_y = std::max(0, y_from - rly); - int max_y = std::min(this->max_y, y_from + rly); - - //*pz_to = 0; - if (int(max_x / 4) < rlx || int(max_y / 4) < rly || free_locations[itype].size() < active_area) { - int ipos = ctx->rng(free_locations[itype].size()); - bel = free_locations[itype][ipos]; -// *px_to = [itype][ipos].x; -// *py_to = [itype][ipos].y; -// *pz_to = [itype][ipos].z; - } else { - int x_rel = ctx->rng(std::max(0, max_x - min_x)+1); - int y_rel = ctx->rng(std::max(0, max_y - min_y)+1); -// *px_to = min_x + x_rel; -// *py_to = min_y + y_rel; -// *px_to = (*px_to) - grid[*px_to][*py_to].width_offset; /* align it */ -// *py_to = (*py_to) - grid[*px_to][*py_to].height_offset; /* align it */ - int px_to = min_x + x_rel; - int py_to = min_y + y_rel; - if (px_to >= int(fast_bels.at(itype).size())) { - bel = BelId(); - return; - } - if (py_to >= int(fast_bels.at(itype).at(px_to).size())) { - bel = BelId(); - return; - } - const auto &fb = fast_bels.at(itype).at(px_to).at(py_to); - if (fb.size() == 0) { - bel = BelId(); - return; - } - bel = fb.at(ctx->rng(int(fb.size()))); - // TODO: Remove locked_bels from fb - if (locked_bels.find(bel) != locked_bels.end()) { - bel = BelId(); - return; - } - } - } - - bool vpr_assess_swap(/*float delta_c,*/ float t) { - -// /* Returns: 1 -> move accepted, 0 -> rejected. */ -// - bool accept; - float prob_fac, fnum; - - if (delta_c <= 0) { - - /* Reduce variation in final solution due to round off */ - fnum = ctx->rng() / float(0x3fffffff); - - accept = true; - return (accept); - } - - if (t == 0.) - return false; - - fnum = ctx->rng() / float(0x3fffffff); - prob_fac = std::exp(-delta_c / t); - if (prob_fac > fnum) { - accept = true; - } - else { - accept = false; - } - return (accept); - } - - bool vpr_exit_crit(float t, float cost) { -// /* Return 1 when the exit criterion is met. */ -// -// if (annealing_sched.type == USER_SCHED) { -// if (t < annealing_sched.exit_t) { -// return (1); -// } else { -// return (0); -// } -// } -// -// auto& cluster_ctx = g_vpr_ctx.clustering(); - - /* Automatic annealing schedule */ - float t_exit = 0.005 * cost / ctx->nets.size(); - - if (t < t_exit) { - return (1); - } else if (std::isnan(t_exit)) { - //May get nan if there are no nets - return (1); - } else { - return (0); - } - } - - /* Update the temperature according to the annealing schedule selected. */ - void vpr_update_t(float success_rat) { - - /* float fac; */ - -// if (annealing_sched.type == USER_SCHED) { -// *t = annealing_sched.alpha_t * (*t); -// } else - { /* AUTO_SCHED */ - if (success_rat > 0.96) { - t *= 0.5; - } else if (success_rat > 0.8) { - t *= 0.9; - } else if (success_rat > 0.15 || rlim > 1.) { - t *= 0.95; - } else { - t *= 0.8; - } - } - } - - void vpr_find_affected_nets_and_update_costs(CellInfo* cell_from, CellInfo* cell_to, BelId bel_from, BelId bel_to, /*e_place_algorithm place_algorithm,*/ float& bb_delta_c /*, float& timing_delta_c, float& delay_delta_c*/) { -// VTR_ASSERT_SAFE(bb_delta_c == 0.); -// VTR_ASSERT_SAFE(timing_delta_c == 0.); -// VTR_ASSERT_SAFE(delay_delta_c == 0.); -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// int num_affected_nets = 0; - affected_nets.clear(); - -// //Go through all the blocks moved -// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { -// ClusterBlockId blk = blocks_affected.moved_blocks[iblk].block_num; -// -// //Go through all the pins in the moved block -// for (ClusterPinId blk_pin : cluster_ctx.clb_nlist.block_pins(blk)) { -// ClusterNetId net_id = cluster_ctx.clb_nlist.pin_net(blk_pin); -// VTR_ASSERT_SAFE_MSG(net_id, "Only valid nets should be found in compressed netlist block pins"); -// -// if (cluster_ctx.clb_nlist.net_is_global(net_id)) -// continue; //Global nets are assumed to span the whole chip, and do not effect costs -// -// //Record effected nets -// record_affected_net(net_id, num_affected_nets); -// -// //Update the net bounding boxes -// // -// //Do not update the net cost here since it should only be updated -// //once per net, not once per pin. -// update_net_bb(net_id, iblk, blk, blk_pin); -// -// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// //Determine the change in timing costs if required -// update_td_delta_costs(net_id, blk_pin, timing_delta_c, delay_delta_c); -// } -// } -// } - - for (const auto &port : cell_from->ports) - if (port.second.net != nullptr) - affected_nets.insert(port.second.net); - - if (cell_to) { - for (const auto &port : cell_to->ports) - if (port.second.net != nullptr) - affected_nets.insert(port.second.net); - } - - ctx->bindBel(bel_to, cell_from->name, STRENGTH_WEAK); - if (cell_to) { - ctx->bindBel(bel_from, cell_to->name, STRENGTH_WEAK); - } - - -// /* Now update the bounding box costs (since the net bounding boxes are up-to-date). -// * The cost is only updated once per net. -// */ -// for (int inet_affected = 0; inet_affected < num_affected_nets; inet_affected++) { -// ClusterNetId net_id = ts_nets_to_update[inet_affected]; -// -// temp_net_cost[net_id] = get_net_cost(net_id, &ts_bb_coord_new[net_id]); -// bb_delta_c += temp_net_cost[net_id] - net_cost[net_id]; -// } - - auto new_wirelength = curr_wirelength; - - // Recalculate wirelengths for all nets touched by the peturbation - for (auto net : affected_nets) { - new_wirelength -= wirelengths.at(net->name); - float temp_tns = 0; - wirelen_t net_new_wl = get_net_wirelength(ctx, net, temp_tns); - new_wirelength += net_new_wl; - new_lengths.push_back(std::make_pair(net->name, net_new_wl)); - } - bb_delta_c = new_wirelength - curr_wirelength; - -// return num_affected_nets; - } - - // Attempt a SA position swap, return true on success or false on failure bool try_swap_position(CellInfo *cell, BelId newBel) { diff --git a/common/place_vpr.inc b/common/place_vpr.inc index 4eb820f9ba..bf8b035b43 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -332,33 +332,6 @@ static void placement_inner_loop(float t, float rlim, /*t_placer_opts placer_opt const ClusteredPinAtomPinsLookup& netlist_pin_lookup, SetupTimingInfo& timing_info*/); -static Context* npnr_ctx = NULL; -static struct { - inline int width() { return w; } - inline int height() { return h; } - int w, h; -} grid; -static struct { - const float inner_num = 10; -} annealing_sched; - -#define VTR_ASSERT NPNR_ASSERT -#define VTR_ASSERT_SAFE NPNR_ASSERT -#define VTR_ASSERT_SAFE_MSG NPNR_ASSERT_MSG -#define vpr_throw(__a, __b, __c, ...) log_error(__VA_ARGS__) -namespace vtr { - constexpr size_t bufsize = 32768; - template - inline void printf_warning(const char*, unsigned, const char* fmt, Args... args) { - log_warning(fmt, std::forward(args)...); - } - template - inline void printf_info(const char* fmt, Args... args) { - log_info(fmt, std::forward(args)...); - } -} - - /*****************************************************************************/ void try_place(/*t_placer_opts placer_opts, t_annealing_sched annealing_sched, From 14ae6ca529679a4a0faae979fe37e335b70a05d7 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 18:38:14 -0700 Subject: [PATCH 023/116] More refactoring --- common/place_vpr.cc | 11 +++++++---- common/place_vpr.inc | 9 +-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index 4b761efb90..4057c74954 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -47,9 +47,9 @@ namespace vpr { static Context* npnr_ctx = NULL; static struct { - inline int width() { return w; } - inline int height() { return h; } - int w, h; + inline int width() { return _width; } + inline int height() { return _height; } + int _width, _height; } grid; static struct { const float inner_num = 10; @@ -140,7 +140,10 @@ class VPRPlacer { log_break(); - vpr::try_place(ctx, max_x + 1, max_y + 1); + vpr::npnr_ctx = ctx; + vpr::grid._width = max_x + 1; + vpr::grid._height = max_y + 1; + vpr::try_place(); size_t placed_cells = 0; // Initial constraints placer diff --git a/common/place_vpr.inc b/common/place_vpr.inc index bf8b035b43..b9d8780824 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -340,10 +340,7 @@ void try_place(/*t_placer_opts placer_opts, #ifdef ENABLE_CLASSIC_VPR_STA t_timing_inf timing_inf, #endif - t_direct_inf *directs, int num_directs*/ - Context *npnr_ctx_, - const int grid_width, - const int grid_height) { + t_direct_inf *directs, int num_directs*/) { // /* Does almost all the work of placing a circuit. Width_fac gives the * // * width of the widest channel. Place_cost_exp says what exponent the * @@ -384,10 +381,6 @@ void try_place(/*t_placer_opts placer_opts, num_swap_aborted = 0; num_ts_called = 0; - npnr_ctx = npnr_ctx_; - grid.w = grid_width; - grid.h = grid_height; - // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE // || placer_opts.enable_timing_computations) { // /*do this before the initial placement to avoid messing up the initial placement */ From eb082094bf8188bd941834edbd0d09814e4dda55 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 18:51:28 -0700 Subject: [PATCH 024/116] Support grid[x][y] --- common/place_vpr.cc | 33 +++++++++++---------------------- common/place_vpr.inc | 12 ++++++------ 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index 4057c74954..9618e0c356 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -49,7 +49,9 @@ namespace vpr { static struct { inline int width() { return _width; } inline int height() { return _height; } + inline const std::vector& operator[](size_t x) { return _bels.at(x); } int _width, _height; + std::vector> _bels; } grid; static struct { const float inner_num = 10; @@ -76,28 +78,6 @@ namespace vpr { NEXTPNR_NAMESPACE_BEGIN -static double get_std_dev(int n, double sum_x_squared, double av_x) { - - /* Returns the standard deviation of data set x. There are n sample points, * - * sum_x_squared is the summation over n of x^2 and av_x is the average x. * - * All operations are done in double precision, since round off error can be * - * a problem in the initial temp. std_dev calculation for big circuits. */ - - double std_dev; - - if (n <= 1) - std_dev = 0.; - else - std_dev = (sum_x_squared - n * av_x * av_x) / (double) (n - 1); - - if (std_dev > 0.) /* Very small variances sometimes round negative */ - std_dev = sqrt(std_dev); - else - std_dev = 0.; - - return (std_dev); -} - class VPRPlacer { private: @@ -143,6 +123,15 @@ class VPRPlacer vpr::npnr_ctx = ctx; vpr::grid._width = max_x + 1; vpr::grid._height = max_y + 1; + vpr::grid._bels.resize(vpr::grid._width); + for (auto& r : vpr::grid._bels) + r.resize(vpr::grid._height, BelId()); + for (auto bel : ctx->getBels()) { + int x, y; + bool gb; + ctx->estimatePosition(bel, x, y, gb); + vpr::grid._bels[x][y] = bel; + } vpr::try_place(); size_t placed_cells = 0; diff --git a/common/place_vpr.inc b/common/place_vpr.inc index b9d8780824..6ed016ef79 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -1787,6 +1787,11 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, px_to, py_to, pz_to*/, bel_to); + if (bel_to == BelId()) { + is_legal = false; + continue; + } + bool gb; npnr_ctx->estimatePosition(bel_to, px_to, py_to, gb); @@ -1853,18 +1858,13 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, // *pz_to = 0; if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || legal_pos[type].size() < active_area) { int ipos = npnr_ctx->rng(legal_pos[type].size()); -// *px_to = legal_pos[itype][ipos].x; -// *py_to = legal_pos[itype][ipos].y; -// *pz_to = legal_pos[itype][ipos].z; bel_to = legal_pos[type][ipos]; } else { int x_rel = npnr_ctx->rng(std::max(0, max_x - min_x)); int y_rel = npnr_ctx->rng(std::max(0, max_y - min_y)); int px_to = min_x + x_rel; int py_to = min_y + y_rel; -// *px_to = (*px_to) - grid[*px_to][*py_to].width_offset; /* align it */ -// *py_to = (*py_to) - grid[*px_to][*py_to].height_offset; /* align it */ - throw; + bel_to = grid[px_to][py_to]; } } From 9ebfb8f7d98d58a1401d97829978633f165a0f35 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 19:01:26 -0700 Subject: [PATCH 025/116] Cleanup --- common/place_vpr.cc | 390 ++------------------------------------------ 1 file changed, 12 insertions(+), 378 deletions(-) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index 9618e0c356..b100f4952f 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -47,10 +47,9 @@ namespace vpr { static Context* npnr_ctx = NULL; static struct { - inline int width() { return _width; } - inline int height() { return _height; } + inline int width() { return _bels.size(); } + inline int height() { return _bels.front().size(); } inline const std::vector& operator[](size_t x) { return _bels.at(x); } - int _width, _height; std::vector> _bels; } grid; static struct { @@ -87,209 +86,30 @@ class VPRPlacer public: VPRPlacer(Context *ctx) : ctx(ctx) { - int num_bel_types = 0; + vpr::npnr_ctx = ctx; + int max_y = 0; for (auto bel : ctx->getBels()) { int x, y; bool gb; ctx->estimatePosition(bel, x, y, gb); - BelType type = ctx->getBelType(bel); - int type_idx; - if (bel_types.find(type) == bel_types.end()) { - type_idx = num_bel_types++; - bel_types[type] = type_idx; - } else { - type_idx = bel_types.at(type); - } - if (int(fast_bels.size()) < type_idx + 1) { - fast_bels.resize(type_idx + 1); - free_locations.resize(type_idx + 1); + if (x >= int(vpr::grid._bels.size())) + vpr::grid._bels.resize(x+1); + if (y >= int(vpr::grid._bels[x].size())) { + vpr::grid._bels[x].resize(y+1,BelId()); + max_y = std::max(y, max_y); } - if (int(fast_bels.at(type_idx).size()) < (x + 1)) - fast_bels.at(type_idx).resize(x + 1); - if (int(fast_bels.at(type_idx).at(x).size()) < (y + 1)) - fast_bels.at(type_idx).at(x).resize(y + 1); - max_x = std::max(max_x, x); - max_y = std::max(max_y, y); - fast_bels.at(type_idx).at(x).at(y).push_back(bel); - free_locations[type_idx].push_back(bel); + vpr::grid._bels[x][y] = bel; } - diameter = std::max(max_x, max_y) + 1; + for (auto& c : vpr::grid._bels) + c.resize(max_y, BelId()); } bool place() { log_break(); - vpr::npnr_ctx = ctx; - vpr::grid._width = max_x + 1; - vpr::grid._height = max_y + 1; - vpr::grid._bels.resize(vpr::grid._width); - for (auto& r : vpr::grid._bels) - r.resize(vpr::grid._height, BelId()); - for (auto bel : ctx->getBels()) { - int x, y; - bool gb; - ctx->estimatePosition(bel, x, y, gb); - vpr::grid._bels[x][y] = bel; - } vpr::try_place(); - size_t placed_cells = 0; - // Initial constraints placer - for (auto &cell_entry : ctx->cells) { - CellInfo *cell = cell_entry.second.get(); - auto loc = cell->attrs.find(ctx->id("BEL")); - if (loc != cell->attrs.end()) { - std::string loc_name = loc->second; - BelId bel = ctx->getBelByName(ctx->id(loc_name)); - //if (bel == BelId()) { - // log_error("No Bel named \'%s\' located for " - // "this chip (processing BEL attribute on \'%s\')\n", - // loc_name.c_str(), cell->name.c_str(ctx)); - //} - - //BelType bel_type = ctx->getBelType(bel); - //if (bel_type != ctx->belTypeFromId(cell->type)) { - // log_error("Bel \'%s\' of type \'%s\' does not match cell " - // "\'%s\' of type \'%s\'", - // loc_name.c_str(), ctx->belTypeToId(bel_type).c_str(ctx), cell->name.c_str(ctx), - // cell->type.c_str(ctx)); - //} - - //ctx->bindBel(bel, cell->name, STRENGTH_USER); - locked_bels.insert(bel); - placed_cells++; - } - } - //int constr_placed_cells = placed_cells; - log_info("Placed %d cells based on constraints.\n", int(placed_cells)); - - // Sort to-place cells for deterministic initial placement - std::vector autoplaced; - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); -// if (ci->bel == BelId()) { - if (ci->belStrength == STRENGTH_WEAK) { - autoplaced.push_back(cell.second.get()); - } - } - std::sort(autoplaced.begin(), autoplaced.end(), [](CellInfo *a, CellInfo *b) { return a->name < b->name; }); - ctx->shuffle(autoplaced); - -// // Place cells randomly initially -// log_info("Creating initial placement for remaining %d cells.\n", int(autoplaced.size())); -// -// for (auto cell : autoplaced) { -// place_initial(cell); -// placed_cells++; -// if ((placed_cells - constr_placed_cells) % 500 == 0) -// log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), -// int(autoplaced.size())); -// } -// if ((placed_cells - constr_placed_cells) % 500 != 0) -// log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells), -// int(autoplaced.size())); - - log_info("Running simulated annealing placer on %d cells.\n", int(autoplaced.size())); - - // Calculate wirelength after initial placement - curr_wirelength = 0; - curr_tns = 0; - for (auto &net : ctx->nets) { - wirelen_t wl = get_net_wirelength(ctx, net.second.get(), curr_tns); - wirelengths[net.first] = wl; - curr_wirelength += wl; - } - - int n_no_progress = 0; - double avg_wirelength = curr_wirelength; - temp = 10000; - - // Main simulated annealing loop - for (int iter = 1;; iter++) { - n_move = n_accept = 0; - improved = false; - - if (iter % 5 == 0 || iter == 1) - log_info(" at iteration #%d: temp = %f, wire length = " - "%.0f, est tns = %.02fns\n", - iter, temp, double(curr_wirelength), curr_tns); - - for (int m = 0; m < 15; ++m) { - // Loop through all automatically placed cells - for (auto cell : autoplaced) { - // Find another random Bel for this cell - BelId try_bel = random_bel_for_cell(cell); - // If valid, try and swap to a new position and see if - // the new position is valid/worthwhile - if (try_bel != BelId() && try_bel != cell->bel) - try_swap_position(cell, try_bel); - } - } - // Heuristic to improve placement on the 8k - if (improved) - n_no_progress = 0; - else - n_no_progress++; - - if (temp <= 1e-3 && n_no_progress >= 5) { - if (iter % 5 != 0) - log_info(" at iteration #%d: temp = %f, wire length = %f\n", iter, temp, double(curr_wirelength)); - break; - } - - double Raccept = double(n_accept) / double(n_move); - - int M = std::max(max_x, max_y) + 1; - - double upper = 0.6, lower = 0.4; - - if (curr_wirelength < 0.95 * avg_wirelength) { - avg_wirelength = 0.8 * avg_wirelength + 0.2 * curr_wirelength; - } else { - if (Raccept >= 0.8) { - temp *= 0.7; - } else if (Raccept > upper) { - if (diameter < M) - diameter++; - else - temp *= 0.9; - } else if (Raccept > lower) { - temp *= 0.95; - } else { - // Raccept < 0.3 - if (diameter > 1) - diameter--; - else - temp *= 0.8; - } - } - // Once cooled below legalise threshold, run legalisation and start requiring - // legal moves only - if (temp < legalise_temp && !require_legal) { - legalise_design(ctx); - require_legal = true; - autoplaced.clear(); - for (auto cell : sorted(ctx->cells)) { - if (cell.second->belStrength < STRENGTH_STRONG) - autoplaced.push_back(cell.second); - } - temp = post_legalise_temp; - diameter *= post_legalise_dia_scale; - ctx->shuffle(autoplaced); - assign_budget(ctx); - } - - // Recalculate total wirelength entirely to avoid rounding errors - // accumulating over time - curr_wirelength = 0; - curr_tns = 0; - for (auto &net : ctx->nets) { - wirelen_t wl = get_net_wirelength(ctx, net.second.get(), curr_tns); - wirelengths[net.first] = wl; - curr_wirelength += wl; - } - } // Final post-pacement validitiy check for (auto bel : ctx->getBels()) { IdString cell = ctx->getBoundBelCell(bel); @@ -312,193 +132,7 @@ class VPRPlacer } private: -#if 0 - // Initial random placement - void place_initial(CellInfo *cell) - { - bool all_placed = false; - int iters = 25; - while (!all_placed) { - BelId best_bel = BelId(); - uint64_t best_score = std::numeric_limits::max(), - best_ripup_score = std::numeric_limits::max(); - CellInfo *ripup_target = nullptr; - BelId ripup_bel = BelId(); - if (cell->bel != BelId()) { - ctx->unbindBel(cell->bel); - } - BelType targetType = ctx->belTypeFromId(cell->type); - for (auto bel : ctx->getBels()) { - if (ctx->getBelType(bel) == targetType && (ctx->isValidBelForCell(cell, bel) || !require_legal)) { - if (ctx->checkBelAvail(bel)) { - uint64_t score = ctx->rng64(); - if (score <= best_score) { - best_score = score; - best_bel = bel; - } - } else { - uint64_t score = ctx->rng64(); - if (score <= best_ripup_score) { - best_ripup_score = score; - ripup_target = ctx->cells.at(ctx->getBoundBelCell(bel)).get(); - ripup_bel = bel; - } - } - } - } - if (best_bel == BelId()) { - if (iters == 0 || ripup_bel == BelId()) - log_error("failed to place cell '%s' of type '%s'\n", cell->name.c_str(ctx), cell->type.c_str(ctx)); - --iters; - ctx->unbindBel(ripup_target->bel); - best_bel = ripup_bel; - } else { - all_placed = true; - } - ctx->bindBel(best_bel, cell->name, STRENGTH_WEAK); - - // Back annotate location - cell->attrs[ctx->id("BEL")] = ctx->getBelName(cell->bel).str(ctx); - cell = ripup_target; - } - } -#endif - - // Attempt a SA position swap, return true on success or false on failure - bool try_swap_position(CellInfo *cell, BelId newBel) - { - static std::unordered_set update; - static std::vector> new_lengths; - new_lengths.clear(); - update.clear(); - BelId oldBel = cell->bel; - IdString other = ctx->getBoundBelCell(newBel); - CellInfo *other_cell = nullptr; - if (other != IdString()) { - other_cell = ctx->cells[other].get(); - if (other_cell->belStrength > STRENGTH_WEAK) - return false; - } - wirelen_t new_wirelength = 0, delta; - ctx->unbindBel(oldBel); - if (other != IdString()) { - ctx->unbindBel(newBel); - } - - for (const auto &port : cell->ports) - if (port.second.net != nullptr) - update.insert(port.second.net); - - if (other != IdString()) { - for (const auto &port : other_cell->ports) - if (port.second.net != nullptr) - update.insert(port.second.net); - } - - ctx->bindBel(newBel, cell->name, STRENGTH_WEAK); - - if (other != IdString()) { - ctx->bindBel(oldBel, other_cell->name, STRENGTH_WEAK); - } - if (require_legal) { - if (!ctx->isBelLocationValid(newBel) || ((other != IdString() && !ctx->isBelLocationValid(oldBel)))) { - ctx->unbindBel(newBel); - if (other != IdString()) - ctx->unbindBel(oldBel); - goto swap_fail; - } - } - - new_wirelength = curr_wirelength; - - // Recalculate wirelengths for all nets touched by the peturbation - for (auto net : update) { - new_wirelength -= wirelengths.at(net->name); - float temp_tns = 0; - wirelen_t net_new_wl = get_net_wirelength(ctx, net, temp_tns); - new_wirelength += net_new_wl; - new_lengths.push_back(std::make_pair(net->name, net_new_wl)); - } - delta = new_wirelength - curr_wirelength; - n_move++; - // SA acceptance criterea - if (delta < 0 || (temp > 1e-6 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) { - n_accept++; - if (delta < 2) - improved = true; - } else { - if (other != IdString()) - ctx->unbindBel(oldBel); - ctx->unbindBel(newBel); - goto swap_fail; - } - curr_wirelength = new_wirelength; - for (auto new_wl : new_lengths) - wirelengths.at(new_wl.first) = new_wl.second; - - return true; - swap_fail: - ctx->bindBel(oldBel, cell->name, STRENGTH_WEAK); - if (other != IdString()) { - ctx->bindBel(newBel, other, STRENGTH_WEAK); - } - return false; - } - - // Find a random Bel of the correct type for a cell, within the specified - // diameter - BelId random_bel_for_cell(CellInfo *cell) - { - BelType targetType = ctx->belTypeFromId(cell->type); - int x, y; - bool gb; - ctx->estimatePosition(cell->bel, x, y, gb); - while (true) { - int nx = ctx->rng(2 * diameter + 1) + std::max(x - diameter, 0); - int ny = ctx->rng(2 * diameter + 1) + std::max(y - diameter, 0); - int beltype_idx = bel_types.at(targetType); - if (nx >= int(fast_bels.at(beltype_idx).size())) - continue; - if (ny >= int(fast_bels.at(beltype_idx).at(nx).size())) - continue; - const auto &fb = fast_bels.at(beltype_idx).at(nx).at(ny); - if (fb.size() == 0) - continue; - BelId bel = fb.at(ctx->rng(int(fb.size()))); - if (locked_bels.find(bel) != locked_bels.end()) - continue; - return bel; - } - } - Context *ctx; - std::unordered_map wirelengths; - wirelen_t curr_wirelength = std::numeric_limits::max(); - float curr_tns = 0; - float temp = 1000; - bool improved = false; - int n_move, n_accept; - int diameter = 35, max_x = 1, max_y = 1; - std::unordered_map bel_types; - std::vector>>> fast_bels; - std::unordered_set locked_bels; - bool require_legal = false; - const float legalise_temp = 1; - const float post_legalise_temp = 20; - const float post_legalise_dia_scale = 2; - std::vector autoplaced; - - float t = 1000; - const float inner_num = 1.0; - int move_lim, tot_iter; - float rlim; - float cost /*, bb_cost*/; - float delta_c, bb_delta_c; - float success_sum; - float success_rat; - std::unordered_set affected_nets; - std::vector> new_lengths; - int num_swap_accepted, num_swap_rejected; }; bool place_design_vpr(Context *ctx) From efa1c1e45996c8fa260db8468edd30a7028cdbe0 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 19:04:47 -0700 Subject: [PATCH 026/116] Cleanup some more --- common/place_vpr.cc | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index b100f4952f..17c0eaf7f9 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -79,10 +79,6 @@ NEXTPNR_NAMESPACE_BEGIN class VPRPlacer { - private: - std::vector> free_locations; - std::vector::iterator> free_locations_front; - public: VPRPlacer(Context *ctx) : ctx(ctx) { @@ -101,7 +97,7 @@ class VPRPlacer vpr::grid._bels[x][y] = bel; } for (auto& c : vpr::grid._bels) - c.resize(max_y, BelId()); + c.resize(max_y+1, BelId()); } bool place() From a384d3a8baa9accfb8089b35ae900e15b415e2ef Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 20:16:56 -0700 Subject: [PATCH 027/116] initial_placement_location() to only pick valid cells too --- common/place_vpr.inc | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/common/place_vpr.inc b/common/place_vpr.inc index 6ed016ef79..8769c7486b 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -219,7 +219,7 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type static void initial_placement_location(/*int * free_locations, ClusterBlockId blk_id, int *pipos, int *px, int *py, int *pz*/ - const std::unordered_map> &free_locations, + std::unordered_map> &free_locations, CellInfo *cell, BelId& bel); @@ -1208,6 +1208,7 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to npnr_ctx->unbindBel(bel_from); npnr_ctx->bindBel(bel_to, b_from->name, STRENGTH_WEAK); + //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel_to)); // // Sets up the blocks moved // imoved_blk = blocks_affected.num_moved_blocks; @@ -1246,6 +1247,8 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to npnr_ctx->unbindBel(bel_from); npnr_ctx->bindBel(bel_to, b_from->name, STRENGTH_WEAK); npnr_ctx->bindBel(bel_from, b_to->name, STRENGTH_WEAK); + //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel_to)); + //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel_from)); // // Sets up the blocks moved // imoved_blk = blocks_affected.num_moved_blocks; @@ -1521,6 +1524,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin // place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; npnr_ctx->bindBel(bel, blk->name, STRENGTH_WEAK); + //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel)); } } @@ -1812,14 +1816,20 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, // is_legal = false; //} - if (!npnr_ctx->isValidBelForCell(cell_from, bel_to)) + if (!npnr_ctx->isValidBelForCell(cell_from, bel_to)) { is_legal = false; - - auto cell_name = npnr_ctx->getBoundBelCell(bel_to); - if (cell_name != IdString()) { - auto cell = npnr_ctx->cells[cell_name].get(); - if (cell->belStrength > STRENGTH_WEAK) - is_legal = false; + } + else { + auto cell_name = npnr_ctx->getBoundBelCell(bel_to); + if (cell_name != IdString()) { + auto cell_to = npnr_ctx->cells[cell_name].get(); + if (cell_to->belStrength > STRENGTH_WEAK) { + is_legal = false; + } + else if (!npnr_ctx->isValidBelForCell(cell_to, cell_from->bel)) { + is_legal = false; + } + } } } @@ -2882,6 +2892,7 @@ static int try_place_macro(/*int itype, int ipos, int imacro*/ } npnr_ctx->bindBel(bel, cell->name, STRENGTH_USER); + //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel)); return true; } @@ -3023,6 +3034,7 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type npnr_ctx->bindBel(bel, cell->name, /*STRENGTH_LOCKED*/STRENGTH_STRONG); else npnr_ctx->bindBel(bel, cell->name, STRENGTH_WEAK); + //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel)); // /* Ensure randomizer doesn't pick this location again, since it's occupied. Could shift all the // * legal positions in legal_pos to remove the entry (choice) we just used, but faster to @@ -3031,7 +3043,7 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; /* overwrite used block position */ // free_locations[itype]--; - free_locations[cell->type].pop_back(); + free_locations.at(cell->type).pop_back(); // } } @@ -3039,7 +3051,7 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type static void initial_placement_location(/*int * free_locations, ClusterBlockId blk_id, int *pipos, int *px, int *py, int *pz*/ - const std::unordered_map> &free_locations, + std::unordered_map> &free_locations, CellInfo *cell, BelId &bel) { @@ -3052,7 +3064,16 @@ static void initial_placement_location(/*int * free_locations, ClusterBlockId bl // *py_to = legal_pos[itype][*pipos].y; // *pz_to = legal_pos[itype][*pipos].z; - bel = free_locations.at(cell->type).back(); + auto it = free_locations.at(cell->type).rbegin(); + auto ie = free_locations.at(cell->type).rend(); + for (; it != ie; ++it) { + if (!npnr_ctx->isValidBelForCell(cell, *it)) + continue; + bel = *it; + std::swap(*it, free_locations.at(cell->type).back()); + return; + } + throw; } static void initial_placement(/*enum e_pad_loc_type pad_loc_type, From 24fe95d682adebcd2662d04660fb38b1b14ac81d Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 20:42:36 -0700 Subject: [PATCH 028/116] Check for global nets --- common/place_vpr.inc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/common/place_vpr.inc b/common/place_vpr.inc index 8769c7486b..cc454e9bc4 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -94,7 +94,6 @@ struct t_placer_statistics { /* Cost of a net, and a temporary cost of a net used during move assessment. */ static std::unordered_map net_cost, temp_net_cost; -//static t_legal_pos **legal_pos = nullptr; /* [0..device_ctx.num_block_types-1][0..type_tsize - 1] */ static std::unordered_map> legal_pos; //static int *num_legal_pos = nullptr; /* [0..num_legal_pos-1] */ @@ -1617,8 +1616,9 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit if (!net_id) continue; VTR_ASSERT_SAFE_MSG(net_id, "Only valid nets should be found in compressed netlist block pins"); -// if (cluster_ctx.clb_nlist.net_is_global(net_id)) -// continue; //Global nets are assumed to span the whole chip, and do not effect costs + + if (!npnr_ctx->isGlobalNet(net_id)) + continue; //Global nets are assumed to span the whole chip, and do not effect costs //Record effected nets record_affected_net(net_id, num_affected_nets); @@ -1919,10 +1919,10 @@ static float recompute_bb_cost() { for (auto& n : npnr_ctx->nets) { /* for each net ... */ auto net_id = n.second.get(); -// if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ + if (!npnr_ctx->isGlobalNet(net_id)) { /* Do only if not global. */ /* Bounding boxes don't have to be recomputed; they're correct. */ cost += net_cost[net_id]; -// } + } } return (cost); @@ -2087,7 +2087,7 @@ static float comp_bb_cost(/*e_cost_methods method*/) { for (auto& n : npnr_ctx->nets) { /* for each net ... */ auto net_id = n.second.get(); -// if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ + if (!npnr_ctx->isGlobalNet(net_id)) { /* Do only if not global. */ /* Small nets don't use incremental updating on their bounding boxes, * * so they can use a fast bounding box calculator. */ if (net_id->users.size() >= SMALL_NET /*&& method == NORMAL*/) { @@ -2102,7 +2102,7 @@ static float comp_bb_cost(/*e_cost_methods method*/) { cost += net_cost[net_id]; // if (method == CHECK) // expected_wirelength += get_net_wirelength_estimate(net_id, &bb_coords[net_id]); -// } + } } // if (method == CHECK) { From 2438c4530ba8468884e9870b2df49d6f19130569 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 20:42:58 -0700 Subject: [PATCH 029/116] Add Arch::isGlobalNet() function for generic arch --- generic/arch.cc | 1 + generic/arch.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/generic/arch.cc b/generic/arch.cc index 5a1f5053d3..78a0e8dedd 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -328,6 +328,7 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort IdString Arch::getPortClock(const CellInfo *cell, IdString port) const { return IdString(); } bool Arch::isClockPort(const CellInfo *cell, IdString port) const { return false; } +bool Arch::isGlobalNet(const NetInfo *net) const { return false; } bool Arch::isIO(const CellInfo *cell) const { return false; } bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; } diff --git a/generic/arch.h b/generic/arch.h index 5c48aebb8e..160f256180 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -169,6 +169,8 @@ struct Arch : BaseCtx bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const; IdString getPortClock(const CellInfo *cell, IdString port) const; bool isClockPort(const CellInfo *cell, IdString port) const; + // Return true if a port is a net + bool isGlobalNet(const NetInfo *net) const; // Return true if cell is a IO bool isIO(const CellInfo* cell) const; From a2edccc152ce2f602483b35c320c30262a88e85b Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 22:51:50 -0700 Subject: [PATCH 030/116] Skip initial placement of already placed cells, plus fix global net check --- common/place_vpr.inc | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/common/place_vpr.inc b/common/place_vpr.inc index cc454e9bc4..704612ee0c 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -1617,7 +1617,7 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit VTR_ASSERT_SAFE_MSG(net_id, "Only valid nets should be found in compressed netlist block pins"); - if (!npnr_ctx->isGlobalNet(net_id)) + if (npnr_ctx->isGlobalNet(net_id)) continue; //Global nets are assumed to span the whole chip, and do not effect costs //Record effected nets @@ -2991,7 +2991,7 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // continue; // } - if (cell->belStrength == STRENGTH_USER) + if (cell->bel != BelId()) continue; // /* Don't do IOs if the user specifies IOs; we'll read those locations later. */ @@ -3148,14 +3148,10 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, for (auto it = free_locations.begin(); it != free_locations.end(); it++) { for (auto ipos = it->second.begin(); ipos != it->second.end(); ) { auto cell_name = npnr_ctx->getBoundBelCell(*ipos); - if (cell_name != IdString()) { - auto cell = npnr_ctx->cells[cell_name].get(); - if (cell->belStrength == STRENGTH_USER) { - ipos = it->second.erase(ipos); - continue; - } - } - ipos++; + if (cell_name != IdString()) + ipos = it->second.erase(ipos); + else + ipos++; } } From ae5e8a4802933882b658f5a23be81cb0c737f220 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 22:52:20 -0700 Subject: [PATCH 031/116] Add ice40 architecture specific place phase for placing all reset/cen gbs --- ice40/main.cc | 3 ++- ice40/place.cc | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ ice40/place.h | 32 ++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 ice40/place.cc create mode 100644 ice40/place.h diff --git a/ice40/main.cc b/ice40/main.cc index b26a5744f5..22bce7468b 100644 --- a/ice40/main.cc +++ b/ice40/main.cc @@ -42,8 +42,8 @@ #include "pack.h" #include "pcf.h" #include "place_legaliser.h" -#include "place_sa.h" #include "place_vpr.h" +#include "place.h" #include "route.h" #include "timing.h" #include "version.h" @@ -370,6 +370,7 @@ int main(int argc, char *argv[]) if (vm.count("no-tmdriv")) ctx.timing_driven = false; if (!vm.count("pack-only")) { + place_gbs(&ctx); if (!place_design_vpr(&ctx) && !ctx.force) log_error("Placing design failed.\n"); ctx.check(); diff --git a/ice40/place.cc b/ice40/place.cc new file mode 100644 index 0000000000..23f9415223 --- /dev/null +++ b/ice40/place.cc @@ -0,0 +1,67 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "design_utils.h" +#include "log.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +void place_gbs(Context *ctx) +{ + std::vector gb_reset; + std::vector gb_cen; + + for (auto bel : ctx->getBels()) { + BelType type = ctx->getBelType(bel); + if (type == TYPE_SB_GB) { + IdString glb_net = ctx->getWireName(ctx->getWireBelPin(bel, PIN_GLOBAL_BUFFER_OUTPUT)); + int glb_id = std::stoi(std::string("") + glb_net.str(ctx).back()); + if (glb_id % 2 == 0) + gb_reset.push_back(bel); + else + gb_cen.push_back(bel); + } + } + for (auto &c : ctx->cells) { + CellInfo *cell = c.second.get(); + if (cell->type == ctx->id_sb_gb) { + bool is_reset = false, is_cen = false; + NPNR_ASSERT(cell->ports.at(ctx->id_glb_buf_out).net != nullptr); + for (auto user : cell->ports.at(ctx->id_glb_buf_out).net->users) { + if (ctx->isResetPort(user)) + is_reset = true; + if (ctx->isEnablePort(user)) + is_cen = true; + } + NPNR_ASSERT(!is_reset || !is_cen); + if (is_reset) { + ctx->bindBel(gb_reset.back(), cell->name, STRENGTH_WEAK); + gb_reset.pop_back(); + } + else if (is_cen) { + ctx->bindBel(gb_cen.back(), cell->name, STRENGTH_WEAK); + gb_cen.pop_back(); + } + } + } +} + +NEXTPNR_NAMESPACE_END diff --git a/ice40/place.h b/ice40/place.h new file mode 100644 index 0000000000..aa2b8264d6 --- /dev/null +++ b/ice40/place.h @@ -0,0 +1,32 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * Copyright (C) 2018 David Shah + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef PLACE_H +#define PLACE_H + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +bool place_gbs(Context *ctx); + +NEXTPNR_NAMESPACE_END + +#endif // PLACE_H From a7d674d0c7601ff049efb6ec995bc1bc2fee0e44 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 14 Jul 2018 22:53:11 -0700 Subject: [PATCH 032/116] Speedup Arch::isValidBelForCell() --- ice40/arch.cc | 50 +++++++++++++++++++++++++++++++++++++++++++++ ice40/arch.h | 7 +++++++ ice40/arch_place.cc | 7 ++++--- ice40/cells.cc | 35 ------------------------------- ice40/cells.h | 20 ++---------------- ice40/pack.cc | 12 +++++------ 6 files changed, 69 insertions(+), 62 deletions(-) diff --git a/ice40/arch.cc b/ice40/arch.cc index 866fd12d89..bd96681be9 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -22,6 +22,7 @@ #include "log.h" #include "nextpnr.h" #include "util.h" +#include "cells.h" NEXTPNR_NAMESPACE_BEGIN // ----------------------------------------------------------------------- @@ -183,6 +184,19 @@ Arch::Arch(ArchArgs args) : args(args) id_i3 = id("I3"); id_dff_en = id("DFF_ENABLE"); id_neg_clk = id("NEG_CLK"); + id_r = id("R"); + id_s = id("S"); + id_e = id("E"); + id_set_ff = { + id("SB_DFF"), id("SB_DFFE"), id("SB_DFFSR"), + id("SB_DFFR"), id("SB_DFFSS"), id("SB_DFFS"), + id("SB_DFFESR"), id("SB_DFFER"), + id("SB_DFFESS"), id("SB_DFFES"), + id("SB_DFFN"), id("SB_DFFNE"), + id("SB_DFFNSR"), id("SB_DFFNR"), + id("SB_DFFNSS"), id("SB_DFFNS"), + id("SB_DFFNESR"), id("SB_DFFNER"), + id("SB_DFFNESS"), id("SB_DFFNES") }; } // ----------------------------------------------------------------------- @@ -543,4 +557,40 @@ bool Arch::isIO(const CellInfo* cell) const return cell->type == id("SB_IO"); } +bool Arch::isClockPort(const PortRef &port) const +{ + if (port.cell == nullptr) + return false; + if (isFF(port.cell)) + return port.port == id("C"); + if (port.cell->type == id("ICESTORM_LC")) + return port.port == id("CLK"); + if (is_ram(this, port.cell) || port.cell->type == id("ICESTORM_RAM")) + return port.port == id("RCLK") || port.port == id("WCLK"); + return false; +} + +bool Arch::isResetPort(const PortRef &port) const +{ + if (port.cell == nullptr) + return false; + if (isFF(port.cell)) + return port.port == id_r || port.port == id_s; + if (port.cell->type == id_icestorm_lc) + return port.port == id_sr; + return false; +} + +bool Arch::isEnablePort(const PortRef &port) const +{ + if (port.cell == nullptr) + return false; + if (isFF(port.cell)) + return port.port == id_e; + if (port.cell->type == id_icestorm_lc) + return port.port == id_cen; + return false; +} + + NEXTPNR_NAMESPACE_END diff --git a/ice40/arch.h b/ice40/arch.h index cfe083f4ae..f40855e113 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -667,6 +667,10 @@ struct Arch : BaseCtx bool isGlobalNet(const NetInfo *net) const; // Return true if cell is a IO bool isIO(const CellInfo* cell) const; + bool isClockPort(const PortRef &port) const; + bool isResetPort(const PortRef &port) const; + bool isEnablePort(const PortRef &port) const; + inline bool isFF(const CellInfo *cell) const { return id_set_ff.count(cell->type); } // ------------------------------------------------- @@ -688,6 +692,9 @@ struct Arch : BaseCtx IdString id_cen, id_clk, id_sr; IdString id_i0, id_i1, id_i2, id_i3; IdString id_dff_en, id_neg_clk; + IdString id_r, id_s; + IdString id_e; + std::unordered_set id_set_ff; }; NEXTPNR_NAMESPACE_END diff --git a/ice40/arch_place.cc b/ice40/arch_place.cc index dc1bc3ebe7..916c211784 100644 --- a/ice40/arch_place.cc +++ b/ice40/arch_place.cc @@ -101,7 +101,8 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const if (cell->type == id_icestorm_lc) { NPNR_ASSERT(getBelType(bel) == TYPE_ICESTORM_LC); - std::vector bel_cells; + static std::vector bel_cells; + bel_cells.clear(); for (auto bel_other : getBelsAtSameTile(bel)) { IdString cell_other = getBoundBelCell(bel_other); @@ -119,9 +120,9 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const bool is_reset = false, is_cen = false; NPNR_ASSERT(cell->ports.at(id_glb_buf_out).net != nullptr); for (auto user : cell->ports.at(id_glb_buf_out).net->users) { - if (is_reset_port(this, user)) + if (isResetPort(user)) is_reset = true; - if (is_enable_port(this, user)) + if (isEnablePort(user)) is_cen = true; } IdString glb_net = getWireName(getWireBelPin(bel, PIN_GLOBAL_BUFFER_OUTPUT)); diff --git a/ice40/cells.cc b/ice40/cells.cc index 1ba409706e..4e8d90e5e9 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -247,39 +247,4 @@ void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio) } } -bool is_clock_port(const BaseCtx *ctx, const PortRef &port) -{ - if (port.cell == nullptr) - return false; - if (is_ff(ctx, port.cell)) - return port.port == ctx->id("C"); - if (port.cell->type == ctx->id("ICESTORM_LC")) - return port.port == ctx->id("CLK"); - if (is_ram(ctx, port.cell) || port.cell->type == ctx->id("ICESTORM_RAM")) - return port.port == ctx->id("RCLK") || port.port == ctx->id("WCLK"); - return false; -} - -bool is_reset_port(const BaseCtx *ctx, const PortRef &port) -{ - if (port.cell == nullptr) - return false; - if (is_ff(ctx, port.cell)) - return port.port == ctx->id("R") || port.port == ctx->id("S"); - if (port.cell->type == ctx->id("ICESTORM_LC")) - return port.port == ctx->id("SR"); - return false; -} - -bool is_enable_port(const BaseCtx *ctx, const PortRef &port) -{ - if (port.cell == nullptr) - return false; - if (is_ff(ctx, port.cell)) - return port.port == ctx->id("E"); - if (port.cell->type == ctx->id("ICESTORM_LC")) - return port.port == ctx->id("CEN"); - return false; -} - NEXTPNR_NAMESPACE_END diff --git a/ice40/cells.h b/ice40/cells.h index 9f99835db4..c17c6d1157 100644 --- a/ice40/cells.h +++ b/ice40/cells.h @@ -33,17 +33,9 @@ std::unique_ptr create_ice_cell(Context *ctx, IdString type, std::stri inline bool is_lut(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_LUT4"); } // Return true if a cell is a flipflop -inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) +inline bool is_ff(const Context *ctx, const CellInfo *cell) { - return cell->type == ctx->id("SB_DFF") || cell->type == ctx->id("SB_DFFE") || cell->type == ctx->id("SB_DFFSR") || - cell->type == ctx->id("SB_DFFR") || cell->type == ctx->id("SB_DFFSS") || cell->type == ctx->id("SB_DFFS") || - cell->type == ctx->id("SB_DFFESR") || cell->type == ctx->id("SB_DFFER") || - cell->type == ctx->id("SB_DFFESS") || cell->type == ctx->id("SB_DFFES") || - cell->type == ctx->id("SB_DFFN") || cell->type == ctx->id("SB_DFFNE") || - cell->type == ctx->id("SB_DFFNSR") || cell->type == ctx->id("SB_DFFNR") || - cell->type == ctx->id("SB_DFFNSS") || cell->type == ctx->id("SB_DFFNS") || - cell->type == ctx->id("SB_DFFNESR") || cell->type == ctx->id("SB_DFFNER") || - cell->type == ctx->id("SB_DFFNESS") || cell->type == ctx->id("SB_DFFNES"); + return ctx->isFF(cell); } inline bool is_carry(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_CARRY"); } @@ -83,14 +75,6 @@ void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_l // Convert a nextpnr IO buffer to a SB_IO void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio); -// Return true if a port is a clock port -bool is_clock_port(const BaseCtx *ctx, const PortRef &port); - -// Return true if a port is a reset port -bool is_reset_port(const BaseCtx *ctx, const PortRef &port); - -// Return true if a port is a clock enable port -bool is_enable_port(const BaseCtx *ctx, const PortRef &port); NEXTPNR_NAMESPACE_END diff --git a/ice40/pack.cc b/ice40/pack.cc index d1be4a29c8..d4dd6d8d5a 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -94,7 +94,7 @@ static void pack_nonlut_ffs(Context *ctx) for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; - if (is_ff(ctx, ci)) { + if (ctx->isFF(ci)) { std::unique_ptr packed = create_ice_cell(ctx, ctx->id("ICESTORM_LC"), ci->name.str(ctx) + "_DFFLC"); std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(packed->attrs, packed->attrs.begin())); @@ -445,8 +445,8 @@ static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen gb->ports[ctx->id("GLOBAL_BUFFER_OUTPUT")].net = glbnet.get(); std::vector keep_users; for (auto user : net->users) { - if (is_clock_port(ctx, user) || (is_reset && is_reset_port(ctx, user)) || - (is_cen && is_enable_port(ctx, user))) { + if (ctx->isClockPort(user) || (is_reset && ctx->isResetPort(user)) || + (is_cen && ctx->isEnablePort(user))) { user.cell->ports[user.port].net = glbnet.get(); glbnet->users.push_back(user); } else { @@ -472,11 +472,11 @@ static void promote_globals(Context *ctx) cen_count[net.first] = 0; for (auto user : ni->users) { - if (is_clock_port(ctx, user)) + if (ctx->isClockPort(user)) clock_count[net.first]++; - if (is_reset_port(ctx, user)) + if (ctx->isResetPort(user)) reset_count[net.first]++; - if (is_enable_port(ctx, user)) + if (ctx->isEnablePort(user)) cen_count[net.first]++; } } From 894b430bde3308006dadaa1377deef2748aee0c8 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 15 Jul 2018 09:58:25 -0700 Subject: [PATCH 033/116] Optimise pick_from_block sampling by using vector of cells --- common/place_vpr.cc | 9 +++++++++ common/place_vpr.inc | 4 +--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/common/place_vpr.cc b/common/place_vpr.cc index 17c0eaf7f9..179cf924c3 100644 --- a/common/place_vpr.cc +++ b/common/place_vpr.cc @@ -46,6 +46,8 @@ namespace vpr { using namespace NEXTPNR_NAMESPACE; static Context* npnr_ctx = NULL; + std::vector npnr_cells; + static struct { inline int width() { return _bels.size(); } inline int height() { return _bels.front().size(); } @@ -98,6 +100,13 @@ class VPRPlacer } for (auto& c : vpr::grid._bels) c.resize(max_y+1, BelId()); + + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->bel == BelId()) { + vpr::npnr_cells.push_back(cell.second.get()); + } + } } bool place() diff --git a/common/place_vpr.inc b/common/place_vpr.inc index 704612ee0c..767ecb6a62 100644 --- a/common/place_vpr.inc +++ b/common/place_vpr.inc @@ -1574,9 +1574,7 @@ static /*ClusterBlockId*/ CellInfo* pick_from_block() { //Pick a block at random //ClusterBlockId b_from = ClusterBlockId(vtr::irand((int) cluster_ctx.clb_nlist.blocks().size() - 1)); - auto it = npnr_ctx->cells.cbegin(); - std::advance(it, npnr_ctx->rng(npnr_ctx->cells.size())); - auto b_from = it->second.get(); + auto b_from = npnr_cells.at(npnr_ctx->rng(npnr_cells.size())); //Record it as tried tried_from_blocks.insert(b_from); From 3aebb48cf94bd423e752bc68b11ad9c8942a2a44 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 15 Jul 2018 10:12:16 -0700 Subject: [PATCH 034/116] Fix header guards --- common/place_sa.h | 6 +++--- ice40/pack.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/place_sa.h b/common/place_sa.h index 1fd8c712a0..d41a5cb17c 100644 --- a/common/place_sa.h +++ b/common/place_sa.h @@ -16,8 +16,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ -#ifndef PLACE_H -#define PLACE_H +#ifndef PLACE_SA_H +#define PLACE_SA_H #include "nextpnr.h" @@ -27,4 +27,4 @@ extern bool place_design_sa(Context *ctx); NEXTPNR_NAMESPACE_END -#endif // PLACE_H +#endif // PLACE_SA_H diff --git a/ice40/pack.h b/ice40/pack.h index cdebdd7904..02f7d05fe7 100644 --- a/ice40/pack.h +++ b/ice40/pack.h @@ -29,4 +29,4 @@ bool pack_design(Context *ctx); NEXTPNR_NAMESPACE_END -#endif // ROUTE_H +#endif // PACK_H From a0db9a05783e9cbc8ceb42a84e527c324201b3d6 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 15 Jul 2018 10:12:30 -0700 Subject: [PATCH 035/116] Add vpr_place function --- ice40/main.cc | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ice40/main.cc b/ice40/main.cc index 22bce7468b..04ffd3a6e1 100644 --- a/ice40/main.cc +++ b/ice40/main.cc @@ -42,6 +42,7 @@ #include "pack.h" #include "pcf.h" #include "place_legaliser.h" +#include "place_sa.h" #include "place_vpr.h" #include "place.h" #include "route.h" @@ -110,6 +111,7 @@ int main(int argc, char *argv[]) options.add_options()("package", po::value(), "set device package"); options.add_options()("save", po::value(), "project file to write"); options.add_options()("load", po::value(), "project file to read"); + options.add_options()("vpr_place", "use VPR placer"); po::variables_map vm; try { @@ -370,9 +372,15 @@ int main(int argc, char *argv[]) if (vm.count("no-tmdriv")) ctx.timing_driven = false; if (!vm.count("pack-only")) { - place_gbs(&ctx); - if (!place_design_vpr(&ctx) && !ctx.force) - log_error("Placing design failed.\n"); + if (vm.count("vpr_place")) { + place_gbs(&ctx); + if (!place_design_vpr(&ctx) && !ctx.force) + log_error("Placing design failed.\n"); + } + else { + if (!place_design_sa(&ctx) && !ctx.force) + log_error("Placing design failed.\n"); + } ctx.check(); if (!route_design(&ctx) && !ctx.force) log_error("Routing design failed.\n"); From cd530c2fd615bb45a9578f000828433c506b23ed Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 15 Jul 2018 12:57:27 -0700 Subject: [PATCH 036/116] Move place_gbs into place_vpr() --- ice40/arch.cc | 47 ++++++++++++++++++++++++++++++++--- ice40/place.cc | 67 -------------------------------------------------- ice40/place.h | 32 ------------------------ 3 files changed, 44 insertions(+), 102 deletions(-) delete mode 100644 ice40/place.cc delete mode 100644 ice40/place.h diff --git a/ice40/arch.cc b/ice40/arch.cc index 6960336f06..c414138d74 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -26,7 +26,6 @@ #include "router1.h" #include "util.h" #include "cells.h" -#include "place.h" #include "place_vpr.h" NEXTPNR_NAMESPACE_BEGIN @@ -464,8 +463,50 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const bool Arch::place() { return placer1(getCtx()); } bool Arch::place_vpr() { - place_gbs(getCtx()); - return place_design_vpr(getCtx()); + auto ctx = getCtx(); + + // VPR's initial placement is greedy and will place each cell + // into a randomly selected bel. However, the ice40's GBs + // have restrictions on which are not amenable to this greedy + // approach. Work around this my placing those restrictive + // GBs first. + std::vector gb_reset; + std::vector gb_cen; + for (auto bel : ctx->getBels()) { + BelType type = ctx->getBelType(bel); + if (type == TYPE_SB_GB) { + IdString glb_net = ctx->getWireName(ctx->getWireBelPin(bel, PIN_GLOBAL_BUFFER_OUTPUT)); + int glb_id = std::stoi(std::string("") + glb_net.str(ctx).back()); + if (glb_id % 2 == 0) + gb_reset.push_back(bel); + else + gb_cen.push_back(bel); + } + } + for (auto &c : ctx->cells) { + CellInfo *cell = c.second.get(); + if (cell->type == ctx->id_sb_gb) { + bool is_reset = false, is_cen = false; + NPNR_ASSERT(cell->ports.at(ctx->id_glb_buf_out).net != nullptr); + for (auto user : cell->ports.at(ctx->id_glb_buf_out).net->users) { + if (ctx->isResetPort(user)) + is_reset = true; + if (ctx->isEnablePort(user)) + is_cen = true; + } + NPNR_ASSERT(!is_reset || !is_cen); + if (is_reset) { + ctx->bindBel(gb_reset.back(), cell->name, STRENGTH_WEAK); + gb_reset.pop_back(); + } + else if (is_cen) { + ctx->bindBel(gb_cen.back(), cell->name, STRENGTH_WEAK); + gb_cen.pop_back(); + } + } + } + + return place_design_vpr(ctx); } bool Arch::route() { return router1(getCtx()); } diff --git a/ice40/place.cc b/ice40/place.cc deleted file mode 100644 index 23f9415223..0000000000 --- a/ice40/place.cc +++ /dev/null @@ -1,67 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Clifford Wolf - * Copyright (C) 2018 David Shah - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include "design_utils.h" -#include "log.h" -#include "util.h" - -NEXTPNR_NAMESPACE_BEGIN - -void place_gbs(Context *ctx) -{ - std::vector gb_reset; - std::vector gb_cen; - - for (auto bel : ctx->getBels()) { - BelType type = ctx->getBelType(bel); - if (type == TYPE_SB_GB) { - IdString glb_net = ctx->getWireName(ctx->getWireBelPin(bel, PIN_GLOBAL_BUFFER_OUTPUT)); - int glb_id = std::stoi(std::string("") + glb_net.str(ctx).back()); - if (glb_id % 2 == 0) - gb_reset.push_back(bel); - else - gb_cen.push_back(bel); - } - } - for (auto &c : ctx->cells) { - CellInfo *cell = c.second.get(); - if (cell->type == ctx->id_sb_gb) { - bool is_reset = false, is_cen = false; - NPNR_ASSERT(cell->ports.at(ctx->id_glb_buf_out).net != nullptr); - for (auto user : cell->ports.at(ctx->id_glb_buf_out).net->users) { - if (ctx->isResetPort(user)) - is_reset = true; - if (ctx->isEnablePort(user)) - is_cen = true; - } - NPNR_ASSERT(!is_reset || !is_cen); - if (is_reset) { - ctx->bindBel(gb_reset.back(), cell->name, STRENGTH_WEAK); - gb_reset.pop_back(); - } - else if (is_cen) { - ctx->bindBel(gb_cen.back(), cell->name, STRENGTH_WEAK); - gb_cen.pop_back(); - } - } - } -} - -NEXTPNR_NAMESPACE_END diff --git a/ice40/place.h b/ice40/place.h deleted file mode 100644 index aa2b8264d6..0000000000 --- a/ice40/place.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Clifford Wolf - * Copyright (C) 2018 David Shah - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef PLACE_H -#define PLACE_H - -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -bool place_gbs(Context *ctx); - -NEXTPNR_NAMESPACE_END - -#endif // PLACE_H From 02f534a07469d42aa845272a63eb2c37fcd8678a Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 15 Jul 2018 13:01:26 -0700 Subject: [PATCH 037/116] Make consistent with upstream --- common/{place_vpr.cc => placer_vpr.cc} | 6 +++--- common/{place_vpr.h => placer_vpr.h} | 8 ++++---- common/{place_vpr.inc => placer_vpr.inc} | 0 ice40/arch.cc | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) rename common/{place_vpr.cc => placer_vpr.cc} (98%) rename common/{place_vpr.h => placer_vpr.h} (89%) rename common/{place_vpr.inc => placer_vpr.inc} (100%) diff --git a/common/place_vpr.cc b/common/placer_vpr.cc similarity index 98% rename from common/place_vpr.cc rename to common/placer_vpr.cc index 179cf924c3..bea48225b3 100644 --- a/common/place_vpr.cc +++ b/common/placer_vpr.cc @@ -21,7 +21,7 @@ * */ -#include "place_vpr.h" +#include "placer_vpr.h" #include #include #include @@ -74,7 +74,7 @@ namespace vpr { } } - #include "place_vpr.inc" + #include "placer_vpr.inc" } NEXTPNR_NAMESPACE_BEGIN @@ -140,7 +140,7 @@ class VPRPlacer Context *ctx; }; -bool place_design_vpr(Context *ctx) +bool placer_vpr(Context *ctx) { try { VPRPlacer placer(ctx); diff --git a/common/place_vpr.h b/common/placer_vpr.h similarity index 89% rename from common/place_vpr.h rename to common/placer_vpr.h index 6b7c6fb9ad..eff3ab939e 100644 --- a/common/place_vpr.h +++ b/common/placer_vpr.h @@ -16,15 +16,15 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ -#ifndef PLACE_VPR_H -#define PLACE_VPR_H +#ifndef PLACER_VPR_H +#define PLACER_VPR_H #include "nextpnr.h" NEXTPNR_NAMESPACE_BEGIN -extern bool place_design_vpr(Context *ctx); +extern bool placer_vpr(Context *ctx); NEXTPNR_NAMESPACE_END -#endif // PLACE_VPR_H +#endif // PLACER_VPR_H diff --git a/common/place_vpr.inc b/common/placer_vpr.inc similarity index 100% rename from common/place_vpr.inc rename to common/placer_vpr.inc diff --git a/ice40/arch.cc b/ice40/arch.cc index c414138d74..0ab92cc88e 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -26,7 +26,7 @@ #include "router1.h" #include "util.h" #include "cells.h" -#include "place_vpr.h" +#include "placer_vpr.h" NEXTPNR_NAMESPACE_BEGIN @@ -466,10 +466,10 @@ bool Arch::place_vpr() auto ctx = getCtx(); // VPR's initial placement is greedy and will place each cell - // into a randomly selected bel. However, the ice40's GBs - // have restrictions on which are not amenable to this greedy - // approach. Work around this my placing those restrictive - // GBs first. + // into a randomly selected bel of the same type. + // However, the ice40's GBs have restrictions on which are + // not amenable to this greedy approach. + // Work around this my placing those restrictive GBs upfront. std::vector gb_reset; std::vector gb_cen; for (auto bel : ctx->getBels()) { @@ -506,7 +506,7 @@ bool Arch::place_vpr() } } - return place_design_vpr(ctx); + return placer_vpr(ctx); } bool Arch::route() { return router1(getCtx()); } From 9d146d41c3f220d74fa25df37c99b5c6383e79cd Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 15 Jul 2018 13:56:36 -0700 Subject: [PATCH 038/116] Fix grid[][] to capture all bels at that location --- common/placer_vpr.cc | 18 ++++++++++-------- common/placer_vpr.inc | 7 ++++++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index bea48225b3..f3c2de76a8 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -51,8 +51,8 @@ namespace vpr { static struct { inline int width() { return _bels.size(); } inline int height() { return _bels.front().size(); } - inline const std::vector& operator[](size_t x) { return _bels.at(x); } - std::vector> _bels; + inline const std::vector>& operator[](size_t x) { return _bels.at(x); } + std::vector>> _bels; } grid; static struct { const float inner_num = 10; @@ -68,6 +68,9 @@ namespace vpr { inline void printf_warning(const char*, unsigned, const char* fmt, Args... args) { log_warning(fmt, std::forward(args)...); } + inline void printf_info(const char* fmt) { + log_info(fmt); + } template inline void printf_info(const char* fmt, Args... args) { log_info(fmt, std::forward(args)...); @@ -92,14 +95,13 @@ class VPRPlacer ctx->estimatePosition(bel, x, y, gb); if (x >= int(vpr::grid._bels.size())) vpr::grid._bels.resize(x+1); - if (y >= int(vpr::grid._bels[x].size())) { - vpr::grid._bels[x].resize(y+1,BelId()); - max_y = std::max(y, max_y); - } - vpr::grid._bels[x][y] = bel; + max_y = std::max(y, max_y); + if (max_y >= int(vpr::grid._bels[x].size())) + vpr::grid._bels[x].resize(max_y+1); + vpr::grid._bels[x][y].push_back(bel); } for (auto& c : vpr::grid._bels) - c.resize(max_y+1, BelId()); + c.resize(max_y+1); for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index 767ecb6a62..e0f7e9fee2 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -1872,7 +1872,12 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, int y_rel = npnr_ctx->rng(std::max(0, max_y - min_y)); int px_to = min_x + x_rel; int py_to = min_y + y_rel; - bel_to = grid[px_to][py_to]; + if (!grid[px_to][py_to].empty()) { + int pz_to = npnr_ctx->rng(grid[px_to][py_to].size()); + bel_to = grid[px_to][py_to][pz_to]; + } + else + bel_to = BelId(); } } From 4c9d8bc836dbe9efde4f9ccabacb2f9bc4139500 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 15 Jul 2018 13:58:51 -0700 Subject: [PATCH 039/116] Add comment for pick_from_block() --- common/placer_vpr.inc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index e0f7e9fee2..045887741c 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -1574,6 +1574,11 @@ static /*ClusterBlockId*/ CellInfo* pick_from_block() { //Pick a block at random //ClusterBlockId b_from = ClusterBlockId(vtr::irand((int) cluster_ctx.clb_nlist.blocks().size() - 1)); + + // Since npnr->cells is an unordered_map with ForwardIterators, + // it is inefficient to sample randomly from that container. + // Instead, I've created a (global) randomly-accessible container + // called npnr_cells that contains the same contents auto b_from = npnr_cells.at(npnr_ctx->rng(npnr_cells.size())); //Record it as tried From 928eec61e8f12ef44d3953defbc5e20283ff2c43 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 15 Jul 2018 15:01:05 -0700 Subject: [PATCH 040/116] Comments --- common/placer_vpr.inc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index 045887741c..4b945abc20 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -1473,6 +1473,9 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin } + // No need to update anything, as we've already done the swap + // in setup_blocks_affected + // /* Update clb data structures since we kept the move. */ // /* Swap physical location */ // for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { @@ -1508,6 +1511,9 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin } // /* Restore the place_ctx.block_locs data structures to their state before the move. */ + + // Since we already swapped in setup_blocks_affected(), unswap here; + // to prevent npnr assertions firing, unplace all bels first for (const auto &b : blocks_affected) { auto blk = b.first; npnr_ctx->unbindBel(blk->bel); From 7a9204fe60a610484496455c1bf7a5833a0deec6 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 15 Jul 2018 15:29:59 -0700 Subject: [PATCH 041/116] Fix off-by-one in find_to_location()'s use of rng() --- common/placer_vpr.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index 4b945abc20..758d170295 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -1879,8 +1879,8 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, int ipos = npnr_ctx->rng(legal_pos[type].size()); bel_to = legal_pos[type][ipos]; } else { - int x_rel = npnr_ctx->rng(std::max(0, max_x - min_x)); - int y_rel = npnr_ctx->rng(std::max(0, max_y - min_y)); + int x_rel = npnr_ctx->rng(std::max(0, max_x - min_x)+1); // +1 because rng() uses % + int y_rel = npnr_ctx->rng(std::max(0, max_y - min_y)+1); // +1 because rng() uses % int px_to = min_x + x_rel; int py_to = min_y + y_rel; if (!grid[px_to][py_to].empty()) { From 14c057507c6803230114f80edd04db54900fc352 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Tue, 17 Jul 2018 08:06:05 -0700 Subject: [PATCH 042/116] Speedup VPR placer by using std::vector not std::unordered_map --- common/placer_vpr.cc | 17 ++++ common/placer_vpr.inc | 186 ++++++++++++++++++++---------------------- 2 files changed, 107 insertions(+), 96 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index f3c2de76a8..0e952acc4e 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -48,6 +48,18 @@ namespace vpr { static Context* npnr_ctx = NULL; std::vector npnr_cells; + static struct { + struct t_clustering { + struct { + struct t_nets { + t_nets& operator()() { return *this; } + size_t size() { return _size; } + size_t _size; + } nets; + } clb_nlist; + t_clustering& operator()() { return *this; } + } clustering; + } g_vpr_ctx; static struct { inline int width() { return _bels.size(); } inline int height() { return _bels.front().size(); } @@ -109,6 +121,11 @@ class VPRPlacer vpr::npnr_cells.push_back(cell.second.get()); } } + auto &num_nets = vpr::g_vpr_ctx.clustering.clb_nlist.nets._size; + for (auto& n : ctx->nets) { + auto net_id = n.second.get(); + num_nets = std::max(num_nets, net_id->name.index+1); + } } bool place() diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index 758d170295..0e4949081a 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -92,9 +92,9 @@ struct t_placer_statistics { ///********************** Variables local to place.c ***************************/ /* Cost of a net, and a temporary cost of a net used during move assessment. */ -static std::unordered_map net_cost, temp_net_cost; +static std::vector net_cost, temp_net_cost; -static std::unordered_map> legal_pos; +static std::vector> legal_pos; //static int *num_legal_pos = nullptr; /* [0..num_legal_pos-1] */ /* [0...cluster_ctx.clb_nlist.nets().size()-1] * @@ -109,7 +109,7 @@ static std::unordered_map> legal_pos; * particular bounding box cannot be updated incrementally before, hence the * * bounding box is got from scratch, so the bounding box would definitely be * * right, DO NOT update again. */ -static std::unordered_map bb_updated_before; +static std::vector bb_updated_before; ///* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1]. What is the value of the timing */ ///* driven portion of the cost function. These arrays will be set to */ @@ -133,7 +133,7 @@ static std::unordered_map bb_updated_before; * blocks on each of a net's bounding box (to allow efficient updates), * * respectively. */ -static std::unordered_map bb_coords, bb_num_on_edges; +static std::vector bb_coords, bb_num_on_edges; /* Store the information on the blocks to be moved in a swap during * * placement, in the form of array of structs instead of struct with * @@ -154,7 +154,7 @@ static std::vector> blocks_affected; // ///* The following arrays are used by the try_swap function for speed. */ ///* [0...cluster_ctx.clb_nlist.nets().size()-1] */ -static std::unordered_map ts_bb_coord_new, ts_bb_edge_new; +static std::vector ts_bb_coord_new, ts_bb_edge_new; static std::vector ts_nets_to_update; ///* The pl_macros array stores all the carry chains placement macros. * @@ -192,9 +192,9 @@ static void alloc_and_load_placement_structs( t_direct_inf *directs, int num_directs*/); //static void alloc_and_load_net_pin_indices(); -// -//static void alloc_and_load_try_swap_structs(); -// + +static void alloc_and_load_try_swap_structs(); + //static void free_placement_structs(t_placer_opts placer_opts); // //static void alloc_and_load_for_fast_cost_update(float place_cost_exp); @@ -214,11 +214,11 @@ static int try_place_macro(/*int itype, int ipos, int imacro*/ static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_locations*/); static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type pad_loc_type*/ - std::unordered_map> &free_locations); + std::vector> &free_locations); static void initial_placement_location(/*int * free_locations, ClusterBlockId blk_id, int *pipos, int *px, int *py, int *pz*/ - std::unordered_map> &free_locations, + std::vector> &free_locations, CellInfo *cell, BelId& bel); @@ -1461,15 +1461,15 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin /* update net cost functions and reset flags. */ for (auto net_id : ts_nets_to_update) { - bb_coords[net_id] = ts_bb_coord_new[net_id]; + bb_coords[net_id->name.index] = ts_bb_coord_new[net_id->name.index]; if (net_id->users.size() >= SMALL_NET) - bb_num_on_edges[net_id] = ts_bb_edge_new[net_id]; + bb_num_on_edges[net_id->name.index] = ts_bb_edge_new[net_id->name.index]; - net_cost[net_id] = temp_net_cost[net_id]; + net_cost[net_id->name.index] = temp_net_cost[net_id->name.index]; /* negative temp_net_cost value is acting as a flag. */ - temp_net_cost[net_id] = -1; - bb_updated_before[net_id] = NOT_UPDATED_YET; + temp_net_cost[net_id->name.index] = -1; + bb_updated_before[net_id->name.index] = NOT_UPDATED_YET; } @@ -1506,8 +1506,8 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin /* Reset the net cost function flags first. */ for (auto net_id : ts_nets_to_update) { - temp_net_cost[net_id] = -1; - bb_updated_before[net_id] = NOT_UPDATED_YET; + temp_net_cost[net_id->name.index] = -1; + bb_updated_before[net_id->name.index] = NOT_UPDATED_YET; } // /* Restore the place_ctx.block_locs data structures to their state before the move. */ @@ -1649,8 +1649,8 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit * The cost is only updated once per net. */ for (auto net_id : ts_nets_to_update) { - temp_net_cost[net_id] = get_net_cost(net_id, &ts_bb_coord_new[net_id]); - bb_delta_c += temp_net_cost[net_id] - net_cost[net_id]; + temp_net_cost[net_id->name.index] = get_net_cost(net_id, &ts_bb_coord_new[net_id->name.index]); + bb_delta_c += temp_net_cost[net_id->name.index] - net_cost[net_id->name.index]; } return num_affected_nets; @@ -1658,13 +1658,13 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_affected_nets) { //Record effected nets - if (temp_net_cost[net] < 0.) { + if (temp_net_cost[net->name.index] < 0.) { //Net not marked yet. ts_nets_to_update.push_back(net); num_affected_nets++; //Flag to say we've marked this net. - temp_net_cost[net] = 1.; + temp_net_cost[net->name.index] = 1.; } } @@ -1675,8 +1675,8 @@ static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const if (net->users.size() < SMALL_NET) { //For small nets brute-force bounding box update is faster - if(bb_updated_before[net] == NOT_UPDATED_YET) { //Only once per-net - get_non_updateable_bb(net, &ts_bb_coord_new[net]); + if(bb_updated_before[net->name.index] == NOT_UPDATED_YET) { //Only once per-net + get_non_updateable_bb(net, &ts_bb_coord_new[net->name.index]); } } else { //For large nets, update bounding box incrementally @@ -1693,8 +1693,8 @@ static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const npnr_ctx->estimatePosition(blk->bel, xnew, ynew, gb); //Incremental bounding box update - update_bb(net, &ts_bb_coord_new[net], - &ts_bb_edge_new[net], + update_bb(net, &ts_bb_coord_new[net->name.index], + &ts_bb_edge_new[net->name.index], /*blocks_affected.moved_blocks[iblk].xold + pin_width_offset*/ xold, /*blocks_affected.moved_blocks[iblk].yold + pin_height_offset*/ yold, /*blocks_affected.moved_blocks[iblk].xnew + pin_width_offset*/ xnew, @@ -1789,7 +1789,7 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, is_legal = true; /* Limit the number of tries when searching for an alternative position */ - if(num_tries >= 2 * std::min(active_area /*/ (type->width * type->height)*/, legal_pos[type].size()) + 10) { + if(num_tries >= 2 * std::min(active_area /*/ (type->width * type->height)*/, legal_pos[type.index].size()) + 10) { /* Tried randomly searching for a suitable position */ return false; } else { @@ -1875,9 +1875,9 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, int max_y = std::min(grid.height() - 1, y_from + rly); // *pz_to = 0; - if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || legal_pos[type].size() < active_area) { - int ipos = npnr_ctx->rng(legal_pos[type].size()); - bel_to = legal_pos[type][ipos]; + if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || legal_pos[type.index].size() < active_area) { + int ipos = npnr_ctx->rng(legal_pos[type.index].size()); + bel_to = legal_pos[type.index][ipos]; } else { int x_rel = npnr_ctx->rng(std::max(0, max_x - min_x)+1); // +1 because rng() uses % int y_rel = npnr_ctx->rng(std::max(0, max_y - min_y)+1); // +1 because rng() uses % @@ -1935,7 +1935,7 @@ static float recompute_bb_cost() { auto net_id = n.second.get(); if (!npnr_ctx->isGlobalNet(net_id)) { /* Do only if not global. */ /* Bounding boxes don't have to be recomputed; they're correct. */ - cost += net_cost[net_id]; + cost += net_cost[net_id->name.index]; } } @@ -2105,15 +2105,15 @@ static float comp_bb_cost(/*e_cost_methods method*/) { /* Small nets don't use incremental updating on their bounding boxes, * * so they can use a fast bounding box calculator. */ if (net_id->users.size() >= SMALL_NET /*&& method == NORMAL*/) { - get_bb_from_scratch(net_id, &bb_coords[net_id], - &bb_num_on_edges[net_id]); + get_bb_from_scratch(net_id, &bb_coords[net_id->name.index], + &bb_num_on_edges[net_id->name.index]); } else { - get_non_updateable_bb(net_id, &bb_coords[net_id]); + get_non_updateable_bb(net_id, &bb_coords[net_id->name.index]); } - net_cost[net_id] = get_net_cost(net_id, &bb_coords[net_id]); - cost += net_cost[net_id]; + net_cost[net_id->name.index] = get_net_cost(net_id, &bb_coords[net_id->name.index]); + cost += net_cost[net_id->name.index]; // if (method == CHECK) // expected_wirelength += get_net_wirelength_estimate(net_id, &bb_coords[net_id]); } @@ -2186,12 +2186,12 @@ static void alloc_and_load_placement_structs( // int max_pins_per_clb, i; // unsigned int ipin; -// + // auto& device_ctx = g_vpr_ctx.device(); -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// size_t num_nets = cluster_ctx.clb_nlist.nets().size(); -// + auto& cluster_ctx = g_vpr_ctx.clustering(); + + size_t num_nets = cluster_ctx.clb_nlist.nets().size(); + // init_placement_context(); // // alloc_legal_placements(); @@ -2236,27 +2236,22 @@ static void alloc_and_load_placement_structs( // } // } - for (auto& n : npnr_ctx->nets) { - auto net_id = n.second.get(); - net_cost.emplace(net_id, -1); - temp_net_cost.emplace(net_id, -1); - bb_updated_before.emplace(net_id, NOT_UPDATED_YET); - } + net_cost.resize(num_nets, -1); + temp_net_cost.resize(num_nets, -1); + bb_coords.resize(num_nets, t_bb()); + bb_num_on_edges.resize(num_nets, t_bb()); + + /* Used to store costs for moves not yet made and to indicate when a net's * + * cost has been recomputed. temp_net_cost[inet] < 0 means net's cost hasn't * + * been recomputed. */ + bb_updated_before.resize(num_nets, NOT_UPDATED_YET); -// bb_coords.resize(num_nets, t_bb()); -// bb_num_on_edges.resize(num_nets, t_bb()); -// -// /* Used to store costs for moves not yet made and to indicate when a net's * -// * cost has been recomputed. temp_net_cost[inet] < 0 means net's cost hasn't * -// * been recomputed. */ -// bb_updated_before.resize(num_nets, NOT_UPDATED_YET); -// // alloc_and_load_for_fast_cost_update(place_cost_exp); // // alloc_and_load_net_pin_indices(); -// -// alloc_and_load_try_swap_structs(); -// + + alloc_and_load_try_swap_structs(); + // num_pl_macros = alloc_and_load_placement_macros(directs, num_directs, &pl_macros); } @@ -2293,23 +2288,22 @@ static void alloc_and_load_placement_structs( // } // } //} -// -//static void alloc_and_load_try_swap_structs() { -// /* Allocate the local bb_coordinate storage, etc. only once. */ -// /* Allocate with size cluster_ctx.clb_nlist.nets().size() for any number of nets affected. */ -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// size_t num_nets = cluster_ctx.clb_nlist.nets().size(); -// -// ts_bb_coord_new.resize(num_nets, t_bb()); -// ts_bb_edge_new.resize(num_nets, t_bb()); + +static void alloc_and_load_try_swap_structs() { + /* Allocate the local bb_coordinate storage, etc. only once. */ + /* Allocate with size cluster_ctx.clb_nlist.nets().size() for any number of nets affected. */ + auto& cluster_ctx = g_vpr_ctx.clustering(); + + size_t num_nets = cluster_ctx.clb_nlist.nets().size(); + + ts_bb_coord_new.resize(num_nets, t_bb()); + ts_bb_edge_new.resize(num_nets, t_bb()); // ts_nets_to_update.resize(num_nets, ClusterNetId::INVALID()); -// + // /* Allocate with size cluster_ctx.clb_nlist.blocks().size() for any number of moved blocks. */ // blocks_affected.moved_blocks = (t_pl_moved_block*) vtr::calloc((int) cluster_ctx.clb_nlist.blocks().size(), sizeof(t_pl_moved_block)); // blocks_affected.num_moved_blocks = 0; -// -//} +} /* This routine finds the bounding box of each net from scratch (i.e. * * from only the block location information). It updates both the * @@ -2566,14 +2560,14 @@ static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, yold = std::max(std::min(yold, /*device_ctx.*/grid.height() - 2), 1); //-2 for no perim channels /* Check if the net had been updated before. */ - if (bb_updated_before[net_id] == GOT_FROM_SCRATCH) { + if (bb_updated_before[net_id->name.index] == GOT_FROM_SCRATCH) { /* The net had been updated from scratch, DO NOT update again! */ return; - } else if (bb_updated_before[net_id] == NOT_UPDATED_YET) { + } else if (bb_updated_before[net_id->name.index] == NOT_UPDATED_YET) { /* The net had NOT been updated before, could use the old values */ - curr_bb_coord = &bb_coords[net_id]; - curr_bb_edge = &bb_num_on_edges[net_id]; - bb_updated_before[net_id] = UPDATED_ONCE; + curr_bb_coord = &bb_coords[net_id->name.index]; + curr_bb_edge = &bb_num_on_edges[net_id->name.index]; + bb_updated_before[net_id->name.index] = UPDATED_ONCE; } else { /* The net had been updated before, must use the new values */ curr_bb_coord = bb_coord_new; @@ -2589,7 +2583,7 @@ static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, if (xold == curr_bb_coord->xmax) { /* Old position at xmax. */ if (curr_bb_edge->xmax == 1) { get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); - bb_updated_before[net_id] = GOT_FROM_SCRATCH; + bb_updated_before[net_id->name.index] = GOT_FROM_SCRATCH; return; } else { bb_edge_new->xmax = curr_bb_edge->xmax - 1; @@ -2621,7 +2615,7 @@ static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, if (xold == curr_bb_coord->xmin) { /* Old position at xmin. */ if (curr_bb_edge->xmin == 1) { get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); - bb_updated_before[net_id] = GOT_FROM_SCRATCH; + bb_updated_before[net_id->name.index] = GOT_FROM_SCRATCH; return; } else { bb_edge_new->xmin = curr_bb_edge->xmin - 1; @@ -2662,7 +2656,7 @@ static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, if (yold == curr_bb_coord->ymax) { /* Old position at ymax. */ if (curr_bb_edge->ymax == 1) { get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); - bb_updated_before[net_id] = GOT_FROM_SCRATCH; + bb_updated_before[net_id->name.index] = GOT_FROM_SCRATCH; return; } else { bb_edge_new->ymax = curr_bb_edge->ymax - 1; @@ -2694,7 +2688,7 @@ static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, if (yold == curr_bb_coord->ymin) { /* Old position at ymin. */ if (curr_bb_edge->ymin == 1) { get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); - bb_updated_before[net_id] = GOT_FROM_SCRATCH; + bb_updated_before[net_id->name.index] = GOT_FROM_SCRATCH; return; } else { bb_edge_new->ymin = curr_bb_edge->ymin - 1; @@ -2726,8 +2720,8 @@ static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, bb_edge_new->ymax = curr_bb_edge->ymax; } - if (bb_updated_before[net_id] == NOT_UPDATED_YET) { - bb_updated_before[net_id] = UPDATED_ONCE; + if (bb_updated_before[net_id->name.index] == NOT_UPDATED_YET) { + bb_updated_before[net_id->name.index] = UPDATED_ONCE; } } @@ -2788,7 +2782,10 @@ static void load_legal_placements() { for (auto bel : npnr_ctx->getBels()) { auto belType = npnr_ctx->getBelType(bel); auto type = npnr_ctx->belTypeToId(belType); - legal_pos[type].push_back(bel); + int itype = type.index; + if (itype >= legal_pos.size()) + legal_pos.resize(itype+1); + legal_pos[itype].push_back(bel); } } @@ -2991,7 +2988,7 @@ static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_l /* Place blocks that are NOT a part of any macro. * We'll randomly place each block in the clustered netlist, one by one. */ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type pad_loc_type*/ - std::unordered_map>& free_locations) { + std::vector>& free_locations) { // int itype, ipos, x, y, z; // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); @@ -3057,7 +3054,7 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; /* overwrite used block position */ // free_locations[itype]--; - free_locations.at(cell->type).pop_back(); + free_locations.at(cell->type.index).pop_back(); // } } @@ -3065,7 +3062,7 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type static void initial_placement_location(/*int * free_locations, ClusterBlockId blk_id, int *pipos, int *px, int *py, int *pz*/ - std::unordered_map> &free_locations, + std::vector> &free_locations, CellInfo *cell, BelId &bel) { @@ -3078,13 +3075,13 @@ static void initial_placement_location(/*int * free_locations, ClusterBlockId bl // *py_to = legal_pos[itype][*pipos].y; // *pz_to = legal_pos[itype][*pipos].z; - auto it = free_locations.at(cell->type).rbegin(); - auto ie = free_locations.at(cell->type).rend(); + auto it = free_locations.at(cell->type.index).rbegin(); + auto ie = free_locations.at(cell->type.index).rend(); for (; it != ie; ++it) { if (!npnr_ctx->isValidBelForCell(cell, *it)) continue; bel = *it; - std::swap(*it, free_locations.at(cell->type).back()); + std::swap(*it, free_locations.at(cell->type.index).back()); return; } throw; @@ -3113,7 +3110,7 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // free_locations[itype] = num_legal_pos[itype]; // } - std::unordered_map> free_locations(legal_pos.begin(), legal_pos.end()); + std::vector> free_locations(legal_pos.begin(), legal_pos.end()); // /* We'll use the grid to record where everything goes. Initialize to the grid has no // * blocks placed anywhere. @@ -3160,13 +3157,10 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // } // Finish updating the legal_pos[][] and free_locations[] array for (auto it = free_locations.begin(); it != free_locations.end(); it++) { - for (auto ipos = it->second.begin(); ipos != it->second.end(); ) { - auto cell_name = npnr_ctx->getBoundBelCell(*ipos); - if (cell_name != IdString()) - ipos = it->second.erase(ipos); - else - ipos++; - } + it->erase(remove_if(it->begin(), it->end(), [](BelId bel) { + auto cell_name = npnr_ctx->getBoundBelCell(bel); + return cell_name != IdString(); + }), it->end()); } initial_placement_blocks(free_locations); From bc2f7b3cd5feae1d8f6f5efbdd22761450035e25 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Tue, 17 Jul 2018 08:19:30 -0700 Subject: [PATCH 043/116] Revert further to VPR structures --- common/placer_vpr.cc | 31 +++++++++++++++----------- common/placer_vpr.inc | 52 +++++++++++++++++++++---------------------- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 0e952acc4e..036c7a96c1 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -48,24 +48,28 @@ namespace vpr { static Context* npnr_ctx = NULL; std::vector npnr_cells; + struct DeviceGrid { + inline int width() const { return _bels.size(); } + inline int height() const { return _bels.front().size(); } + inline const std::vector>& operator[](size_t x) { return _bels.at(x); } + std::vector>> _bels; + }; static struct { struct t_clustering { struct { struct t_nets { - t_nets& operator()() { return *this; } size_t size() { return _size; } size_t _size; + t_nets& operator()() { return *this; } } nets; } clb_nlist; t_clustering& operator()() { return *this; } } clustering; + struct t_device { + DeviceGrid grid; + t_device& operator()() { return *this; } + } device; } g_vpr_ctx; - static struct { - inline int width() { return _bels.size(); } - inline int height() { return _bels.front().size(); } - inline const std::vector>& operator[](size_t x) { return _bels.at(x); } - std::vector>> _bels; - } grid; static struct { const float inner_num = 10; } annealing_sched; @@ -101,18 +105,19 @@ class VPRPlacer { vpr::npnr_ctx = ctx; int max_y = 0; + auto &grid = vpr::g_vpr_ctx.device.grid; for (auto bel : ctx->getBels()) { int x, y; bool gb; ctx->estimatePosition(bel, x, y, gb); - if (x >= int(vpr::grid._bels.size())) - vpr::grid._bels.resize(x+1); + if (x >= int(grid._bels.size())) + grid._bels.resize(x+1); max_y = std::max(y, max_y); - if (max_y >= int(vpr::grid._bels[x].size())) - vpr::grid._bels[x].resize(max_y+1); - vpr::grid._bels[x][y].push_back(bel); + if (max_y >= int(grid._bels[x].size())) + grid._bels[x].resize(max_y+1); + grid._bels[x][y].push_back(bel); } - for (auto& c : vpr::grid._bels) + for (auto& c : grid._bels) c.resize(max_y+1); for (auto &cell : ctx->cells) { diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index 0e4949081a..ec673b51e8 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -253,7 +253,7 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, static void update_t(float *t, float rlim, float success_rat/*, t_annealing_sched annealing_sched*/); -static void update_rlim(float *rlim, float success_rat /*, const DeviceGrid& grid*/); +static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid); static int exit_crit(float t, float cost /*, t_annealing_sched annealing_sched*/); @@ -363,8 +363,8 @@ void try_place(/*t_placer_opts placer_opts, //#ifdef ENABLE_CLASSIC_VPR_STA // t_slack * slacks = NULL; //#endif -// -// auto& device_ctx = g_vpr_ctx.device(); + + auto& device_ctx = g_vpr_ctx.device(); // auto& cluster_ctx = g_vpr_ctx.clustering(); // // std::shared_ptr timing_info; @@ -563,7 +563,7 @@ void try_place(/*t_placer_opts placer_opts, inner_recompute_limit = move_lim + 1; // } - rlim = (float) std::max(grid.width(), grid.height()); + rlim = (float) std::max(device_ctx.grid.width(), device_ctx.grid.height()); first_rlim = rlim; /*used in timing-driven placement for exponent computation */ final_rlim = 1; @@ -690,7 +690,7 @@ void try_place(/*t_placer_opts placer_opts, sprintf(msg, "Cost: %g BB Cost %g TD Cost %g Temperature: %g", cost, bb_cost, timing_cost, t); // update_screen(ScreenUpdatePriority::MINOR, msg, PLACEMENT, timing_info); - update_rlim(&rlim, success_rat /*, device_ctx.grid*/); + update_rlim(&rlim, success_rat, device_ctx.grid); // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { // crit_exponent = (1 - (rlim - final_rlim) * inverse_delta_rlim) @@ -1049,7 +1049,7 @@ static double get_std_dev(int n, double sum_x_squared, double av_x) { return (std_dev); } -static void update_rlim(float *rlim, float success_rat /*, const DeviceGrid& grid*/) { +static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid) { /* Update the range limited to keep acceptance prob. near 0.44. Use * * a floating point rlim to allow gradual transitions at low temps. */ @@ -1757,7 +1757,7 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, bool is_legal; // int itype; -// auto& grid = g_vpr_ctx.device().grid; + auto& grid = g_vpr_ctx.device().grid; // auto& place_ctx = g_vpr_ctx.placement(); // // auto grid_type = grid[x_from][y_from].type; @@ -1859,10 +1859,10 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, int *px_to, int *py_to, int *pz_to*/, BelId &bel_to) { -// auto& device_ctx = g_vpr_ctx.device(); -// auto& grid = device_ctx.grid; -// -// int itype = type->index; + auto& device_ctx = g_vpr_ctx.device(); + auto& grid = device_ctx.grid; + + int itype = type.index; int rlx = std::min(grid.width() - 1, rlim); @@ -1875,9 +1875,9 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, int max_y = std::min(grid.height() - 1, y_from + rly); // *pz_to = 0; - if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || legal_pos[type.index].size() < active_area) { - int ipos = npnr_ctx->rng(legal_pos[type.index].size()); - bel_to = legal_pos[type.index][ipos]; + if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || legal_pos[itype].size() < active_area) { + int ipos = npnr_ctx->rng(legal_pos[itype].size()); + bel_to = legal_pos[itype][ipos]; } else { int x_rel = npnr_ctx->rng(std::max(0, max_x - min_x)+1); // +1 because rng() uses % int y_rel = npnr_ctx->rng(std::max(0, max_y - min_y)+1); // +1 because rng() uses % @@ -2316,8 +2316,8 @@ static void get_bb_from_scratch(/*ClusterNetId*/ NetInfo *net_id, t_bb *coords, // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.placement(); -// auto& device_ctx = g_vpr_ctx.device(); -// auto& grid = device_ctx.grid; + auto& device_ctx = g_vpr_ctx.device(); + auto& grid = device_ctx.grid; // ClusterBlockId bnum = cluster_ctx.clb_nlist.net_driver_block(net_id); // pnum = cluster_ctx.clb_nlist.net_pin_physical_index(net_id, 0); @@ -2485,7 +2485,7 @@ static void get_non_updateable_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coo // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.placement(); -// auto& device_ctx = g_vpr_ctx.device(); + auto& device_ctx = g_vpr_ctx.device(); auto bnum = net_id->driver.cell; // pnum = cluster_ctx.clb_nlist.net_pin_physical_index(net_id, 0); @@ -2527,10 +2527,10 @@ static void get_non_updateable_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coo * clip to 1 in both directions as well (since minimum channel index * * is 0). See route_common.cpp for a channel diagram. */ - bb_coord_new->xmin = std::max(std::min(xmin, grid.width() - 2), 1); //-2 for no perim channels - bb_coord_new->ymin = std::max(std::min(ymin, grid.height() - 2), 1); //-2 for no perim channels - bb_coord_new->xmax = std::max(std::min(xmax, grid.width() - 2), 1); //-2 for no perim channels - bb_coord_new->ymax = std::max(std::min(ymax, grid.height() - 2), 1); //-2 for no perim channels + bb_coord_new->xmin = std::max(std::min(xmin, device_ctx.grid.width() - 2), 1); //-2 for no perim channels + bb_coord_new->ymin = std::max(std::min(ymin, device_ctx.grid.height() - 2), 1); //-2 for no perim channels + bb_coord_new->xmax = std::max(std::min(xmax, device_ctx.grid.width() - 2), 1); //-2 for no perim channels + bb_coord_new->ymax = std::max(std::min(ymax, device_ctx.grid.height() - 2), 1); //-2 for no perim channels } static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, @@ -2552,12 +2552,12 @@ static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, t_bb *curr_bb_edge, *curr_bb_coord; -// auto& device_ctx = g_vpr_ctx.device(); + auto& device_ctx = g_vpr_ctx.device(); - xnew = std::max(std::min(xnew, /*device_ctx.*/grid.width() - 2), 1); //-2 for no perim channels - ynew = std::max(std::min(ynew, /*device_ctx.*/grid.height() - 2), 1); //-2 for no perim channels - xold = std::max(std::min(xold, /*device_ctx.*/grid.width() - 2), 1); //-2 for no perim channels - yold = std::max(std::min(yold, /*device_ctx.*/grid.height() - 2), 1); //-2 for no perim channels + xnew = std::max(std::min(xnew, device_ctx.grid.width() - 2), 1); //-2 for no perim channels + ynew = std::max(std::min(ynew, device_ctx.grid.height() - 2), 1); //-2 for no perim channels + xold = std::max(std::min(xold, device_ctx.grid.width() - 2), 1); //-2 for no perim channels + yold = std::max(std::min(yold, device_ctx.grid.height() - 2), 1); //-2 for no perim channels /* Check if the net had been updated before. */ if (bb_updated_before[net_id->name.index] == GOT_FROM_SCRATCH) { From 7a0c566025bf35b09fcccd90d2c86e7e4bfa1cdd Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Tue, 17 Jul 2018 08:21:53 -0700 Subject: [PATCH 044/116] Add comment --- common/placer_vpr.inc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index ec673b51e8..f2ed37ed9d 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -2236,6 +2236,11 @@ static void alloc_and_load_placement_structs( // } // } + // We tradeoff memory for speed here: + // npnr nets are actually pointers, with non sequential indices + // associated with their names, thus num_nets actually contains + // the max index of all net indices, which is likely to be more + // than the actual number of nets net_cost.resize(num_nets, -1); temp_net_cost.resize(num_nets, -1); bb_coords.resize(num_nets, t_bb()); From 9c24d2a8367560e22360a4e1e71be77a1a7e5a3b Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Wed, 18 Jul 2018 00:14:19 -0700 Subject: [PATCH 045/116] Keep track of total swaps considered/accepted and print at end --- common/placer1.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/placer1.cc b/common/placer1.cc index 74a1104036..0813f9ab96 100644 --- a/common/placer1.cc +++ b/common/placer1.cc @@ -148,6 +148,8 @@ class SAPlacer double avg_metric = curr_metric; temp = 10000; + tot_move = tot_accept = 0; + // Main simulated annealing loop for (int iter = 1;; iter++) { n_move = n_accept = 0; @@ -232,7 +234,13 @@ class SAPlacer metrics[net.first] = wl; curr_metric += wl; } + + tot_move += n_move; + tot_accept += n_accept; } + + log_info(" swaps attempted: %d acceptance rate: %.3f\n", tot_move, double(tot_accept)/double(tot_move)); + // Final post-pacement validitiy check for (auto bel : ctx->getBels()) { IdString cell = ctx->getBoundBelCell(bel); @@ -419,6 +427,7 @@ class SAPlacer float temp = 1000; bool improved = false; int n_move, n_accept; + int tot_move, tot_accept; int diameter = 35, max_x = 1, max_y = 1; std::unordered_map bel_types; std::vector>>> fast_bels; From 1f4d8cbfab3fa913bbc0da597871fc4645ec0c15 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Wed, 18 Jul 2018 01:13:33 -0700 Subject: [PATCH 046/116] WIP for timing based placement --- common/placer_vpr.cc | 25 ++- common/placer_vpr.inc | 401 ++++++++++++++++++++++-------------------- 2 files changed, 228 insertions(+), 198 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 036c7a96c1..dc246203cc 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -60,8 +60,16 @@ namespace vpr { struct t_nets { size_t size() { return _size; } size_t _size; - t_nets& operator()() { return *this; } + std::unordered_map>& operator()() const { return npnr_ctx->nets; } } nets; + bool net_is_global(const NetInfo* net) const { return npnr_ctx->isGlobalNet(net); } + std::vector& net_sinks(NetInfo* net) const { return net->users; } + struct t_net_pins { + t_net_pins(NetInfo* net) : _net(net) {} + size_t size() const { return _net->users.size() + 1; } + NetInfo* _net; + }; + t_net_pins net_pins(NetInfo* net) const { return t_net_pins(net); } } clb_nlist; t_clustering& operator()() { return *this; } } clustering; @@ -70,9 +78,19 @@ namespace vpr { t_device& operator()() { return *this; } } device; } g_vpr_ctx; - static struct { + static struct t_annealing_sched { const float inner_num = 10; } annealing_sched; + static struct t_placer_opts { + bool enable_timing_computations; + const float td_place_exp_first = 1.0; + } placer_opts; + + // timing_place.cpp + float get_timing_place_crit(NetInfo* /*net_id*/, const PortRef& ipin) + { + return ipin.budget; + } #define VTR_ASSERT NPNR_ASSERT #define VTR_ASSERT_SAFE NPNR_ASSERT @@ -131,13 +149,14 @@ class VPRPlacer auto net_id = n.second.get(); num_nets = std::max(num_nets, net_id->name.index+1); } + vpr::placer_opts.enable_timing_computations = ctx->timing_driven; } bool place() { log_break(); - vpr::try_place(); + vpr::try_place(vpr::placer_opts, vpr::annealing_sched); // Final post-pacement validitiy check for (auto bel : ctx->getBels()) { diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index f2ed37ed9d..cafce10ebb 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -62,13 +62,13 @@ #define UPDATED_ONCE 'U' #define GOT_FROM_SCRATCH 'S' -///* For comp_cost. NORMAL means use the method that generates updateable * -// * bounding boxes for speed. CHECK means compute all bounding boxes from * -// * scratch using a very simple routine to allow checks of the other * -// * costs. */ -//enum e_cost_methods { -// NORMAL, CHECK -//}; +/* For comp_cost. NORMAL means use the method that generates updateable * + * bounding boxes for speed. CHECK means compute all bounding boxes from * + * scratch using a very simple routine to allow checks of the other * + * costs. */ +enum e_cost_methods { + NORMAL /*, CHECK*/ +}; /* This is for the placement swap routines. A swap attempt could be * * rejected, accepted or aborted (due to the limitations placed on the * @@ -111,18 +111,18 @@ static std::vector> legal_pos; * right, DO NOT update again. */ static std::vector bb_updated_before; -///* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1]. What is the value of the timing */ -///* driven portion of the cost function. These arrays will be set to */ -///* (criticality * delay) for each point to point connection. */ -// -//static vtr::vector point_to_point_timing_cost; -//static vtr::vector temp_point_to_point_timing_cost; -// +/* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1]. What is the value of the timing */ +/* driven portion of the cost function. These arrays will be set to */ +/* (criticality * delay) for each point to point connection. */ + +static std::vector point_to_point_timing_cost; +static std::vector temp_point_to_point_timing_cost; + ///* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1]. What is the value of the delay */ ///* for each connection in the circuit */ -//static vtr::vector_map point_to_point_delay_cost; -//static vtr::vector_map temp_point_to_point_delay_cost; -// +static std::vector point_to_point_delay_cost; +static std::vector temp_point_to_point_delay_cost; + ///* [0..cluster_ctx.clb_nlist.blocks().size()-1][0..pins_per_clb-1]. Indicates which pin on the net */ ///* this block corresponds to, this is only required during timing-driven */ ///* placement. It is used to allow us to update individual connections on */ @@ -188,7 +188,7 @@ static const float cross_count[50] = { /* [0..49] */1.0, 1.0, 1.0, 1.0828, 1.153 //#endif static void alloc_and_load_placement_structs( - /*float place_cost_exp, t_placer_opts placer_opts, + /*float place_cost_exp,*/ t_placer_opts placer_opts /*, t_direct_inf *directs, int num_directs*/); //static void alloc_and_load_net_pin_indices(); @@ -225,7 +225,7 @@ static void initial_placement_location(/*int * free_locations, ClusterBlockId bl static void initial_placement(/*enum e_pad_loc_type pad_loc_type, const char *pad_loc_file*/); -static float comp_bb_cost(/*e_cost_methods method*/); +static float comp_bb_cost(e_cost_methods method); static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, int y_to, int z_to*/ BelId bel_to); @@ -258,22 +258,22 @@ static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid); static int exit_crit(float t, float cost /*, t_annealing_sched annealing_sched*/); -//static int count_connections(); +static int count_connections(); static double get_std_dev(int n, double sum_x_squared, double av_x); static float recompute_bb_cost(); -//static float comp_td_point_to_point_delay(ClusterNetId net_id, int ipin); -// -//static void comp_td_point_to_point_delays(); -// +static float comp_td_point_to_point_delay(/*ClusterNetId*/ NetInfo* net_id, /*int ipin*/ const PortRef& ipin); + +static void comp_td_point_to_point_delays(); + //static void update_td_cost(); // //static bool driven_by_moved_block(const ClusterNetId net); -// -//static void comp_td_costs(float *timing_cost, float *connection_delay_sum); -// + +static void comp_td_costs(float *timing_cost, float *connection_delay_sum); + static e_swap_result assess_swap(float delta_c, float t); static bool find_to(/*t_type_ptr type,*/ float rlim, @@ -332,8 +332,8 @@ static void placement_inner_loop(float t, float rlim, /*t_placer_opts placer_opt SetupTimingInfo& timing_info*/); /*****************************************************************************/ -void try_place(/*t_placer_opts placer_opts, - t_annealing_sched annealing_sched, +void try_place(t_placer_opts placer_opts, + t_annealing_sched annealing_sched/*, t_chan_width_dist chan_width_dist, t_router_opts router_opts, t_det_routing_arch *det_routing_arch, t_segment_inf * segment_inf, #ifdef ENABLE_CLASSIC_VPR_STA @@ -394,7 +394,7 @@ void try_place(/*t_placer_opts placer_opts, // // init_chan(width_fac, chan_width_dist); - alloc_and_load_placement_structs(/*placer_opts.place_cost_exp, placer_opts, + alloc_and_load_placement_structs(/*placer_opts.place_cost_exp,*/ placer_opts /*, directs, num_directs*/); initial_placement(/*placer_opts.pad_loc_type, placer_opts.pad_loc_file.c_str()*/); @@ -403,24 +403,24 @@ void try_place(/*t_placer_opts placer_opts, // // //Enables fast look-up of atom pins connect to CLB pins // ClusteredPinAtomPinsLookup netlist_pin_lookup(cluster_ctx.clb_nlist, pb_gpin_lookup); -// -// /* Gets initial cost and loads bounding boxes. */ -// -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE || placer_opts.enable_timing_computations) { -// bb_cost = comp_bb_cost(NORMAL); -// -// crit_exponent = placer_opts.td_place_exp_first; /*this will be modified when rlim starts to change */ -// -// num_connections = count_connections(); -// vtr::printf_info("\n"); -// vtr::printf_info("There are %d point to point connections in this circuit.\n", num_connections); -// vtr::printf_info("\n"); -// -// place_delay_value = 0; -// -// //Update the point-to-point delays from the initial placement -// comp_td_point_to_point_delays(); -// + + /* Gets initial cost and loads bounding boxes. */ + + if (/*placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE ||*/ placer_opts.enable_timing_computations) { + bb_cost = comp_bb_cost(NORMAL); + + crit_exponent = placer_opts.td_place_exp_first; /*this will be modified when rlim starts to change */ + + num_connections = count_connections(); + vtr::printf_info("\n"); + vtr::printf_info("There are %d point to point connections in this circuit.\n", num_connections); + vtr::printf_info("\n"); + + place_delay_value = 0; + + //Update the point-to-point delays from the initial placement + comp_td_point_to_point_delays(); + // /* // * Initialize timing analysis // */ @@ -443,23 +443,23 @@ void try_place(/*t_placer_opts placer_opts, // tatum::write_echo(getEchoFileName(E_ECHO_INITIAL_PLACEMENT_TIMING_GRAPH), // *timing_ctx.graph, *timing_ctx.constraints, *placement_delay_calc, timing_info->analyzer()); // } -// -//#ifdef ENABLE_CLASSIC_VPR_STA -// load_timing_graph_net_delays(point_to_point_delay_cost); -// do_timing_analysis(slacks, timing_inf, false, true); -// -// float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); -// if(cpd_diff_ns > ERROR_TOL) { -// print_classic_cpds(); -// print_tatum_cpds(timing_info->critical_paths()); -// -// vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); -// } -//#endif -// -// /*now we can properly compute costs */ -// comp_td_costs(&timing_cost, &delay_cost); /*also updates values in point_to_point_delay_cost */ -// + +#ifdef ENABLE_CLASSIC_VPR_STA + load_timing_graph_net_delays(point_to_point_delay_cost); + do_timing_analysis(slacks, timing_inf, false, true); + + float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); + if(cpd_diff_ns > ERROR_TOL) { + print_classic_cpds(); + print_tatum_cpds(timing_info->critical_paths()); + + vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); + } +#endif + + /*now we can properly compute costs */ + comp_td_costs(&timing_cost, &delay_cost); /*also updates values in point_to_point_delay_cost */ + // if (getEchoEnabled()) { //#ifdef ENABLE_CLASSIC_VPR_STA // if(isEchoFileEnabled(E_ECHO_INITIAL_PLACEMENT_SLACK)) @@ -468,15 +468,15 @@ void try_place(/*t_placer_opts placer_opts, // print_criticality(slacks, getEchoFileName(E_ECHO_INITIAL_PLACEMENT_CRITICALITY)); //#endif // } -// outer_crit_iter_count = 1; -// -// inverse_prev_timing_cost = 1 / timing_cost; -// inverse_prev_bb_cost = 1 / bb_cost; -// cost = 1; /*our new cost function uses normalized values of */ -// /*bb_cost and timing_cost, the value of cost will be reset */ -// /*to 1 at each temperature when *_TIMING_DRIVEN_PLACE is true */ -// } else { /*BOUNDING_BOX_PLACE */ - cost = bb_cost = comp_bb_cost(/*NORMAL*/); + outer_crit_iter_count = 1; + + inverse_prev_timing_cost = 1 / timing_cost; + inverse_prev_bb_cost = 1 / bb_cost; + cost = 1; /*our new cost function uses normalized values of */ + /*bb_cost and timing_cost, the value of cost will be reset */ + /*to 1 at each temperature when *_TIMING_DRIVEN_PLACE is true */ + } else { /*BOUNDING_BOX_PLACE */ + cost = bb_cost = comp_bb_cost(NORMAL); timing_cost = 0; delay_cost = 0; place_delay_value = 0; @@ -486,8 +486,8 @@ void try_place(/*t_placer_opts placer_opts, inverse_prev_timing_cost = 0; /*inverses not used */ inverse_prev_bb_cost = 0; -// } -// + } + // //Sanity check that initial placement is legal // check_place(bb_cost, timing_cost, placer_opts.place_algorithm, delay_cost); // @@ -1011,21 +1011,22 @@ static void placement_inner_loop(float t, float rlim, /*t_placer_opts placer_opt /* Inner loop ends */ } -///*only count non-global connections */ -//static int count_connections() { -// -// int count = 0; -// -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// for (auto net_id : cluster_ctx.clb_nlist.nets()) { -// if (cluster_ctx.clb_nlist.net_is_global(net_id)) -// continue; -// -// count += cluster_ctx.clb_nlist.net_sinks(net_id).size(); -// } -// -// return (count); -//} +/*only count non-global connections */ +static int count_connections() { + + int count = 0; + + auto& cluster_ctx = g_vpr_ctx.clustering(); + for (const auto& net : cluster_ctx.clb_nlist.nets()) { + auto net_id = net.second.get(); + if (cluster_ctx.clb_nlist.net_is_global(net_id)) + continue; + + count += cluster_ctx.clb_nlist.net_sinks(net_id).size(); + } + + return (count); +} static double get_std_dev(int n, double sum_x_squared, double av_x) { @@ -1942,23 +1943,26 @@ static float recompute_bb_cost() { return (cost); } -///*returns the delay of one point to point connection */ -//static float comp_td_point_to_point_delay(ClusterNetId net_id, int ipin) { -// auto& cluster_ctx = g_vpr_ctx.clustering(); +/*returns the delay of one point to point connection */ +static float comp_td_point_to_point_delay(/*ClusterNetId*/ NetInfo* net_id, /*int ipin*/ const PortRef& ipin) { + auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.placement(); -// -// float delay_source_to_sink = 0.; -// -// if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { -// //Only estimate delay for signals routed through the inter-block -// //routing network. Global signals are assumed to have zero delay. -// + + float delay_source_to_sink = 0.; + + if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { + //Only estimate delay for signals routed through the inter-block + //routing network. Global signals are assumed to have zero delay. + // ClusterBlockId source_block = cluster_ctx.clb_nlist.net_driver_block(net_id); // ClusterBlockId sink_block = cluster_ctx.clb_nlist.net_pin_block(net_id, ipin); // // VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.block_type(source_block) != nullptr); // VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.block_type(sink_block) != nullptr); -// + + auto drv_wire = npnr_ctx->getWireBelPin(net_id->driver.cell->bel, npnr_ctx->portPinFromId(net_id->driver.port)); + auto user_wire = npnr_ctx->getWireBelPin(ipin.cell->bel, npnr_ctx->portPinFromId(ipin.port)); + // int delta_x = abs(place_ctx.block_locs[sink_block].x - place_ctx.block_locs[source_block].x); // int delta_y = abs(place_ctx.block_locs[sink_block].y - place_ctx.block_locs[source_block].y); // @@ -1969,29 +1973,33 @@ static float recompute_bb_cost() { // * carry-chain connections. // */ // delay_source_to_sink = get_delta_delay(delta_x, delta_y); + + delay_source_to_sink = npnr_ctx->estimateDelay(drv_wire, user_wire); + // if (delay_source_to_sink < 0) { // vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, // "in comp_td_point_to_point_delay: Bad delay_source_to_sink value delta(%d, %d) delay of %g\n" // "in comp_td_point_to_point_delay: Delay is less than 0\n", // delta_x, delta_y, delay_source_to_sink); // } -// } -// -// -// return (delay_source_to_sink); -//} -// + } + + + return (delay_source_to_sink); +} + ////Recompute all point to point delays, updating point_to_point_delay_cost -//static void comp_td_point_to_point_delays() { -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// for (auto net_id : cluster_ctx.clb_nlist.nets()) { -// for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ++ipin) { -// point_to_point_delay_cost[net_id][ipin] = comp_td_point_to_point_delay(net_id, ipin); -// } -// } -//} -// +static void comp_td_point_to_point_delays() { + auto& cluster_ctx = g_vpr_ctx.clustering(); + + for (const auto& net : cluster_ctx.clb_nlist.nets()) { + auto net_id = net.second.get(); + for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ++ipin) { + point_to_point_delay_cost[net_id->name.index][ipin] = comp_td_point_to_point_delay(net_id, net_id->users[ipin-1]); + } + } +} + ///* Update the point_to_point_timing_cost values from the temporary * //* values for all connections that have changed. */ //static void update_td_cost() { @@ -2044,46 +2052,47 @@ static float recompute_bb_cost() { // } // return false; //} -// -//static void comp_td_costs(float *timing_cost, float *connection_delay_sum) { -// /* Computes the cost (from scratch) due to the delays and criticalities * -// * on all point to point connections, we define the timing cost of * -// * each connection as criticality*delay. */ -// -// unsigned ipin; -// float loc_timing_cost, loc_connection_delay_sum, temp_delay_cost, -// temp_timing_cost; -// -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// loc_timing_cost = 0.; -// loc_connection_delay_sum = 0.; -// -// for (auto net_id : cluster_ctx.clb_nlist.nets()) { /* For each net ... */ -// -// if (cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ -// continue; -// } -// -// for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { -// temp_delay_cost = comp_td_point_to_point_delay(net_id, ipin); -// temp_timing_cost = temp_delay_cost * get_timing_place_crit(net_id, ipin); -// -// loc_connection_delay_sum += temp_delay_cost; -// point_to_point_delay_cost[net_id][ipin] = temp_delay_cost; -// temp_point_to_point_delay_cost[net_id][ipin] = -1; /* Undefined */ -// -// point_to_point_timing_cost[net_id][ipin] = temp_timing_cost; -// temp_point_to_point_timing_cost[net_id][ipin] = -1; /* Undefined */ -// loc_timing_cost += temp_timing_cost; -// } -// } -// -// /* Make sure timing cost does not go above MIN_TIMING_COST. */ -// *timing_cost = loc_timing_cost; -// -// *connection_delay_sum = loc_connection_delay_sum; -//} + +static void comp_td_costs(float *timing_cost, float *connection_delay_sum) { + /* Computes the cost (from scratch) due to the delays and criticalities * + * on all point to point connections, we define the timing cost of * + * each connection as criticality*delay. */ + + unsigned ipin; + float loc_timing_cost, loc_connection_delay_sum, temp_delay_cost, + temp_timing_cost; + + auto& cluster_ctx = g_vpr_ctx.clustering(); + + loc_timing_cost = 0.; + loc_connection_delay_sum = 0.; + + for (const auto& net : cluster_ctx.clb_nlist.nets()) { /* For each net ... */ + auto net_id = net.second.get(); + + if (cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ + continue; + } + + for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { + temp_delay_cost = comp_td_point_to_point_delay(net_id, net_id->users[ipin-1]); + temp_timing_cost = temp_delay_cost * get_timing_place_crit(net_id, net_id->users[ipin-1]); + + loc_connection_delay_sum += temp_delay_cost; + point_to_point_delay_cost[net_id->name.index][ipin] = temp_delay_cost; + temp_point_to_point_delay_cost[net_id->name.index][ipin] = -1; /* Undefined */ + + point_to_point_timing_cost[net_id->name.index][ipin] = temp_timing_cost; + temp_point_to_point_timing_cost[net_id->name.index][ipin] = -1; /* Undefined */ + loc_timing_cost += temp_timing_cost; + } + } + + /* Make sure timing cost does not go above MIN_TIMING_COST. */ + *timing_cost = loc_timing_cost; + + *connection_delay_sum = loc_connection_delay_sum; +} /* Finds the cost from scratch. Done only when the placement * @@ -2094,7 +2103,7 @@ static float recompute_bb_cost() { * are found via the non_updateable_bb routine, to provide a * * cost which can be used to check the correctness of the * * other routine. */ -static float comp_bb_cost(/*e_cost_methods method*/) { +static float comp_bb_cost(e_cost_methods method) { float cost = 0; // double expected_wirelength = 0.0; // auto& cluster_ctx = g_vpr_ctx.clustering(); @@ -2104,7 +2113,7 @@ static float comp_bb_cost(/*e_cost_methods method*/) { if (!npnr_ctx->isGlobalNet(net_id)) { /* Do only if not global. */ /* Small nets don't use incremental updating on their bounding boxes, * * so they can use a fast bounding box calculator. */ - if (net_id->users.size() >= SMALL_NET /*&& method == NORMAL*/) { + if (net_id->users.size() >= SMALL_NET && method == NORMAL) { get_bb_from_scratch(net_id, &bb_coords[net_id->name.index], &bb_num_on_edges[net_id->name.index]); } @@ -2181,11 +2190,11 @@ static float comp_bb_cost(/*e_cost_methods method*/) { /* Allocates the major structures needed only by the placer, primarily for * * computing costs quickly and such. */ static void alloc_and_load_placement_structs( - /*float place_cost_exp, t_placer_opts placer_opts, + /*float place_cost_exp,*/ t_placer_opts placer_opts /*, t_direct_inf *directs, int num_directs*/) { // int max_pins_per_clb, i; -// unsigned int ipin; + unsigned int ipin; // auto& device_ctx = g_vpr_ctx.device(); auto& cluster_ctx = g_vpr_ctx.clustering(); @@ -2202,39 +2211,41 @@ static void alloc_and_load_placement_structs( // max_pins_per_clb = max(max_pins_per_clb, device_ctx.block_types[i].num_pins); // } // -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE -// || placer_opts.enable_timing_computations) { -// /* Allocate structures associated with timing driven placement */ -// /* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1] */ -// point_to_point_delay_cost.resize(num_nets); -// temp_point_to_point_delay_cost.resize(num_nets); -// -// point_to_point_timing_cost.resize(num_nets); -// temp_point_to_point_timing_cost.resize(num_nets); -// -// for (auto net_id : cluster_ctx.clb_nlist.nets()) { -// size_t num_sinks = cluster_ctx.clb_nlist.net_sinks(net_id).size(); -// /* In the following, subract one so index starts at * -// * 1 instead of 0 */ -// point_to_point_delay_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); -// point_to_point_delay_cost[net_id]--; -// -// temp_point_to_point_delay_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); -// temp_point_to_point_delay_cost[net_id]--; -// -// point_to_point_timing_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); -// point_to_point_timing_cost[net_id]--; -// -// temp_point_to_point_timing_cost[net_id] = (float *)vtr::malloc(num_sinks * sizeof(float)); -// temp_point_to_point_timing_cost[net_id]--; -// } -// for (auto net_id : cluster_ctx.clb_nlist.nets()) { -// for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { -// point_to_point_delay_cost[net_id][ipin] = 0; -// temp_point_to_point_delay_cost[net_id][ipin] = 0; -// } -// } -// } + if (/*placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + ||*/ placer_opts.enable_timing_computations) { + /* Allocate structures associated with timing driven placement */ + /* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1] */ + point_to_point_delay_cost.resize(num_nets); + temp_point_to_point_delay_cost.resize(num_nets); + + point_to_point_timing_cost.resize(num_nets); + temp_point_to_point_timing_cost.resize(num_nets); + + for (const auto& net : cluster_ctx.clb_nlist.nets()) { + auto net_id = net.second.get(); + size_t num_sinks = cluster_ctx.clb_nlist.net_sinks(net_id).size(); + /* In the following, subract one so index starts at * + * 1 instead of 0 */ + point_to_point_delay_cost[net_id->name.index] = (float *)malloc(num_sinks * sizeof(float)); + point_to_point_delay_cost[net_id->name.index]--; + + temp_point_to_point_delay_cost[net_id->name.index] = (float *)malloc(num_sinks * sizeof(float)); + temp_point_to_point_delay_cost[net_id->name.index]--; + + point_to_point_timing_cost[net_id->name.index] = (float *)malloc(num_sinks * sizeof(float)); + point_to_point_timing_cost[net_id->name.index]--; + + temp_point_to_point_timing_cost[net_id->name.index] = (float *)malloc(num_sinks * sizeof(float)); + temp_point_to_point_timing_cost[net_id->name.index]--; + } + for (const auto& net : cluster_ctx.clb_nlist.nets()) { + auto net_id = net.second.get(); + for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { + point_to_point_delay_cost[net_id->name.index][ipin] = 0; + temp_point_to_point_delay_cost[net_id->name.index][ipin] = 0; + } + } + } // We tradeoff memory for speed here: // npnr nets are actually pointers, with non sequential indices From 821bc54546f900aeb981b6626b82ba8ad255baf3 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Wed, 18 Jul 2018 02:02:28 -0700 Subject: [PATCH 047/116] Compiles but doesn't work yet --- common/placer_vpr.cc | 38 ++-- common/placer_vpr.inc | 418 ++++++++++++++++++++++-------------------- 2 files changed, 238 insertions(+), 218 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index dc246203cc..253477c632 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -54,22 +54,34 @@ namespace vpr { inline const std::vector>& operator[](size_t x) { return _bels.at(x); } std::vector>> _bels; }; + enum PinType + { + DRIVER = PortType::PORT_OUT, + SINK = PortType::PORT_IN, + }; static struct { struct t_clustering { struct { - struct t_nets { - size_t size() { return _size; } - size_t _size; - std::unordered_map>& operator()() const { return npnr_ctx->nets; } - } nets; + std::unordered_map>& nets() const { return npnr_ctx->nets; } bool net_is_global(const NetInfo* net) const { return npnr_ctx->isGlobalNet(net); } std::vector& net_sinks(NetInfo* net) const { return net->users; } struct t_net_pins { - t_net_pins(NetInfo* net) : _net(net) {} + t_net_pins(const NetInfo* net) : _net(net) {} size_t size() const { return _net->users.size() + 1; } - NetInfo* _net; + const NetInfo* _net; }; - t_net_pins net_pins(NetInfo* net) const { return t_net_pins(net); } + t_net_pins net_pins(const NetInfo* net) const { return t_net_pins(net); } + std::unordered_map& block_pins(CellInfo* cell) { return cell->ports; } + PinType pin_type(const PortInfo& port) { return static_cast(port.type); } + CellInfo* net_driver_block(const NetInfo* net) const { return net->driver.cell; } + size_t pin_net_index(const PortInfo& port) { + auto net = port.net; + for (auto it = net->users.begin(); it != net->users.end(); ++it) { + if (it->port == port.name) + return it - net->users.begin() + 1; + } + throw; + } } clb_nlist; t_clustering& operator()() { return *this; } } clustering; @@ -84,12 +96,13 @@ namespace vpr { static struct t_placer_opts { bool enable_timing_computations; const float td_place_exp_first = 1.0; + const float timing_tradeoff = 0.5; } placer_opts; // timing_place.cpp - float get_timing_place_crit(NetInfo* /*net_id*/, const PortRef& ipin) + float get_timing_place_crit(NetInfo* net_id, int ipin) { - return ipin.budget; + return net_id->users[ipin-1].budget; } #define VTR_ASSERT NPNR_ASSERT @@ -144,11 +157,6 @@ class VPRPlacer vpr::npnr_cells.push_back(cell.second.get()); } } - auto &num_nets = vpr::g_vpr_ctx.clustering.clb_nlist.nets._size; - for (auto& n : ctx->nets) { - auto net_id = n.second.get(); - num_nets = std::max(num_nets, net_id->name.index+1); - } vpr::placer_opts.enable_timing_computations = ctx->timing_driven; } diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index cafce10ebb..11864f0d7e 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -195,8 +195,8 @@ static void alloc_and_load_placement_structs( static void alloc_and_load_try_swap_structs(); -//static void free_placement_structs(t_placer_opts placer_opts); -// +static void free_placement_structs(t_placer_opts placer_opts); + //static void alloc_and_load_for_fast_cost_update(float place_cost_exp); // //static void free_fast_cost_update(); @@ -233,7 +233,7 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, float rlim, - /*enum e_place_algorithm place_algorithm, float timing_tradeoff,*/ + /*enum e_place_algorithm place_algorithm,*/ float timing_tradeoff, float inverse_prev_bb_cost, float inverse_prev_timing_cost, float *delay_cost); @@ -246,7 +246,7 @@ static /*ClusterBlockId*/ CellInfo* pick_from_block(); static float starting_t(float *cost_ptr, float *bb_cost_ptr, float *timing_cost_ptr, /*t_annealing_sched annealing_sched,*/ int max_moves, float rlim, - /*enum e_place_algorithm place_algorithm, float timing_tradeoff,*/ + /*enum e_place_algorithm place_algorithm,*/ float timing_tradeoff, float inverse_prev_bb_cost, float inverse_prev_timing_cost, float *delay_cost_ptr); @@ -264,13 +264,13 @@ static double get_std_dev(int n, double sum_x_squared, double av_x); static float recompute_bb_cost(); -static float comp_td_point_to_point_delay(/*ClusterNetId*/ NetInfo* net_id, /*int ipin*/ const PortRef& ipin); +static float comp_td_point_to_point_delay(/*ClusterNetId*/ NetInfo* net_id, int ipin); static void comp_td_point_to_point_delays(); -//static void update_td_cost(); -// -//static bool driven_by_moved_block(const ClusterNetId net); +static void update_td_cost(); + +static bool driven_by_moved_block(const /*ClusterNetId*/ NetInfo* net); static void comp_td_costs(float *timing_cost, float *connection_delay_sum); @@ -296,7 +296,7 @@ static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_af static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin*/ CellInfo* blk, BelId bel_from); -//static void update_td_delta_costs(const ClusterNetId net, const ClusterPinId pin, float& delta_timing_cost, float& delta_delay_cost); +static void update_td_delta_costs(/*const ClusterNetId*/ NetInfo *net, const /*ClusterPinId*/ PortInfo &pin, float& delta_timing_cost, float& delta_delay_cost); static float get_net_cost(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_ptr); @@ -349,8 +349,8 @@ void try_place(t_placer_opts placer_opts, int tot_iter, move_lim, moves_since_cost_recompute, /*width_fac,*/ num_connections, outer_crit_iter_count, inner_recompute_limit; //unsigned int ipin; - float t, success_rat, rlim, cost, timing_cost, bb_cost, new_bb_cost, /*new_timing_cost,*/ - delay_cost, /*new_delay_cost,*/ place_delay_value, inverse_prev_bb_cost, inverse_prev_timing_cost, + float t, success_rat, rlim, cost, timing_cost, bb_cost, new_bb_cost, new_timing_cost, + delay_cost, new_delay_cost, place_delay_value, inverse_prev_bb_cost, inverse_prev_timing_cost, oldt, crit_exponent, first_rlim, final_rlim , inverse_delta_rlim; // tatum::TimingPathInfo critical_path; @@ -571,7 +571,7 @@ void try_place(t_placer_opts placer_opts, t = starting_t(&cost, &bb_cost, &timing_cost, /*annealing_sched,*/ move_lim, rlim, - /*placer_opts.place_algorithm, placer_opts.timing_tradeoff,*/ + /*placer_opts.place_algorithm,*/ placer_opts.timing_tradeoff, inverse_prev_bb_cost, inverse_prev_timing_cost, &delay_cost); tot_iter = 0; @@ -620,20 +620,21 @@ void try_place(t_placer_opts placer_opts, bb_cost = new_bb_cost; // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// comp_td_costs(&new_timing_cost, &new_delay_cost); -// if (fabs(new_timing_cost - timing_cost) > timing_cost * ERROR_TOL) { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "in try_place: new_timing_cost = %g, old timing_cost = %g, ERROR_TOL = %g\n", -// new_timing_cost, timing_cost, ERROR_TOL); -// } -// if (fabs(new_delay_cost - delay_cost) > delay_cost * ERROR_TOL) { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "in try_place: new_delay_cost = %g, old delay_cost = %g, ERROR_TOL = %g\n", -// new_delay_cost, delay_cost, ERROR_TOL); -// } -// timing_cost = new_timing_cost; -// } -// + if (placer_opts.enable_timing_computations) { + comp_td_costs(&new_timing_cost, &new_delay_cost); + if (fabs(new_timing_cost - timing_cost) > timing_cost * ERROR_TOL) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "in try_place: new_timing_cost = %g, old timing_cost = %g, ERROR_TOL = %g\n", + new_timing_cost, timing_cost, ERROR_TOL); + } + if (fabs(new_delay_cost - delay_cost) > delay_cost * ERROR_TOL) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "in try_place: new_delay_cost = %g, old delay_cost = %g, ERROR_TOL = %g\n", + new_delay_cost, delay_cost, ERROR_TOL); + } + timing_cost = new_timing_cost; + } + // if (placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { cost = new_bb_cost; // } @@ -860,7 +861,7 @@ void try_place(t_placer_opts placer_opts, vtr::printf_info("\tSwaps rejected: %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_rejected, 100*reject_rate); vtr::printf_info("\tSwaps aborted : %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_aborted, 100*abort_rate); -// free_placement_structs(placer_opts); + free_placement_structs(placer_opts); // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE // || placer_opts.enable_timing_computations) { // @@ -951,7 +952,7 @@ static void placement_inner_loop(float t, float rlim, /*t_placer_opts placer_opt /* Inner loop begins */ for (inner_iter = 0; inner_iter < move_lim; inner_iter++) { e_swap_result swap_result = try_swap(t, cost, bb_cost, timing_cost, rlim, - /*placer_opts.place_algorithm, placer_opts.timing_tradeoff,*/ + /*placer_opts.place_algorithm,*/ placer_opts.timing_tradeoff, inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost); if (swap_result == ACCEPTED) { @@ -973,33 +974,34 @@ static void placement_inner_loop(float t, float rlim, /*t_placer_opts placer_opt // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// -// /* Do we want to re-timing analyze the circuit to get updated slack and criticality values? -// * We do this only once in a while, since it is expensive. -// */ -// if (inner_crit_iter_count >= inner_recompute_limit -// && inner_iter != move_lim - 1) { /*on last iteration don't recompute */ -// -// inner_crit_iter_count = 0; -//#ifdef VERBOSE -// vtr::printf("Inner loop recompute criticalities\n"); -//#endif -// /* Using the delays in net_delay, do a timing analysis to update slacks and -// * criticalities; then update the timing cost since it will change. -// */ -// //Inner loop timing update + if (placer_opts.enable_timing_computations) { + + /* Do we want to re-timing analyze the circuit to get updated slack and criticality values? + * We do this only once in a while, since it is expensive. + */ + if (inner_crit_iter_count >= inner_recompute_limit + && inner_iter != move_lim - 1) { /*on last iteration don't recompute */ + + inner_crit_iter_count = 0; +#ifdef VERBOSE + vtr::printf("Inner loop recompute criticalities\n"); +#endif + /* Using the delays in net_delay, do a timing analysis to update slacks and + * criticalities; then update the timing cost since it will change. + */ + //Inner loop timing update // timing_info.update(); // load_criticalities(timing_info, crit_exponent, netlist_pin_lookup); -// -//#ifdef ENABLE_CLASSIC_VPR_STA -// load_timing_graph_net_delays(point_to_point_delay_cost); -// do_timing_analysis(slacks, timing_inf, false, true); -//#endif -// -// comp_td_costs(timing_cost, delay_cost); -// } -// inner_crit_iter_count++; -// } + +#ifdef ENABLE_CLASSIC_VPR_STA + load_timing_graph_net_delays(point_to_point_delay_cost); + do_timing_analysis(slacks, timing_inf, false, true); +#endif + + comp_td_costs(timing_cost, delay_cost); + } + inner_crit_iter_count++; + } #ifdef VERBOSE vtr::printf("t = %g cost = %g bb_cost = %g timing_cost = %g move = %d dmax = %g\n", t, *cost, *bb_cost, *timing_cost, inner_iter, *delay_cost); @@ -1115,7 +1117,7 @@ static int exit_crit(float t, float cost/*, static float starting_t(float *cost_ptr, float *bb_cost_ptr, float *timing_cost_ptr, /*t_annealing_sched annealing_sched,*/ int max_moves, float rlim, - /*enum e_place_algorithm place_algorithm, float timing_tradeoff,*/ + /*enum e_place_algorithm place_algorithm,*/ float timing_tradeoff, float inverse_prev_bb_cost, float inverse_prev_timing_cost, float *delay_cost_ptr) { @@ -1139,7 +1141,7 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, for (i = 0; i < move_lim; i++) { e_swap_result swap_result = try_swap(HUGE_POSITIVE_FLOAT, cost_ptr, bb_cost_ptr, timing_cost_ptr, rlim, - /*place_algorithm, timing_tradeoff,*/ + /*place_algorithm,*/ timing_tradeoff, inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost_ptr); if (swap_result == ACCEPTED) { @@ -1355,7 +1357,7 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, float rlim, - /*enum e_place_algorithm place_algorithm, float timing_tradeoff,*/ + /*enum e_place_algorithm place_algorithm,*/ float timing_tradeoff, float inverse_prev_bb_cost, float inverse_prev_timing_cost, float *delay_cost) { @@ -1434,15 +1436,16 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin /*int num_nets_affected =*/ find_affected_nets_and_update_costs(/*place_algorithm,*/ bb_delta_c, timing_delta_c, delay_delta_c); // if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// /*in this case we redefine delta_c as a combination of timing and bb. * -// *additionally, we normalize all values, therefore delta_c is in * -// *relation to 1*/ -// -// delta_c = (1 - timing_tradeoff) * bb_delta_c * inverse_prev_bb_cost -// + timing_tradeoff * timing_delta_c * inverse_prev_timing_cost; -// } else { + if (placer_opts.enable_timing_computations) { + /*in this case we redefine delta_c as a combination of timing and bb. * + *additionally, we normalize all values, therefore delta_c is in * + *relation to 1*/ + + delta_c = (1 - timing_tradeoff) * bb_delta_c * inverse_prev_bb_cost + + timing_tradeoff * timing_delta_c * inverse_prev_timing_cost; + } else { delta_c = bb_delta_c; -// } + } /* 1 -> move accepted, 0 -> rejected. */ e_swap_result keep_switch = assess_swap(delta_c, t); @@ -1452,13 +1455,14 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin *bb_cost = *bb_cost + bb_delta_c; // if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// /*update the point_to_point_timing_cost and point_to_point_delay_cost -// * values from the temporary values */ -// *timing_cost = *timing_cost + timing_delta_c; -// *delay_cost = *delay_cost + delay_delta_c; -// -// update_td_cost(); -// } + if (placer_opts.enable_timing_computations) { + /*update the point_to_point_timing_cost and point_to_point_delay_cost + * values from the temporary values */ + *timing_cost = *timing_cost + timing_delta_c; + *delay_cost = *delay_cost + delay_delta_c; + + update_td_cost(); + } /* update net cost functions and reset flags. */ for (auto net_id : ts_nets_to_update) { @@ -1621,8 +1625,9 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit auto bel = b.second; //Go through all the pins in the moved block - for (const auto &port : blk->ports) { - auto net_id = port.second.net; + for (const auto &p : blk->ports) { + const auto& port = p.second; + auto net_id = port.net; if (!net_id) continue; VTR_ASSERT_SAFE_MSG(net_id, "Only valid nets should be found in compressed netlist block pins"); @@ -1640,9 +1645,10 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit update_net_bb(net_id, /*iblk, blk, blk_pin,*/ blk, bel); // if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// //Determine the change in timing costs if required -// update_td_delta_costs(net_id, blk_pin, timing_delta_c, delay_delta_c); -// } + if (placer_opts.enable_timing_computations) { + //Determine the change in timing costs if required + update_td_delta_costs(net_id, /*blk_pin*/ port, timing_delta_c, delay_delta_c); + } } } @@ -1704,43 +1710,43 @@ static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const } -//static void update_td_delta_costs(const ClusterNetId net, const ClusterPinId pin, float& delta_timing_cost, float& delta_delay_cost) { -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// if (cluster_ctx.clb_nlist.pin_type(pin) == PinType::DRIVER) { -// //This pin is a net driver on a moved block. -// //Re-compute all point to point connections for this net. -// for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net).size(); ipin++) { -// float temp_delay = comp_td_point_to_point_delay(net, ipin); -// temp_point_to_point_delay_cost[net][ipin] = temp_delay; -// -// temp_point_to_point_timing_cost[net][ipin] = get_timing_place_crit(net, ipin) * temp_delay; -// delta_timing_cost += temp_point_to_point_timing_cost[net][ipin] - point_to_point_timing_cost[net][ipin]; -// delta_delay_cost += temp_point_to_point_delay_cost[net][ipin] - point_to_point_delay_cost[net][ipin]; -// -// } -// } else { -// //This pin is a net sink on a moved block -// VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.pin_type(pin) == PinType::SINK); -// -// //If this net is being driven by a moved block, we do not -// //need to compute the change in the timing cost (here) since it will -// //be computed by the net's driver pin (since the driver block moved). -// // -// //Computing it here would double count the change, and mess up the -// //delta_timing_cost value. -// if (!driven_by_moved_block(net)) { -// int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin); -// -// float temp_delay = comp_td_point_to_point_delay(net, net_pin); -// temp_point_to_point_delay_cost[net][net_pin] = temp_delay; -// -// temp_point_to_point_timing_cost[net][net_pin] = get_timing_place_crit(net, net_pin) * temp_delay; -// delta_timing_cost += temp_point_to_point_timing_cost[net][net_pin] - point_to_point_timing_cost[net][net_pin]; -// delta_delay_cost += temp_point_to_point_delay_cost[net][net_pin] - point_to_point_delay_cost[net][net_pin]; -// } -// } -//} +static void update_td_delta_costs(/*const ClusterNetId*/ NetInfo *net, const /*ClusterPinId*/ PortInfo &pin, float& delta_timing_cost, float& delta_delay_cost) { + auto& cluster_ctx = g_vpr_ctx.clustering(); + + if (cluster_ctx.clb_nlist.pin_type(pin) == PinType::DRIVER) { + //This pin is a net driver on a moved block. + //Re-compute all point to point connections for this net. + for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net).size(); ipin++) { + float temp_delay = comp_td_point_to_point_delay(net, ipin); + temp_point_to_point_delay_cost[net->name.index][ipin] = temp_delay; + + temp_point_to_point_timing_cost[net->name.index][ipin] = get_timing_place_crit(net, ipin) * temp_delay; + delta_timing_cost += temp_point_to_point_timing_cost[net->name.index][ipin] - point_to_point_timing_cost[net->name.index][ipin]; + delta_delay_cost += temp_point_to_point_delay_cost[net->name.index][ipin] - point_to_point_delay_cost[net->name.index][ipin]; + + } + } else { + //This pin is a net sink on a moved block + VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.pin_type(pin) == PinType::SINK); + + //If this net is being driven by a moved block, we do not + //need to compute the change in the timing cost (here) since it will + //be computed by the net's driver pin (since the driver block moved). + // + //Computing it here would double count the change, and mess up the + //delta_timing_cost value. + if (!driven_by_moved_block(net)) { + int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin); + + float temp_delay = comp_td_point_to_point_delay(net, net_pin); + temp_point_to_point_delay_cost[net->name.index][net_pin] = temp_delay; + + temp_point_to_point_timing_cost[net->name.index][net_pin] = get_timing_place_crit(net, net_pin) * temp_delay; + delta_timing_cost += temp_point_to_point_timing_cost[net->name.index][net_pin] - point_to_point_timing_cost[net->name.index][net_pin]; + delta_delay_cost += temp_point_to_point_delay_cost[net->name.index][net_pin] - point_to_point_delay_cost[net->name.index][net_pin]; + } + } +} static bool find_to(/*t_type_ptr type,*/ float rlim, int x_from, int y_from, @@ -1944,7 +1950,7 @@ static float recompute_bb_cost() { } /*returns the delay of one point to point connection */ -static float comp_td_point_to_point_delay(/*ClusterNetId*/ NetInfo* net_id, /*int ipin*/ const PortRef& ipin) { +static float comp_td_point_to_point_delay(/*ClusterNetId*/ NetInfo* net_id, int ipin) { auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.placement(); @@ -1961,7 +1967,7 @@ static float comp_td_point_to_point_delay(/*ClusterNetId*/ NetInfo* net_id, /*in // VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.block_type(sink_block) != nullptr); auto drv_wire = npnr_ctx->getWireBelPin(net_id->driver.cell->bel, npnr_ctx->portPinFromId(net_id->driver.port)); - auto user_wire = npnr_ctx->getWireBelPin(ipin.cell->bel, npnr_ctx->portPinFromId(ipin.port)); + auto user_wire = npnr_ctx->getWireBelPin(net_id->users[ipin-1].cell->bel, npnr_ctx->portPinFromId(net_id->users[ipin-1].port)); // int delta_x = abs(place_ctx.block_locs[sink_block].x - place_ctx.block_locs[source_block].x); // int delta_y = abs(place_ctx.block_locs[sink_block].y - place_ctx.block_locs[source_block].y); @@ -1995,63 +2001,66 @@ static void comp_td_point_to_point_delays() { for (const auto& net : cluster_ctx.clb_nlist.nets()) { auto net_id = net.second.get(); for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ++ipin) { - point_to_point_delay_cost[net_id->name.index][ipin] = comp_td_point_to_point_delay(net_id, net_id->users[ipin-1]); + point_to_point_delay_cost[net_id->name.index][ipin] = comp_td_point_to_point_delay(net_id, ipin); } } } -///* Update the point_to_point_timing_cost values from the temporary * -//* values for all connections that have changed. */ -//static void update_td_cost() { -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// /* Go through all the blocks moved. */ -// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { -// ClusterBlockId bnum = blocks_affected.moved_blocks[iblk].block_num; -// for (ClusterPinId pin_id : cluster_ctx.clb_nlist.block_pins(bnum)) { -// ClusterNetId net_id = cluster_ctx.clb_nlist.pin_net(pin_id); -// -// if (cluster_ctx.clb_nlist.net_is_global(net_id)) -// continue; -// -// if (cluster_ctx.clb_nlist.pin_type(pin_id) == PinType::DRIVER) { -// //This net is being driven by a moved block, recompute -// //all point to point connections on this net. -// for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { -// point_to_point_delay_cost[net_id][ipin] = temp_point_to_point_delay_cost[net_id][ipin]; -// temp_point_to_point_delay_cost[net_id][ipin] = -1; -// point_to_point_timing_cost[net_id][ipin] = temp_point_to_point_timing_cost[net_id][ipin]; -// temp_point_to_point_timing_cost[net_id][ipin] = -1; -// } -// } else { -// //This pin is a net sink on a moved block -// VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.pin_type(pin_id) == PinType::SINK); -// -// /* The following "if" prevents the value from being updated twice. */ -// if (!driven_by_moved_block(net_id)) { -// int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin_id); -// -// point_to_point_delay_cost[net_id][net_pin] = temp_point_to_point_delay_cost[net_id][net_pin]; -// temp_point_to_point_delay_cost[net_id][net_pin] = -1; -// point_to_point_timing_cost[net_id][net_pin] = temp_point_to_point_timing_cost[net_id][net_pin]; -// temp_point_to_point_timing_cost[net_id][net_pin] = -1; -// } -// } -// } /* Finished going through all the pins in the moved block */ -// } /* Finished going through all the blocks moved */ -//} -// -//static bool driven_by_moved_block(const ClusterNetId net) { -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// ClusterBlockId net_driver_block = cluster_ctx.clb_nlist.net_driver_block(net); -// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { -// if (net_driver_block == blocks_affected.moved_blocks[iblk].block_num) { -// return true; -// } -// } -// return false; -//} +/* Update the point_to_point_timing_cost values from the temporary * +* values for all connections that have changed. */ +static void update_td_cost() { + auto& cluster_ctx = g_vpr_ctx.clustering(); + + /* Go through all the blocks moved. */ + for (const auto& b : blocks_affected) { + auto bnum = b.first; + for (const auto& pin : cluster_ctx.clb_nlist.block_pins(bnum)) { + const auto& pin_id = pin.second; + auto net_id = pin_id.net; + if (!net_id) continue; + + if (cluster_ctx.clb_nlist.net_is_global(net_id)) + continue; + + if (cluster_ctx.clb_nlist.pin_type(pin_id) == PinType::DRIVER) { + //This net is being driven by a moved block, recompute + //all point to point connections on this net. + for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { + point_to_point_delay_cost[net_id->name.index][ipin] = temp_point_to_point_delay_cost[net_id->name.index][ipin]; + temp_point_to_point_delay_cost[net_id->name.index][ipin] = -1; + point_to_point_timing_cost[net_id->name.index][ipin] = temp_point_to_point_timing_cost[net_id->name.index][ipin]; + temp_point_to_point_timing_cost[net_id->name.index][ipin] = -1; + } + } else { + //This pin is a net sink on a moved block + VTR_ASSERT_SAFE(cluster_ctx.clb_nlist.pin_type(pin_id) == PinType::SINK); + + /* The following "if" prevents the value from being updated twice. */ + if (!driven_by_moved_block(net_id)) { + int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin_id); + + point_to_point_delay_cost[net_id->name.index][net_pin] = temp_point_to_point_delay_cost[net_id->name.index][net_pin]; + temp_point_to_point_delay_cost[net_id->name.index][net_pin] = -1; + point_to_point_timing_cost[net_id->name.index][net_pin] = temp_point_to_point_timing_cost[net_id->name.index][net_pin]; + temp_point_to_point_timing_cost[net_id->name.index][net_pin] = -1; + } + } + } /* Finished going through all the pins in the moved block */ + } /* Finished going through all the blocks moved */ +} + +static bool driven_by_moved_block(const /*ClusterNetId*/ NetInfo* net) { + auto& cluster_ctx = g_vpr_ctx.clustering(); + + auto net_driver_block = cluster_ctx.clb_nlist.net_driver_block(net); + for (const auto& b : blocks_affected) { + auto blk = b.first; + if (net_driver_block == blk) { + return true; + } + } + return false; +} static void comp_td_costs(float *timing_cost, float *connection_delay_sum) { /* Computes the cost (from scratch) due to the delays and criticalities * @@ -2075,8 +2084,8 @@ static void comp_td_costs(float *timing_cost, float *connection_delay_sum) { } for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { - temp_delay_cost = comp_td_point_to_point_delay(net_id, net_id->users[ipin-1]); - temp_timing_cost = temp_delay_cost * get_timing_place_crit(net_id, net_id->users[ipin-1]); + temp_delay_cost = comp_td_point_to_point_delay(net_id, ipin); + temp_timing_cost = temp_delay_cost * get_timing_place_crit(net_id, ipin); loc_connection_delay_sum += temp_delay_cost; point_to_point_delay_cost[net_id->name.index][ipin] = temp_delay_cost; @@ -2136,42 +2145,43 @@ static float comp_bb_cost(e_cost_methods method) { } -///* Frees the major structures needed by the placer (and not needed * -//* elsewhere). */ -//static void free_placement_structs(t_placer_opts placer_opts) { +/* Frees the major structures needed by the placer (and not needed * +* elsewhere). */ +static void free_placement_structs(t_placer_opts placer_opts) { // int imacro; -// -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// + + auto& cluster_ctx = g_vpr_ctx.clustering(); + // free_legal_placements(); // free_fast_cost_update(); // -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE -// || placer_opts.enable_timing_computations) { -// -// for (auto net_id : cluster_ctx.clb_nlist.nets()) { -// /*add one to the address since it is indexed from 1 not 0 */ -// point_to_point_timing_cost[net_id]++; -// free(point_to_point_timing_cost[net_id]); -// -// temp_point_to_point_timing_cost[net_id]++; -// free(temp_point_to_point_timing_cost[net_id]); -// -// point_to_point_delay_cost[net_id]++; -// free(point_to_point_delay_cost[net_id]); -// -// temp_point_to_point_delay_cost[net_id]++; -// free(temp_point_to_point_delay_cost[net_id]); -// } -// -// point_to_point_timing_cost.clear(); -// point_to_point_delay_cost.clear(); -// temp_point_to_point_timing_cost.clear(); -// temp_point_to_point_delay_cost.clear(); -// + if (/*placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + ||*/ placer_opts.enable_timing_computations) { + + for (const auto& net : cluster_ctx.clb_nlist.nets()) { + auto net_id = net.second.get(); + /*add one to the address since it is indexed from 1 not 0 */ + point_to_point_timing_cost[net_id->name.index]++; + free(point_to_point_timing_cost[net_id->name.index]); + + temp_point_to_point_timing_cost[net_id->name.index]++; + free(temp_point_to_point_timing_cost[net_id->name.index]); + + point_to_point_delay_cost[net_id->name.index]++; + free(point_to_point_delay_cost[net_id->name.index]); + + temp_point_to_point_delay_cost[net_id->name.index]++; + free(temp_point_to_point_delay_cost[net_id->name.index]); + } + + point_to_point_timing_cost.clear(); + point_to_point_delay_cost.clear(); + temp_point_to_point_timing_cost.clear(); + temp_point_to_point_delay_cost.clear(); + // net_pin_indices.clear(); -// } -// + } + // free_placement_macros_structs(); // // for (imacro = 0; imacro < num_pl_macros; imacro++) @@ -2184,8 +2194,8 @@ static float comp_bb_cost(e_cost_methods method) { // /* Frees up all the data structure used in vpr_utils. */ // free_port_pin_from_blk_pin(); // free_blk_pin_from_port_pin(); -// -//} + +} /* Allocates the major structures needed only by the placer, primarily for * * computing costs quickly and such. */ @@ -2200,6 +2210,7 @@ static void alloc_and_load_placement_structs( auto& cluster_ctx = g_vpr_ctx.clustering(); size_t num_nets = cluster_ctx.clb_nlist.nets().size(); + for (const auto& n : cluster_ctx.clb_nlist.nets()) num_nets = std::max(num_nets, n.second.get()->name.index); // init_placement_context(); // @@ -2311,6 +2322,7 @@ static void alloc_and_load_try_swap_structs() { auto& cluster_ctx = g_vpr_ctx.clustering(); size_t num_nets = cluster_ctx.clb_nlist.nets().size(); + for (const auto& n : cluster_ctx.clb_nlist.nets()) num_nets = std::max(num_nets, n.second.get()->name.index); ts_bb_coord_new.resize(num_nets, t_bb()); ts_bb_edge_new.resize(num_nets, t_bb()); From 3d878222b2f647ce8d93edab798af8a7682a4749 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Thu, 19 Jul 2018 08:25:48 -0700 Subject: [PATCH 048/116] Merge from master, resolve conflicts --- common/nextpnr.h | 21 +++-- ecp5/arch.cc | 33 ++++++- ecp5/arch.h | 24 ++++++ ecp5/archdefs.h | 8 +- ecp5/cells.cc | 10 ++- ecp5/main.cc | 8 +- ecp5/pack.cc | 131 +++++++++++++++++++++++++--- ecp5/synth/blinky.v | 86 +++++++++---------- ecp5/synth/blinky.ys | 9 +- ecp5/synth/blinky_nopack.ys | 2 - ecp5/synth/cells.v | 49 ----------- ecp5/synth/simple_map.v | 68 --------------- ecp5/synth/ulx3s.v | 18 ---- ecp5/synth/ulx3s.ys | 9 -- ecp5/synth/wire.v | 11 --- ecp5/synth/wire.ys | 9 -- ecp5/trellis_import.py | 78 +++++++++++++++++ generic/archdefs.h | 8 +- gui/base.qrc | 2 +- gui/basewindow.cc | 35 -------- gui/basewindow.h | 5 -- gui/designwidget.cc | 57 +++++++------ gui/designwidget.h | 3 +- gui/ice40/mainwindow.cc | 1 - gui/ice40/worker.cc | 3 +- gui/infotab.cc | 58 ------------- gui/infotab.h | 48 ----------- gui/resources/cross.png | Bin 0 -> 655 bytes gui/resources/splash.png | Bin 4651 -> 0 bytes gui/yosys_edit.cc | 96 --------------------- gui/yosys_edit.h | 57 ------------- gui/yosystab.cc | 107 ----------------------- gui/yosystab.h | 59 ------------- ice40/arch.cc | 59 +++++++++++-- ice40/arch.h | 26 +++++- ice40/arch_place.cc | 36 +++----- ice40/archdefs.h | 24 +++++- ice40/bitstream.cc | 99 +++++++++++++++++++-- ice40/blinky.ys | 2 +- ice40/cells.cc | 65 ++++++++++++++ ice40/cells.h | 2 + ice40/chipdb.py | 40 ++++++++- ice40/gfx.cc | 9 +- ice40/gfx.h | 3 +- ice40/pack.cc | 28 ++++++ ice40/picorv32.cpu | Bin 0 -> 842064 bytes ice40/picorv32.sh | 2 +- ice40/picorv32_arachne.sh | 9 -- ice40/picorv32_new.cpu | Bin 0 -> 262880 bytes ice40/picorv32_new.pdf | Bin 0 -> 26977 bytes ice40/picorv32_new2.pdf | Bin 0 -> 26339 bytes ice40/picorv32_vpr.cpu | Bin 0 -> 230336 bytes ice40/picorv32_vpr.pdf | Bin 0 -> 25927 bytes ice40/picovr32.cpu | Bin 0 -> 312368 bytes ice40/picovr32.orig.cpu | Bin 0 -> 986792 bytes ice40/picovr32.orig.log | 152 +++++++++++++++++++++++++++++++++ ice40/picovr32.orig.pdf | Bin 0 -> 29729 bytes ice40/picovr32.pdf | Bin 0 -> 27064 bytes ice40/picovr32_.pdf | Bin 0 -> 27125 bytes ice40/picovr32_vpr.cpu | Bin 0 -> 716816 bytes ice40/place_legaliser.cc | 3 + ice40/transform_arachne_loc.py | 24 ------ 62 files changed, 871 insertions(+), 825 deletions(-) delete mode 100644 ecp5/synth/blinky_nopack.ys delete mode 100644 ecp5/synth/cells.v delete mode 100644 ecp5/synth/simple_map.v delete mode 100644 ecp5/synth/ulx3s.v delete mode 100644 ecp5/synth/ulx3s.ys delete mode 100644 ecp5/synth/wire.v delete mode 100644 ecp5/synth/wire.ys delete mode 100644 gui/infotab.cc delete mode 100644 gui/infotab.h create mode 100644 gui/resources/cross.png delete mode 100644 gui/resources/splash.png delete mode 100644 gui/yosys_edit.cc delete mode 100644 gui/yosys_edit.h delete mode 100644 gui/yosystab.cc delete mode 100644 gui/yosystab.h create mode 100644 ice40/picorv32.cpu delete mode 100755 ice40/picorv32_arachne.sh create mode 100644 ice40/picorv32_new.cpu create mode 100644 ice40/picorv32_new.pdf create mode 100644 ice40/picorv32_new2.pdf create mode 100644 ice40/picorv32_vpr.cpu create mode 100644 ice40/picorv32_vpr.pdf create mode 100644 ice40/picovr32.cpu create mode 100644 ice40/picovr32.orig.cpu create mode 100644 ice40/picovr32.orig.log create mode 100644 ice40/picovr32.orig.pdf create mode 100644 ice40/picovr32.pdf create mode 100644 ice40/picovr32_.pdf create mode 100644 ice40/picovr32_vpr.cpu delete mode 100755 ice40/transform_arachne_loc.py diff --git a/common/nextpnr.h b/common/nextpnr.h index 3d0cc955b8..bc64adb501 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -72,21 +72,22 @@ class assertion_failure : public std::runtime_error int line; }; -inline void except_assert_impl(bool expr, const char *message, const char *expr_str, const char *filename, int line) +NPNR_NORETURN +inline bool assert_fail_impl(const char *message, const char *expr_str, const char *filename, int line) { - if (!expr) - throw assertion_failure(message, expr_str, filename, line); + throw assertion_failure(message, expr_str, filename, line); } NPNR_NORETURN -inline void assert_false_impl(std::string message, std::string filename, int line) +inline bool assert_fail_impl_str(std::string message, const char *expr_str, const char *filename, int line) { - throw assertion_failure(message, "false", filename, line); + throw assertion_failure(message, expr_str, filename, line); } -#define NPNR_ASSERT(cond) except_assert_impl((cond), #cond, #cond, __FILE__, __LINE__) -#define NPNR_ASSERT_MSG(cond, msg) except_assert_impl((cond), msg, #cond, __FILE__, __LINE__) -#define NPNR_ASSERT_FALSE(msg) assert_false_impl(msg, __FILE__, __LINE__) +#define NPNR_ASSERT(cond) ((void)((cond) || (assert_fail_impl(#cond, #cond, __FILE__, __LINE__)))) +#define NPNR_ASSERT_MSG(cond, msg) ((void)((cond) || (assert_fail_impl(msg, #cond, __FILE__, __LINE__)))) +#define NPNR_ASSERT_FALSE(msg) (assert_fail_impl(msg, "false", __FILE__, __LINE__)) +#define NPNR_ASSERT_FALSE_STR(msg) (assert_fail_impl_str(msg, "false", __FILE__, __LINE__)) struct BaseCtx; struct Context; @@ -203,6 +204,8 @@ struct PipMap struct NetInfo : ArchNetInfo { IdString name; + int32_t udata; + PortRef driver; std::vector users; std::unordered_map attrs; @@ -228,6 +231,8 @@ struct PortInfo struct CellInfo : ArchCellInfo { IdString name, type; + int32_t udata; + std::unordered_map ports; std::unordered_map attrs, params; diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 0ffede3b94..1510a27ff1 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -118,6 +118,16 @@ Arch::Arch(ArchArgs args) : args(args) log_error("Unsupported ECP5 chip type.\n"); } #endif + package_info = nullptr; + for (int i = 0; i < chip_info->num_packages; i++) { + if (args.package == chip_info->package_info[i].name.get()) { + package_info = &(chip_info->package_info[i]); + break; + } + } + + if (!package_info) + log_error("Unsupported package '%s' for '%s'.\n", args.package.c_str(), getChipName().c_str()); id_trellis_slice = id("TRELLIS_SLICE"); id_clk = id("CLK"); @@ -282,9 +292,28 @@ IdString Arch::getPipName(PipId pip) const // ----------------------------------------------------------------------- -BelId Arch::getPackagePinBel(const std::string &pin) const { return BelId(); } +BelId Arch::getPackagePinBel(const std::string &pin) const +{ + for (int i = 0; i < package_info->num_pins; i++) { + if (package_info->pin_data[i].name.get() == pin) { + BelId bel; + bel.location = package_info->pin_data[i].abs_loc; + bel.index = package_info->pin_data[i].bel_index; + return bel; + } + } + return BelId(); +} -std::string Arch::getBelPackagePin(BelId bel) const { return ""; } +std::string Arch::getBelPackagePin(BelId bel) const +{ + for (int i = 0; i < package_info->num_pins; i++) { + if (package_info->pin_data[i].abs_loc == bel.location && package_info->pin_data[i].bel_index == bel.index) { + return package_info->pin_data[i].name.get(); + } + } + return ""; +} // ----------------------------------------------------------------------- void Arch::estimatePosition(BelId bel, int &x, int &y, bool &gb) const diff --git a/ecp5/arch.h b/ecp5/arch.h index 4bb71b47a8..944aedeabe 100644 --- a/ecp5/arch.h +++ b/ecp5/arch.h @@ -96,13 +96,36 @@ NPNR_PACKED_STRUCT(struct LocationTypePOD { RelPtr pip_data; }); +NPNR_PACKED_STRUCT(struct PIOInfoPOD { + Location abs_loc; + int32_t bel_index; + RelPtr function_name; + int16_t bank; + int16_t padding; +}); + +NPNR_PACKED_STRUCT(struct PackagePinPOD { + RelPtr name; + Location abs_loc; + int32_t bel_index; +}); + +NPNR_PACKED_STRUCT(struct PackageInfoPOD { + RelPtr name; + int32_t num_pins; + RelPtr pin_data; +}); + NPNR_PACKED_STRUCT(struct ChipInfoPOD { int32_t width, height; int32_t num_tiles; int32_t num_location_types; + int32_t num_packages, num_pios; RelPtr locations; RelPtr location_type; RelPtr> tiletype_names; + RelPtr package_info; + RelPtr pio_info; }); #if defined(_MSC_VER) @@ -340,6 +363,7 @@ struct ArchArgs struct Arch : BaseCtx { const ChipInfoPOD *chip_info; + const PackageInfoPOD *package_info; mutable std::unordered_map bel_by_name; mutable std::unordered_map wire_by_name; diff --git a/ecp5/archdefs.h b/ecp5/archdefs.h index df1add4465..941607baa3 100644 --- a/ecp5/archdefs.h +++ b/ecp5/archdefs.h @@ -129,8 +129,12 @@ struct DecalId } }; -struct ArchNetInfo { }; -struct ArchCellInfo { }; +struct ArchNetInfo +{ +}; +struct ArchCellInfo +{ +}; NEXTPNR_NAMESPACE_END diff --git a/ecp5/cells.cc b/ecp5/cells.cc index 59504735aa..e3532f36d7 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -116,6 +116,14 @@ std::unique_ptr create_ecp5_cell(Context *ctx, IdString type, std::str add_port(ctx, new_cell.get(), "I", PORT_IN); add_port(ctx, new_cell.get(), "T", PORT_IN); add_port(ctx, new_cell.get(), "O", PORT_OUT); + } else if (type == ctx->id("LUT4")) { + new_cell->params[ctx->id("INIT")] = "0"; + + add_port(ctx, new_cell.get(), "A", PORT_IN); + add_port(ctx, new_cell.get(), "B", PORT_IN); + add_port(ctx, new_cell.get(), "C", PORT_IN); + add_port(ctx, new_cell.get(), "D", PORT_IN); + add_port(ctx, new_cell.get(), "Z", PORT_OUT); } else { log_error("unable to create ECP5 cell of type %s", type.c_str(ctx)); } @@ -169,7 +177,7 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index) { - lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] = str_or_default(lc->params, ctx->id("INIT"), "0"); + lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] = str_or_default(lut->params, ctx->id("INIT"), "0"); replace_port(lut, ctx->id("A"), lc, ctx->id("A" + std::to_string(index))); replace_port(lut, ctx->id("B"), lc, ctx->id("B" + std::to_string(index))); replace_port(lut, ctx->id("C"), lc, ctx->id("C" + std::to_string(index))); diff --git a/ecp5/main.cc b/ecp5/main.cc index 7521b88c3b..5a4a900a55 100644 --- a/ecp5/main.cc +++ b/ecp5/main.cc @@ -68,6 +68,8 @@ int main(int argc, char *argv[]) options.add_options()("45k", "set device type to LFE5U-45F"); options.add_options()("85k", "set device type to LFE5U-85F"); + options.add_options()("package", po::value(), "select device package (defaults to CABGA381)"); + options.add_options()("json", po::value(), "JSON design file to ingest"); options.add_options()("seed", po::value(), "seed value for random number generator"); @@ -123,8 +125,10 @@ int main(int argc, char *argv[]) args.type = ArchArgs::LFE5U_45F; if (vm.count("85k")) args.type = ArchArgs::LFE5U_85F; - - args.package = "CABGA381"; + if (vm.count("package")) + args.package = vm["package"].as(); + else + args.package = "CABGA381"; args.speed = 6; std::unique_ptr ctx = std::unique_ptr(new Context(args)); diff --git a/ecp5/pack.cc b/ecp5/pack.cc index c0427d46ef..11cc264746 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -71,6 +71,15 @@ class Ecp5Packer } } + const NetInfo *net_or_nullptr(CellInfo *cell, IdString port) + { + auto fnd = cell->ports.find(port); + if (fnd == cell->ports.end()) + return nullptr; + else + return fnd->second.net; + } + // Return whether two FFs can be packed together in the same slice bool can_pack_ffs(CellInfo *ff0, CellInfo *ff1) { @@ -88,11 +97,11 @@ class Ecp5Packer if (str_or_default(ff0->params, ctx->id("CLKMUX"), "CLK") != str_or_default(ff1->params, ctx->id("CLKMUX"), "CLK")) return false; - if (ff0->ports.at(ctx->id("CLK")).net != ff1->ports.at(ctx->id("CLK")).net) + if (net_or_nullptr(ff0, ctx->id("CLK")) != net_or_nullptr(ff1, ctx->id("CLK"))) return false; - if (ff0->ports.at(ctx->id("CE")).net != ff1->ports.at(ctx->id("CE")).net) + if (net_or_nullptr(ff0, ctx->id("CE")) != net_or_nullptr(ff1, ctx->id("CE"))) return false; - if (ff0->ports.at(ctx->id("LSR")).net != ff1->ports.at(ctx->id("LSR")).net) + if (net_or_nullptr(ff0, ctx->id("LSR")) != net_or_nullptr(ff1, ctx->id("LSR"))) return false; return true; } @@ -223,8 +232,23 @@ class Ecp5Packer } else { log_error("TRELLIS_IO required on all top level IOs...\n"); } + packed_cells.insert(ci->name); std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(trio->attrs, trio->attrs.begin())); + + auto loc_attr = trio->attrs.find(ctx->id("LOC")); + if (loc_attr != trio->attrs.end()) { + std::string pin = loc_attr->second; + BelId pinBel = ctx->getPackagePinBel(pin); + if (pinBel == BelId()) { + log_error("IO pin '%s' constrained to pin '%s', which does not exist for package '%s'.\n", + trio->name.c_str(ctx), pin.c_str(), ctx->args.package.c_str()); + } else { + log_info("pin '%s' constrained to Bel '%s'.\n", trio->name.c_str(ctx), + ctx->getBelName(pinBel).c_str(ctx)); + } + trio->attrs[ctx->id("BEL")] = ctx->getBelName(pinBel).str(ctx); + } } } flush_cells(); @@ -275,6 +299,8 @@ class Ecp5Packer ff_to_slice(ctx, ff, packed.get(), 0, true); packed_cells.insert(ff->name); sliceUsage[packed->name].ff0_used = true; + lutffPairs.erase(ci->name); + fflutPairs.erase(ff->name); } new_cells.push_back(std::move(packed)); @@ -304,10 +330,14 @@ class Ecp5Packer if (ff0 != lutffPairs.end()) { ff_to_slice(ctx, ctx->cells.at(ff0->second).get(), slice.get(), 0, true); packed_cells.insert(ff0->second); + lutffPairs.erase(lut0->name); + fflutPairs.erase(ff0->second); } if (ff1 != lutffPairs.end()) { ff_to_slice(ctx, ctx->cells.at(ff1->second).get(), slice.get(), 1, true); packed_cells.insert(ff1->second); + lutffPairs.erase(lut1->name); + fflutPairs.erase(ff1->second); } new_cells.push_back(std::move(slice)); @@ -333,6 +363,8 @@ class Ecp5Packer if (ff != lutffPairs.end()) { ff_to_slice(ctx, ctx->cells.at(ff->second).get(), slice.get(), 0, true); packed_cells.insert(ff->second); + lutffPairs.erase(ci->name); + fflutPairs.erase(ff->second); } new_cells.push_back(std::move(slice)); @@ -374,21 +406,98 @@ class Ecp5Packer new_init |= (1 << i); } } - cell->params[ctx->id("INIT")] = std::to_string(init); - NetInfo *innet = cell->ports.at(input).net; - if (innet != nullptr) { - innet->users.erase( - std::remove_if(innet->users.begin(), innet->users.end(), - [cell, input](PortRef port) { return port.cell == cell && port.port == input; }), - innet->users.end()); - } + cell->params[ctx->id("INIT")] = std::to_string(new_init); cell->ports.at(input).net = nullptr; } + // Merge a net into a constant net + void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constnet, bool constval) + { + orig->driver.cell = nullptr; + for (auto user : orig->users) { + if (user.cell != nullptr) { + CellInfo *uc = user.cell; + if (ctx->verbose) + log_info("%s user %s\n", orig->name.c_str(ctx), uc->name.c_str(ctx)); + if (is_lut(ctx, uc)) { + set_lut_input_constant(uc, user.port, constval); + } else if (is_ff(ctx, uc) && user.port == ctx->id("CE")) { + uc->params[ctx->id("CEMUX")] = constval ? "1" : "0"; + uc->ports[user.port].net = nullptr; + } else if (is_ff(ctx, uc) && user.port == ctx->id("LSR") && + ((!constval && str_or_default(uc->params, ctx->id("LSRMUX"), "LSR") == "LSR") || + (constval && str_or_default(uc->params, ctx->id("LSRMUX"), "LSR") == "INV"))) { + uc->ports[user.port].net = nullptr; + } else { + uc->ports[user.port].net = constnet; + constnet->users.push_back(user); + } + } + } + orig->users.clear(); + } + + // Pack constants (simple implementation) + void pack_constants() + { + log_info("Packing constants..\n"); + + std::unique_ptr gnd_cell = create_ecp5_cell(ctx, ctx->id("LUT4"), "$PACKER_GND"); + gnd_cell->params[ctx->id("INIT")] = "0"; + std::unique_ptr gnd_net = std::unique_ptr(new NetInfo); + gnd_net->name = ctx->id("$PACKER_GND_NET"); + gnd_net->driver.cell = gnd_cell.get(); + gnd_net->driver.port = ctx->id("Z"); + gnd_cell->ports.at(ctx->id("Z")).net = gnd_net.get(); + + std::unique_ptr vcc_cell = create_ecp5_cell(ctx, ctx->id("LUT4"), "$PACKER_VCC"); + vcc_cell->params[ctx->id("INIT")] = "65535"; + std::unique_ptr vcc_net = std::unique_ptr(new NetInfo); + vcc_net->name = ctx->id("$PACKER_VCC_NET"); + vcc_net->driver.cell = vcc_cell.get(); + vcc_net->driver.port = ctx->id("Z"); + vcc_cell->ports.at(ctx->id("Z")).net = vcc_net.get(); + + std::vector dead_nets; + + bool gnd_used = false, vcc_used = false; + + for (auto net : sorted(ctx->nets)) { + NetInfo *ni = net.second; + if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("GND")) { + IdString drv_cell = ni->driver.cell->name; + set_net_constant(ctx, ni, gnd_net.get(), false); + gnd_used = true; + dead_nets.push_back(net.first); + ctx->cells.erase(drv_cell); + } else if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("VCC")) { + IdString drv_cell = ni->driver.cell->name; + set_net_constant(ctx, ni, vcc_net.get(), true); + vcc_used = true; + dead_nets.push_back(net.first); + ctx->cells.erase(drv_cell); + } + } + + if (gnd_used) { + ctx->cells[gnd_cell->name] = std::move(gnd_cell); + ctx->nets[gnd_net->name] = std::move(gnd_net); + } + if (vcc_used) { + ctx->cells[vcc_cell->name] = std::move(vcc_cell); + ctx->nets[vcc_net->name] = std::move(vcc_net); + } + + for (auto dn : dead_nets) { + ctx->nets.erase(dn); + } + } + public: void pack() { pack_io(); + pack_constants(); find_lutff_pairs(); pack_lut5s(); pair_luts(); diff --git a/ecp5/synth/blinky.v b/ecp5/synth/blinky.v index ac7c6ea3d3..9c6b187be0 100644 --- a/ecp5/synth/blinky.v +++ b/ecp5/synth/blinky.v @@ -1,46 +1,46 @@ -module top(input clk_pin, input btn_pin, output [3:0] led_pin, output gpio0_pin); +module top(input clk_pin, input btn_pin, output [7:0] led_pin, output gpio0_pin); - wire clk; - wire [7:0] led; + wire clk; + wire [7:0] led; wire btn; wire gpio0; - (* BEL="X0/Y35/PIOA" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("INPUT")) clk_buf (.B(clk_pin), .O(clk)); + (* LOC="G2" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("INPUT")) clk_buf (.B(clk_pin), .O(clk)); - (* BEL="X4/Y71/PIOA" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("INPUT")) btn_buf (.B(btn_pin), .O(btn)); + (* LOC="R1" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("INPUT")) btn_buf (.B(btn_pin), .O(btn)); - (* BEL="X0/Y23/PIOC" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_0 (.B(led_pin[0]), .I(led[0])); - (* BEL="X0/Y23/PIOD" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_1 (.B(led_pin[1]), .I(led[1])); - (* BEL="X0/Y26/PIOA" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_2 (.B(led_pin[2]), .I(led[2])); - (* BEL="X0/Y26/PIOC" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_3 (.B(led_pin[3]), .I(led[3])); + (* LOC="B2" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_0 (.B(led_pin[0]), .I(led[0])); + (* LOC="C2" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_1 (.B(led_pin[1]), .I(led[1])); + (* LOC="C1" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_2 (.B(led_pin[2]), .I(led[2])); + (* LOC="D2" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_3 (.B(led_pin[3]), .I(led[3])); - (* BEL="X0/Y26/PIOB" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_4 (.B(led_pin[4]), .I(led[4])); - (* BEL="X0/Y32/PIOD" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_5 (.B(led_pin[5]), .I(led[5])); - (* BEL="X0/Y26/PIOD" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_6 (.B(led_pin[6]), .I(led[6])); - (* BEL="X0/Y29/PIOD" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf_7 (.B(led_pin[7]), .I(led[7])); + (* LOC="D1" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_4 (.B(led_pin[4]), .I(led[4])); + (* LOC="E2" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_5 (.B(led_pin[5]), .I(led[5])); + (* LOC="E1" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_6 (.B(led_pin[6]), .I(led[6])); + (* LOC="H3" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) led_buf_7 (.B(led_pin[7]), .I(led[7])); - (* BEL="X0/Y62/PIOD" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) gpio0_buf (.B(gpio0_pin), .I(gpio0)); + (* LOC="L2" *) (* IO_TYPE="LVCMOS33" *) + TRELLIS_IO #(.DIR("OUTPUT")) gpio0_buf (.B(gpio0_pin), .I(gpio0)); localparam ctr_width = 24; localparam ctr_max = 2**ctr_width - 1; - reg [ctr_width-1:0] ctr = 0; - reg [9:0] pwm_ctr = 0; + reg [ctr_width-1:0] ctr = 0; + reg [9:0] pwm_ctr = 0; reg dir = 0; - always@(posedge clk) begin - ctr <= btn ? ctr : (dir ? ctr - 1'b1 : ctr + 1'b1); + always@(posedge clk) begin + ctr <= btn ? ctr : (dir ? ctr - 1'b1 : ctr + 1'b1); if (ctr[ctr_width-1 : ctr_width-3] == 0 && dir == 1) dir <= 1'b0; else if (ctr[ctr_width-1 : ctr_width-3] == 7 && dir == 0) @@ -54,24 +54,24 @@ module top(input clk_pin, input btn_pin, output [3:0] led_pin, output gpio0_pin) genvar i; generate - for (i = 0; i < 8; i=i+1) begin - always @ (posedge clk) begin - if (ctr[ctr_width-1 : ctr_width-3] == i) - brightness[i] <= bright_max; - else if (ctr[ctr_width-1 : ctr_width-3] == (i - 1)) - brightness[i] <= ctr[ctr_width-4:ctr_width-13]; - else if (ctr[ctr_width-1 : ctr_width-3] == (i + 1)) - brightness[i] <= bright_max - ctr[ctr_width-4:ctr_width-13]; - else - brightness[i] <= 0; - led_reg[i] <= pwm_ctr < brightness[i]; - end - end + for (i = 0; i < 8; i=i+1) begin + always @ (posedge clk) begin + if (ctr[ctr_width-1 : ctr_width-3] == i) + brightness[i] <= bright_max; + else if (ctr[ctr_width-1 : ctr_width-3] == (i - 1)) + brightness[i] <= ctr[ctr_width-4:ctr_width-13]; + else if (ctr[ctr_width-1 : ctr_width-3] == (i + 1)) + brightness[i] <= bright_max - ctr[ctr_width-4:ctr_width-13]; + else + brightness[i] <= 0; + led_reg[i] <= pwm_ctr < brightness[i]; + end + end endgenerate assign led = led_reg; // Tie GPIO0, keep board from rebooting - TRELLIS_SLICE #(.MODE("LOGIC"), .LUT0_INITVAL(16'hFFFF)) vcc (.F0(gpio0)); + assign gpio0 = 1'b1; endmodule diff --git a/ecp5/synth/blinky.ys b/ecp5/synth/blinky.ys index c0b74636dc..fb359380ee 100644 --- a/ecp5/synth/blinky.ys +++ b/ecp5/synth/blinky.ys @@ -1,9 +1,2 @@ read_verilog blinky.v -read_verilog -lib cells.v -synth -top top -abc -lut 4 -techmap -map simple_map.v -splitnets -opt_clean -stat -write_json blinky.json +synth_ecp5 -noccu2 -nomux -nodram -json blinky.json diff --git a/ecp5/synth/blinky_nopack.ys b/ecp5/synth/blinky_nopack.ys deleted file mode 100644 index fb359380ee..0000000000 --- a/ecp5/synth/blinky_nopack.ys +++ /dev/null @@ -1,2 +0,0 @@ -read_verilog blinky.v -synth_ecp5 -noccu2 -nomux -nodram -json blinky.json diff --git a/ecp5/synth/cells.v b/ecp5/synth/cells.v deleted file mode 100644 index 353b8adae0..0000000000 --- a/ecp5/synth/cells.v +++ /dev/null @@ -1,49 +0,0 @@ -(* blackbox *) -module TRELLIS_SLICE( - input A0, B0, C0, D0, - input A1, B1, C1, D1, - input M0, M1, - input FCI, FXA, FXB, - - input CLK, LSR, CE, - input DI0, DI1, - - input WD0, WD1, - input WAD0, WAD1, WAD2, WAD3, - input WRE, WCK, - - output F0, Q0, - output F1, Q1, - output FCO, OFX0, OFX1, - - output WDO0, WDO1, WDO2, WDO3, - output WADO0, WADO1, WADO2, WADO3 -); - -parameter MODE = "LOGIC"; -parameter GSR = "ENABLED"; -parameter SRMODE = "LSR_OVER_CE"; -parameter CEMUX = "1"; -parameter CLKMUX = "CLK"; -parameter LSRMUX = "LSR"; -parameter LUT0_INITVAL = 16'h0000; -parameter LUT1_INITVAL = 16'h0000; -parameter REG0_SD = "0"; -parameter REG1_SD = "0"; -parameter REG0_REGSET = "RESET"; -parameter REG1_REGSET = "RESET"; -parameter CCU2_INJECT1_0 = "NO"; -parameter CCU2_INJECT1_1 = "NO"; - -endmodule - -(* blackbox *) (* keep *) -module TRELLIS_IO( - inout B, - input I, - input T, - output O, -); -parameter DIR = "INPUT"; - -endmodule diff --git a/ecp5/synth/simple_map.v b/ecp5/synth/simple_map.v deleted file mode 100644 index 550fa92ced..0000000000 --- a/ecp5/synth/simple_map.v +++ /dev/null @@ -1,68 +0,0 @@ -module \$_DFF_P_ (input D, C, output Q); - TRELLIS_SLICE #( - .MODE("LOGIC"), - .CLKMUX("CLK"), - .CEMUX("1"), - .REG0_SD("0"), - .REG0_REGSET("RESET"), - .SRMODE("LSR_OVER_CE"), - .GSR("DISABLED") - ) _TECHMAP_REPLACE_ ( - .CLK(C), - .M0(D), - .Q0(Q) - ); -endmodule - -module \$lut (A, Y); - parameter WIDTH = 0; - parameter LUT = 0; - - input [WIDTH-1:0] A; - output Y; - - generate - if (WIDTH == 1) begin - TRELLIS_SLICE #( - .MODE("LOGIC"), - .LUT0_INITVAL({8{LUT[1:0]}}) - ) _TECHMAP_REPLACE_ ( - .A0(A[0]), - .F0(Y) - ); - end - if (WIDTH == 2) begin - TRELLIS_SLICE #( - .MODE("LOGIC"), - .LUT0_INITVAL({4{LUT[3:0]}}) - ) _TECHMAP_REPLACE_ ( - .A0(A[0]), - .B0(A[1]), - .F0(Y) - ); - end - if (WIDTH == 3) begin - TRELLIS_SLICE #( - .MODE("LOGIC"), - .LUT0_INITVAL({2{LUT[7:0]}}) - ) _TECHMAP_REPLACE_ ( - .A0(A[0]), - .B0(A[1]), - .C0(A[2]), - .F0(Y) - ); - end - if (WIDTH == 4) begin - TRELLIS_SLICE #( - .MODE("LOGIC"), - .LUT0_INITVAL(LUT) - ) _TECHMAP_REPLACE_ ( - .A0(A[0]), - .B0(A[1]), - .C0(A[2]), - .D0(A[3]), - .F0(Y) - ); - end - endgenerate -endmodule diff --git a/ecp5/synth/ulx3s.v b/ecp5/synth/ulx3s.v deleted file mode 100644 index 08f6e65b05..0000000000 --- a/ecp5/synth/ulx3s.v +++ /dev/null @@ -1,18 +0,0 @@ -module top(input a_pin, output led_pin, output led2_pin, output gpio0_pin); - - wire a; - wire led, led2; - wire gpio0; - (* BEL="X4/Y71/PIOA" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("INPUT")) a_buf (.B(a_pin), .O(a)); - (* BEL="X0/Y23/PIOC" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led_buf (.B(led_pin), .I(led)); - (* BEL="X0/Y26/PIOA" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) led2_buf (.B(led2_pin), .I(led2)); - (* BEL="X0/Y62/PIOD" *) (* IO_TYPE="LVCMOS33" *) - TRELLIS_IO #(.DIR("OUTPUT")) gpio0_buf (.B(gpio0_pin), .I(gpio0)); - assign led = a; - assign led2 = !a; - - TRELLIS_SLICE #(.MODE("LOGIC"), .LUT0_INITVAL(16'hFFFF)) vcc (.F0(gpio0)); -endmodule diff --git a/ecp5/synth/ulx3s.ys b/ecp5/synth/ulx3s.ys deleted file mode 100644 index d741c985aa..0000000000 --- a/ecp5/synth/ulx3s.ys +++ /dev/null @@ -1,9 +0,0 @@ -read_verilog ulx3s.v -read_verilog -lib cells.v -synth -top top -abc -lut 4 -techmap -map simple_map.v -splitnets -opt_clean -stat -write_json ulx3s.json diff --git a/ecp5/synth/wire.v b/ecp5/synth/wire.v deleted file mode 100644 index 2af68ed2be..0000000000 --- a/ecp5/synth/wire.v +++ /dev/null @@ -1,11 +0,0 @@ -module top(input a_pin, output [3:0] led_pin); - - wire a; - wire [3:0] led; - - TRELLIS_IO #(.DIR("INPUT")) a_buf (.B(a_pin), .O(a)); - TRELLIS_IO #(.DIR("OUTPUT")) led_buf [3:0] (.B(led_pin), .I(led)); - - //assign led[0] = !a; - always @(posedge a) led[0] <= !led[0]; -endmodule diff --git a/ecp5/synth/wire.ys b/ecp5/synth/wire.ys deleted file mode 100644 index f916588b8f..0000000000 --- a/ecp5/synth/wire.ys +++ /dev/null @@ -1,9 +0,0 @@ -read_verilog wire.v -read_verilog -lib cells.v -synth -top top -abc -lut 4 -techmap -map simple_map.v -splitnets -opt_clean -stat -write_json wire.json diff --git a/ecp5/trellis_import.py b/ecp5/trellis_import.py index 2bc3216903..af5386e7a6 100755 --- a/ecp5/trellis_import.py +++ b/ecp5/trellis_import.py @@ -2,6 +2,8 @@ import pytrellis import database import argparse +import json +from os import path location_types = dict() type_at_location = dict() @@ -319,6 +321,49 @@ def write_binary(self, f): "PIO": 2 } +def get_bel_index(ddrg, loc, name): + loctype = ddrg.locationTypes[ddrg.typeAtLocation[loc]] + idx = 0 + for bel in loctype.bels: + if ddrg.to_str(bel.name) == name: + return idx + idx += 1 + assert loc.y == max_row # Only missing IO should be special pins at bottom of device + return None + + +packages = {} +pindata = [] + +def process_pio_db(ddrg, device): + piofile = path.join(database.get_db_root(), "ECP5", dev_names[device], "iodb.json") + with open(piofile, 'r') as f: + piodb = json.load(f) + for pkgname, pkgdata in sorted(piodb["packages"].items()): + pins = [] + for name, pinloc in sorted(pkgdata.items()): + x = pinloc["col"] + y = pinloc["row"] + loc = pytrellis.Location(x, y) + pio = "PIO" + pinloc["pio"] + bel_idx = get_bel_index(ddrg, loc, pio) + if bel_idx is not None: + pins.append((name, loc, bel_idx)) + packages[pkgname] = pins + for metaitem in piodb["pio_metadata"]: + x = metaitem["col"] + y = metaitem["row"] + loc = pytrellis.Location(x, y) + pio = "PIO" + metaitem["pio"] + bank = metaitem["bank"] + if "function" in metaitem: + pinfunc = metaitem["function"] + else: + pinfunc = None + bel_idx = get_bel_index(ddrg, loc, pio) + if bel_idx is not None: + pindata.append((loc, bel_idx, bank, pinfunc)) + def write_database(dev_name, ddrg, endianness): def write_loc(loc, sym_name): @@ -409,6 +454,32 @@ def write_loc(loc, sym_name): for y in range(0, max_row+1): for x in range(0, max_col+1): bba.u32(loctypes.index(ddrg.typeAtLocation[pytrellis.Location(x, y)]), "loctype") + for package, pkgdata in sorted(packages.items()): + bba.l("package_data_%s" % package, "PackagePinPOD") + for pin in pkgdata: + name, loc, bel_idx = pin + bba.s(name, "name") + write_loc(loc, "abs_loc") + bba.u32(bel_idx, "bel_index") + + bba.l("package_data", "PackageInfoPOD") + for package, pkgdata in sorted(packages.items()): + bba.s(package, "name") + bba.u32(len(pkgdata), "num_pins") + bba.r("package_data_%s" % package, "pin_data") + + bba.l("pio_info", "PIOInfoPOD") + for pin in pindata: + loc, bel_idx, bank, func = pin + write_loc(loc, "abs_loc") + bba.u32(bel_idx, "bel_index") + if func is not None: + bba.s(func, "function_name") + else: + bba.r(None, "function_name") + bba.u16(bank, "bank") + bba.u16(0, "padding") + bba.l("tiletype_names", "RelPtr") for tt in tiletype_names: @@ -419,9 +490,15 @@ def write_loc(loc, sym_name): bba.u32(max_row + 1, "height") bba.u32((max_col + 1) * (max_row + 1), "num_tiles") bba.u32(len(location_types), "num_location_types") + bba.u32(len(packages), "num_packages") + bba.u32(len(pindata), "num_pios") + bba.r("locations", "locations") bba.r("location_types", "location_type") bba.r("tiletype_names", "tiletype_names") + bba.r("package_data", "package_info") + bba.r("pio_info", "pio_info") + bba.finalize() return bba @@ -451,6 +528,7 @@ def main(): ddrg = pytrellis.make_dedup_chipdb(chip) max_row = chip.get_max_row() max_col = chip.get_max_col() + process_pio_db(ddrg, args.device) print("{} unique location types".format(len(ddrg.locationTypes))) bba = write_database(args.device, ddrg, "le") diff --git a/generic/archdefs.h b/generic/archdefs.h index f59997767f..06d4ec6e16 100644 --- a/generic/archdefs.h +++ b/generic/archdefs.h @@ -52,7 +52,11 @@ typedef IdString PipId; typedef IdString GroupId; typedef IdString DecalId; -struct ArchNetInfo { }; -struct ArchCellInfo { }; +struct ArchNetInfo +{ +}; +struct ArchCellInfo +{ +}; NEXTPNR_NAMESPACE_END diff --git a/gui/base.qrc b/gui/base.qrc index bf21986b13..1a848f5404 100644 --- a/gui/base.qrc +++ b/gui/base.qrc @@ -9,6 +9,6 @@ resources/resultset_previous.png resources/resultset_next.png resources/resultset_last.png - resources/splash.png + resources/cross.png diff --git a/gui/basewindow.cc b/gui/basewindow.cc index 81c89e4515..4a225bd69a 100644 --- a/gui/basewindow.cc +++ b/gui/basewindow.cc @@ -29,7 +29,6 @@ #include "log.h" #include "mainwindow.h" #include "pythontab.h" -#include "yosystab.h" static void initBasenameResource() { Q_INIT_RESOURCE(base); } @@ -95,29 +94,12 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr context, QWidget *parent splitter_v->addWidget(centralTabWidget); splitter_v->addWidget(tabWidget); - displaySplash(); } BaseMainWindow::~BaseMainWindow() {} void BaseMainWindow::closeTab(int index) { delete centralTabWidget->widget(index); } -void BaseMainWindow::displaySplash() -{ - splash = new QSplashScreen(); - splash->setPixmap(QPixmap(":/icons/resources/splash.png")); - splash->show(); - connect(designview, SIGNAL(finishContextLoad()), splash, SLOT(close())); - connect(designview, SIGNAL(contextLoadStatus(std::string)), this, SLOT(displaySplashMessage(std::string))); - QCoreApplication::instance()->processEvents(); -} - -void BaseMainWindow::displaySplashMessage(std::string msg) -{ - splash->showMessage(msg.c_str(), Qt::AlignCenter | Qt::AlignBottom, Qt::white); - QCoreApplication::instance()->processEvents(); -} - void BaseMainWindow::writeInfo(std::string text) { console->info(text); } void BaseMainWindow::createMenusAndBars() @@ -147,10 +129,6 @@ void BaseMainWindow::createMenusAndBars() actionExit->setStatusTip("Exit the application"); connect(actionExit, SIGNAL(triggered()), this, SLOT(close())); - QAction *actionYosys = new QAction("Yosys", this); - actionYosys->setStatusTip("Run Yosys"); - connect(actionYosys, SIGNAL(triggered()), this, SLOT(yosys())); - QAction *actionAbout = new QAction("About", this); menuBar = new QMenuBar(); @@ -183,19 +161,6 @@ void BaseMainWindow::createMenusAndBars() mainToolBar->addAction(actionNew); mainToolBar->addAction(actionOpen); mainToolBar->addAction(actionSave); - mainToolBar->addAction(actionYosys); } -void BaseMainWindow::yosys() -{ - QString folder = QFileDialog::getExistingDirectory(0, ("Select Work Folder"), QDir::currentPath(), - QFileDialog::ShowDirsOnly); - if (!folder.isEmpty() && !folder.isNull()) { - YosysTab *yosysTab = new YosysTab(folder); - yosysTab->setAttribute(Qt::WA_DeleteOnClose); - centralTabWidget->addTab(yosysTab, "Yosys"); - centralTabWidget->setCurrentWidget(yosysTab); - centralTabWidget->setTabToolTip(centralTabWidget->indexOf(yosysTab), folder); - } -} NEXTPNR_NAMESPACE_END diff --git a/gui/basewindow.h b/gui/basewindow.h index 087880ed5f..eee426c72c 100644 --- a/gui/basewindow.h +++ b/gui/basewindow.h @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -50,17 +49,14 @@ class BaseMainWindow : public QMainWindow protected: void createMenusAndBars(); - void displaySplash(); protected Q_SLOTS: void writeInfo(std::string text); - void displaySplashMessage(std::string msg); void closeTab(int index); virtual void new_proj() = 0; virtual void open_proj() = 0; virtual bool save_proj() = 0; - void yosys(); Q_SIGNALS: void contextChanged(Context *ctx); @@ -78,7 +74,6 @@ class BaseMainWindow : public QMainWindow QAction *actionNew; QAction *actionOpen; QProgressBar *progressBar; - QSplashScreen *splash; DesignWidget *designview; }; diff --git a/gui/designwidget.cc b/gui/designwidget.cc index 335ed92982..4123bf30ec 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -125,11 +125,27 @@ DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), net updateButtons(); }); + actionClear = new QAction("", this); + actionClear->setIcon(QIcon(":/icons/resources/cross.png")); + actionClear->setEnabled(true); + connect(actionClear, &QAction::triggered, this, [this] { + history_index = -1; + history.clear(); + QTreeWidgetItem *clickItem = treeWidget->selectedItems().at(0); + if (clickItem->parent()) { + ElementType type = static_cast(clickItem)->getType(); + if (type != ElementType::NONE) + addToHistory(treeWidget->selectedItems().at(0)); + } + updateButtons(); + }); + QToolBar *toolbar = new QToolBar(); toolbar->addAction(actionFirst); toolbar->addAction(actionPrev); toolbar->addAction(actionNext); toolbar->addAction(actionLast); + toolbar->addAction(actionClear); QWidget *topWidget = new QWidget(); QVBoxLayout *vbox1 = new QVBoxLayout(); @@ -230,7 +246,6 @@ void DesignWidget::newContext(Context *ctx) bel_root->setText(0, "Bels"); treeWidget->insertTopLevelItem(0, bel_root); if (ctx) { - Q_EMIT contextLoadStatus("Configuring bels..."); for (auto bel : ctx->getBels()) { auto id = ctx->getBelName(bel); QStringList items = QString(id.c_str(ctx)).split("/"); @@ -263,7 +278,6 @@ void DesignWidget::newContext(Context *ctx) wire_root->setText(0, "Wires"); treeWidget->insertTopLevelItem(0, wire_root); if (ctx) { - Q_EMIT contextLoadStatus("Configuring wires..."); for (auto wire : ctx->getWires()) { auto id = ctx->getWireName(wire); QStringList items = QString(id.c_str(ctx)).split("/"); @@ -295,7 +309,6 @@ void DesignWidget::newContext(Context *ctx) pip_root->setText(0, "Pips"); treeWidget->insertTopLevelItem(0, pip_root); if (ctx) { - Q_EMIT contextLoadStatus("Configuring pips..."); for (auto pip : ctx->getPips()) { auto id = ctx->getPipName(pip); QStringList items = QString(id.c_str(ctx)).split("/"); @@ -331,8 +344,6 @@ void DesignWidget::newContext(Context *ctx) cells_root = new QTreeWidgetItem(treeWidget); cells_root->setText(0, "Cells"); treeWidget->insertTopLevelItem(0, cells_root); - - Q_EMIT finishContextLoad(); } void DesignWidget::updateTree() @@ -477,13 +488,12 @@ void DesignWidget::onItemSelectionChanged() addToHistory(clickItem); clearProperties(); - if (type == ElementType::BEL) { - IdString c = static_cast(clickItem)->getData(); - BelId bel = ctx->getBelByName(c); - decals.push_back(ctx->getBelDecal(bel)); - Q_EMIT selected(decals); + IdString c = static_cast(clickItem)->getData(); + Q_EMIT selected(getDecals(type, c)); + if (type == ElementType::BEL) { + BelId bel = ctx->getBelByName(c); QtProperty *topItem = addTopLevelProperty("Bel"); addProperty(topItem, QVariant::String, "Name", c.c_str(ctx)); @@ -494,12 +504,7 @@ void DesignWidget::onItemSelectionChanged() ElementType::CELL); } else if (type == ElementType::WIRE) { - IdString c = static_cast(clickItem)->getData(); WireId wire = ctx->getWireByName(c); - - decals.push_back(ctx->getWireDecal(wire)); - Q_EMIT selected(decals); - QtProperty *topItem = addTopLevelProperty("Wire"); addProperty(topItem, QVariant::String, "Name", c.c_str(ctx)); @@ -551,12 +556,7 @@ void DesignWidget::onItemSelectionChanged() } } } else if (type == ElementType::PIP) { - IdString c = static_cast(clickItem)->getData(); PipId pip = ctx->getPipByName(c); - - decals.push_back(ctx->getPipDecal(pip)); - Q_EMIT selected(decals); - QtProperty *topItem = addTopLevelProperty("Pip"); addProperty(topItem, QVariant::String, "Name", c.c_str(ctx)); @@ -576,7 +576,6 @@ void DesignWidget::onItemSelectionChanged() addProperty(delayItem, QVariant::Double, "Fall", delay.fallDelay()); addProperty(delayItem, QVariant::Double, "Average", delay.avgDelay()); } else if (type == ElementType::NET) { - IdString c = static_cast(clickItem)->getData(); NetInfo *net = ctx->nets.at(c).get(); QtProperty *topItem = addTopLevelProperty("Net"); @@ -613,7 +612,7 @@ void DesignWidget::onItemSelectionChanged() auto name = ctx->getWireName(item.first).c_str(ctx); QtProperty *wireItem = addSubGroup(wiresItem, name); - addProperty(wireItem, QVariant::String, "Name", name); + addProperty(wireItem, QVariant::String, "Wire", name, ElementType::WIRE); if (item.second.pip != PipId()) addProperty(wireItem, QVariant::String, "Pip", ctx->getPipName(item.second.pip).c_str(ctx), @@ -625,7 +624,6 @@ void DesignWidget::onItemSelectionChanged() } } else if (type == ElementType::CELL) { - IdString c = static_cast(clickItem)->getData(); CellInfo *cell = ctx->cells.at(c).get(); QtProperty *topItem = addTopLevelProperty("Cell"); @@ -689,19 +687,28 @@ std::vector DesignWidget::getDecals(ElementType type, IdString value) WireId wire = ctx->getWireByName(value); if (wire != WireId()) { decals.push_back(ctx->getWireDecal(wire)); - Q_EMIT selected(decals); } } break; case ElementType::PIP: { PipId pip = ctx->getPipByName(value); if (pip != PipId()) { decals.push_back(ctx->getPipDecal(pip)); - Q_EMIT selected(decals); } } break; case ElementType::NET: { + NetInfo *net = ctx->nets.at(value).get(); + for (auto &item : net->wires) { + decals.push_back(ctx->getWireDecal(item.first)); + if (item.second.pip != PipId()) { + decals.push_back(ctx->getPipDecal(item.second.pip)); + } + } } break; case ElementType::CELL: { + CellInfo *cell = ctx->cells.at(value).get(); + if (cell->bel != BelId()) { + decals.push_back(ctx->getBelDecal(cell->bel)); + } } break; default: break; diff --git a/gui/designwidget.h b/gui/designwidget.h index 1afe817d15..b5877f6037 100644 --- a/gui/designwidget.h +++ b/gui/designwidget.h @@ -65,8 +65,6 @@ class DesignWidget : public QWidget void info(std::string text); void selected(std::vector decal); void highlight(std::vector decal, int group); - void finishContextLoad(); - void contextLoadStatus(std::string text); private Q_SLOTS: void prepareMenuProperty(const QPoint &pos); @@ -104,6 +102,7 @@ class DesignWidget : public QWidget QAction *actionPrev; QAction *actionNext; QAction *actionLast; + QAction *actionClear; QColor highlightColors[8]; QMap highlightSelected; diff --git a/gui/ice40/mainwindow.cc b/gui/ice40/mainwindow.cc index 4b1f2c57c4..28792ed3f6 100644 --- a/gui/ice40/mainwindow.cc +++ b/gui/ice40/mainwindow.cc @@ -226,7 +226,6 @@ void MainWindow::new_proj() ctx = std::unique_ptr(new Context(chipArgs)); actionLoadJSON->setEnabled(true); - Q_EMIT displaySplash(); Q_EMIT contextChanged(ctx.get()); } } diff --git a/gui/ice40/worker.cc b/gui/ice40/worker.cc index 09093ec8c3..f0087b4a7a 100644 --- a/gui/ice40/worker.cc +++ b/gui/ice40/worker.cc @@ -121,7 +121,8 @@ void Worker::place(bool timing_driven) Q_EMIT taskStarted(); try { ctx->timing_driven = timing_driven; - Q_EMIT place_finished(ctx->place()); + //Q_EMIT place_finished(ctx->place()); + Q_EMIT place_finished(ctx->place_vpr()); } catch (WorkerInterruptionRequested) { Q_EMIT taskCanceled(); } diff --git a/gui/infotab.cc b/gui/infotab.cc deleted file mode 100644 index dd44b8061a..0000000000 --- a/gui/infotab.cc +++ /dev/null @@ -1,58 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include "infotab.h" -#include - -NEXTPNR_NAMESPACE_BEGIN - -InfoTab::InfoTab(QWidget *parent) : QWidget(parent) -{ - plainTextEdit = new QPlainTextEdit(); - plainTextEdit->setReadOnly(true); - QFont f("unexistent"); - f.setStyleHint(QFont::Monospace); - plainTextEdit->setFont(f); - - plainTextEdit->setContextMenuPolicy(Qt::CustomContextMenu); - QAction *clearAction = new QAction("Clear &buffer", this); - clearAction->setStatusTip("Clears display buffer"); - connect(clearAction, SIGNAL(triggered()), this, SLOT(clearBuffer())); - contextMenu = plainTextEdit->createStandardContextMenu(); - contextMenu->addSeparator(); - contextMenu->addAction(clearAction); - connect(plainTextEdit, SIGNAL(customContextMenuRequested(const QPoint)), this, SLOT(showContextMenu(const QPoint))); - - QGridLayout *mainLayout = new QGridLayout(); - mainLayout->addWidget(plainTextEdit); - setLayout(mainLayout); -} - -void InfoTab::info(std::string str) -{ - plainTextEdit->moveCursor(QTextCursor::End); - plainTextEdit->insertPlainText(str.c_str()); - plainTextEdit->moveCursor(QTextCursor::End); -} - -void InfoTab::showContextMenu(const QPoint &pt) { contextMenu->exec(mapToGlobal(pt)); } - -void InfoTab::clearBuffer() { plainTextEdit->clear(); } - -NEXTPNR_NAMESPACE_END diff --git a/gui/infotab.h b/gui/infotab.h deleted file mode 100644 index 4152997305..0000000000 --- a/gui/infotab.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef INFOTAB_H -#define INFOTAB_H - -#include -#include -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -class InfoTab : public QWidget -{ - Q_OBJECT - - public: - explicit InfoTab(QWidget *parent = 0); - void info(std::string str); - public Q_SLOTS: - void clearBuffer(); - private Q_SLOTS: - void showContextMenu(const QPoint &pt); - - private: - QPlainTextEdit *plainTextEdit; - QMenu *contextMenu; -}; - -NEXTPNR_NAMESPACE_END - -#endif // INFOTAB_H diff --git a/gui/resources/cross.png b/gui/resources/cross.png new file mode 100644 index 0000000000000000000000000000000000000000..1514d51a3cf1b67e1c5b9ada36f1fd474e2d214a GIT binary patch literal 655 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 diff --git a/gui/yosys_edit.cc b/gui/yosys_edit.cc deleted file mode 100644 index 72d2430d0d..0000000000 --- a/gui/yosys_edit.cc +++ /dev/null @@ -1,96 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * Copyright (C) 2018 Alex Tsui - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include "yosys_edit.h" -#include -#include - -NEXTPNR_NAMESPACE_BEGIN - -YosysLineEditor::YosysLineEditor(QWidget *parent) : QLineEdit(parent), index(0) -{ - setContextMenuPolicy(Qt::CustomContextMenu); - QAction *clearAction = new QAction("Clear &history", this); - clearAction->setStatusTip("Clears line edit history"); - connect(clearAction, SIGNAL(triggered()), this, SLOT(clearHistory())); - contextMenu = createStandardContextMenu(); - contextMenu->addSeparator(); - contextMenu->addAction(clearAction); - - connect(this, SIGNAL(returnPressed()), SLOT(textInserted())); - connect(this, SIGNAL(customContextMenuRequested(const QPoint)), this, SLOT(showContextMenu(const QPoint))); -} - -void YosysLineEditor::keyPressEvent(QKeyEvent *ev) -{ - - if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down) { - QToolTip::hideText(); - if (lines.empty()) - return; - if (ev->key() == Qt::Key_Up) - index--; - if (ev->key() == Qt::Key_Down) - index++; - - if (index < 0) - index = 0; - if (index >= lines.size()) { - index = lines.size(); - clear(); - return; - } - setText(lines[index]); - } else if (ev->key() == Qt::Key_Escape) { - QToolTip::hideText(); - clear(); - return; - } else if (ev->key() == Qt::Key_Tab) { - return; - } - QToolTip::hideText(); - - QLineEdit::keyPressEvent(ev); -} - -// This makes TAB work -bool YosysLineEditor::focusNextPrevChild(bool next) { return false; } - -void YosysLineEditor::textInserted() -{ - if (lines.empty() || lines.back() != text()) - lines += text(); - if (lines.size() > 100) - lines.removeFirst(); - index = lines.size(); - clear(); - Q_EMIT textLineInserted(lines.back()); -} - -void YosysLineEditor::showContextMenu(const QPoint &pt) { contextMenu->exec(mapToGlobal(pt)); } - -void YosysLineEditor::clearHistory() -{ - lines.clear(); - index = 0; - clear(); -} - -NEXTPNR_NAMESPACE_END diff --git a/gui/yosys_edit.h b/gui/yosys_edit.h deleted file mode 100644 index 05e4ae36ed..0000000000 --- a/gui/yosys_edit.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * Copyright (C) 2018 Alex Tsui - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef YOSYS_EDIT_H -#define YOSYS_EDIT_H - -#include -#include -#include "nextpnr.h" - -NEXTPNR_NAMESPACE_BEGIN - -class YosysLineEditor : public QLineEdit -{ - Q_OBJECT - - public: - explicit YosysLineEditor(QWidget *parent = 0); - - private Q_SLOTS: - void textInserted(); - void showContextMenu(const QPoint &pt); - void clearHistory(); - - Q_SIGNALS: - void textLineInserted(QString); - - protected: - void keyPressEvent(QKeyEvent *) Q_DECL_OVERRIDE; - bool focusNextPrevChild(bool next) Q_DECL_OVERRIDE; - - private: - int index; - QStringList lines; - QMenu *contextMenu; -}; - -NEXTPNR_NAMESPACE_END - -#endif // YOSYS_EDIT_H diff --git a/gui/yosystab.cc b/gui/yosystab.cc deleted file mode 100644 index 9bbd9c1279..0000000000 --- a/gui/yosystab.cc +++ /dev/null @@ -1,107 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#include "yosystab.h" -#include -#include - -NEXTPNR_NAMESPACE_BEGIN - -YosysTab::YosysTab(QString folder, QWidget *parent) : QWidget(parent) -{ - QFont f("unexistent"); - f.setStyleHint(QFont::Monospace); - - console = new QPlainTextEdit(); - console->setMinimumHeight(100); - console->setReadOnly(true); - console->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); - console->setFont(f); - - console->setContextMenuPolicy(Qt::CustomContextMenu); - QAction *clearAction = new QAction("Clear &buffer", this); - clearAction->setStatusTip("Clears display buffer"); - connect(clearAction, SIGNAL(triggered()), this, SLOT(clearBuffer())); - contextMenu = console->createStandardContextMenu(); - contextMenu->addSeparator(); - contextMenu->addAction(clearAction); - connect(console, SIGNAL(customContextMenuRequested(const QPoint)), this, SLOT(showContextMenu(const QPoint))); - - lineEdit = new YosysLineEditor(); - lineEdit->setMinimumHeight(30); - lineEdit->setMaximumHeight(30); - lineEdit->setFont(f); - lineEdit->setFocus(); - lineEdit->setEnabled(false); - lineEdit->setPlaceholderText("yosys>"); - connect(lineEdit, SIGNAL(textLineInserted(QString)), this, SLOT(editLineReturnPressed(QString))); - - QGridLayout *mainLayout = new QGridLayout(); - mainLayout->addWidget(console, 0, 0); - mainLayout->addWidget(lineEdit, 1, 0); - setLayout(mainLayout); - - process = new QProcess(); - connect(process, SIGNAL(readyReadStandardError()), this, SLOT(onReadyReadStandardError())); - connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(onReadyReadStandardOutput())); - connect(process, &QProcess::started, this, [this] { lineEdit->setEnabled(true); }); - -#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) - connect(process, static_cast(&QProcess::error), this, [this](QProcess::ProcessError error) { -#else - connect(process, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) { -#endif - if (error == QProcess::FailedToStart) { - QMessageBox::critical( - this, QString::fromUtf8("Yosys cannot be started!"), - QString::fromUtf8("

Please make sure you have Yosys installed and available in path

")); - Q_EMIT deleteLater(); - } - }); - process->setWorkingDirectory(folder); - process->start("yosys"); -} - -YosysTab::~YosysTab() -{ - process->terminate(); - process->waitForFinished(1000); // in ms - process->kill(); - process->close(); -} - -void YosysTab::displayString(QString text) -{ - QTextCursor cursor = console->textCursor(); - cursor.movePosition(QTextCursor::End); - cursor.insertText(text); - cursor.movePosition(QTextCursor::End); - console->setTextCursor(cursor); -} - -void YosysTab::onReadyReadStandardOutput() { displayString(process->readAllStandardOutput()); } -void YosysTab::onReadyReadStandardError() { displayString(process->readAllStandardError()); } - -void YosysTab::editLineReturnPressed(QString text) { process->write(text.toLatin1() + "\n"); } - -void YosysTab::showContextMenu(const QPoint &pt) { contextMenu->exec(mapToGlobal(pt)); } - -void YosysTab::clearBuffer() { console->clear(); } - -NEXTPNR_NAMESPACE_END diff --git a/gui/yosystab.h b/gui/yosystab.h deleted file mode 100644 index 1c668d15e1..0000000000 --- a/gui/yosystab.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Miodrag Milanovic - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ - -#ifndef YOSYSTAB_H -#define YOSYSTAB_H - -#include -#include -#include -#include -#include "nextpnr.h" -#include "yosys_edit.h" - -NEXTPNR_NAMESPACE_BEGIN - -class YosysTab : public QWidget -{ - Q_OBJECT - - public: - explicit YosysTab(QString folder, QWidget *parent = 0); - ~YosysTab(); - - private: - void displayString(QString text); - private Q_SLOTS: - void showContextMenu(const QPoint &pt); - void editLineReturnPressed(QString text); - void onReadyReadStandardOutput(); - void onReadyReadStandardError(); - public Q_SLOTS: - void clearBuffer(); - - private: - QPlainTextEdit *console; - YosysLineEditor *lineEdit; - QMenu *contextMenu; - QProcess *process; -}; - -NEXTPNR_NAMESPACE_END - -#endif // YOSYSTAB_H diff --git a/ice40/arch.cc b/ice40/arch.cc index 1ef6a51f0b..02d3d1cd88 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -46,8 +46,8 @@ IdString Arch::belTypeToId(BelType type) const return id("ICESTORM_PLL"); if (type == TYPE_SB_WARMBOOT) return id("SB_WARMBOOT"); - if (type == TYPE_SB_MAC16) - return id("SB_MAC16"); + if (type == TYPE_ICESTORM_DSP) + return id("ICESTORM_DSP"); if (type == TYPE_ICESTORM_HFOSC) return id("ICESTORM_HFOSC"); if (type == TYPE_ICESTORM_LFOSC) @@ -81,8 +81,8 @@ BelType Arch::belTypeFromId(IdString type) const return TYPE_ICESTORM_PLL; if (type == id("SB_WARMBOOT")) return TYPE_SB_WARMBOOT; - if (type == id("SB_MAC16")) - return TYPE_SB_MAC16; + if (type == id("ICESTORM_DSP")) + return TYPE_ICESTORM_DSP; if (type == id("ICESTORM_HFOSC")) return TYPE_ICESTORM_HFOSC; if (type == id("ICESTORM_LFOSC")) @@ -672,8 +672,7 @@ std::vector Arch::getDecalGraphics(DecalId decal) const } if (bel_type == TYPE_ICESTORM_RAM) { - for (int i = 0; i < 2; i++) - { + for (int i = 0; i < 2; i++) { int tx = chip_info->bel_data[bel.index].x; int ty = chip_info->bel_data[bel.index].y + i; @@ -683,7 +682,7 @@ std::vector Arch::getDecalGraphics(DecalId decal) const el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1; el.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2; el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1; - el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 + 7*logic_cell_pitch; + el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 + 7 * logic_cell_pitch; el.z = 0; ret.push_back(el); @@ -774,7 +773,10 @@ bool Arch::isClockPort(const PortRef &port) const if (port.cell->type == id("ICESTORM_LC")) return port.port == id("CLK"); if (is_ram(this, port.cell) || port.cell->type == id("ICESTORM_RAM")) - return port.port == id("RCLK") || port.port == id("WCLK"); + return port.port == id("RCLK") || port.port == id("WCLK") || port.port == id("RCLKN") || + port.port == id("WCLKN"); + if (is_sb_mac16(this, port.cell) || port.cell->type == id("ICESTORM_DSP")) + return port.port == id("CLK"); return false; } @@ -786,6 +788,9 @@ bool Arch::isResetPort(const PortRef &port) const return port.port == id_r || port.port == id_s; if (port.cell->type == id_icestorm_lc) return port.port == id_sr; +// if (is_sb_mac16(this, port.cell) || port.cell->type == id("ICESTORM_DSP")) +// return port.port == id("IRSTTOP") || port.port == id("IRSTBOT") || port.port == id("ORSTTOP") || +// port.port == id("ORSTBOT"); return false; } @@ -797,8 +802,46 @@ bool Arch::isEnablePort(const PortRef &port) const return port.port == id_e; if (port.cell->type == id_icestorm_lc) return port.port == id_cen; + // FIXME + // if (is_sb_mac16(ctx, port.cell) || port.cell->type == ctx->id("ICESTORM_DSP")) + // return port.port == ctx->id("CE"); return false; } +// Assign arch arg info +void Arch::assignArchInfo() +{ + for (auto &net : getCtx()->nets) { + NetInfo *ni = net.second.get(); + if (isGlobalNet(ni)) + ni->is_global = true; + } + for (auto &cell : getCtx()->cells) { + CellInfo *ci = cell.second.get(); + assignCellInfo(ci); + } +} + +void Arch::assignCellInfo(CellInfo *cell) +{ + cell->belType = belTypeFromId(cell->type); + if (cell->type == id_icestorm_lc) { + cell->lcInfo.dffEnable = bool_or_default(cell->params, id_dff_en); + cell->lcInfo.negClk = bool_or_default(cell->params, id_neg_clk); + cell->lcInfo.clk = get_net_or_empty(cell, id_clk); + cell->lcInfo.cen = get_net_or_empty(cell, id_cen); + cell->lcInfo.sr = get_net_or_empty(cell, id_sr); + cell->lcInfo.inputCount = 0; + if (get_net_or_empty(cell, id_i0)) + cell->lcInfo.inputCount++; + if (get_net_or_empty(cell, id_i1)) + cell->lcInfo.inputCount++; + if (get_net_or_empty(cell, id_i2)) + cell->lcInfo.inputCount++; + if (get_net_or_empty(cell, id_i3)) + cell->lcInfo.inputCount++; + } +} + NEXTPNR_NAMESPACE_END diff --git a/ice40/arch.h b/ice40/arch.h index 6da6219a5d..cb76ec6a0e 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -153,15 +153,31 @@ NPNR_PACKED_STRUCT(struct BitstreamInfoPOD { RelPtr ierens; }); +NPNR_PACKED_STRUCT(struct BelConfigEntryPOD { + RelPtr entry_name; + RelPtr cbit_name; + int8_t x, y; + int16_t padding; +}); + +// Stores mapping between bel parameters and config bits, +// for extra cells where this mapping is non-trivial +NPNR_PACKED_STRUCT(struct BelConfigPOD { + int32_t bel_index; + int32_t num_entries; + RelPtr entries; +}); + NPNR_PACKED_STRUCT(struct ChipInfoPOD { int32_t width, height; int32_t num_bels, num_wires, num_pips; - int32_t num_switches, num_packages; + int32_t num_switches, num_belcfgs, num_packages; RelPtr bel_data; RelPtr wire_data; RelPtr pip_data; RelPtr tile_grid; RelPtr bits_info; + RelPtr bel_config; RelPtr packages_data; }); @@ -584,7 +600,7 @@ struct Arch : BaseCtx range.e.cursor = chip_info->num_pips; return range; } - + IdString getPipName(PipId pip) const; uint32_t getPipChecksum(PipId pip) const { return pip.index; } @@ -712,6 +728,12 @@ struct Arch : BaseCtx // Helper function for above bool logicCellsCompatible(const std::vector &cells) const; + // ------------------------------------------------- + // Assign architecure-specific arguments to nets and cells, which must be called between packing or further + // netlist modifications, and validity checks + void assignArchInfo(); + void assignCellInfo(CellInfo *cell); + IdString id_glb_buf_out; IdString id_icestorm_lc, id_sb_io, id_sb_gb; IdString id_cen, id_clk, id_sr; diff --git a/ice40/arch_place.cc b/ice40/arch_place.cc index 916c211784..90a342b657 100644 --- a/ice40/arch_place.cc +++ b/ice40/arch_place.cc @@ -31,45 +31,37 @@ bool Arch::logicCellsCompatible(const std::vector &cells) cons int locals_count = 0; for (auto cell : cells) { - if (bool_or_default(cell->params, id_dff_en)) { + NPNR_ASSERT(cell->belType == TYPE_ICESTORM_LC); + if (cell->lcInfo.dffEnable) { if (!dffs_exist) { dffs_exist = true; - cen = get_net_or_empty(cell, id_cen); - clk = get_net_or_empty(cell, id_clk); - sr = get_net_or_empty(cell, id_sr); + cen = cell->lcInfo.cen; + clk = cell->lcInfo.clk; + sr = cell->lcInfo.sr; - if (!isGlobalNet(cen) && cen != nullptr) + if (cen != nullptr && !cen->is_global) locals_count++; - if (!isGlobalNet(clk) && clk != nullptr) + if (clk != nullptr && !clk->is_global) locals_count++; - if (!isGlobalNet(sr) && sr != nullptr) + if (sr != nullptr && !sr->is_global) locals_count++; - if (bool_or_default(cell->params, id_neg_clk)) { + if (cell->lcInfo.negClk) { dffs_neg = true; } } else { - if (cen != get_net_or_empty(cell, id_cen)) + if (cen != cell->lcInfo.cen) return false; - if (clk != get_net_or_empty(cell, id_clk)) + if (clk != cell->lcInfo.clk) return false; - if (sr != get_net_or_empty(cell, id_sr)) + if (sr != cell->lcInfo.sr) return false; - if (dffs_neg != bool_or_default(cell->params, id_neg_clk)) + if (dffs_neg != cell->lcInfo.negClk) return false; } } - const NetInfo *i0 = get_net_or_empty(cell, id_i0), *i1 = get_net_or_empty(cell, id_i1), - *i2 = get_net_or_empty(cell, id_i2), *i3 = get_net_or_empty(cell, id_i3); - if (i0 != nullptr) - locals_count++; - if (i1 != nullptr) - locals_count++; - if (i2 != nullptr) - locals_count++; - if (i3 != nullptr) - locals_count++; + locals_count += cell->lcInfo.inputCount; } return locals_count <= 32; diff --git a/ice40/archdefs.h b/ice40/archdefs.h index ce7d3f52a2..55e2c2fb96 100644 --- a/ice40/archdefs.h +++ b/ice40/archdefs.h @@ -54,7 +54,7 @@ enum BelType : int32_t TYPE_SB_GB, TYPE_ICESTORM_PLL, TYPE_SB_WARMBOOT, - TYPE_SB_MAC16, + TYPE_ICESTORM_DSP, TYPE_ICESTORM_HFOSC, TYPE_ICESTORM_LFOSC, TYPE_SB_I2C, @@ -150,8 +150,26 @@ struct DecalId bool operator!=(const DecalId &other) const { return (type != other.type) || (index != other.index); } }; -struct ArchNetInfo { }; -struct ArchCellInfo { }; +struct ArchNetInfo +{ + bool is_global = false; +}; + +struct NetInfo; + +struct ArchCellInfo +{ + BelType belType = TYPE_NONE; + union + { + struct + { + bool dffEnable, negClk; + int inputCount; + const NetInfo *clk, *cen, *sr; + } lcInfo; + }; +}; NEXTPNR_NAMESPACE_END diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index a62c6c0927..c12fa90e56 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -36,7 +36,7 @@ const ConfigEntryPOD &find_config(const TileInfoPOD &tile, const std::string &na return tile.entries[i]; } } - NPNR_ASSERT_FALSE("unable to find config bit " + name); + NPNR_ASSERT_FALSE_STR("unable to find config bit " + name); } std::tuple get_ieren(const BitstreamInfoPOD &bi, int8_t x, int8_t y, int8_t z) @@ -90,12 +90,79 @@ std::string get_param_str_or_def(const CellInfo *cell, const IdString param, std char get_hexdigit(int i) { return std::string("0123456789ABCDEF").at(i); } +static const BelConfigPOD &get_ec_config(const ChipInfoPOD *chip, BelId bel) +{ + for (int i = 0; i < chip->num_belcfgs; i++) { + if (chip->bel_config[i].bel_index == bel.index) + return chip->bel_config[i]; + } + NPNR_ASSERT_FALSE("failed to find bel config"); +} + +typedef std::vector>>> chipconfig_t; + +static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfigPOD &cell_cbits, std::string name, + bool value) +{ + const ChipInfoPOD *chip = ctx->chip_info; + + for (int i = 0; i < cell_cbits.num_entries; i++) { + const auto &cbit = cell_cbits.entries[i]; + if (cbit.entry_name.get() == name) { + const auto &ti = chip->bits_info->tiles_nonrouting[tile_at(ctx, cbit.x, cbit.y)]; + set_config(ti, config.at(cbit.y).at(cbit.x), std::string("IpConfig.") + cbit.cbit_name.get(), value); + return; + } + } + NPNR_ASSERT_FALSE_STR("failed to config extra cell config bit " + name); +} + +void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *cell, + const std::vector> ¶ms, bool string_style) +{ + const ChipInfoPOD *chip = ctx->chip_info; + const auto &bc = get_ec_config(chip, cell->bel); + for (auto p : params) { + std::vector value; + if (string_style) { + // Lattice's weird string style params, not sure if + // prefixes other than 0b should be supported, only 0b features in docs + std::string raw = get_param_str_or_def(cell, ctx->id(p.first), "0b0"); + assert(raw.substr(0, 2) == "0b"); + raw = raw.substr(2); + value.resize(raw.length()); + for (int i = 0; i < (int)raw.length(); i++) { + if (raw[i] == '1') { + value[(raw.length() - 1) - i] = 1; + } else { + assert(raw[i] == '0'); + value[(raw.length() - 1) - i] = 0; + } + } + } else { + int ival = get_param_or_def(cell, ctx->id(p.first), 0); + + for (int i = 0; i < p.second; i++) + value.push_back((ival >> i) & 0x1); + } + + value.resize(p.second); + if (p.second == 1) { + set_ec_cbit(config, ctx, bc, p.first, value.at(0)); + } else { + for (int i = 0; i < p.second; i++) { + set_ec_cbit(config, ctx, bc, p.first + "_" + std::to_string(i), value.at(i)); + } + } + } +} + void write_asc(const Context *ctx, std::ostream &out) { // [y][x][row][col] const ChipInfoPOD &ci = *ctx->chip_info; const BitstreamInfoPOD &bi = *ci.bits_info; - std::vector>>> config; + chipconfig_t config; config.resize(ci.height); for (int y = 0; y < ci.height; y++) { config.at(y).resize(ci.width); @@ -192,7 +259,7 @@ void write_asc(const Context *ctx, std::ostream &out) bool val = (pin_type >> i) & 0x01; set_config(ti, config.at(y).at(x), "IOB_" + std::to_string(z) + ".PINTYPE_" + std::to_string(i), val); } - + set_config(ti, config.at(y).at(x), "NegClk", neg_trigger); auto ieren = get_ieren(bi, x, y, z); int iex, iey, iez; std::tie(iex, iey, iez) = ieren; @@ -265,6 +332,27 @@ void write_asc(const Context *ctx, std::ostream &out) NPNR_ASSERT(false); } } + } else if (cell.second->type == ctx->id("ICESTORM_DSP")) { + const std::vector> mac16_params = {{"C_REG", 1}, + {"A_REG", 1}, + {"B_REG", 1}, + {"D_REG", 1}, + {"TOP_8x8_MULT_REG", 1}, + {"BOT_8x8_MULT_REG", 1}, + {"PIPELINE_16x16_MULT_REG1", 1}, + {"PIPELINE_16x16_MULT_REG2", 1}, + {"TOPOUTPUT_SELECT", 2}, + {"TOPADDSUB_LOWERINPUT", 2}, + {"TOPADDSUB_UPPERINPUT", 1}, + {"TOPADDSUB_CARRYSELECT", 2}, + {"BOTOUTPUT_SELECT", 2}, + {"BOTADDSUB_LOWERINPUT", 2}, + {"BOTADDSUB_UPPERINPUT", 1}, + {"BOTADDSUB_CARRYSELECT", 2}, + {"MODE_8x8", 1}, + {"A_SIGNED", 1}, + {"B_SIGNED", 1}}; + configure_extra_cell(config, ctx, cell.second.get(), mac16_params, false); } else { NPNR_ASSERT(false); } @@ -341,8 +429,9 @@ void write_asc(const Context *ctx, std::ostream &out) set_config(ti, config.at(y).at(x), "Cascade.IPCON_LC0" + std::to_string(lc_idx) + "_inmux02_5", true); else - set_config(ti, config.at(y).at(x), "Cascade.MULT" + std::to_string(int(tile - TILE_DSP0)) + - "_LC0" + std::to_string(lc_idx) + "_inmux02_5", + set_config(ti, config.at(y).at(x), + "Cascade.MULT" + std::to_string(int(tile - TILE_DSP0)) + "_LC0" + + std::to_string(lc_idx) + "_inmux02_5", true); } } diff --git a/ice40/blinky.ys b/ice40/blinky.ys index bad0a8b46a..a5dd2c85d5 100644 --- a/ice40/blinky.ys +++ b/ice40/blinky.ys @@ -1,3 +1,3 @@ read_verilog blinky.v -synth_ice40 -top blinky -nocarry +synth_ice40 -top blinky write_json blinky.json diff --git a/ice40/cells.cc b/ice40/cells.cc index 4e8d90e5e9..178445695e 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -142,6 +142,71 @@ std::unique_ptr create_ice_cell(Context *ctx, IdString type, std::stri for (int i = 0; i < 4; i++) { add_port(ctx, new_cell.get(), "MASKWREN_" + std::to_string(i), PORT_IN); } + } else if (type == ctx->id("ICESTORM_DSP")) { + new_cell->params[ctx->id("NEG_TRIGGER")] = "0"; + + new_cell->params[ctx->id("C_REG")] = "0"; + new_cell->params[ctx->id("A_REG")] = "0"; + new_cell->params[ctx->id("B_REG")] = "0"; + new_cell->params[ctx->id("D_REG")] = "0"; + new_cell->params[ctx->id("TOP_8x8_MULT_REG")] = "0"; + new_cell->params[ctx->id("BOT_8x8_MULT_REG")] = "0"; + new_cell->params[ctx->id("PIPELINE_16x16_MULT_REG1")] = "0"; + new_cell->params[ctx->id("PIPELINE_16x16_MULT_REG2")] = "0"; + + new_cell->params[ctx->id("TOPOUTPUT_SELECT")] = "0"; + new_cell->params[ctx->id("TOPADDSUB_LOWERINPUT")] = "0"; + new_cell->params[ctx->id("TOPADDSUB_UPPERINPUT")] = "0"; + new_cell->params[ctx->id("TOPADDSUB_CARRYSELECT")] = "0"; + + new_cell->params[ctx->id("BOTOUTPUT_SELECT")] = "0"; + new_cell->params[ctx->id("BOTADDSUB_LOWERINPUT")] = "0"; + new_cell->params[ctx->id("BOTADDSUB_UPPERINPUT")] = "0"; + new_cell->params[ctx->id("BOTADDSUB_CARRYSELECT")] = "0"; + + new_cell->params[ctx->id("MODE_8x8")] = "0"; + new_cell->params[ctx->id("A_SIGNED")] = "0"; + new_cell->params[ctx->id("B_SIGNED")] = "0"; + + add_port(ctx, new_cell.get(), "CLK", PORT_IN); + add_port(ctx, new_cell.get(), "CE", PORT_IN); + for (int i = 0; i < 16; i++) { + add_port(ctx, new_cell.get(), "C_" + std::to_string(i), PORT_IN); + add_port(ctx, new_cell.get(), "A_" + std::to_string(i), PORT_IN); + add_port(ctx, new_cell.get(), "B_" + std::to_string(i), PORT_IN); + add_port(ctx, new_cell.get(), "D_" + std::to_string(i), PORT_IN); + } + add_port(ctx, new_cell.get(), "AHOLD", PORT_IN); + add_port(ctx, new_cell.get(), "BHOLD", PORT_IN); + add_port(ctx, new_cell.get(), "CHOLD", PORT_IN); + add_port(ctx, new_cell.get(), "DHOLD", PORT_IN); + + add_port(ctx, new_cell.get(), "IRSTTOP", PORT_IN); + add_port(ctx, new_cell.get(), "IRSTBOT", PORT_IN); + add_port(ctx, new_cell.get(), "ORSTTOP", PORT_IN); + add_port(ctx, new_cell.get(), "ORSTBOT", PORT_IN); + + add_port(ctx, new_cell.get(), "OLOADTOP", PORT_IN); + add_port(ctx, new_cell.get(), "OLOADBOT", PORT_IN); + + add_port(ctx, new_cell.get(), "ADDSUBTOP", PORT_IN); + add_port(ctx, new_cell.get(), "ADDSUBBOT", PORT_IN); + + add_port(ctx, new_cell.get(), "OHOLDTOP", PORT_IN); + add_port(ctx, new_cell.get(), "OHOLDBOT", PORT_IN); + + add_port(ctx, new_cell.get(), "CI", PORT_IN); + add_port(ctx, new_cell.get(), "ACCUMCI", PORT_IN); + add_port(ctx, new_cell.get(), "SIGNEXTIN", PORT_IN); + + for (int i = 0; i < 32; i++) { + add_port(ctx, new_cell.get(), "O_" + std::to_string(i), PORT_OUT); + } + + add_port(ctx, new_cell.get(), "CO", PORT_OUT); + add_port(ctx, new_cell.get(), "ACCUMCO", PORT_OUT); + add_port(ctx, new_cell.get(), "SIGNEXTOUT", PORT_OUT); + } else { log_error("unable to create iCE40 cell of type %s", type.c_str(ctx)); } diff --git a/ice40/cells.h b/ice40/cells.h index c17c6d1157..5793099581 100644 --- a/ice40/cells.h +++ b/ice40/cells.h @@ -61,6 +61,8 @@ inline bool is_sb_hfosc(const BaseCtx *ctx, const CellInfo *cell) { return cell- inline bool is_sb_spram(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_SPRAM256KA"); } +inline bool is_sb_mac16(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_MAC16"); } + // Convert a SB_LUT primitive to (part of) an ICESTORM_LC, swapping ports // as needed. Set no_dff if a DFF is not being used, so that the output // can be reconnected diff --git a/ice40/chipdb.py b/ice40/chipdb.py index 698cd1737e..329fef5619 100644 --- a/ice40/chipdb.py +++ b/ice40/chipdb.py @@ -38,7 +38,7 @@ ierens = list() extra_cells = dict() - +extra_cell_config = dict() packages = list() wire_uphill_belport = dict() @@ -159,7 +159,7 @@ def wire_type(name): name = name.split('/')[-1] wt = None - if name.startswith("glb_netwk_"): + if name.startswith("glb_netwk_") or name.startswith("padin_"): wt = "GLOBAL" elif name.startswith("D_IN_") or name.startswith("D_OUT_"): wt = "LOCAL" @@ -432,6 +432,19 @@ def init_tiletypes(device): extra_cells[mode[1]].append((line[0], (int(line[1]), int(line[2]), line[3]))) continue +def add_wire(x, y, name): + global num_wires + wire_idx = num_wires + num_wires = num_wires + 1 + wname = (x, y, name) + wire_names[wname] = wire_idx + wire_names_r[wire_idx] = wname + wire_segments[wire_idx] = dict() + +# Add virtual padin wires +for i in range(8): + add_wire(0, 0, "padin_%d" % i) + def add_bel_input(bel, wire, port): if wire not in wire_downhill_belports: wire_downhill_belports[wire] = set() @@ -567,6 +580,7 @@ def is_ec_output(ec_entry): def add_bel_ec(ec): ectype, x, y, z = ec bel = len(bel_name) + extra_cell_config[bel] = [] bel_name.append("X%d/Y%d/%s_%d" % (x, y, ectype.lower(), z)) bel_type.append(ectype) bel_pos.append((x, y, z)) @@ -578,8 +592,7 @@ def add_bel_ec(ec): else: add_bel_input(bel, wire_names[entry[1]], entry[0]) else: - # Configuration bit, need to create a structure for these - pass + extra_cell_config[bel].append(entry) for tile_xy, tile_type in sorted(tiles.items()): if tile_type == "logic": @@ -1175,6 +1188,23 @@ def write_binary(self, f): for t in tilegrid: bba.u32(tiletypes[t], "tiletype") +for bel_idx, entries in sorted(extra_cell_config.items()): + if len(entries) > 0: + bba.l("bel%d_config_entries" % bel_idx, "BelConfigEntryPOD") + for entry in entries: + bba.s(entry[0], "entry_name") + bba.s(entry[1][2], "cbit_name") + bba.u8(entry[1][0], "x") + bba.u8(entry[1][1], "y") + bba.u16(0, "padding") + +if len(extra_cell_config) > 0: + bba.l("bel_config_%s" % dev_name, "BelConfigPOD") + for bel_idx, entries in sorted(extra_cell_config.items()): + bba.u32(bel_idx, "bel_index") + bba.u32(len(entries), "num_entries") + bba.r("bel%d_config_entries" % bel_idx if len(entries) > 0 else None, "entries") + bba.l("package_info_%s" % dev_name, "PackageInfoPOD") for info in packageinfo: bba.s(info[0], "name") @@ -1188,12 +1218,14 @@ def write_binary(self, f): bba.u32(num_wires, "num_wires") bba.u32(len(pipinfo), "num_pips") bba.u32(len(switchinfo), "num_switches") +bba.u32(len(extra_cell_config), "num_belcfgs") bba.u32(len(packageinfo), "num_packages") bba.r("bel_data_%s" % dev_name, "bel_data") bba.r("wire_data_%s" % dev_name, "wire_data") bba.r("pip_data_%s" % dev_name, "pip_data") bba.r("tile_grid_%s" % dev_name, "tile_grid") bba.r("bits_info_%s" % dev_name, "bits_info") +bba.r("bel_config_%s" % dev_name if len(extra_cell_config) > 0 else None, "bel_config") bba.r("package_info_%s" % dev_name, "packages_data") bba.finalize() diff --git a/ice40/gfx.cc b/ice40/gfx.cc index aa2fc9ce12..f6ed789f01 100644 --- a/ice40/gfx.cc +++ b/ice40/gfx.cc @@ -640,10 +640,8 @@ static bool getWireXY_local(GfxTileWireId id, float &x, float &y) return false; } -void pipGfx(std::vector &g, int x, int y, - float x1, float y1, float x2, float y2, - float swx1, float swy1, float swx2, float swy2, - GraphicElement::style_t style) +void pipGfx(std::vector &g, int x, int y, float x1, float y1, float x2, float y2, float swx1, + float swy1, float swx2, float swy2, GraphicElement::style_t style) { float tx = 0.5 * (x1 + x2); float ty = 0.5 * (y1 + y2); @@ -693,7 +691,8 @@ void pipGfx(std::vector &g, int x, int y, g.push_back(el); } -void gfxTilePip(std::vector &g, int x, int y, GfxTileWireId src, GfxTileWireId dst, GraphicElement::style_t style) +void gfxTilePip(std::vector &g, int x, int y, GfxTileWireId src, GfxTileWireId dst, + GraphicElement::style_t style) { float x1, y1, x2, y2; diff --git a/ice40/gfx.h b/ice40/gfx.h index a1cbd65bb5..8a55407da1 100644 --- a/ice40/gfx.h +++ b/ice40/gfx.h @@ -468,7 +468,8 @@ enum GfxTileWireId }; void gfxTileWire(std::vector &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style); -void gfxTilePip(std::vector &g, int x, int y, GfxTileWireId src, GfxTileWireId dst, GraphicElement::style_t style); +void gfxTilePip(std::vector &g, int x, int y, GfxTileWireId src, GfxTileWireId dst, + GraphicElement::style_t style); NEXTPNR_NAMESPACE_END diff --git a/ice40/pack.cc b/ice40/pack.cc index 5299e61949..fb27cb5e20 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -277,6 +277,10 @@ static void pack_ram(Context *ctx) if (bpos != std::string::npos) { newname = newname.substr(0, bpos) + "_" + newname.substr(bpos + 1, (newname.size() - bpos) - 2); } + if (pi.name == ctx->id("RCLKN")) + newname = "RCLK"; + else if (pi.name == ctx->id("WCLKN")) + newname = "WCLK"; replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname)); } new_cells.push_back(std::move(packed)); @@ -303,6 +307,10 @@ static void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constne if ((is_lut(ctx, uc) || is_lc(ctx, uc) || is_carry(ctx, uc)) && (user.port.str(ctx).at(0) == 'I') && !constval) { uc->ports[user.port].net = nullptr; + } else if ((is_sb_mac16(ctx, uc) || uc->type == ctx->id("ICESTORM_DSP")) && + (user.port != ctx->id("CLK") && + ((constval && user.port == ctx->id("CE")) || (!constval && user.port != ctx->id("CE"))))) { + uc->ports[user.port].net = nullptr; } else { uc->ports[user.port].net = constnet; constnet->users.push_back(user); @@ -564,6 +572,25 @@ static void pack_special(Context *ctx) replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname)); } new_cells.push_back(std::move(packed)); + } else if (is_sb_mac16(ctx, ci)) { + std::unique_ptr packed = + create_ice_cell(ctx, ctx->id("ICESTORM_DSP"), ci->name.str(ctx) + "_DSP"); + packed_cells.insert(ci->name); + for (auto attr : ci->attrs) + packed->attrs[attr.first] = attr.second; + for (auto param : ci->params) + packed->params[param.first] = param.second; + + for (auto port : ci->ports) { + PortInfo &pi = port.second; + std::string newname = pi.name.str(ctx); + size_t bpos = newname.find('['); + if (bpos != std::string::npos) { + newname = newname.substr(0, bpos) + "_" + newname.substr(bpos + 1, (newname.size() - bpos) - 2); + } + replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname)); + } + new_cells.push_back(std::move(packed)); } } @@ -589,6 +616,7 @@ bool Arch::pack() pack_carries(ctx); pack_ram(ctx); pack_special(ctx); + ctx->assignArchInfo(); log_info("Checksum: 0x%08x\n", ctx->checksum()); return true; } catch (log_execution_error_exception) { diff --git a/ice40/picorv32.cpu b/ice40/picorv32.cpu new file mode 100644 index 0000000000000000000000000000000000000000..ebe18fc616469e1146b149a5d3640d2316f4c07b GIT binary patch literal 842064 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

;I5%r}JabyV{xQzMi+gQSYBe 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 PE&)+o?z@MFFCYE~j>pv! literal 0 HcmV?d00001 diff --git a/ice40/picorv32.sh b/ice40/picorv32.sh index 2c67f641de..87426cde0b 100755 --- a/ice40/picorv32.sh +++ b/ice40/picorv32.sh @@ -2,5 +2,5 @@ set -ex rm -f picorv32.v wget https://raw.githubusercontent.com/cliffordwolf/picorv32/master/picorv32.v -yosys -p 'synth_ice40 -nocarry -json picorv32.json -top top' picorv32.v picorv32_top.v +yosys -p 'synth_ice40 -json picorv32.json -top top' picorv32.v picorv32_top.v ../nextpnr-ice40 --hx8k --asc picorv32.asc --json picorv32.json diff --git a/ice40/picorv32_arachne.sh b/ice40/picorv32_arachne.sh deleted file mode 100755 index b3960fdc43..0000000000 --- a/ice40/picorv32_arachne.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -ex -rm -f picorv32.v -wget https://raw.githubusercontent.com/cliffordwolf/picorv32/master/picorv32.v -yosys -p 'synth_ice40 -nocarry -blif picorv32.blif -top top' picorv32.v picorv32_top.v -arachne-pnr -d 8k --post-place-blif picorv32_place.blif picorv32.blif -o picorv32_arachne_all.asc -yosys -p "read_blif -wideports picorv32_place.blif; read_verilog -lib +/ice40/cells_sim.v; write_json picorv32_place.json" -./transform_arachne_loc.py picorv32_place.json > picorv32_place_nx.json -../nextpnr-ice40 --hx8k --asc picorv32_ar_placed.asc --json picorv32_place_nx.json --force diff --git a/ice40/picorv32_new.cpu b/ice40/picorv32_new.cpu new file mode 100644 index 0000000000000000000000000000000000000000..539865785e1613e11a6e74bae1ed6dc0a95377af GIT binary patch literal 262880 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 z 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]ddh_M+_S5y_&EMbLUH|7ot1*>i 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

m(>_RB?}xjiQOak0YDevX?$T`j&{j%uI3u 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 literal 0 HcmV?d00001 diff --git a/ice40/picorv32_vpr.cpu b/ice40/picorv32_vpr.cpu new file mode 100644 index 0000000000000000000000000000000000000000..77c4c7b95d0114ccb607645960e3110549911d26 GIT binary patch literal 230336 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 b$}C8KUml#Qc~>_WP&qDlm1fO4VZ{Fdh-6mI literal 0 HcmV?d00001 diff --git a/ice40/picorv32_vpr.pdf b/ice40/picorv32_vpr.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b14ebc344c83e7c75f2cc69168f557fb4db36ce7 GIT binary patch literal 25927 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 Jud1)c^j}vymjD0& literal 0 HcmV?d00001 diff --git a/ice40/picovr32.cpu b/ice40/picovr32.cpu new file mode 100644 index 0000000000000000000000000000000000000000..b1d2715d4c154cc69b4179b06098b080af168251 GIT binary patch literal 312368 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 {YMfjq| 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

f6x0>wFV&0QOz?>*GJ5qq`rwweKW5 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 Xe(d5u%nTIMK*hW~8t&bD>GA&`{=r@2 literal 0 HcmV?d00001 diff --git a/ice40/picovr32.orig.log b/ice40/picovr32.orig.log new file mode 100644 index 0000000000..88a3db8f88 --- /dev/null +++ b/ice40/picovr32.orig.log @@ -0,0 +1,152 @@ +Info: Importing module top +Info: Rule checker, Verifying pre-placed design +Info: Checksum: 0xf9b3ecfa + +Info: Packing constants.. +Info: Promoting globals.. +Info: Packing IOs.. +Info: Packing LUT-FFs.. +Info: Packing non-LUT FFs.. +Info: Packing carries.. +Info: Packing RAMs.. +Info: Packing special functions.. +Info: Checksum: 0x8b3404a8 + +Info: Annotating ports with timing budgets +Info: Checksum: 0x45e4b18e + +Info: Device utilisation: +Info: ICESTORM_LC: 1808/ 7680 23% +Info: ICESTORM_RAM: 4/ 32 12% +Info: SB_IO: 106/ 256 41% +Info: SB_GB: 8/ 8 100% +Info: ICESTORM_PLL: 0/ 2 0% +Info: SB_WARMBOOT: 0/ 1 0% + +Info: Placed 0 cells based on constraints. +Info: Creating initial placement for remaining 1926 cells. +Info: initial placement placed 500/1926 cells +Info: initial placement placed 1000/1926 cells +Info: initial placement placed 1500/1926 cells +Info: initial placement placed 1926/1926 cells +Info: Running simulated annealing placer. +Info: at iteration #1: temp = 10000.000000, wire length = 77641, est tns = -70.48ns +Info: at iteration #5: temp = 2401.000000, wire length = 77348, est tns = -76.91ns +Info: at iteration #10: temp = 403.536072, wire length = 77382, est tns = -86.55ns +Info: at iteration #15: temp = 138.412872, wire length = 72333, est tns = -56.75ns +Info: at iteration #20: temp = 67.822304, wire length = 68195, est tns = -31.45ns +Info: at iteration #25: temp = 47.475613, wire length = 65216, est tns = -19.08ns +Info: at iteration #30: temp = 38.455246, wire length = 64059, est tns = -26.91ns +Info: at iteration #35: temp = 31.148750, wire length = 59193, est tns = -9.09ns +Info: at iteration #40: temp = 25.230488, wire length = 56814, est tns = -6.92ns +Info: at iteration #45: temp = 22.770515, wire length = 54446, est tns = -2.39ns +Info: at iteration #50: temp = 20.550390, wire length = 52666, est tns = -5.73ns +Info: at iteration #55: temp = 18.546728, wire length = 49849, est tns = -0.65ns +Info: at iteration #60: temp = 15.901502, wire length = 47680, est tns = -0.10ns +Info: at iteration #65: temp = 14.351105, wire length = 46997, est tns = -0.36ns +Info: at iteration #70: temp = 13.633550, wire length = 44929, est tns = -0.34ns +Info: at iteration #75: temp = 13.633550, wire length = 44155, est tns = -0.82ns +Info: at iteration #80: temp = 13.633550, wire length = 44715, est tns = -0.29ns +Info: at iteration #85: temp = 13.633550, wire length = 43272, est tns = -0.52ns +Info: at iteration #90: temp = 12.951872, wire length = 41284, est tns = -1.16ns +Info: at iteration #95: temp = 12.304278, wire length = 39771, est tns = 0.00ns +Info: at iteration #100: temp = 11.689064, wire length = 40213, est tns = -0.30ns +Info: at iteration #105: temp = 11.104610, wire length = 39433, est tns = -0.80ns +Info: at iteration #110: temp = 10.549380, wire length = 37709, est tns = -0.12ns +Info: at iteration #115: temp = 10.021912, wire length = 36097, est tns = -1.26ns +Info: at iteration #120: temp = 9.520816, wire length = 34598, est tns = 0.00ns +Info: at iteration #125: temp = 9.044775, wire length = 33608, est tns = 0.00ns +Info: at iteration #130: temp = 8.162910, wire length = 32927, est tns = 0.00ns +Info: at iteration #135: temp = 7.754764, wire length = 31602, est tns = 0.00ns +Info: at iteration #140: temp = 7.367026, wire length = 28738, est tns = 0.00ns +Info: at iteration #145: temp = 6.998674, wire length = 28030, est tns = 0.00ns +Info: at iteration #150: temp = 6.648741, wire length = 26978, est tns = 0.00ns +Info: at iteration #155: temp = 6.000489, wire length = 25385, est tns = 0.00ns +Info: at iteration #160: temp = 5.700464, wire length = 24104, est tns = 0.00ns +Info: at iteration #165: temp = 5.144669, wire length = 23993, est tns = 0.00ns +Info: at iteration #170: temp = 4.887435, wire length = 22839, est tns = 0.00ns +Info: at iteration #175: temp = 4.643064, wire length = 21926, est tns = 0.00ns +Info: at iteration #180: temp = 4.190365, wire length = 20030, est tns = 0.00ns +Info: at iteration #185: temp = 3.980847, wire length = 19305, est tns = 0.00ns +Info: at iteration #190: temp = 3.781804, wire length = 18874, est tns = 0.00ns +Info: at iteration #195: temp = 3.592714, wire length = 18087, est tns = 0.00ns +Info: at iteration #200: temp = 3.242424, wire length = 17292, est tns = 0.00ns +Info: at iteration #205: temp = 3.080303, wire length = 16011, est tns = 0.00ns +Info: at iteration #210: temp = 2.779974, wire length = 15993, est tns = 0.00ns +Info: at iteration #215: temp = 2.640975, wire length = 14940, est tns = 0.00ns +Info: at iteration #220: temp = 2.508926, wire length = 14409, est tns = 0.00ns +Info: at iteration #225: temp = 2.264306, wire length = 13574, est tns = 0.00ns +Info: at iteration #230: temp = 2.151091, wire length = 12986, est tns = 0.00ns +Info: at iteration #235: temp = 1.941359, wire length = 12984, est tns = 0.00ns +Info: at iteration #240: temp = 1.844291, wire length = 12165, est tns = 0.00ns +Info: at iteration #245: temp = 1.664473, wire length = 11866, est tns = 0.00ns +Info: at iteration #250: temp = 1.581249, wire length = 11282, est tns = 0.00ns +Info: at iteration #255: temp = 1.427078, wire length = 10748, est tns = 0.00ns +Info: at iteration #260: temp = 1.355724, wire length = 10406, est tns = 0.00ns +Info: at iteration #265: temp = 1.223541, wire length = 9846, est tns = 0.00ns +Info: Legalising design.. + +Info: Annotating ports with timing budgets +Info: Checksum: 0x54430fb5 +Info: at iteration #270: temp = 6.860000, wire length = 30219, est tns = 0.00ns +Info: at iteration #275: temp = 5.587513, wire length = 25358, est tns = 0.00ns +Info: at iteration #280: temp = 4.323511, wire length = 21186, est tns = 0.00ns +Info: at iteration #285: temp = 3.521526, wire length = 18653, est tns = 0.00ns +Info: at iteration #290: temp = 2.868305, wire length = 16543, est tns = 0.00ns +Info: at iteration #295: temp = 2.219440, wire length = 14237, est tns = 0.00ns +Info: at iteration #300: temp = 1.807748, wire length = 12902, est tns = 0.00ns +Info: at iteration #305: temp = 1.398801, wire length = 11683, est tns = 0.00ns +Info: at iteration #310: temp = 0.911465, wire length = 10580, est tns = 0.00ns +Info: at iteration #315: temp = 0.729172, wire length = 9221, est tns = 0.00ns +Info: at iteration #320: temp = 0.583338, wire length = 8774, est tns = 0.00ns +Info: at iteration #325: temp = 0.466670, wire length = 8500, est tns = 0.00ns +Info: at iteration #330: temp = 0.298669, wire length = 8222, est tns = 0.00ns +Info: at iteration #335: temp = 0.152919, wire length = 8058, est tns = 0.00ns +Info: at iteration #340: temp = 0.062635, wire length = 7976, est tns = 0.00ns +Info: at iteration #345: temp = 0.020524, wire length = 7950, est tns = 0.00ns +Info: at iteration #350: temp = 0.006725, wire length = 7933, est tns = 0.00ns +Info: at iteration #355: temp = 0.002755, wire length = 7917, est tns = 0.00ns +Info: at iteration #360: temp = 0.000903, wire length = 7908, est tns = 0.00ns +Info: at iteration #365: temp = 0.000296, wire length = 7892, est tns = 0.00ns +Info: at iteration #370: temp = 0.000097, wire length = 7886, est tns = 0.00ns +Info: at iteration #375: temp = 0.000032, wire length = 7885, est tns = 0.00ns +Info: at iteration #380: temp = 0.000010, wire length = 7882, est tns = 0.00ns +Info: at iteration #385: temp = 0.000003, wire length = 7882, est tns = 0.00ns +Info: at iteration #390: temp = 0.000001, wire length = 7880, est tns = 0.00ns +Info: at iteration #395: temp = 0.000000, wire length = 7880, est tns = 0.00ns +Info: Checksum: 0x64b16b40 + +Info: Routing.. +Info: found 1915 unrouted nets. starting routing procedure. +Info: estimated total wire delay: 4807800.00 (avg 697.49) +Info: routing queue contains 1915 nets. +Info: processed 100 nets. (100 routed, 0 failed) +Info: processed 200 nets. (200 routed, 0 failed) +Info: processed 300 nets. (300 routed, 0 failed) +Info: processed 400 nets. (400 routed, 0 failed) +Info: processed 500 nets. (500 routed, 0 failed) +Info: processed 600 nets. (600 routed, 0 failed) +Info: processed 700 nets. (700 routed, 0 failed) +Info: processed 800 nets. (800 routed, 0 failed) +Info: processed 900 nets. (900 routed, 0 failed) +Info: processed 1000 nets. (1000 routed, 0 failed) +Info: processed 1100 nets. (1100 routed, 0 failed) +Info: processed 1200 nets. (1200 routed, 0 failed) +Info: processed 1300 nets. (1300 routed, 0 failed) +Info: processed 1400 nets. (1400 routed, 0 failed) +Info: processed 1500 nets. (1499 routed, 1 failed) +Info: processed 1600 nets. (1599 routed, 1 failed) +Info: processed 1700 nets. (1699 routed, 1 failed) +Info: processed 1800 nets. (1799 routed, 1 failed) +Info: processed 1900 nets. (1897 routed, 3 failed) +Info: processed 1915 nets. (1912 routed, 3 failed) +Info: failed to route 3 nets. re-routing in ripup mode. +Info: routed 3 nets, ripped 37 nets. +Info: iteration 1: routed 1912 nets without ripup, routed 3 nets with ripup. +Info: iteration 2: routed 35 nets without ripup, routed 2 nets with ripup. +Info: iteration 3: routed 1 nets without ripup, routed 1 nets with ripup. +Info: iteration 4: routed 0 nets without ripup, routed 1 nets with ripup. +Info: iteration 5: routed 1 nets without ripup, routed 0 nets with ripup. +Info: routing complete after 5 iterations. +Info: visited 214816 PIPs (2.33% revisits, 1.38% overtime revisits). +Info: Checksum: 0xea141172 diff --git a/ice40/picovr32.orig.pdf b/ice40/picovr32.orig.pdf new file mode 100644 index 0000000000000000000000000000000000000000..333478da6fe4a9b6bacd54959cd1e0e6c2052323 GIT binary patch literal 29729 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¥%LxTS1#=TX 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 literal 0 HcmV?d00001 diff --git a/ice40/picovr32.pdf b/ice40/picovr32.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c43ba185791f27b3ccc7913333e3d8685bdbf132 GIT binary patch literal 27064 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

1Y$JG$Vu&Y-xG$C5@y3uwbgf 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 literal 0 HcmV?d00001 diff --git a/ice40/picovr32_.pdf b/ice40/picovr32_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9d41501b05aa683e8f4f532be019420853ca8480 GIT binary patch literal 27125 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 Rg;?7td$1nvTn{lS1_Q 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 R!I4Nbj6p<1$3&Ome*kDQqXGZ` literal 0 HcmV?d00001 diff --git a/ice40/picovr32_vpr.cpu b/ice40/picovr32_vpr.cpu new file mode 100644 index 0000000000000000000000000000000000000000..4e82762e0d2c1e7dbddf4a879e9f8c6c70b0aef1 GIT binary patch literal 716816 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 Qoj}pJJknUU?3nHU7jxi;M*si- literal 0 HcmV?d00001 diff --git a/ice40/place_legaliser.cc b/ice40/place_legaliser.cc index 5fffb4fbc6..2aefb839ac 100644 --- a/ice40/place_legaliser.cc +++ b/ice40/place_legaliser.cc @@ -127,6 +127,7 @@ class PlacementLegaliser legalise_others(); legalise_logic_tiles(); bool replaced_cells = replace_cells(); + ctx->assignArchInfo(); return legalised_carries && replaced_cells; } @@ -371,6 +372,7 @@ class PlacementLegaliser NPNR_ASSERT(ctx->nets.find(co_i3_name) == ctx->nets.end()); ctx->nets[co_i3_name] = std::move(co_i3_net); IdString name = lc->name; + ctx->assignCellInfo(lc.get()); ctx->cells[lc->name] = std::move(lc); createdCells.insert(name); return ctx->cells[name].get(); @@ -415,6 +417,7 @@ class PlacementLegaliser ctx->nets[out_net_name] = std::move(out_net); IdString name = lc->name; + ctx->assignCellInfo(lc.get()); ctx->cells[lc->name] = std::move(lc); createdCells.insert(name); return ctx->cells[name].get(); diff --git a/ice40/transform_arachne_loc.py b/ice40/transform_arachne_loc.py deleted file mode 100755 index 1479284527..0000000000 --- a/ice40/transform_arachne_loc.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 -import json -import sys -import re - -with open(sys.argv[1]) as f: - data = json.load(f) - -for mod, moddata in data["modules"].items(): - if "cells" in moddata: - for cell, celldata in moddata["cells"].items(): - pos = re.split('[,/]', celldata["attributes"]["loc"]) - pos = [int(_) for _ in pos] - if celldata["type"] == "ICESTORM_LC": - celldata["attributes"]["BEL"] = "X%d/Y%d/lc%d" % (pos[0], pos[1], pos[2]) - elif celldata["type"] == "SB_IO": - celldata["attributes"]["BEL"] = "X%d/Y%d/io%d" % (pos[0], pos[1], pos[2]) - elif "RAM" in celldata["type"]: - celldata["attributes"]["BEL"] = "X%d/Y%d/ram" % (pos[0], pos[1]) - elif celldata["type"] == "SB_GB": - celldata["attributes"]["BEL"] = "X%d/Y%d/gb" % (pos[0], pos[1]) - else: - assert False -print(json.dumps(data, sort_keys=True, indent=4)) \ No newline at end of file From 51c5fb8e770e5a1653bfba3b011d1ed5724a3886 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Thu, 19 Jul 2018 08:50:45 -0700 Subject: [PATCH 049/116] Use NetInfo->udata instead of ->name.index --- common/placer_vpr.cc | 11 ++- common/placer_vpr.inc | 156 ++++++++++++++++++++--------------------- ice40/picorv32_vpr.cpu | Bin 230336 -> 0 bytes 3 files changed, 88 insertions(+), 79 deletions(-) delete mode 100644 ice40/picorv32_vpr.cpu diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 253477c632..005756709f 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -95,6 +95,7 @@ namespace vpr { } annealing_sched; static struct t_placer_opts { bool enable_timing_computations; + const int inner_loop_recompute_divider = 0; const float td_place_exp_first = 1.0; const float timing_tradeoff = 0.5; } placer_opts; @@ -151,13 +152,21 @@ class VPRPlacer for (auto& c : grid._bels) c.resize(max_y+1); + int32_t cell_idx = 0; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->bel == BelId()) { vpr::npnr_cells.push_back(cell.second.get()); } + ci->udata = cell_idx++; } - vpr::placer_opts.enable_timing_computations = ctx->timing_driven; + int32_t net_idx = 0; + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + ni->udata = net_idx++; + } + + vpr::placer_opts.enable_timing_computations = /*ctx->timing_driven*/ false; } bool place() diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index 11864f0d7e..b579dbd3df 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -555,13 +555,13 @@ void try_place(t_placer_opts placer_opts, if (move_lim <= 0) move_lim = 1; -// if (placer_opts.inner_loop_recompute_divider != 0) { -// inner_recompute_limit = (int) -// (0.5 + (float) move_lim / (float) placer_opts.inner_loop_recompute_divider); -// } else { + if (placer_opts.inner_loop_recompute_divider != 0) { + inner_recompute_limit = (int) + (0.5 + (float) move_lim / (float) placer_opts.inner_loop_recompute_divider); + } else { /*don't do an inner recompute */ inner_recompute_limit = move_lim + 1; -// } + } rlim = (float) std::max(device_ctx.grid.width(), device_ctx.grid.height()); @@ -1466,15 +1466,15 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin /* update net cost functions and reset flags. */ for (auto net_id : ts_nets_to_update) { - bb_coords[net_id->name.index] = ts_bb_coord_new[net_id->name.index]; + bb_coords[net_id->udata] = ts_bb_coord_new[net_id->udata]; if (net_id->users.size() >= SMALL_NET) - bb_num_on_edges[net_id->name.index] = ts_bb_edge_new[net_id->name.index]; + bb_num_on_edges[net_id->udata] = ts_bb_edge_new[net_id->udata]; - net_cost[net_id->name.index] = temp_net_cost[net_id->name.index]; + net_cost[net_id->udata] = temp_net_cost[net_id->udata]; /* negative temp_net_cost value is acting as a flag. */ - temp_net_cost[net_id->name.index] = -1; - bb_updated_before[net_id->name.index] = NOT_UPDATED_YET; + temp_net_cost[net_id->udata] = -1; + bb_updated_before[net_id->udata] = NOT_UPDATED_YET; } @@ -1511,8 +1511,8 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin /* Reset the net cost function flags first. */ for (auto net_id : ts_nets_to_update) { - temp_net_cost[net_id->name.index] = -1; - bb_updated_before[net_id->name.index] = NOT_UPDATED_YET; + temp_net_cost[net_id->udata] = -1; + bb_updated_before[net_id->udata] = NOT_UPDATED_YET; } // /* Restore the place_ctx.block_locs data structures to their state before the move. */ @@ -1656,8 +1656,8 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit * The cost is only updated once per net. */ for (auto net_id : ts_nets_to_update) { - temp_net_cost[net_id->name.index] = get_net_cost(net_id, &ts_bb_coord_new[net_id->name.index]); - bb_delta_c += temp_net_cost[net_id->name.index] - net_cost[net_id->name.index]; + temp_net_cost[net_id->udata] = get_net_cost(net_id, &ts_bb_coord_new[net_id->udata]); + bb_delta_c += temp_net_cost[net_id->udata] - net_cost[net_id->udata]; } return num_affected_nets; @@ -1665,13 +1665,13 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_affected_nets) { //Record effected nets - if (temp_net_cost[net->name.index] < 0.) { + if (temp_net_cost[net->udata] < 0.) { //Net not marked yet. ts_nets_to_update.push_back(net); num_affected_nets++; //Flag to say we've marked this net. - temp_net_cost[net->name.index] = 1.; + temp_net_cost[net->udata] = 1.; } } @@ -1682,8 +1682,8 @@ static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const if (net->users.size() < SMALL_NET) { //For small nets brute-force bounding box update is faster - if(bb_updated_before[net->name.index] == NOT_UPDATED_YET) { //Only once per-net - get_non_updateable_bb(net, &ts_bb_coord_new[net->name.index]); + if(bb_updated_before[net->udata] == NOT_UPDATED_YET) { //Only once per-net + get_non_updateable_bb(net, &ts_bb_coord_new[net->udata]); } } else { //For large nets, update bounding box incrementally @@ -1700,8 +1700,8 @@ static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const npnr_ctx->estimatePosition(blk->bel, xnew, ynew, gb); //Incremental bounding box update - update_bb(net, &ts_bb_coord_new[net->name.index], - &ts_bb_edge_new[net->name.index], + update_bb(net, &ts_bb_coord_new[net->udata], + &ts_bb_edge_new[net->udata], /*blocks_affected.moved_blocks[iblk].xold + pin_width_offset*/ xold, /*blocks_affected.moved_blocks[iblk].yold + pin_height_offset*/ yold, /*blocks_affected.moved_blocks[iblk].xnew + pin_width_offset*/ xnew, @@ -1718,11 +1718,11 @@ static void update_td_delta_costs(/*const ClusterNetId*/ NetInfo *net, const /*C //Re-compute all point to point connections for this net. for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net).size(); ipin++) { float temp_delay = comp_td_point_to_point_delay(net, ipin); - temp_point_to_point_delay_cost[net->name.index][ipin] = temp_delay; + temp_point_to_point_delay_cost[net->udata][ipin] = temp_delay; - temp_point_to_point_timing_cost[net->name.index][ipin] = get_timing_place_crit(net, ipin) * temp_delay; - delta_timing_cost += temp_point_to_point_timing_cost[net->name.index][ipin] - point_to_point_timing_cost[net->name.index][ipin]; - delta_delay_cost += temp_point_to_point_delay_cost[net->name.index][ipin] - point_to_point_delay_cost[net->name.index][ipin]; + temp_point_to_point_timing_cost[net->udata][ipin] = get_timing_place_crit(net, ipin) * temp_delay; + delta_timing_cost += temp_point_to_point_timing_cost[net->udata][ipin] - point_to_point_timing_cost[net->udata][ipin]; + delta_delay_cost += temp_point_to_point_delay_cost[net->udata][ipin] - point_to_point_delay_cost[net->udata][ipin]; } } else { @@ -1739,11 +1739,11 @@ static void update_td_delta_costs(/*const ClusterNetId*/ NetInfo *net, const /*C int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin); float temp_delay = comp_td_point_to_point_delay(net, net_pin); - temp_point_to_point_delay_cost[net->name.index][net_pin] = temp_delay; + temp_point_to_point_delay_cost[net->udata][net_pin] = temp_delay; - temp_point_to_point_timing_cost[net->name.index][net_pin] = get_timing_place_crit(net, net_pin) * temp_delay; - delta_timing_cost += temp_point_to_point_timing_cost[net->name.index][net_pin] - point_to_point_timing_cost[net->name.index][net_pin]; - delta_delay_cost += temp_point_to_point_delay_cost[net->name.index][net_pin] - point_to_point_delay_cost[net->name.index][net_pin]; + temp_point_to_point_timing_cost[net->udata][net_pin] = get_timing_place_crit(net, net_pin) * temp_delay; + delta_timing_cost += temp_point_to_point_timing_cost[net->udata][net_pin] - point_to_point_timing_cost[net->udata][net_pin]; + delta_delay_cost += temp_point_to_point_delay_cost[net->udata][net_pin] - point_to_point_delay_cost[net->udata][net_pin]; } } } @@ -1942,7 +1942,7 @@ static float recompute_bb_cost() { auto net_id = n.second.get(); if (!npnr_ctx->isGlobalNet(net_id)) { /* Do only if not global. */ /* Bounding boxes don't have to be recomputed; they're correct. */ - cost += net_cost[net_id->name.index]; + cost += net_cost[net_id->udata]; } } @@ -2001,7 +2001,7 @@ static void comp_td_point_to_point_delays() { for (const auto& net : cluster_ctx.clb_nlist.nets()) { auto net_id = net.second.get(); for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ++ipin) { - point_to_point_delay_cost[net_id->name.index][ipin] = comp_td_point_to_point_delay(net_id, ipin); + point_to_point_delay_cost[net_id->udata][ipin] = comp_td_point_to_point_delay(net_id, ipin); } } } @@ -2026,10 +2026,10 @@ static void update_td_cost() { //This net is being driven by a moved block, recompute //all point to point connections on this net. for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { - point_to_point_delay_cost[net_id->name.index][ipin] = temp_point_to_point_delay_cost[net_id->name.index][ipin]; - temp_point_to_point_delay_cost[net_id->name.index][ipin] = -1; - point_to_point_timing_cost[net_id->name.index][ipin] = temp_point_to_point_timing_cost[net_id->name.index][ipin]; - temp_point_to_point_timing_cost[net_id->name.index][ipin] = -1; + point_to_point_delay_cost[net_id->udata][ipin] = temp_point_to_point_delay_cost[net_id->udata][ipin]; + temp_point_to_point_delay_cost[net_id->udata][ipin] = -1; + point_to_point_timing_cost[net_id->udata][ipin] = temp_point_to_point_timing_cost[net_id->udata][ipin]; + temp_point_to_point_timing_cost[net_id->udata][ipin] = -1; } } else { //This pin is a net sink on a moved block @@ -2039,10 +2039,10 @@ static void update_td_cost() { if (!driven_by_moved_block(net_id)) { int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin_id); - point_to_point_delay_cost[net_id->name.index][net_pin] = temp_point_to_point_delay_cost[net_id->name.index][net_pin]; - temp_point_to_point_delay_cost[net_id->name.index][net_pin] = -1; - point_to_point_timing_cost[net_id->name.index][net_pin] = temp_point_to_point_timing_cost[net_id->name.index][net_pin]; - temp_point_to_point_timing_cost[net_id->name.index][net_pin] = -1; + point_to_point_delay_cost[net_id->udata][net_pin] = temp_point_to_point_delay_cost[net_id->udata][net_pin]; + temp_point_to_point_delay_cost[net_id->udata][net_pin] = -1; + point_to_point_timing_cost[net_id->udata][net_pin] = temp_point_to_point_timing_cost[net_id->udata][net_pin]; + temp_point_to_point_timing_cost[net_id->udata][net_pin] = -1; } } } /* Finished going through all the pins in the moved block */ @@ -2088,11 +2088,11 @@ static void comp_td_costs(float *timing_cost, float *connection_delay_sum) { temp_timing_cost = temp_delay_cost * get_timing_place_crit(net_id, ipin); loc_connection_delay_sum += temp_delay_cost; - point_to_point_delay_cost[net_id->name.index][ipin] = temp_delay_cost; - temp_point_to_point_delay_cost[net_id->name.index][ipin] = -1; /* Undefined */ + point_to_point_delay_cost[net_id->udata][ipin] = temp_delay_cost; + temp_point_to_point_delay_cost[net_id->udata][ipin] = -1; /* Undefined */ - point_to_point_timing_cost[net_id->name.index][ipin] = temp_timing_cost; - temp_point_to_point_timing_cost[net_id->name.index][ipin] = -1; /* Undefined */ + point_to_point_timing_cost[net_id->udata][ipin] = temp_timing_cost; + temp_point_to_point_timing_cost[net_id->udata][ipin] = -1; /* Undefined */ loc_timing_cost += temp_timing_cost; } } @@ -2123,15 +2123,15 @@ static float comp_bb_cost(e_cost_methods method) { /* Small nets don't use incremental updating on their bounding boxes, * * so they can use a fast bounding box calculator. */ if (net_id->users.size() >= SMALL_NET && method == NORMAL) { - get_bb_from_scratch(net_id, &bb_coords[net_id->name.index], - &bb_num_on_edges[net_id->name.index]); + get_bb_from_scratch(net_id, &bb_coords[net_id->udata], + &bb_num_on_edges[net_id->udata]); } else { - get_non_updateable_bb(net_id, &bb_coords[net_id->name.index]); + get_non_updateable_bb(net_id, &bb_coords[net_id->udata]); } - net_cost[net_id->name.index] = get_net_cost(net_id, &bb_coords[net_id->name.index]); - cost += net_cost[net_id->name.index]; + net_cost[net_id->udata] = get_net_cost(net_id, &bb_coords[net_id->udata]); + cost += net_cost[net_id->udata]; // if (method == CHECK) // expected_wirelength += get_net_wirelength_estimate(net_id, &bb_coords[net_id]); } @@ -2161,17 +2161,17 @@ static void free_placement_structs(t_placer_opts placer_opts) { for (const auto& net : cluster_ctx.clb_nlist.nets()) { auto net_id = net.second.get(); /*add one to the address since it is indexed from 1 not 0 */ - point_to_point_timing_cost[net_id->name.index]++; - free(point_to_point_timing_cost[net_id->name.index]); + point_to_point_timing_cost[net_id->udata]++; + free(point_to_point_timing_cost[net_id->udata]); - temp_point_to_point_timing_cost[net_id->name.index]++; - free(temp_point_to_point_timing_cost[net_id->name.index]); + temp_point_to_point_timing_cost[net_id->udata]++; + free(temp_point_to_point_timing_cost[net_id->udata]); - point_to_point_delay_cost[net_id->name.index]++; - free(point_to_point_delay_cost[net_id->name.index]); + point_to_point_delay_cost[net_id->udata]++; + free(point_to_point_delay_cost[net_id->udata]); - temp_point_to_point_delay_cost[net_id->name.index]++; - free(temp_point_to_point_delay_cost[net_id->name.index]); + temp_point_to_point_delay_cost[net_id->udata]++; + free(temp_point_to_point_delay_cost[net_id->udata]); } point_to_point_timing_cost.clear(); @@ -2210,7 +2210,7 @@ static void alloc_and_load_placement_structs( auto& cluster_ctx = g_vpr_ctx.clustering(); size_t num_nets = cluster_ctx.clb_nlist.nets().size(); - for (const auto& n : cluster_ctx.clb_nlist.nets()) num_nets = std::max(num_nets, n.second.get()->name.index); + ++num_nets; // Because VPR needs it so // init_placement_context(); // @@ -2237,23 +2237,23 @@ static void alloc_and_load_placement_structs( size_t num_sinks = cluster_ctx.clb_nlist.net_sinks(net_id).size(); /* In the following, subract one so index starts at * * 1 instead of 0 */ - point_to_point_delay_cost[net_id->name.index] = (float *)malloc(num_sinks * sizeof(float)); - point_to_point_delay_cost[net_id->name.index]--; + point_to_point_delay_cost[net_id->udata] = (float *)malloc(num_sinks * sizeof(float)); + point_to_point_delay_cost[net_id->udata]--; - temp_point_to_point_delay_cost[net_id->name.index] = (float *)malloc(num_sinks * sizeof(float)); - temp_point_to_point_delay_cost[net_id->name.index]--; + temp_point_to_point_delay_cost[net_id->udata] = (float *)malloc(num_sinks * sizeof(float)); + temp_point_to_point_delay_cost[net_id->udata]--; - point_to_point_timing_cost[net_id->name.index] = (float *)malloc(num_sinks * sizeof(float)); - point_to_point_timing_cost[net_id->name.index]--; + point_to_point_timing_cost[net_id->udata] = (float *)malloc(num_sinks * sizeof(float)); + point_to_point_timing_cost[net_id->udata]--; - temp_point_to_point_timing_cost[net_id->name.index] = (float *)malloc(num_sinks * sizeof(float)); - temp_point_to_point_timing_cost[net_id->name.index]--; + temp_point_to_point_timing_cost[net_id->udata] = (float *)malloc(num_sinks * sizeof(float)); + temp_point_to_point_timing_cost[net_id->udata]--; } for (const auto& net : cluster_ctx.clb_nlist.nets()) { auto net_id = net.second.get(); for (ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { - point_to_point_delay_cost[net_id->name.index][ipin] = 0; - temp_point_to_point_delay_cost[net_id->name.index][ipin] = 0; + point_to_point_delay_cost[net_id->udata][ipin] = 0; + temp_point_to_point_delay_cost[net_id->udata][ipin] = 0; } } } @@ -2322,7 +2322,7 @@ static void alloc_and_load_try_swap_structs() { auto& cluster_ctx = g_vpr_ctx.clustering(); size_t num_nets = cluster_ctx.clb_nlist.nets().size(); - for (const auto& n : cluster_ctx.clb_nlist.nets()) num_nets = std::max(num_nets, n.second.get()->name.index); + ++num_nets; // Because VPR needs it so ts_bb_coord_new.resize(num_nets, t_bb()); ts_bb_edge_new.resize(num_nets, t_bb()); @@ -2588,14 +2588,14 @@ static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, yold = std::max(std::min(yold, device_ctx.grid.height() - 2), 1); //-2 for no perim channels /* Check if the net had been updated before. */ - if (bb_updated_before[net_id->name.index] == GOT_FROM_SCRATCH) { + if (bb_updated_before[net_id->udata] == GOT_FROM_SCRATCH) { /* The net had been updated from scratch, DO NOT update again! */ return; - } else if (bb_updated_before[net_id->name.index] == NOT_UPDATED_YET) { + } else if (bb_updated_before[net_id->udata] == NOT_UPDATED_YET) { /* The net had NOT been updated before, could use the old values */ - curr_bb_coord = &bb_coords[net_id->name.index]; - curr_bb_edge = &bb_num_on_edges[net_id->name.index]; - bb_updated_before[net_id->name.index] = UPDATED_ONCE; + curr_bb_coord = &bb_coords[net_id->udata]; + curr_bb_edge = &bb_num_on_edges[net_id->udata]; + bb_updated_before[net_id->udata] = UPDATED_ONCE; } else { /* The net had been updated before, must use the new values */ curr_bb_coord = bb_coord_new; @@ -2611,7 +2611,7 @@ static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, if (xold == curr_bb_coord->xmax) { /* Old position at xmax. */ if (curr_bb_edge->xmax == 1) { get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); - bb_updated_before[net_id->name.index] = GOT_FROM_SCRATCH; + bb_updated_before[net_id->udata] = GOT_FROM_SCRATCH; return; } else { bb_edge_new->xmax = curr_bb_edge->xmax - 1; @@ -2643,7 +2643,7 @@ static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, if (xold == curr_bb_coord->xmin) { /* Old position at xmin. */ if (curr_bb_edge->xmin == 1) { get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); - bb_updated_before[net_id->name.index] = GOT_FROM_SCRATCH; + bb_updated_before[net_id->udata] = GOT_FROM_SCRATCH; return; } else { bb_edge_new->xmin = curr_bb_edge->xmin - 1; @@ -2684,7 +2684,7 @@ static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, if (yold == curr_bb_coord->ymax) { /* Old position at ymax. */ if (curr_bb_edge->ymax == 1) { get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); - bb_updated_before[net_id->name.index] = GOT_FROM_SCRATCH; + bb_updated_before[net_id->udata] = GOT_FROM_SCRATCH; return; } else { bb_edge_new->ymax = curr_bb_edge->ymax - 1; @@ -2716,7 +2716,7 @@ static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, if (yold == curr_bb_coord->ymin) { /* Old position at ymin. */ if (curr_bb_edge->ymin == 1) { get_bb_from_scratch(net_id, bb_coord_new, bb_edge_new); - bb_updated_before[net_id->name.index] = GOT_FROM_SCRATCH; + bb_updated_before[net_id->udata] = GOT_FROM_SCRATCH; return; } else { bb_edge_new->ymin = curr_bb_edge->ymin - 1; @@ -2748,8 +2748,8 @@ static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, bb_edge_new->ymax = curr_bb_edge->ymax; } - if (bb_updated_before[net_id->name.index] == NOT_UPDATED_YET) { - bb_updated_before[net_id->name.index] = UPDATED_ONCE; + if (bb_updated_before[net_id->udata] == NOT_UPDATED_YET) { + bb_updated_before[net_id->udata] = UPDATED_ONCE; } } diff --git a/ice40/picorv32_vpr.cpu b/ice40/picorv32_vpr.cpu deleted file mode 100644 index 77c4c7b95d0114ccb607645960e3110549911d26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 230336 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 b$}C8KUml#Qc~>_WP&qDlm1fO4VZ{Fdh-6mI From 1356e406a9604f2dc4de1f016987cf4f588c13c7 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 20 Jul 2018 17:53:27 -0700 Subject: [PATCH 050/116] Enable outer_loop_recompute_criticalities --- common/placer_vpr.cc | 13 ++- common/placer_vpr.inc | 219 ++++++++++++++++++++++++------------------ 2 files changed, 136 insertions(+), 96 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 005756709f..a95a1be41a 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -96,14 +96,16 @@ namespace vpr { static struct t_placer_opts { bool enable_timing_computations; const int inner_loop_recompute_divider = 0; + const int recompute_crit_iter = 1; const float td_place_exp_first = 1.0; + const float td_place_exp_last = 8.0; const float timing_tradeoff = 0.5; } placer_opts; // timing_place.cpp float get_timing_place_crit(NetInfo* net_id, int ipin) { - return net_id->users[ipin-1].budget; + return 1; } #define VTR_ASSERT NPNR_ASSERT @@ -123,6 +125,13 @@ namespace vpr { inline void printf_info(const char* fmt, Args... args) { log_info(fmt, std::forward(args)...); } + inline void printf(const char* fmt) { + log_info(fmt); + } + template + inline void printf(const char* fmt, Args... args) { + log_info(fmt, std::forward(args)...); + } } #include "placer_vpr.inc" @@ -166,7 +175,7 @@ class VPRPlacer ni->udata = net_idx++; } - vpr::placer_opts.enable_timing_computations = /*ctx->timing_driven*/ false; + vpr::placer_opts.enable_timing_computations = ctx->timing_driven; } bool place() diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index b579dbd3df..8a7f81a783 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -67,7 +67,7 @@ * scratch using a very simple routine to allow checks of the other * * costs. */ enum e_cost_methods { - NORMAL /*, CHECK*/ + NORMAL, CHECK }; /* This is for the placement swap routines. A swap attempt could be * @@ -83,13 +83,13 @@ struct t_placer_statistics { int success_sum; }; -//#define MAX_INV_TIMING_COST 1.e9 -///* Stops inverse timing cost from going to infinity with very lax timing constraints, -//which avoids multiplying by a gigantic inverse_prev_timing_cost when auto-normalizing. -//The exact value of this cost has relatively little impact, but should not be -//large enough to be on the order of timing costs for normal constraints. */ -// -///********************** Variables local to place.c ***************************/ +#define MAX_INV_TIMING_COST 1.e9 +/* Stops inverse timing cost from going to infinity with very lax timing constraints, +which avoids multiplying by a gigantic inverse_prev_timing_cost when auto-normalizing. +The exact value of this cost has relatively little impact, but should not be +large enough to be on the order of timing costs for normal constraints. */ + +/********************** Variables local to place.c ***************************/ /* Cost of a net, and a temporary cost of a net used during move assessment. */ static std::vector net_cost, temp_net_cost; @@ -307,19 +307,19 @@ static void get_bb_from_scratch(/*ClusterNetId*/ NetInfo* net, t_bb *coords, // //static void free_try_swap_arrays(); -//static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, -// int num_connections, float crit_exponent, float bb_cost, -// float * place_delay_value, float * timing_cost, float * delay_cost, -// int * outer_crit_iter_count, float * inverse_prev_timing_cost, -// float * inverse_prev_bb_cost, -// const ClusteredPinAtomPinsLookup& netlist_pin_lookup, -//#ifdef ENABLE_CLASSIC_VPR_STA -// t_slack* slacks, -// t_timing_inf timing_inf, -//#endif -// SetupTimingInfo& timing_info); +static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, + int num_connections, float crit_exponent, float bb_cost, + float * place_delay_value, float * timing_cost, float * delay_cost, + int * outer_crit_iter_count, float * inverse_prev_timing_cost, + float * inverse_prev_bb_cost/*, + const ClusteredPinAtomPinsLookup& netlist_pin_lookup, +#ifdef ENABLE_CLASSIC_VPR_STA + t_slack* slacks, + t_timing_inf timing_inf, +#endif + SetupTimingInfo& timing_info*/); -static void placement_inner_loop(float t, float rlim, /*t_placer_opts placer_opts,*/ +static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, float crit_exponent, int inner_recompute_limit, t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, @@ -581,20 +581,21 @@ void try_place(t_placer_opts placer_opts, while (exit_crit(t, cost /*, annealing_sched*/) == 0) { // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// cost = 1; -// } + if (placer_opts.enable_timing_computations) { + cost = 1; + } -// outer_loop_recompute_criticalities(placer_opts, num_connections, -// crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, -// &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, -// netlist_pin_lookup, -//#ifdef ENABLE_CLASSIC_VPR_STA -// slacks, -// timing_inf, -//#endif -// *timing_info); + outer_loop_recompute_criticalities(placer_opts, num_connections, + crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, + &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost/*, + netlist_pin_lookup, +#ifdef ENABLE_CLASSIC_VPR_STA + slacks, + timing_inf, +#endif + *timing_info*/); - placement_inner_loop(t, rlim, /*placer_opts,*/ inverse_prev_bb_cost, inverse_prev_timing_cost, + placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, move_lim, crit_exponent, inner_recompute_limit, &stats, &cost, &bb_cost, &timing_cost, &delay_cost/*, #ifdef ENABLE_CLASSIC_VPR_STA @@ -636,8 +637,9 @@ void try_place(t_placer_opts placer_opts, } // if (placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { + else { cost = new_bb_cost; -// } + } moves_since_cost_recompute = 0; } @@ -694,10 +696,11 @@ void try_place(t_placer_opts placer_opts, update_rlim(&rlim, success_rat, device_ctx.grid); // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// crit_exponent = (1 - (rlim - final_rlim) * inverse_delta_rlim) -// * (placer_opts.td_place_exp_last - placer_opts.td_place_exp_first) -// + placer_opts.td_place_exp_first; -// } + if (placer_opts.enable_timing_computations) { + crit_exponent = (1 - (rlim - final_rlim) * inverse_delta_rlim) + * (placer_opts.td_place_exp_last - placer_opts.td_place_exp_first) + + placer_opts.td_place_exp_first; + } #ifdef VERBOSE if (getEchoEnabled()) { print_clb_placement("first_iteration_clb_placement.echo"); @@ -707,23 +710,23 @@ void try_place(t_placer_opts placer_opts, /* Outer loop of the simmulated annealing ends */ -// outer_loop_recompute_criticalities(placer_opts, num_connections, -// crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, -// &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, -// netlist_pin_lookup, -//#ifdef ENABLE_CLASSIC_VPR_STA -// slacks, -// timing_inf, -//#endif -// *timing_info); + outer_loop_recompute_criticalities(placer_opts, num_connections, + crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, + &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost /*, + netlist_pin_lookup, +#ifdef ENABLE_CLASSIC_VPR_STA + slacks, + timing_inf, +#endif + *timing_info*/); t = 0; /* freeze out */ /* Run inner loop again with temperature = 0 so as to accept only swaps * which reduce the cost of the placement */ - placement_inner_loop(t, rlim, /*placer_opts,*/ inverse_prev_bb_cost, inverse_prev_timing_cost, + placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, move_lim, crit_exponent, inner_recompute_limit, &stats, - &cost, &bb_cost, &timing_cost, &delay_cost/*, + &cost, &bb_cost, &timing_cost, &delay_cost /*, #ifdef ENABLE_CLASSIC_VPR_STA slacks, timing_inf, @@ -876,57 +879,58 @@ void try_place(t_placer_opts placer_opts, } ///* Function to recompute the criticalities before the inner loop of the annealing */ -//static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, -// int num_connections, float crit_exponent, float bb_cost, -// float * place_delay_value, float * timing_cost, float * delay_cost, -// int * outer_crit_iter_count, float * inverse_prev_timing_cost, -// float * inverse_prev_bb_cost, -// const ClusteredPinAtomPinsLookup& netlist_pin_lookup, -//#ifdef ENABLE_CLASSIC_VPR_STA -// t_slack* slacks, -// t_timing_inf timing_inf, -//#endif -// SetupTimingInfo& timing_info) { -// +static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, + int num_connections, float crit_exponent, float bb_cost, + float * place_delay_value, float * timing_cost, float * delay_cost, + int * outer_crit_iter_count, float * inverse_prev_timing_cost, + float * inverse_prev_bb_cost/*, + const ClusteredPinAtomPinsLookup& netlist_pin_lookup, +#ifdef ENABLE_CLASSIC_VPR_STA + t_slack* slacks, + t_timing_inf timing_inf, +#endif + SetupTimingInfo& timing_info*/) { + // if (placer_opts.place_algorithm != PATH_TIMING_DRIVEN_PLACE) -// return; -// -// /*at each temperature change we update these values to be used */ -// /*for normalizing the tradeoff between timing and wirelength (bb) */ -// if (*outer_crit_iter_count >= placer_opts.recompute_crit_iter -// || placer_opts.inner_loop_recompute_divider != 0) { -//#ifdef VERBOSE -// vtr::printf_info("Outer loop recompute criticalities\n"); -//#endif -// num_connections = std::max(num_connections, 1); //Avoid division by zero -// VTR_ASSERT(num_connections > 0); -// -// *place_delay_value = (*delay_cost) / num_connections; -// + if (!placer_opts.enable_timing_computations) + return; + + /*at each temperature change we update these values to be used */ + /*for normalizing the tradeoff between timing and wirelength (bb) */ + if (*outer_crit_iter_count >= placer_opts.recompute_crit_iter + || placer_opts.inner_loop_recompute_divider != 0) { +#ifdef VERBOSE + vtr::printf_info("Outer loop recompute criticalities\n"); +#endif + num_connections = std::max(num_connections, 1); //Avoid division by zero + VTR_ASSERT(num_connections > 0); + + *place_delay_value = (*delay_cost) / num_connections; + // //Per-temperature timing update // timing_info.update(); // load_criticalities(timing_info, crit_exponent, netlist_pin_lookup); -// -//#ifdef ENABLE_CLASSIC_VPR_STA -// load_timing_graph_net_delays(point_to_point_delay_cost); -// do_timing_analysis(slacks, timing_inf, false, true); -//#endif -// -// /*recompute costs from scratch, based on new criticalities */ -// comp_td_costs(timing_cost, delay_cost); -// *outer_crit_iter_count = 0; -// } -// (*outer_crit_iter_count)++; -// -// /*at each temperature change we update these values to be used */ -// /*for normalizing the tradeoff between timing and wirelength (bb) */ -//*inverse_prev_bb_cost = 1 / bb_cost; -///*Prevent inverse timing cost from going to infinity */ -//*inverse_prev_timing_cost = min(1 / (*timing_cost), (float)MAX_INV_TIMING_COST); -//} + +#ifdef ENABLE_CLASSIC_VPR_STA + load_timing_graph_net_delays(point_to_point_delay_cost); + do_timing_analysis(slacks, timing_inf, false, true); +#endif + + /*recompute costs from scratch, based on new criticalities */ + comp_td_costs(timing_cost, delay_cost); + *outer_crit_iter_count = 0; + } + (*outer_crit_iter_count)++; + + /*at each temperature change we update these values to be used */ + /*for normalizing the tradeoff between timing and wirelength (bb) */ +*inverse_prev_bb_cost = 1 / bb_cost; +/*Prevent inverse timing cost from going to infinity */ +*inverse_prev_timing_cost = std::min(1 / (*timing_cost), (float)MAX_INV_TIMING_COST); +} /* Function which contains the inner loop of the simulated annealing */ -static void placement_inner_loop(float t, float rlim, /*t_placer_opts placer_opts,*/ +static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, float crit_exponent, int inner_recompute_limit, t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, @@ -1464,6 +1468,32 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin update_td_cost(); } +// ********************************* +// if (placer_opts.enable_timing_computations) { +// float new_timing_cost, new_delay_cost; +// comp_td_costs(&new_timing_cost, &new_delay_cost); +// static int here = 0; +// if (fabs(new_timing_cost - *timing_cost) > *timing_cost * ERROR_TOL) { +// printf("here = %d\n", here); +// asm("int3"); +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "in try_swap: new_timing_cost = %g, old timing_cost = %g, ERROR_TOL = %g\n", +// new_timing_cost, *timing_cost, ERROR_TOL); +// } +// if (fabs(new_delay_cost - *delay_cost) > *delay_cost * ERROR_TOL) { +// printf("here = %d\n", here); +// asm("int3"); +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "in try_swap: new_delay_cost = %g, old delay_cost = %g, ERROR_TOL = %g\n", +// new_delay_cost, *delay_cost, ERROR_TOL); +// } +// here++; +// *timing_cost = new_timing_cost; +// *delay_cost = new_delay_cost; +// } +// ********************************* + + /* update net cost functions and reset flags. */ for (auto net_id : ts_nets_to_update) { bb_coords[net_id->udata] = ts_bb_coord_new[net_id->udata]; @@ -1980,7 +2010,8 @@ static float comp_td_point_to_point_delay(/*ClusterNetId*/ NetInfo* net_id, int // */ // delay_source_to_sink = get_delta_delay(delta_x, delta_y); - delay_source_to_sink = npnr_ctx->estimateDelay(drv_wire, user_wire); + delay_source_to_sink = npnr_ctx->getDelayNS(npnr_ctx->estimateDelay(drv_wire, user_wire)); + NPNR_ASSERT(delay_source_to_sink >= 0); // if (delay_source_to_sink < 0) { // vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, From 17cdeacd2271370acd57259462ed636dddec36b9 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 20 Jul 2018 18:09:28 -0700 Subject: [PATCH 051/116] More compatibility, and enable check_place --- common/placer_vpr.cc | 5 + common/placer_vpr.inc | 245 +++++++++++++++++++++--------------------- 2 files changed, 125 insertions(+), 125 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index a95a1be41a..b6505b6eb4 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -82,6 +82,7 @@ namespace vpr { } throw; } + std::unordered_map>& blocks() const { return npnr_ctx->cells; } } clb_nlist; t_clustering& operator()() { return *this; } } clustering; @@ -125,6 +126,10 @@ namespace vpr { inline void printf_info(const char* fmt, Args... args) { log_info(fmt, std::forward(args)...); } + template + inline void printf_error(const char* fmt, Args... args) { + log_info(fmt, std::forward(args)...); + } inline void printf(const char* fmt) { log_info(fmt); } diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index 8a7f81a783..fd4407ea13 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -239,9 +239,9 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin static /*ClusterBlockId*/ CellInfo* pick_from_block(); -//static void check_place(float bb_cost, float timing_cost, -// enum e_place_algorithm place_algorithm, -// float delay_cost); +static void check_place(float bb_cost, float timing_cost, + /*enum e_place_algorithm place_algorithm,*/ + float delay_cost); static float starting_t(float *cost_ptr, float *bb_cost_ptr, float *timing_cost_ptr, @@ -303,8 +303,8 @@ static float get_net_cost(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_ptr); static void get_bb_from_scratch(/*ClusterNetId*/ NetInfo* net, t_bb *coords, t_bb *num_on_edges); -//static double get_net_wirelength_estimate(ClusterNetId net_id, t_bb *bbptr); -// +static double get_net_wirelength_estimate(/*ClusterNetId*/ NetInfo* net_id, t_bb *bbptr); + //static void free_try_swap_arrays(); static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, @@ -365,14 +365,14 @@ void try_place(t_placer_opts placer_opts, //#endif auto& device_ctx = g_vpr_ctx.device(); -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// + auto& cluster_ctx = g_vpr_ctx.clustering(); + // std::shared_ptr timing_info; // std::shared_ptr placement_delay_calc; // // /* Allocated here because it goes into timing critical code where each memory allocation is expensive */ // IntraLbPbPinLookup pb_gpin_lookup(device_ctx.block_types, device_ctx.num_block_types); -// + /* init file scope variables */ num_swap_rejected = 0; @@ -488,9 +488,9 @@ void try_place(t_placer_opts placer_opts, inverse_prev_bb_cost = 0; } -// //Sanity check that initial placement is legal -// check_place(bb_cost, timing_cost, placer_opts.place_algorithm, delay_cost); -// + //Sanity check that initial placement is legal + check_place(bb_cost, timing_cost, /*placer_opts.place_algorithm,*/ delay_cost); + //Initial pacement statistics vtr::printf_info("Initial placement cost: %g bb_cost: %g td_cost: %g delay_cost: %g\n", cost, bb_cost, timing_cost, delay_cost); @@ -546,7 +546,7 @@ void try_place(t_placer_opts placer_opts, // //Draw the initial placement // update_screen(ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info); - move_lim = (int) (annealing_sched.inner_num * pow(npnr_ctx->cells.size(), 1.3333)); + move_lim = (int) (annealing_sched.inner_num * pow(cluster_ctx.clb_nlist.blocks().size(), 1.3333)); /* Sometimes I want to run the router with a random placement. Avoid * * using 0 moves to stop division by 0 and 0 length vector problems, * @@ -777,7 +777,7 @@ void try_place(t_placer_opts placer_opts, } #endif -// check_place(bb_cost, timing_cost, placer_opts.place_algorithm, delay_cost); + check_place(bb_cost, timing_cost, /*placer_opts.place_algorithm,*/ delay_cost); //Some stats vtr::printf_info("\n"); @@ -1102,11 +1102,11 @@ static int exit_crit(float t, float cost/*, // return (0); // } // } -// -// auto& cluster_ctx = g_vpr_ctx.clustering(); + + auto& cluster_ctx = g_vpr_ctx.clustering(); /* Automatic annealing schedule */ - float t_exit = 0.005 * cost / npnr_ctx->nets.size(); + float t_exit = 0.005 * cost / cluster_ctx.clb_nlist.nets().size(); if (t < t_exit) { return (1); @@ -1132,10 +1132,10 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, // if (annealing_sched.type == USER_SCHED) // return (annealing_sched.init_t); -// -// auto& cluster_ctx = g_vpr_ctx.clustering(); - move_lim = std::min(max_moves, (int) npnr_ctx->cells.size()); + auto& cluster_ctx = g_vpr_ctx.clustering(); + + move_lim = std::min(max_moves, (int) cluster_ctx.clb_nlist.blocks().size()); num_accepted = 0; av = 0.; @@ -1605,13 +1605,13 @@ static /*ClusterBlockId*/ CellInfo* pick_from_block() { * * * We need to track the blocks we have tried to avoid an infinite * * loop if all blocks are fixed. */ -// auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); std::unordered_set tried_from_blocks; //So long as untried blocks remain - while (tried_from_blocks.size() < npnr_ctx->cells.size()) { + while (tried_from_blocks.size() < cluster_ctx.clb_nlist.blocks().size()) { //Pick a block at random //ClusterBlockId b_from = ClusterBlockId(vtr::irand((int) cluster_ctx.clb_nlist.blocks().size() - 1)); @@ -1966,9 +1966,9 @@ static float recompute_bb_cost() { float cost = 0; -// auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& cluster_ctx = g_vpr_ctx.clustering(); - for (auto& n : npnr_ctx->nets) { /* for each net ... */ + for (auto& n : cluster_ctx.clb_nlist.nets()) { /* for each net ... */ auto net_id = n.second.get(); if (!npnr_ctx->isGlobalNet(net_id)) { /* Do only if not global. */ /* Bounding boxes don't have to be recomputed; they're correct. */ @@ -2145,10 +2145,10 @@ static void comp_td_costs(float *timing_cost, float *connection_delay_sum) { * other routine. */ static float comp_bb_cost(e_cost_methods method) { float cost = 0; -// double expected_wirelength = 0.0; -// auto& cluster_ctx = g_vpr_ctx.clustering(); + double expected_wirelength = 0.0; + auto& cluster_ctx = g_vpr_ctx.clustering(); - for (auto& n : npnr_ctx->nets) { /* for each net ... */ + for (auto& n : cluster_ctx.clb_nlist.nets()) { /* for each net ... */ auto net_id = n.second.get(); if (!npnr_ctx->isGlobalNet(net_id)) { /* Do only if not global. */ /* Small nets don't use incremental updating on their bounding boxes, * @@ -2163,15 +2163,15 @@ static float comp_bb_cost(e_cost_methods method) { net_cost[net_id->udata] = get_net_cost(net_id, &bb_coords[net_id->udata]); cost += net_cost[net_id->udata]; -// if (method == CHECK) -// expected_wirelength += get_net_wirelength_estimate(net_id, &bb_coords[net_id]); + if (method == CHECK) + expected_wirelength += get_net_wirelength_estimate(net_id, &bb_coords[net_id->udata]); } } -// if (method == CHECK) { -// vtr::printf_info("\n"); -// vtr::printf_info("BB estimate of min-dist (placement) wire length: %.0f\n", expected_wirelength); -// } + if (method == CHECK) { + vtr::printf_info("\n"); + vtr::printf_info("BB estimate of min-dist (placement) wire length: %.0f\n", expected_wirelength); + } return cost; } @@ -2185,7 +2185,7 @@ static void free_placement_structs(t_placer_opts placer_opts) { // free_legal_placements(); // free_fast_cost_update(); -// + if (/*placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE ||*/ placer_opts.enable_timing_computations) { @@ -2289,11 +2289,6 @@ static void alloc_and_load_placement_structs( } } - // We tradeoff memory for speed here: - // npnr nets are actually pointers, with non sequential indices - // associated with their names, thus num_nets actually contains - // the max index of all net indices, which is likely to be more - // than the actual number of nets net_cost.resize(num_nets, -1); temp_net_cost.resize(num_nets, -1); bb_coords.resize(num_nets, t_bb()); @@ -2459,41 +2454,41 @@ static void get_bb_from_scratch(/*ClusterNetId*/ NetInfo *net_id, t_bb *coords, num_on_edges->ymax = ymax_edge; } -//static double get_net_wirelength_estimate(ClusterNetId net_id, t_bb *bbptr) { -// -// /* WMF: Finds the estimate of wirelength due to one net by looking at * -// * its coordinate bounding box. */ -// -// double ncost, crossing; -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// /* Get the expected "crossing count" of a net, based on its number * -// * of pins. Extrapolate for very large nets. */ -// -// if (((cluster_ctx.clb_nlist.net_pins(net_id).size()) > 50) -// && ((cluster_ctx.clb_nlist.net_pins(net_id).size()) < 85)) { -// crossing = 2.7933 + 0.02616 * ((cluster_ctx.clb_nlist.net_pins(net_id).size()) - 50); -// } else if ((cluster_ctx.clb_nlist.net_pins(net_id).size()) >= 85) { -// crossing = 2.7933 + 0.011 * (cluster_ctx.clb_nlist.net_pins(net_id).size()) -// - 0.0000018 * (cluster_ctx.clb_nlist.net_pins(net_id).size()) -// * (cluster_ctx.clb_nlist.net_pins(net_id).size()); -// } else { -// crossing = cross_count[cluster_ctx.clb_nlist.net_pins(net_id).size() - 1]; -// } -// -// /* Could insert a check for xmin == xmax. In that case, assume * -// * connection will be made with no bends and hence no x-cost. * -// * Same thing for y-cost. */ -// -// /* Cost = wire length along channel * cross_count / average * -// * channel capacity. Do this for x, then y direction and add. */ -// -// ncost = (bbptr->xmax - bbptr->xmin + 1) * crossing; -// -// ncost += (bbptr->ymax - bbptr->ymin + 1) * crossing; -// -// return (ncost); -//} +static double get_net_wirelength_estimate(/*ClusterNetId*/ NetInfo* net_id, t_bb *bbptr) { + + /* WMF: Finds the estimate of wirelength due to one net by looking at * + * its coordinate bounding box. */ + + double ncost, crossing; + auto& cluster_ctx = g_vpr_ctx.clustering(); + + /* Get the expected "crossing count" of a net, based on its number * + * of pins. Extrapolate for very large nets. */ + + if (((cluster_ctx.clb_nlist.net_pins(net_id).size()) > 50) + && ((cluster_ctx.clb_nlist.net_pins(net_id).size()) < 85)) { + crossing = 2.7933 + 0.02616 * ((cluster_ctx.clb_nlist.net_pins(net_id).size()) - 50); + } else if ((cluster_ctx.clb_nlist.net_pins(net_id).size()) >= 85) { + crossing = 2.7933 + 0.011 * (cluster_ctx.clb_nlist.net_pins(net_id).size()) + - 0.0000018 * (cluster_ctx.clb_nlist.net_pins(net_id).size()) + * (cluster_ctx.clb_nlist.net_pins(net_id).size()); + } else { + crossing = cross_count[cluster_ctx.clb_nlist.net_pins(net_id).size() - 1]; + } + + /* Could insert a check for xmin == xmax. In that case, assume * + * connection will be made with no bends and hence no x-cost. * + * Same thing for y-cost. */ + + /* Cost = wire length along channel * cross_count / average * + * channel capacity. Do this for x, then y direction and add. */ + + ncost = (bbptr->xmax - bbptr->xmin + 1) * crossing; + + ncost += (bbptr->ymax - bbptr->ymin + 1) * crossing; + + return (ncost); +} static float get_net_cost(/*ClusterNetId*/ NetInfo* net_id, t_bb *bbptr) { @@ -3049,13 +3044,12 @@ static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_l static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type pad_loc_type*/ std::vector>& free_locations) { // int itype, ipos, x, y, z; -// auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); // auto& device_ctx = g_vpr_ctx.device(); // -// for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { - for (auto& cell_entry : npnr_ctx->cells) { - auto cell = cell_entry.second.get(); + for (auto& blk_id : cluster_ctx.clb_nlist.blocks()) { + auto cell = blk_id.second.get(); // if (place_ctx.block_locs[blk_id].x != -1) { // -1 is a sentinel for an empty block // // block placed. // continue; @@ -3339,11 +3333,11 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // (double) place_cost_exp); // } //} -// -//static void check_place(float bb_cost, float timing_cost, -// enum e_place_algorithm place_algorithm, -// float delay_cost) { -// + +static void check_place(float bb_cost, float timing_cost, + /*enum e_place_algorithm place_algorithm,*/ + float delay_cost) { + // /* Checks that the placement has not confused our data structures. * // * i.e. the clb and block structures agree about the locations of * // * every block, blocks are in legal spots, etc. Also recomputes * @@ -3351,40 +3345,41 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // * within roundoff of what we think the cost is. */ // // vtr::vector bdone; -// int error = 0; + int error = 0; // ClusterBlockId bnum, head_iblk, member_iblk; -// float bb_cost_check; + float bb_cost_check; // int usage_check; -// float timing_cost_check, delay_cost_check; + float timing_cost_check, delay_cost_check; // int imacro, imember, member_x, member_y, member_z; -// -// bb_cost_check = comp_bb_cost(CHECK); -// //vtr::printf_info("bb_cost recomputed from scratch: %g\n", bb_cost_check); -// if (fabs(bb_cost_check - bb_cost) > bb_cost * ERROR_TOL) { -// vtr::printf_error(__FILE__, __LINE__, -// "bb_cost_check: %g and bb_cost: %g differ in check_place.\n", -// bb_cost_check, bb_cost); -// error++; -// } -// + + bb_cost_check = comp_bb_cost(CHECK); + //vtr::printf_info("bb_cost recomputed from scratch: %g\n", bb_cost_check); + if (fabs(bb_cost_check - bb_cost) > bb_cost * ERROR_TOL) { + vtr::printf_error(__FILE__, __LINE__, + "bb_cost_check: %g and bb_cost: %g differ in check_place.\n", + bb_cost_check, bb_cost); + error++; + } + // if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// comp_td_costs(&timing_cost_check, &delay_cost_check); -// //vtr::printf_info("timing_cost recomputed from scratch: %g\n", timing_cost_check); -// if (fabs(timing_cost_check - timing_cost) > timing_cost * ERROR_TOL) { -// vtr::printf_error(__FILE__, __LINE__, -// "timing_cost_check: %g and timing_cost: %g differ in check_place.\n", -// timing_cost_check, timing_cost); -// error++; -// } -// //vtr::printf_info("delay_cost recomputed from scratch: %g\n", delay_cost_check); -// if (fabs(delay_cost_check - delay_cost) > delay_cost * ERROR_TOL) { -// vtr::printf_error(__FILE__, __LINE__, -// "delay_cost_check: %g and delay_cost: %g differ in check_place.\n", -// delay_cost_check, delay_cost); -// error++; -// } -// } -// + if (placer_opts.enable_timing_computations) { + comp_td_costs(&timing_cost_check, &delay_cost_check); + //vtr::printf_info("timing_cost recomputed from scratch: %g\n", timing_cost_check); + if (fabs(timing_cost_check - timing_cost) > timing_cost * ERROR_TOL) { + vtr::printf_error(__FILE__, __LINE__, + "timing_cost_check: %g and timing_cost: %g differ in check_place.\n", + timing_cost_check, timing_cost); + error++; + } + //vtr::printf_info("delay_cost recomputed from scratch: %g\n", delay_cost_check); + if (fabs(delay_cost_check - delay_cost) > delay_cost * ERROR_TOL) { + vtr::printf_error(__FILE__, __LINE__, + "delay_cost_check: %g and delay_cost: %g differ in check_place.\n", + delay_cost_check, delay_cost); + error++; + } + } + // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.placement(); // auto& device_ctx = g_vpr_ctx.device(); @@ -3472,19 +3467,19 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // } // } // Finish going through all the members // } // Finish going through all the macros -// -// if (error == 0) { -// vtr::printf_info("\n"); -// vtr::printf_info("Completed placement consistency check successfully.\n"); -// -// } else { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "\nCompleted placement consistency check, %d errors found.\n" -// "Aborting program.\n", error); -// } -// -//} -// + + if (error == 0) { + vtr::printf_info("\n"); + vtr::printf_info("Completed placement consistency check successfully.\n"); + + } else { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "\nCompleted placement consistency check, %d errors found.\n" + "Aborting program.\n", error); + } + +} + //#ifdef VERBOSE //static void print_clb_placement(const char *fname) { // From b3e7206611058000f603219e215f72a6467727c5 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 20 Jul 2018 18:18:53 -0700 Subject: [PATCH 052/116] Cleanup --- common/placer_vpr.cc | 4 ++-- common/placer_vpr.inc | 45 ++++++++++--------------------------------- 2 files changed, 12 insertions(+), 37 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index b6505b6eb4..caeabf99dc 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -127,8 +127,8 @@ namespace vpr { log_info(fmt, std::forward(args)...); } template - inline void printf_error(const char* fmt, Args... args) { - log_info(fmt, std::forward(args)...); + inline void printf_error(const char*, int, const char* fmt, Args... args) { + log_error(fmt, std::forward(args)...); } inline void printf(const char* fmt) { log_info(fmt); diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index fd4407ea13..acb6d830e8 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -623,16 +623,16 @@ void try_place(t_placer_opts placer_opts, // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { if (placer_opts.enable_timing_computations) { comp_td_costs(&new_timing_cost, &new_delay_cost); - if (fabs(new_timing_cost - timing_cost) > timing_cost * ERROR_TOL) { - vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, - "in try_place: new_timing_cost = %g, old timing_cost = %g, ERROR_TOL = %g\n", - new_timing_cost, timing_cost, ERROR_TOL); - } - if (fabs(new_delay_cost - delay_cost) > delay_cost * ERROR_TOL) { - vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, - "in try_place: new_delay_cost = %g, old delay_cost = %g, ERROR_TOL = %g\n", - new_delay_cost, delay_cost, ERROR_TOL); - } +// if (fabs(new_timing_cost - timing_cost) > timing_cost * ERROR_TOL) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "in try_place: new_timing_cost = %g, old timing_cost = %g, ERROR_TOL = %g\n", +// new_timing_cost, timing_cost, ERROR_TOL); +// } +// if (fabs(new_delay_cost - delay_cost) > delay_cost * ERROR_TOL) { +// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, +// "in try_place: new_delay_cost = %g, old delay_cost = %g, ERROR_TOL = %g\n", +// new_delay_cost, delay_cost, ERROR_TOL); +// } timing_cost = new_timing_cost; } @@ -1468,31 +1468,6 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin update_td_cost(); } -// ********************************* -// if (placer_opts.enable_timing_computations) { -// float new_timing_cost, new_delay_cost; -// comp_td_costs(&new_timing_cost, &new_delay_cost); -// static int here = 0; -// if (fabs(new_timing_cost - *timing_cost) > *timing_cost * ERROR_TOL) { -// printf("here = %d\n", here); -// asm("int3"); -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "in try_swap: new_timing_cost = %g, old timing_cost = %g, ERROR_TOL = %g\n", -// new_timing_cost, *timing_cost, ERROR_TOL); -// } -// if (fabs(new_delay_cost - *delay_cost) > *delay_cost * ERROR_TOL) { -// printf("here = %d\n", here); -// asm("int3"); -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "in try_swap: new_delay_cost = %g, old delay_cost = %g, ERROR_TOL = %g\n", -// new_delay_cost, *delay_cost, ERROR_TOL); -// } -// here++; -// *timing_cost = new_timing_cost; -// *delay_cost = new_delay_cost; -// } -// ********************************* - /* update net cost functions and reset flags. */ for (auto net_id : ts_nets_to_update) { From dab39b68ef617b313adb1fd584d7958d416e0850 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 20 Jul 2018 18:24:35 -0700 Subject: [PATCH 053/116] Revert to original net_is_global() function --- common/placer_vpr.inc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index acb6d830e8..1f3a1dc2d9 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -1637,7 +1637,7 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit VTR_ASSERT_SAFE_MSG(net_id, "Only valid nets should be found in compressed netlist block pins"); - if (npnr_ctx->isGlobalNet(net_id)) + if (cluster_ctx.clb_nlist.net_is_global(net_id)) continue; //Global nets are assumed to span the whole chip, and do not effect costs //Record effected nets @@ -1945,7 +1945,7 @@ static float recompute_bb_cost() { for (auto& n : cluster_ctx.clb_nlist.nets()) { /* for each net ... */ auto net_id = n.second.get(); - if (!npnr_ctx->isGlobalNet(net_id)) { /* Do only if not global. */ + if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ /* Bounding boxes don't have to be recomputed; they're correct. */ cost += net_cost[net_id->udata]; } @@ -2125,7 +2125,7 @@ static float comp_bb_cost(e_cost_methods method) { for (auto& n : cluster_ctx.clb_nlist.nets()) { /* for each net ... */ auto net_id = n.second.get(); - if (!npnr_ctx->isGlobalNet(net_id)) { /* Do only if not global. */ + if (!cluster_ctx.clb_nlist.net_is_global(net_id)) { /* Do only if not global. */ /* Small nets don't use incremental updating on their bounding boxes, * * so they can use a fast bounding box calculator. */ if (net_id->users.size() >= SMALL_NET && method == NORMAL) { From 7a82505c523e4da22046c8fa82a94ca57b34a7b4 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 20 Jul 2018 18:26:42 -0700 Subject: [PATCH 054/116] Missing line --- common/placer_vpr.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index 1f3a1dc2d9..575052a837 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -1620,7 +1620,7 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit VTR_ASSERT_SAFE(bb_delta_c == 0.); VTR_ASSERT_SAFE(timing_delta_c == 0.); VTR_ASSERT_SAFE(delay_delta_c == 0.); -// auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& cluster_ctx = g_vpr_ctx.clustering(); int num_affected_nets = 0; From 3b77e0f20c2f7da01f12c7ebf25e67789f9d659d Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 20 Jul 2018 19:15:48 -0700 Subject: [PATCH 055/116] Fix pin_net_index() --- common/placer_vpr.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index caeabf99dc..1a137aa76d 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -74,10 +74,10 @@ namespace vpr { std::unordered_map& block_pins(CellInfo* cell) { return cell->ports; } PinType pin_type(const PortInfo& port) { return static_cast(port.type); } CellInfo* net_driver_block(const NetInfo* net) const { return net->driver.cell; } - size_t pin_net_index(const PortInfo& port) { + size_t pin_net_index(const PortInfo& port, const CellInfo* cell) { auto net = port.net; for (auto it = net->users.begin(); it != net->users.end(); ++it) { - if (it->port == port.name) + if (it->port == port.name && it->cell == cell) return it - net->users.begin() + 1; } throw; From 840f51146819bf3c9e0f24368f43674801758db0 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 20 Jul 2018 19:16:38 -0700 Subject: [PATCH 056/116] Fix timing update issue due to pin_net_index --- common/placer_vpr.inc | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index 575052a837..4981dba300 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -296,7 +296,8 @@ static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_af static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin*/ CellInfo* blk, BelId bel_from); -static void update_td_delta_costs(/*const ClusterNetId*/ NetInfo *net, const /*ClusterPinId*/ PortInfo &pin, float& delta_timing_cost, float& delta_delay_cost); +static void update_td_delta_costs(/*const ClusterNetId*/ NetInfo *net, const /*ClusterPinId*/ PortInfo &pin, float& delta_timing_cost, float& delta_delay_cost, + CellInfo* blk); static float get_net_cost(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_ptr); @@ -623,16 +624,16 @@ void try_place(t_placer_opts placer_opts, // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { if (placer_opts.enable_timing_computations) { comp_td_costs(&new_timing_cost, &new_delay_cost); -// if (fabs(new_timing_cost - timing_cost) > timing_cost * ERROR_TOL) { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "in try_place: new_timing_cost = %g, old timing_cost = %g, ERROR_TOL = %g\n", -// new_timing_cost, timing_cost, ERROR_TOL); -// } -// if (fabs(new_delay_cost - delay_cost) > delay_cost * ERROR_TOL) { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "in try_place: new_delay_cost = %g, old delay_cost = %g, ERROR_TOL = %g\n", -// new_delay_cost, delay_cost, ERROR_TOL); -// } + if (fabs(new_timing_cost - timing_cost) > timing_cost * ERROR_TOL) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "in try_place: new_timing_cost = %g, old timing_cost = %g, ERROR_TOL = %g\n", + new_timing_cost, timing_cost, ERROR_TOL); + } + if (fabs(new_delay_cost - delay_cost) > delay_cost * ERROR_TOL) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "in try_place: new_delay_cost = %g, old delay_cost = %g, ERROR_TOL = %g\n", + new_delay_cost, delay_cost, ERROR_TOL); + } timing_cost = new_timing_cost; } @@ -878,7 +879,7 @@ void try_place(t_placer_opts placer_opts, // free_try_swap_arrays(); } -///* Function to recompute the criticalities before the inner loop of the annealing */ +/* Function to recompute the criticalities before the inner loop of the annealing */ static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, int num_connections, float crit_exponent, float bb_cost, float * place_delay_value, float * timing_cost, float * delay_cost, @@ -1630,7 +1631,7 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit auto bel = b.second; //Go through all the pins in the moved block - for (const auto &p : blk->ports) { + for (const auto &p : cluster_ctx.clb_nlist.block_pins(blk)) { const auto& port = p.second; auto net_id = port.net; if (!net_id) continue; @@ -1652,7 +1653,7 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit // if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { if (placer_opts.enable_timing_computations) { //Determine the change in timing costs if required - update_td_delta_costs(net_id, /*blk_pin*/ port, timing_delta_c, delay_delta_c); + update_td_delta_costs(net_id, /*blk_pin*/ port, timing_delta_c, delay_delta_c, blk); } } } @@ -1715,7 +1716,8 @@ static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const } -static void update_td_delta_costs(/*const ClusterNetId*/ NetInfo *net, const /*ClusterPinId*/ PortInfo &pin, float& delta_timing_cost, float& delta_delay_cost) { +static void update_td_delta_costs(/*const ClusterNetId*/ NetInfo *net, const /*ClusterPinId*/ PortInfo &pin, float& delta_timing_cost, float& delta_delay_cost, + CellInfo* blk) { auto& cluster_ctx = g_vpr_ctx.clustering(); if (cluster_ctx.clb_nlist.pin_type(pin) == PinType::DRIVER) { @@ -1741,7 +1743,7 @@ static void update_td_delta_costs(/*const ClusterNetId*/ NetInfo *net, const /*C //Computing it here would double count the change, and mess up the //delta_timing_cost value. if (!driven_by_moved_block(net)) { - int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin); + int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin, blk); float temp_delay = comp_td_point_to_point_delay(net, net_pin); temp_point_to_point_delay_cost[net->udata][net_pin] = temp_delay; @@ -2032,8 +2034,10 @@ static void update_td_cost() { //This net is being driven by a moved block, recompute //all point to point connections on this net. for (size_t ipin = 1; ipin < cluster_ctx.clb_nlist.net_pins(net_id).size(); ipin++) { + NPNR_ASSERT(temp_point_to_point_delay_cost[net_id->udata][ipin] >= 0); point_to_point_delay_cost[net_id->udata][ipin] = temp_point_to_point_delay_cost[net_id->udata][ipin]; temp_point_to_point_delay_cost[net_id->udata][ipin] = -1; + NPNR_ASSERT(temp_point_to_point_timing_cost[net_id->udata][ipin] >= 0); point_to_point_timing_cost[net_id->udata][ipin] = temp_point_to_point_timing_cost[net_id->udata][ipin]; temp_point_to_point_timing_cost[net_id->udata][ipin] = -1; } @@ -2043,10 +2047,12 @@ static void update_td_cost() { /* The following "if" prevents the value from being updated twice. */ if (!driven_by_moved_block(net_id)) { - int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin_id); + int net_pin = cluster_ctx.clb_nlist.pin_net_index(pin_id, bnum); + NPNR_ASSERT(temp_point_to_point_delay_cost[net_id->udata][net_pin] >= 0); point_to_point_delay_cost[net_id->udata][net_pin] = temp_point_to_point_delay_cost[net_id->udata][net_pin]; temp_point_to_point_delay_cost[net_id->udata][net_pin] = -1; + NPNR_ASSERT(temp_point_to_point_timing_cost[net_id->udata][net_pin] >= 0); point_to_point_timing_cost[net_id->udata][net_pin] = temp_point_to_point_timing_cost[net_id->udata][net_pin]; temp_point_to_point_timing_cost[net_id->udata][net_pin] = -1; } From 457d3cdb6f95e277cc3ebb3ca55378f383886dd6 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 20 Jul 2018 19:23:48 -0700 Subject: [PATCH 057/116] Use VPR words --- common/placer_vpr.inc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/placer_vpr.inc b/common/placer_vpr.inc index 4981dba300..fed14ac034 100644 --- a/common/placer_vpr.inc +++ b/common/placer_vpr.inc @@ -1632,8 +1632,8 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit //Go through all the pins in the moved block for (const auto &p : cluster_ctx.clb_nlist.block_pins(blk)) { - const auto& port = p.second; - auto net_id = port.net; + const auto& blk_pin = p.second; + auto net_id = blk_pin.net; if (!net_id) continue; VTR_ASSERT_SAFE_MSG(net_id, "Only valid nets should be found in compressed netlist block pins"); @@ -1653,7 +1653,7 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit // if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { if (placer_opts.enable_timing_computations) { //Determine the change in timing costs if required - update_td_delta_costs(net_id, /*blk_pin*/ port, timing_delta_c, delay_delta_c, blk); + update_td_delta_costs(net_id, blk_pin, timing_delta_c, delay_delta_c, blk); } } } From 696d5386465186f2e76440c7d3d149dd2990d6be Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 20 Jul 2018 20:24:31 -0700 Subject: [PATCH 058/116] Fix invalid memory access --- common/vpr_timing_place.cpp.inc | 113 ++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 common/vpr_timing_place.cpp.inc diff --git a/common/vpr_timing_place.cpp.inc b/common/vpr_timing_place.cpp.inc new file mode 100644 index 0000000000..12c8d9105f --- /dev/null +++ b/common/vpr_timing_place.cpp.inc @@ -0,0 +1,113 @@ +//#include +//#include +//using namespace std; +// +//#include "vtr_util.h" +//#include "vtr_memory.h" +//#include "vtr_log.h" +// +//#include "vpr_types.h" +//#include "vpr_utils.h" +//#include "globals.h" +//#include "path_delay.h" +//#include "path_delay2.h" +//#include "net_delay.h" +//#include "timing_place_lookup.h" +//#include "timing_place.h" +// +//#include "timing_info.h" + +static /*vtr*/std::vector f_timing_place_crit; /* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1] */ + +//static vtr::t_chunk f_timing_place_crit_ch; + +/******** prototypes ******************/ +static void alloc_crit(/*vtr::t_chunk *chunk_list_ptr*/); + +static void free_crit(/*vtr::t_chunk *chunk_list_ptr*/); + +/**************************************/ + +/* Allocates space for the f_timing_place_crit data structure * +* I chunk the data to save space on large problems. */ +static void alloc_crit(/*vtr::t_chunk *chunk_list_ptr*/) { + auto& cluster_ctx = g_vpr_ctx.clustering(); + float *tmp_ptr; + + f_timing_place_crit.resize(cluster_ctx.clb_nlist.nets().size()); + + for (const auto& net : cluster_ctx.clb_nlist.nets()) { + auto net_id = net.second.get(); + //tmp_ptr = (float *) vtr::chunk_malloc( + // (cluster_ctx.clb_nlist.net_sinks(net_id).size()) * sizeof(float), chunk_list_ptr); + tmp_ptr = (float *) malloc((cluster_ctx.clb_nlist.net_sinks(net_id).size()) * sizeof(float)); + f_timing_place_crit[net_id->udata] = tmp_ptr - 1; /* [1..num_sinks] */ + } +} + +///**************************************/ +static void free_crit(/*vtr::t_chunk *chunk_list_ptr*/){ +// vtr::free_chunk_memory(chunk_list_ptr); + for (auto i : f_timing_place_crit) + free(i+1); +} + +/**************************************/ +void load_criticalities(/*SetupTimingInfo& timing_info,*/ float crit_exponent /*, const ClusteredPinAtomPinsLookup& pin_lookup*/) { + /* Performs a 1-to-1 mapping from criticality to f_timing_place_crit. + For every pin on every net (or, equivalently, for every tedge ending + in that pin), f_timing_place_crit = criticality^(criticality exponent) */ + + auto& cluster_ctx = g_vpr_ctx.clustering(); + for (const auto& net : cluster_ctx.clb_nlist.nets()) { + auto net_id = net.second.get(); + if (cluster_ctx.clb_nlist.net_is_global(net_id)) + continue; + + int ipin = 1; + for (const auto& clb_pin : cluster_ctx.clb_nlist.net_sinks(net_id)) { + //int ipin = cluster_ctx.clb_nlist.pin_net_index(clb_pin); + + float clb_pin_crit = calculate_clb_net_pin_criticality(/*timing_info, pin_lookup,*/ clb_pin, net_id); + + /* The placer likes a great deal of contrast between criticalities. + Since path criticality varies much more than timing, we "sharpen" timing + criticality by taking it to some power, crit_exponent (between 1 and 8 by default). */ + f_timing_place_crit[net_id->udata][ipin] = pow(clb_pin_crit, crit_exponent); + + ++ipin; + } + } +} + + +float get_timing_place_crit(/*ClusterNetId*/ NetInfo* net_id, int ipin) { + return f_timing_place_crit[net_id->udata][ipin]; +} + +void set_timing_place_crit(/*ClusterNetId*/ NetInfo* net_id, int ipin, float val) { + f_timing_place_crit[net_id->udata][ipin] = val; +} + +/**************************************/ +void alloc_lookups_and_criticalities(/*t_chan_width_dist chan_width_dist, + t_router_opts router_opts, + t_det_routing_arch *det_routing_arch, t_segment_inf * segment_inf, + const t_direct_inf *directs, + const int num_directs*/) { + +// compute_delay_lookup_tables(router_opts, det_routing_arch, segment_inf, +// chan_width_dist, directs, num_directs); + + alloc_crit(/*&f_timing_place_crit_ch*/); +} + +/**************************************/ +void free_lookups_and_criticalities() { +// //TODO: May need to free f_timing_place_crit ? + free_crit(/*&f_timing_place_crit_ch*/); + +// free_place_lookup_structs(); +} + +/**************************************/ From 9cd2b079be25014cc9c6a1e86eb69a144786831b Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 20 Jul 2018 22:50:37 -0700 Subject: [PATCH 059/116] Add more timing stuff --- common/placer_vpr.cc | 35 +++++++++++-- common/{placer_vpr.inc => vpr_place.cpp.inc} | 54 ++++++++++---------- 2 files changed, 59 insertions(+), 30 deletions(-) rename common/{placer_vpr.inc => vpr_place.cpp.inc} (99%) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 1a137aa76d..7d200b64ca 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -103,10 +103,37 @@ namespace vpr { const float timing_tradeoff = 0.5; } placer_opts; - // timing_place.cpp - float get_timing_place_crit(NetInfo* net_id, int ipin) + // timing_util.cpp + float calculate_clb_net_pin_criticality(/*timing_info, pin_lookup,*/ const PortRef& load, const NetInfo* net) { + NPNR_ASSERT(npnr_ctx->timing_driven); + +#if 1 + int driver_x, driver_y; + bool driver_gb; + CellInfo *driver_cell = net->driver.cell; + if (!driver_cell) + return 0; + if (driver_cell->bel == BelId()) + return 0; + npnr_ctx->estimatePosition(driver_cell->bel, driver_x, driver_y, driver_gb); + WireId drv_wire = npnr_ctx->getWireBelPin(driver_cell->bel, npnr_ctx->portPinFromId(net->driver.port)); + if (driver_gb) + return 0; + if (load.cell == nullptr) + return 0; + CellInfo *load_cell = load.cell; + if (load_cell->bel == BelId()) + return 0; + WireId user_wire = npnr_ctx->getWireBelPin(load_cell->bel, npnr_ctx->portPinFromId(load.port)); + delay_t raw_wl = npnr_ctx->estimateDelay(drv_wire, user_wire); + float slack = npnr_ctx->getDelayNS(load.budget) - npnr_ctx->getDelayNS(raw_wl); + if (slack <= 0) + return 1 - slack; + return 1/slack; +#else return 1; +#endif } #define VTR_ASSERT NPNR_ASSERT @@ -139,7 +166,9 @@ namespace vpr { } } - #include "placer_vpr.inc" + #include "vpr_types.h" + #include "vpr_timing_place.cpp.inc" + #include "vpr_place.cpp.inc" } NEXTPNR_NAMESPACE_BEGIN diff --git a/common/placer_vpr.inc b/common/vpr_place.cpp.inc similarity index 99% rename from common/placer_vpr.inc rename to common/vpr_place.cpp.inc index fed14ac034..317c5b629d 100644 --- a/common/placer_vpr.inc +++ b/common/vpr_place.cpp.inc @@ -10,7 +10,7 @@ //#include "vtr_random.h" //#include "vtr_matrix.h" // -#include "vpr_types.h" +//#include "vpr_types.h" //#include "vpr_error.h" //#include "vpr_utils.h" // @@ -381,16 +381,16 @@ void try_place(t_placer_opts placer_opts, num_swap_aborted = 0; num_ts_called = 0; -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE -// || placer_opts.enable_timing_computations) { -// /*do this before the initial placement to avoid messing up the initial placement */ -// alloc_lookups_and_criticalities(chan_width_dist, router_opts, det_routing_arch, segment_inf, directs, num_directs); -// -//#ifdef ENABLE_CLASSIC_VPR_STA -// slacks = alloc_and_load_timing_graph(timing_inf); -//#endif -// } -// + if (/*placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + ||*/ placer_opts.enable_timing_computations) { + /*do this before the initial placement to avoid messing up the initial placement */ + alloc_lookups_and_criticalities(/*chan_width_dist, router_opts, det_routing_arch, segment_inf, directs, num_directs*/); + +#ifdef ENABLE_CLASSIC_VPR_STA + slacks = alloc_and_load_timing_graph(timing_inf); +#endif + } + // width_fac = placer_opts.place_chan_width; // // init_chan(width_fac, chan_width_dist); @@ -431,10 +431,10 @@ void try_place(t_placer_opts placer_opts, // // timing_info->update(); // timing_info->set_warn_unconstrained(false); //Don't warn again about unconstrained nodes again during placement -// -// //Initial slack estimates -// load_criticalities(*timing_info, crit_exponent, netlist_pin_lookup); -// + + //Initial slack estimates + load_criticalities(/**timing_info,*/ crit_exponent /*, netlist_pin_lookup*/); + // critical_path = timing_info->least_slack_critical_path(); // // //Write out the initial timing echo file @@ -866,16 +866,16 @@ void try_place(t_placer_opts placer_opts, vtr::printf_info("\tSwaps aborted : %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_aborted, 100*abort_rate); free_placement_structs(placer_opts); -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE -// || placer_opts.enable_timing_computations) { -// -//#ifdef ENABLE_CLASSIC_VPR_STA -// free_timing_graph(slacks); -//#endif -// -// free_lookups_and_criticalities(); -// } -// + if (/*placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + ||*/ placer_opts.enable_timing_computations) { + +#ifdef ENABLE_CLASSIC_VPR_STA + free_timing_graph(slacks); +#endif + + free_lookups_and_criticalities(); + } + // free_try_swap_arrays(); } @@ -910,7 +910,7 @@ static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, // //Per-temperature timing update // timing_info.update(); -// load_criticalities(timing_info, crit_exponent, netlist_pin_lookup); + load_criticalities(/*timing_info,*/ crit_exponent /*, netlist_pin_lookup*/); #ifdef ENABLE_CLASSIC_VPR_STA load_timing_graph_net_delays(point_to_point_delay_cost); @@ -996,7 +996,7 @@ static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, */ //Inner loop timing update // timing_info.update(); -// load_criticalities(timing_info, crit_exponent, netlist_pin_lookup); + load_criticalities(/*timing_info,*/ crit_exponent /*, netlist_pin_lookup*/); #ifdef ENABLE_CLASSIC_VPR_STA load_timing_graph_net_delays(point_to_point_delay_cost); From 5e064aa3e448f25ae4037c9aae4949e45e8e52cb Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 21 Jul 2018 14:30:42 -0700 Subject: [PATCH 060/116] Fix compile issue --- ice40/arch.cc | 5 +++++ ice40/arch.h | 1 + 2 files changed, 6 insertions(+) diff --git a/ice40/arch.cc b/ice40/arch.cc index c0661a7f4e..228ffc267f 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -781,6 +781,11 @@ bool Arch::isGlobalNet(const NetInfo *net) const return net->driver.cell != nullptr && net->driver.port == id_glb_buf_out; } +bool Arch::isIO(const CellInfo* cell) const +{ + return cell->type == id("SB_IO"); +} + // Assign arch arg info void Arch::assignArchInfo() { diff --git a/ice40/arch.h b/ice40/arch.h index 11098f96b6..55abdb807d 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -730,6 +730,7 @@ struct Arch : BaseCtx bool isClockPort(const CellInfo *cell, IdString port) const; // Return true if a port is a net bool isGlobalNet(const NetInfo *net) const; + bool isIO(const CellInfo* cell) const; // ------------------------------------------------- From b9e3aa162485dc72f51dc3afc4f0482fa95ae44f Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 21 Jul 2018 14:42:12 -0700 Subject: [PATCH 061/116] Fix calculate_clb_net_pin_criticality() to not blow up with small slacks --- common/placer_vpr.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 7d200b64ca..703c9b544b 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -130,7 +130,7 @@ namespace vpr { float slack = npnr_ctx->getDelayNS(load.budget) - npnr_ctx->getDelayNS(raw_wl); if (slack <= 0) return 1 - slack; - return 1/slack; + return 1/std::max(1, slack); #else return 1; #endif From a02b10cd14c6245323e174984f7eefebec35cd80 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 21 Jul 2018 14:51:58 -0700 Subject: [PATCH 062/116] Disable router1 update_budget for now --- common/router1.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/router1.cc b/common/router1.cc index 767e10fdfc..8f94975106 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -748,7 +748,7 @@ bool router1(Context *ctx) total_delay += ctx->estimateDelay(src, last_wire); return total_delay; }; - update_budget(ctx, actual_delay); + //update_budget(ctx, actual_delay); } bool printNets = ctx->verbose && (jobQueue.size() < 10); From ff6863fb785ed10aa9f2b606020c488b8cb6cf8c Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 21 Jul 2018 14:54:12 -0700 Subject: [PATCH 063/116] Support timing_info.update() to call update_budget() --- common/placer_vpr.cc | 4 +++ common/vpr_place.cpp.inc | 54 ++++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 703c9b544b..276f7eac2a 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -102,6 +102,10 @@ namespace vpr { const float td_place_exp_last = 8.0; const float timing_tradeoff = 0.5; } placer_opts; + struct SetupTimingInfo { + void update() { update_budget(npnr_ctx); } + }; + static SetupTimingInfo timing_info; // timing_util.cpp float calculate_clb_net_pin_criticality(/*timing_info, pin_lookup,*/ const PortRef& load, const NetInfo* net) diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index 317c5b629d..191d86675a 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -312,25 +312,25 @@ static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, int num_connections, float crit_exponent, float bb_cost, float * place_delay_value, float * timing_cost, float * delay_cost, int * outer_crit_iter_count, float * inverse_prev_timing_cost, - float * inverse_prev_bb_cost/*, - const ClusteredPinAtomPinsLookup& netlist_pin_lookup, + float * inverse_prev_bb_cost, + /*const ClusteredPinAtomPinsLookup& netlist_pin_lookup,*/ #ifdef ENABLE_CLASSIC_VPR_STA t_slack* slacks, t_timing_inf timing_inf, #endif - SetupTimingInfo& timing_info*/); + SetupTimingInfo& timing_info); static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, float crit_exponent, int inner_recompute_limit, t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, - float * delay_cost/*, + float * delay_cost, #ifdef ENABLE_CLASSIC_VPR_STA t_slack* slacks, t_timing_inf timing_inf, #endif - const ClusteredPinAtomPinsLookup& netlist_pin_lookup, - SetupTimingInfo& timing_info*/); + /*const ClusteredPinAtomPinsLookup& netlist_pin_lookup,*/ + SetupTimingInfo& timing_info); /*****************************************************************************/ void try_place(t_placer_opts placer_opts, @@ -588,23 +588,23 @@ void try_place(t_placer_opts placer_opts, outer_loop_recompute_criticalities(placer_opts, num_connections, crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, - &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost/*, - netlist_pin_lookup, + &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, + /*netlist_pin_lookup,*/ #ifdef ENABLE_CLASSIC_VPR_STA slacks, timing_inf, #endif - *timing_info*/); + /***/timing_info); placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, move_lim, crit_exponent, inner_recompute_limit, &stats, - &cost, &bb_cost, &timing_cost, &delay_cost/*, + &cost, &bb_cost, &timing_cost, &delay_cost, #ifdef ENABLE_CLASSIC_VPR_STA slacks, timing_inf, #endif - netlist_pin_lookup, - *timing_info*/); + /*netlist_pin_lookup,*/ + /***/timing_info); /* Lines below prevent too much round-off error from accumulating * * in the cost over many iterations. This round-off can lead to * @@ -713,13 +713,13 @@ void try_place(t_placer_opts placer_opts, outer_loop_recompute_criticalities(placer_opts, num_connections, crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, - &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost /*, - netlist_pin_lookup, + &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, + /*netlist_pin_lookup,*/ #ifdef ENABLE_CLASSIC_VPR_STA slacks, timing_inf, #endif - *timing_info*/); + /***/timing_info); t = 0; /* freeze out */ @@ -727,13 +727,13 @@ void try_place(t_placer_opts placer_opts, * which reduce the cost of the placement */ placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, move_lim, crit_exponent, inner_recompute_limit, &stats, - &cost, &bb_cost, &timing_cost, &delay_cost /*, + &cost, &bb_cost, &timing_cost, &delay_cost, #ifdef ENABLE_CLASSIC_VPR_STA slacks, timing_inf, #endif - netlist_pin_lookup, - *timing_info*/); + /*netlist_pin_lookup,*/ + /***/timing_info); tot_iter += move_lim; success_rat = ((float) stats.success_sum) / move_lim; @@ -884,13 +884,13 @@ static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, int num_connections, float crit_exponent, float bb_cost, float * place_delay_value, float * timing_cost, float * delay_cost, int * outer_crit_iter_count, float * inverse_prev_timing_cost, - float * inverse_prev_bb_cost/*, - const ClusteredPinAtomPinsLookup& netlist_pin_lookup, + float * inverse_prev_bb_cost, + /*const ClusteredPinAtomPinsLookup& netlist_pin_lookup,*/ #ifdef ENABLE_CLASSIC_VPR_STA t_slack* slacks, t_timing_inf timing_inf, #endif - SetupTimingInfo& timing_info*/) { + SetupTimingInfo& timing_info) { // if (placer_opts.place_algorithm != PATH_TIMING_DRIVEN_PLACE) if (!placer_opts.enable_timing_computations) @@ -908,8 +908,8 @@ static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, *place_delay_value = (*delay_cost) / num_connections; -// //Per-temperature timing update -// timing_info.update(); + //Per-temperature timing update + timing_info.update(); load_criticalities(/*timing_info,*/ crit_exponent /*, netlist_pin_lookup*/); #ifdef ENABLE_CLASSIC_VPR_STA @@ -935,13 +935,13 @@ static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, float inverse_prev_bb_cost, float inverse_prev_timing_cost, int move_lim, float crit_exponent, int inner_recompute_limit, t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, - float * delay_cost /*, + float * delay_cost, #ifdef ENABLE_CLASSIC_VPR_STA t_slack* slacks, t_timing_inf timing_inf, #endif - const ClusteredPinAtomPinsLookup& netlist_pin_lookup, - SetupTimingInfo& timing_info*/) { + /*const ClusteredPinAtomPinsLookup& netlist_pin_lookup,*/ + SetupTimingInfo& timing_info) { int inner_crit_iter_count, inner_iter; @@ -995,7 +995,7 @@ static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, * criticalities; then update the timing cost since it will change. */ //Inner loop timing update -// timing_info.update(); + timing_info.update(); load_criticalities(/*timing_info,*/ crit_exponent /*, netlist_pin_lookup*/); #ifdef ENABLE_CLASSIC_VPR_STA From ca12ec770abd93e9a3ebc3a98c89d4cf458f15dc Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 10:36:08 -0700 Subject: [PATCH 064/116] WIP for VPR macro support --- common/placer_vpr.cc | 61 ++++++++++++++++++++++++++++++++++++++++ common/vpr_place.cpp.inc | 13 ++++----- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 276f7eac2a..4f865442ec 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -106,6 +106,14 @@ namespace vpr { void update() { update_budget(npnr_ctx); } }; static SetupTimingInfo timing_info; + struct t_pl_macro_member { + t_pl_macro_member(CellInfo* blk_index, int x_offset, int y_offset, int z_offset) : blk_index(blk_index), x_offset(x_offset), y_offset(y_offset), z_offset(z_offset) {} + CellInfo* blk_index; + int x_offset; + int y_offset; + int z_offset; + }; + typedef std::vector t_pl_macro; // timing_util.cpp float calculate_clb_net_pin_criticality(/*timing_info, pin_lookup,*/ const PortRef& load, const NetInfo* net) @@ -139,6 +147,59 @@ namespace vpr { return 1; #endif } + + // place_macro.cpp + int alloc_and_load_placement_macros(/*t_direct_inf* directs, int num_directs, t_pl_macro ** */ std::vector ¯os) + { + auto id_lc = npnr_ctx->id("ICESTORM_LC"); + auto id_cin = npnr_ctx->id("CIN"); + auto id_cout = npnr_ctx->id("COUT"); + + for (auto &cell_entry : npnr_ctx->cells) { + auto cell = cell_entry.second.get(); + if (cell->type != id_lc) continue; + + auto it = cell->ports.find(id_cin); + if (it == cell->ports.end()) continue; + auto net = it->second.net; + it = cell->ports.find(id_cout); + if (net) { + // If CIN is driven, check if it's by COUT + auto driver = net->driver; + if (driver.port == id_cout) break; + } + // Check that COUT is driven + if (it == cell->ports.end()) continue; + net = it->second.net; + + t_pl_macro entry; + entry.emplace_back(cell, 0, 0, 0); + + while (net) { + NPNR_ASSERT(net->users.size() > 0); + CellInfo* sink_cell = nullptr; + for (const auto& load : net->users) { + if (sink_cell && load.cell != sink_cell) { + sink_cell = nullptr; + break; + } + sink_cell = load.cell; + } + if (!sink_cell) break; + + entry.emplace_back(sink_cell, 0, entry.size(), 0); + auto it = sink_cell->ports.find(id_cout); + if (it == sink_cell->ports.end()) break; + net = it->second.net; + }; + + if (!entry.empty()) { + log_info("Cell %s is a carry with open CIN and %d dependents\n", cell->name.c_str(npnr_ctx), entry.size()); + macros.emplace_back(std::move(entry)); + } + } + return macros.size(); + } #define VTR_ASSERT NPNR_ASSERT #define VTR_ASSERT_SAFE NPNR_ASSERT diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index 191d86675a..34cb5eacdb 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -54,7 +54,7 @@ ///* The maximum number of tries when trying to place a carry chain at a * // * random location before trying exhaustive placement - find the fist * // * legal position and place it during initial placement. */ -//#define MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY 4 +#define MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY 4 /* Flags for the states of the bounding box. * * Stored as char for memory efficiency. */ @@ -159,8 +159,7 @@ static std::vector ts_nets_to_update; ///* The pl_macros array stores all the carry chains placement macros. * // * [0...num_pl_macros-1] */ -//static t_pl_macro * pl_macros = nullptr; -//static int num_pl_macros; +static std::vector pl_macros; /* These file-scoped variables keep track of the number of swaps * * rejected, accepted or aborted. The total number of swap attempts * @@ -211,7 +210,7 @@ static void load_legal_placements(); static int try_place_macro(/*int itype, int ipos, int imacro*/ CellInfo* cell, const std::string& loc_name); -static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_locations*/); +static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std::vector> &free_locations); static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type pad_loc_type*/ std::vector> &free_locations); @@ -2286,7 +2285,7 @@ static void alloc_and_load_placement_structs( alloc_and_load_try_swap_structs(); -// num_pl_macros = alloc_and_load_placement_macros(directs, num_directs, &pl_macros); + /*num_pl_macros =*/ alloc_and_load_placement_macros(/*directs, num_directs, &*/ pl_macros); } ///* Allocates and loads net_pin_indices array, this array allows us to quickly * @@ -2943,7 +2942,7 @@ static int try_place_macro(/*int itype, int ipos, int imacro*/ return true; } -static void initial_placement_pl_macros(/*int macros_max_num_tries, int * free_locations*/) { +static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std::vector> &free_locations) { // int macro_placed; // int imacro, itype, itry, ipos; @@ -3168,7 +3167,7 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // place_ctx.block_locs[blk_id].z = OPEN; // } - initial_placement_pl_macros(/*MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY, free_locations*/); + initial_placement_pl_macros(MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY, free_locations); // // All the macros are placed, update the legal_pos[][] array // for (itype = 0; itype < device_ctx.num_block_types; itype++) { From 69c69b10009d7f6de1551b665e43c2ef04ea95eb Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 10:40:44 -0700 Subject: [PATCH 065/116] Cleanup initial_placement() --- common/vpr_place.cpp.inc | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index 34cb5eacdb..a39faef8e4 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -3169,39 +3169,28 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, initial_placement_pl_macros(MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY, free_locations); -// // All the macros are placed, update the legal_pos[][] array -// for (itype = 0; itype < device_ctx.num_block_types; itype++) { -// VTR_ASSERT(free_locations[itype] >= 0); -// for (ipos = 0; ipos < free_locations[itype]; ipos++) { -// x = legal_pos[itype][ipos].x; -// y = legal_pos[itype][ipos].y; -// z = legal_pos[itype][ipos].z; -// -// // Check if that location is occupied. If it is, remove from legal_pos -// if (place_ctx.grid_blocks[x][y].blocks[z] != EMPTY_BLOCK_ID && place_ctx.grid_blocks[x][y].blocks[z] != INVALID_BLOCK_ID) { -// legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; -// free_locations[itype]--; -// -// // After the move, I need to check this particular entry again -// ipos--; -// continue; -// } -// } -// } // Finish updating the legal_pos[][] and free_locations[] array - + // All the macros are placed, update the legal_pos[][] array for (auto it = free_locations.begin(); it != free_locations.end(); it++) { it->erase(remove_if(it->begin(), it->end(), [](BelId bel) { auto cell_name = npnr_ctx->getBoundBelCell(bel); return cell_name != IdString(); }), it->end()); } + for (auto it = legal_locations.begin(); it != legal_locations.end(); it++) { + it->erase(remove_if(it->begin(), it->end(), [](BelId bel) { + auto cell_name = npnr_ctx->getBoundBelCell(bel); + return cell_name != IdString(); + }), it->end()); + } initial_placement_blocks(free_locations); + // All constraints (including user pads) are placed during + // initial_placement_pl_macros() above // if (pad_loc_type == USER) { // read_user_pad_loc(pad_loc_file); // } -// + // /* Restore legal_pos */ // load_legal_placements(); // From 1a7e8dc1e6b670cacb81d8f5314d74d64f104df3 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 12:22:01 -0700 Subject: [PATCH 066/116] WIP for initial macro placement --- common/placer_vpr.cc | 28 ++- common/vpr_place.cpp.inc | 365 +++++++++++++++++---------------------- 2 files changed, 185 insertions(+), 208 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 4f865442ec..c819f43a13 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -83,6 +83,8 @@ namespace vpr { throw; } std::unordered_map>& blocks() const { return npnr_ctx->cells; } + IdString block_type(const CellInfo* cell) const { return cell->type; } + std::string block_name(const CellInfo* cell) const { return cell->name.str(npnr_ctx); } } clb_nlist; t_clustering& operator()() { return *this; } } clustering; @@ -149,7 +151,7 @@ namespace vpr { } // place_macro.cpp - int alloc_and_load_placement_macros(/*t_direct_inf* directs, int num_directs, t_pl_macro ** */ std::vector ¯os) + int alloc_and_load_placement_macros(/*t_direct_inf* directs, int num_directs, t_pl_macro ** */ std::unordered_map ¯os) { auto id_lc = npnr_ctx->id("ICESTORM_LC"); auto id_cin = npnr_ctx->id("CIN"); @@ -195,7 +197,7 @@ namespace vpr { if (!entry.empty()) { log_info("Cell %s is a carry with open CIN and %d dependents\n", cell->name.c_str(npnr_ctx), entry.size()); - macros.emplace_back(std::move(entry)); + macros.emplace(cell, std::move(entry)); } } return macros.size(); @@ -229,6 +231,8 @@ namespace vpr { inline void printf(const char* fmt, Args... args) { log_info(fmt, std::forward(args)...); } + + int irand(int imax) { return npnr_ctx->rng(imax+1); } } #include "vpr_types.h" @@ -267,6 +271,26 @@ class VPRPlacer vpr::npnr_cells.push_back(cell.second.get()); } ci->udata = cell_idx++; + + auto loc = ci->attrs.find(ctx->id("BEL")); + if (loc != ci->attrs.end()) { + const std::string& loc_name = loc->second; + auto bel = ctx->getBelByName(ctx->id(loc_name)); + if (bel == BelId()) { + log_error("No Bel named \'%s\' located for " + "this chip (processing BEL attribute on \'%s\')\n", + loc_name.c_str(), ci->name.c_str(ctx)); + } + + auto bel_type = ctx->getBelType(bel); + if (bel_type != ctx->belTypeFromId(ci->type)) { + log_error("Bel \'%s\' of type \'%s\' does not match cell " + "\'%s\' of type \'%s\'", + loc_name.c_str(), ctx->belTypeToId(bel_type).c_str(ctx), ci->name.c_str(ctx), + ci->type.c_str(ctx)); + } + ctx->bindBel(bel, ci->name, STRENGTH_USER); + } } int32_t net_idx = 0; for (auto &net : ctx->nets) { diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index a39faef8e4..94908da22e 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -118,8 +118,8 @@ static std::vector bb_updated_before; static std::vector point_to_point_timing_cost; static std::vector temp_point_to_point_timing_cost; -///* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1]. What is the value of the delay */ -///* for each connection in the circuit */ +/* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1]. What is the value of the delay */ +/* for each connection in the circuit */ static std::vector point_to_point_delay_cost; static std::vector temp_point_to_point_delay_cost; @@ -151,15 +151,15 @@ static std::vector> blocks_affected; // */ //static float** chanx_place_cost_fac; //[0...device_ctx.grid.width()-2] //static float** chany_place_cost_fac; //[0...device_ctx.grid.height()-2] -// -///* The following arrays are used by the try_swap function for speed. */ + +/* The following arrays are used by the try_swap function for speed. */ ///* [0...cluster_ctx.clb_nlist.nets().size()-1] */ static std::vector ts_bb_coord_new, ts_bb_edge_new; static std::vector ts_nets_to_update; -///* The pl_macros array stores all the carry chains placement macros. * -// * [0...num_pl_macros-1] */ -static std::vector pl_macros; +/* The pl_macros array stores all the carry chains placement macros. * + * [0...num_pl_macros-1] */ +static std::unordered_map pl_macros; /* These file-scoped variables keep track of the number of swaps * * rejected, accepted or aborted. The total number of swap attempts * @@ -202,13 +202,12 @@ static void free_placement_structs(t_placer_opts placer_opts); // //static void alloc_legal_placements(); static void load_legal_placements(); -// + //static void free_legal_placements(); -// -//static int check_macro_can_be_placed(int imacro, int itype, int x, int y, int z); -static int try_place_macro(/*int itype, int ipos, int imacro*/ - CellInfo* cell, const std::string& loc_name); +static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, int y, int z); + +static int try_place_macro(int itype, int ipos, /*int*/ CellInfo* macro); static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std::vector> &free_locations); @@ -2832,190 +2831,171 @@ static void load_legal_placements() { // free(legal_pos); /* Free the mapping list */ // free(num_legal_pos); //} -// -// -// -//static int check_macro_can_be_placed(int imacro, int itype, int x, int y, int z) { -// -// int imember; -// size_t member_x, member_y, member_z; -// -// auto& device_ctx = g_vpr_ctx.device(); + + + +static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, int y, int z) { + + int imember; + size_t member_x, member_y, member_z; + + auto& device_ctx = g_vpr_ctx.device(); // auto& place_ctx = g_vpr_ctx.placement(); -// -// // Every macro can be placed until proven otherwise -// int macro_can_be_placed = true; -// -// // Check whether all the members can be placed -// for (imember = 0; imember < pl_macros[imacro].num_blocks; imember++) { -// member_x = x + pl_macros[imacro].members[imember].x_offset; -// member_y = y + pl_macros[imacro].members[imember].y_offset; -// member_z = z + pl_macros[imacro].members[imember].z_offset; -// -// // Check whether the location could accept block of this type -// // Then check whether the location could still accomodate more blocks -// // Also check whether the member position is valid, that is the member's location -// // still within the chip's dimemsion and the member_z is allowed at that location on the grid -// if (member_x < device_ctx.grid.width() && member_y < device_ctx.grid.height() -// && device_ctx.grid[member_x][member_y].type->index == itype -// && place_ctx.grid_blocks[member_x][member_y].blocks[member_z] == EMPTY_BLOCK_ID) { -// // Can still accomodate blocks here, check the next position -// continue; -// } else { -// // Cant be placed here - skip to the next try -// macro_can_be_placed = false; -// break; -// } -// } -// -// return (macro_can_be_placed); -//} + // Every macro can be placed until proven otherwise + int macro_can_be_placed = true; + + // Check whether all the members can be placed + for (imember = 0; imember < pl_macros[imacro].size(); imember++) { + member_x = x + pl_macros[imacro]/*.members*/[imember].x_offset; + member_y = y + pl_macros[imacro]/*.members*/[imember].y_offset; + member_z = z + pl_macros[imacro]/*.members*/[imember].z_offset; + + // Check whether the location could accept block of this type + // Then check whether the location could still accomodate more blocks + // Also check whether the member position is valid, that is the member's location + // still within the chip's dimemsion and the member_z is allowed at that location on the grid + if (member_x < device_ctx.grid.width() && member_y < device_ctx.grid.height() + && npnr_ctx->belTypeToId(npnr_ctx->getBelType(device_ctx.grid[member_x][member_y][member_z])).index == itype + && npnr_ctx->checkBelAvail(device_ctx.grid[member_x][member_y][member_z]) + && npnr_ctx->isValidBelForCell(pl_macros[imacro][imember].blk_index, device_ctx.grid[member_x][member_y][member_z])) { + // Can still accomodate blocks here, check the next position + continue; + } else { + // Cant be placed here - skip to the next try + macro_can_be_placed = false; + break; + } + } -static int try_place_macro(/*int itype, int ipos, int imacro*/ - CellInfo* cell, const std::string& loc_name) { + return (macro_can_be_placed); +} + + +static int try_place_macro(int itype, int ipos, /*int*/ CellInfo* imacro) { + + int x, y, z, member_x, member_y, member_z, imember; -// int x, y, z, member_x, member_y, member_z, imember; -// // auto& place_ctx = g_vpr_ctx.mutable_placement(); -// -// int macro_placed = false; -// -// // Choose a random position for the head -// x = legal_pos[itype][ipos].x; -// y = legal_pos[itype][ipos].y; -// z = legal_pos[itype][ipos].z; -// -// // If that location is occupied, do nothing. -// if (place_ctx.grid_blocks[x][y].blocks[z] != EMPTY_BLOCK_ID) { -// return (macro_placed); -// } -// -// int macro_can_be_placed = check_macro_can_be_placed(imacro, itype, x, y, z); -// -// if (macro_can_be_placed) { -// -// // Place down the macro -// macro_placed = true; -// for (imember = 0; imember < pl_macros[imacro].num_blocks; imember++) { -// -// member_x = x + pl_macros[imacro].members[imember].x_offset; -// member_y = y + pl_macros[imacro].members[imember].y_offset; -// member_z = z + pl_macros[imacro].members[imember].z_offset; -// -// ClusterBlockId iblk = pl_macros[imacro].members[imember].blk_index; -// place_ctx.block_locs[iblk].x = member_x; -// place_ctx.block_locs[iblk].y = member_y; -// place_ctx.block_locs[iblk].z = member_z; -// -// place_ctx.grid_blocks[member_x][member_y].blocks[member_z] = pl_macros[imacro].members[imember].blk_index; -// place_ctx.grid_blocks[member_x][member_y].usage++; -// -// // Could not ensure that the randomiser would not pick this location again -// // So, would have to do a lazy removal - whenever I come across a block that could not be placed, -// // go ahead and remove it from the legal_pos[][] array -// -// } // Finish placing all the members in the macro -// -// } // End of this choice of legal_pos -// -// return (macro_placed); + auto& device_ctx = g_vpr_ctx.device(); + auto& grid = device_ctx.grid; - auto bel = npnr_ctx->getBelByName(npnr_ctx->id(loc_name)); - if (bel == BelId()) { - log_error("No Bel named \'%s\' located for " - "this chip (processing BEL attribute on \'%s\')\n", - loc_name.c_str(), cell->name.c_str(npnr_ctx)); - } + int macro_placed = false; - auto bel_type = npnr_ctx->getBelType(bel); - if (bel_type != npnr_ctx->belTypeFromId(cell->type)) { - log_error("Bel \'%s\' of type \'%s\' does not match cell " - "\'%s\' of type \'%s\'", - loc_name.c_str(), npnr_ctx->belTypeToId(bel_type).c_str(npnr_ctx), cell->name.c_str(npnr_ctx), - cell->type.c_str(npnr_ctx)); - } + // Choose a random position for the head + auto bel = legal_pos[itype][ipos]; + bool gb; + npnr_ctx->estimatePosition(bel, x, y, gb); + z = 0; + + // If that location is occupied, do nothing. + if (!npnr_ctx->checkBelAvail(bel)) { + return (macro_placed); + } + + if (!npnr_ctx->isValidBelForCell(imacro, bel)) + return macro_placed; + + int macro_can_be_placed = check_macro_can_be_placed(imacro, itype, x, y, z); + + if (macro_can_be_placed) { + + // Place down the macro + macro_placed = true; + for (imember = 0; imember < pl_macros[imacro].size(); imember++) { + + member_x = x + pl_macros[imacro]/*.members*/[imember].x_offset; + member_y = y + pl_macros[imacro]/*.members*/[imember].y_offset; + member_z = z + pl_macros[imacro]/*.members*/[imember].z_offset; - npnr_ctx->bindBel(bel, cell->name, STRENGTH_USER); - //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel)); + auto iblk = pl_macros[imacro]/*.members*/[imember].blk_index; + auto bel = grid[member_x][member_y][member_z]; - return true; + npnr_ctx->bindBel(bel, imacro->name, STRENGTH_WEAK); + + // Could not ensure that the randomiser would not pick this location again + // So, would have to do a lazy removal - whenever I come across a block that could not be placed, + // go ahead and remove it from the legal_pos[][] array + + } // Finish placing all the members in the macro + + } // End of this choice of legal_pos + + return (macro_placed); } static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std::vector> &free_locations) { -// int macro_placed; -// int imacro, itype, itry, ipos; + int macro_placed; + int /*imacro,*/ itype, itry, ipos; // ClusterBlockId blk_id; -// -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// auto& device_ctx = g_vpr_ctx.device(); + + auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& device_ctx = g_vpr_ctx.device(); /* Macros are harder to place. Do them first */ // for (imacro = 0; imacro < num_pl_macros; imacro++) { - for (auto &cell_entry : npnr_ctx->cells) { - auto cell = cell_entry.second.get(); - -// // Every macro are not placed in the beginnning -// macro_placed = false; -// -// // Assume that all the blocks in the macro are of the same type -// blk_id = pl_macros[imacro].members[0].blk_index; -// itype = cluster_ctx.clb_nlist.block_type(blk_id)->index; -// if (free_locations[itype] < pl_macros[imacro].num_blocks) { -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "Initial placement failed.\n" -// "Could not place macro length %d with head block %s (#%zu); not enough free locations of type %s (#%d).\n" -// "VPR cannot auto-size for your circuit, please resize the FPGA manually.\n", -// pl_macros[imacro].num_blocks, cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id), device_ctx.block_types[itype].name, itype); -// } -// -// // Try to place the macro first, if can be placed - place them, otherwise try again -// for (itry = 0; itry < macros_max_num_tries && macro_placed == false; itry++) { -// -// // Choose a random position for the head -// ipos = vtr::irand(free_locations[itype] - 1); -// -// // Try to place the macro -// macro_placed = try_place_macro(itype, ipos, imacro); -// -// } // Finished all tries + for (auto &b : cluster_ctx.clb_nlist.blocks()) { + auto imacro = b.second.get(); - auto loc = cell->attrs.find(npnr_ctx->id("BEL")); - if (loc == cell->attrs.end()) - continue; + // If not part of macro, ignore + if (!pl_macros.count(imacro)) continue; - try_place_macro(cell, loc->second); + // Every macro are not placed in the beginnning + macro_placed = false; -// if (macro_placed == false){ -// // if a macro still could not be placed after macros_max_num_tries times, -// // go through the chip exhaustively to find a legal placement for the macro -// // place the macro on the first location that is legal -// // then set macro_placed = true; -// // if there are no legal positions, error out -// -// // Exhaustive placement of carry macros -// for (ipos = 0; ipos < free_locations[itype] && macro_placed == false; ipos++) { -// -// // Try to place the macro -// macro_placed = try_place_macro(itype, ipos, imacro); -// -// } // Exhausted all the legal placement position for this macro -// -// // If macro could not be placed after exhaustive placement, error out -// if (macro_placed == false) { -// // Error out -// vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, -// "Initial placement failed.\n" -// "Could not place macro length %d with head block %s (#%zu); not enough free locations of type %s (#%d).\n" -// "Please manually size the FPGA because VPR can't do this yet.\n", -// pl_macros[imacro].num_blocks, cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id), device_ctx.block_types[itype].name, itype); -// } -// -// } else { -// // This macro has been placed successfully, proceed to place the next macro -// continue; -// } + auto blk_id = imacro; + + // Assume that all the blocks in the macro are of the same type + itype = cluster_ctx.clb_nlist.block_type(blk_id).index; + if (free_locations[itype].size() < pl_macros[imacro].size()) { + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "Initial placement failed.\n" + "Could not place macro length %d with head block %s (#%zu); not enough free locations of type %s (#%d).\n" + "VPR cannot auto-size for your circuit, please resize the FPGA manually.\n", + pl_macros[imacro].size(), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id->udata), cluster_ctx.clb_nlist.block_type(blk_id).c_str(npnr_ctx), itype); + } + + // Try to place the macro first, if can be placed - place them, otherwise try again + for (itry = 0; itry < macros_max_num_tries && macro_placed == false; itry++) { + + // Choose a random position for the head + ipos = vtr::irand(free_locations[itype].size() - 1); + + // Try to place the macro + macro_placed = try_place_macro(itype, ipos, imacro); + + } // Finished all tries + + if (macro_placed == false){ + // if a macro still could not be placed after macros_max_num_tries times, + // go through the chip exhaustively to find a legal placement for the macro + // place the macro on the first location that is legal + // then set macro_placed = true; + // if there are no legal positions, error out + + // Exhaustive placement of carry macros + for (ipos = 0; ipos < free_locations[itype].size() && macro_placed == false; ipos++) { + + // Try to place the macro + macro_placed = try_place_macro(itype, ipos, imacro); + + } // Exhausted all the legal placement position for this macro + + // If macro could not be placed after exhaustive placement, error out + if (macro_placed == false) { + // Error out + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, + "Initial placement failed.\n" + "Could not place macro length %d with head block %s (#%zu); not enough free locations of type %s (#%d).\n" + "Please manually size the FPGA because VPR can't do this yet.\n", + pl_macros[imacro].size(), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id), imacro->type.c_str(npnr_ctx), itype); + } + + } else { + // This macro has been placed successfully, proceed to place the next macro + continue; + } } // Finish placing all the pl_macros successfully } @@ -3137,36 +3117,9 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // auto& device_ctx = g_vpr_ctx.device(); // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); -// -// free_locations = (int *) vtr::malloc(device_ctx.num_block_types * sizeof(int)); -// for (itype = 0; itype < device_ctx.num_block_types; itype++) { -// free_locations[itype] = num_legal_pos[itype]; -// } std::vector> free_locations(legal_pos.begin(), legal_pos.end()); -// /* We'll use the grid to record where everything goes. Initialize to the grid has no -// * blocks placed anywhere. -// */ -// for (size_t i = 0; i < device_ctx.grid.width(); i++) { -// for (size_t j = 0; j < device_ctx.grid.height(); j++) { -// place_ctx.grid_blocks[i][j].usage = 0; -// itype = device_ctx.grid[i][j].type->index; -// for (int k = 0; k < device_ctx.block_types[itype].capacity; k++) { -// if (place_ctx.grid_blocks[i][j].blocks[k] != INVALID_BLOCK_ID) { -// place_ctx.grid_blocks[i][j].blocks[k] = EMPTY_BLOCK_ID; -// } -// } -// } -// } -// -// /* Similarly, mark all blocks as not being placed yet. */ -// for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { -// place_ctx.block_locs[blk_id].x = OPEN; -// place_ctx.block_locs[blk_id].y = OPEN; -// place_ctx.block_locs[blk_id].z = OPEN; -// } - initial_placement_pl_macros(MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY, free_locations); // All the macros are placed, update the legal_pos[][] array @@ -3176,7 +3129,7 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, return cell_name != IdString(); }), it->end()); } - for (auto it = legal_locations.begin(); it != legal_locations.end(); it++) { + for (auto it = legal_pos.begin(); it != legal_pos.end(); it++) { it->erase(remove_if(it->begin(), it->end(), [](BelId bel) { auto cell_name = npnr_ctx->getBoundBelCell(bel); return cell_name != IdString(); @@ -3185,8 +3138,8 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, initial_placement_blocks(free_locations); - // All constraints (including user pads) are placed during - // initial_placement_pl_macros() above + // All constraints (including user pads) are placed + // before try_place() // if (pad_loc_type == USER) { // read_user_pad_loc(pad_loc_file); // } From 37d93325cc353e9eae295a53d0869dfcdfe00607 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 13:19:17 -0700 Subject: [PATCH 067/116] Move from estimatePosition() to getBelLocation() --- common/placer_vpr.cc | 18 +++++----- common/vpr_place.cpp.inc | 75 ++++++++++++++++++++-------------------- 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index c819f43a13..5051ad69d2 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -251,15 +251,15 @@ class VPRPlacer int max_y = 0; auto &grid = vpr::g_vpr_ctx.device.grid; for (auto bel : ctx->getBels()) { - int x, y; - bool gb; - ctx->estimatePosition(bel, x, y, gb); - if (x >= int(grid._bels.size())) - grid._bels.resize(x+1); - max_y = std::max(y, max_y); - if (max_y >= int(grid._bels[x].size())) - grid._bels[x].resize(max_y+1); - grid._bels[x][y].push_back(bel); + auto loc = ctx->getBelLocation(bel); + if (loc.x >= int(grid._bels.size())) + grid._bels.resize(loc.x+1); + max_y = std::max(loc.y, max_y); + if (max_y >= int(grid._bels[loc.x].size())) + grid._bels[loc.x].resize(max_y+1); + if (loc.z >= int(grid._bels[loc.x][loc.y].size())) + grid._bels[loc.x][loc.y].resize(loc.z+1); + grid._bels[loc.x][loc.y][loc.z] = bel; } for (auto& c : grid._bels) c.resize(max_y+1); diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index 94908da22e..b33817e395 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -1389,12 +1389,11 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin return ABORTED; //No movable block found } - int x_from /*= place_ctx.block_locs[b_from].x*/; - int y_from /*= place_ctx.block_locs[b_from].y*/; -// int z_from = place_ctx.block_locs[b_from].z; + auto loc = npnr_ctx->getBelLocation(b_from->bel); - bool gb; - npnr_ctx->estimatePosition(b_from->bel, x_from, y_from, gb); + int x_from = loc.x /*place_ctx.block_locs[b_from].x*/; + int y_from = loc.y /*place_ctx.block_locs[b_from].y*/; + int z_from = loc.z /*place_ctx.block_locs[b_from].z*/; // int x_to = OPEN; // int y_to = OPEN; @@ -1697,19 +1696,16 @@ static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const // int pin_width_offset = blk_type->pin_width_offset[iblk_pin]; // int pin_height_offset = blk_type->pin_height_offset[iblk_pin]; - int xold, yold; - int xnew, ynew; - bool gb; - npnr_ctx->estimatePosition(bel_from, xold, yold, gb); - npnr_ctx->estimatePosition(blk->bel, xnew, ynew, gb); + auto loc_old = npnr_ctx->getBelLocation(bel_from); + auto loc_new = npnr_ctx->getBelLocation(blk->bel); //Incremental bounding box update update_bb(net, &ts_bb_coord_new[net->udata], &ts_bb_edge_new[net->udata], - /*blocks_affected.moved_blocks[iblk].xold + pin_width_offset*/ xold, - /*blocks_affected.moved_blocks[iblk].yold + pin_height_offset*/ yold, - /*blocks_affected.moved_blocks[iblk].xnew + pin_width_offset*/ xnew, - /*blocks_affected.moved_blocks[iblk].ynew + pin_height_offset*/ ynew); + loc_old.x /* + pin_width_offset*/, + loc_old.y /* + pin_height_offset*/, + loc_new.x /* + pin_width_offset*/, + loc_new.y /* + pin_height_offset*/); } } @@ -1817,8 +1813,9 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, continue; } - bool gb; - npnr_ctx->estimatePosition(bel_to, px_to, py_to, gb); + auto loc = npnr_ctx->getBelLocation(bel_to); + px_to = loc.x; + py_to = loc.y; if((x_from == px_to) && (y_from == py_to)) { is_legal = false; @@ -2352,13 +2349,11 @@ static void get_bb_from_scratch(/*ClusterNetId*/ NetInfo *net_id, t_bb *coords, auto& device_ctx = g_vpr_ctx.device(); auto& grid = device_ctx.grid; -// ClusterBlockId bnum = cluster_ctx.clb_nlist.net_driver_block(net_id); -// pnum = cluster_ctx.clb_nlist.net_pin_physical_index(net_id, 0); -// x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; -// y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; auto bnum = net_id->driver.cell; - bool gb; - npnr_ctx->estimatePosition(bnum->bel, x, y, gb); +// pnum = cluster_ctx.clb_nlist.net_pin_physical_index(net_id, 0); + auto loc = npnr_ctx->getBelLocation(bnum->bel); + x = loc.x /*+ cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]*/; + y = loc.y /*+ cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]*/; x = std::max(std::min(x, grid.width() - 2), 1); y = std::max(std::min(y, grid.height() - 2), 1); @@ -2375,9 +2370,9 @@ static void get_bb_from_scratch(/*ClusterNetId*/ NetInfo *net_id, t_bb *coords, for (auto pin_id : net_id->users) { bnum = pin_id.cell; //pnum = cluster_ctx.clb_nlist.pin_physical_index(pin_id); - //x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; - //y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; - npnr_ctx->estimatePosition(bnum->bel, x, y, gb); + auto loc = npnr_ctx->getBelLocation(bnum->bel); + x = loc.x /*+ cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]*/; + y = loc.y /*+ cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]*/; /* Code below counts IO blocks as being within the 1..grid.width()-2, 1..grid.height()-2 clb array. * * This is because channels do not go out of the 0..grid.width()-2, 0..grid.height()-2 range, and * @@ -2522,10 +2517,10 @@ static void get_non_updateable_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coo auto bnum = net_id->driver.cell; // pnum = cluster_ctx.clb_nlist.net_pin_physical_index(net_id, 0); -// x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; -// y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; - bool gb; - npnr_ctx->estimatePosition(bnum->bel, x, y, gb); + auto loc = npnr_ctx->getBelLocation(bnum->bel); + x = loc.x /*+ cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]*/; + y = loc.y /*+ cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]*/; + xmin = x; ymin = y; @@ -2535,9 +2530,9 @@ static void get_non_updateable_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coo for (auto pin_id : net_id->users) { bnum = pin_id.cell; //pnum = cluster_ctx.clb_nlist.pin_physical_index(pin_id); - //x = place_ctx.block_locs[bnum].x + cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]; - //y = place_ctx.block_locs[bnum].y + cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]; - npnr_ctx->estimatePosition(bnum->bel, x, y, gb); + auto loc = npnr_ctx->getBelLocation(bnum->bel); + x = loc.x /*+ cluster_ctx.clb_nlist.block_type(bnum)->pin_width_offset[pnum]*/; + y = loc.y /*+ cluster_ctx.clb_nlist.block_type(bnum)->pin_height_offset[pnum]*/; if (x < xmin) { xmin = x; @@ -2851,11 +2846,16 @@ static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, member_y = y + pl_macros[imacro]/*.members*/[imember].y_offset; member_z = z + pl_macros[imacro]/*.members*/[imember].z_offset; + volatile auto mx = member_x; + volatile auto my = member_y; + volatile auto mz = member_z; + // Check whether the location could accept block of this type // Then check whether the location could still accomodate more blocks // Also check whether the member position is valid, that is the member's location // still within the chip's dimemsion and the member_z is allowed at that location on the grid - if (member_x < device_ctx.grid.width() && member_y < device_ctx.grid.height() + if (member_x < device_ctx.grid.width() && member_y < device_ctx.grid.height() + && member_z < device_ctx.grid[member_x][member_y].size() && npnr_ctx->belTypeToId(npnr_ctx->getBelType(device_ctx.grid[member_x][member_y][member_z])).index == itype && npnr_ctx->checkBelAvail(device_ctx.grid[member_x][member_y][member_z]) && npnr_ctx->isValidBelForCell(pl_macros[imacro][imember].blk_index, device_ctx.grid[member_x][member_y][member_z])) { @@ -2884,9 +2884,10 @@ static int try_place_macro(int itype, int ipos, /*int*/ CellInfo* imacro) { // Choose a random position for the head auto bel = legal_pos[itype][ipos]; - bool gb; - npnr_ctx->estimatePosition(bel, x, y, gb); - z = 0; + auto loc = npnr_ctx->getBelLocation(bel); + x = loc.x; + y = loc.y; + z = loc.z; // If that location is occupied, do nothing. if (!npnr_ctx->checkBelAvail(bel)) { @@ -2931,7 +2932,7 @@ static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std // ClusterBlockId blk_id; auto& cluster_ctx = g_vpr_ctx.clustering(); - auto& device_ctx = g_vpr_ctx.device(); +// auto& device_ctx = g_vpr_ctx.device(); /* Macros are harder to place. Do them first */ // for (imacro = 0; imacro < num_pl_macros; imacro++) { From 1ca7c941c333285b8b110962cc1da9778867b318 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 13:49:19 -0700 Subject: [PATCH 068/116] Set z_offset for carry chains --- common/placer_vpr.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 5051ad69d2..5829272122 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -189,7 +189,7 @@ namespace vpr { } if (!sink_cell) break; - entry.emplace_back(sink_cell, 0, entry.size(), 0); + entry.emplace_back(sink_cell, 0, entry.size() / 8, entry.size() % 8); auto it = sink_cell->ports.find(id_cout); if (it == sink_cell->ports.end()) break; net = it->second.net; From dd1aba45bc8389b8422f93ffba7c12b8ef6ed5a6 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 13:49:39 -0700 Subject: [PATCH 069/116] free_locations for macro placement is z == 0 --- common/vpr_place.cpp.inc | 52 ++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index b33817e395..e5ce0d588c 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -207,7 +207,7 @@ static void load_legal_placements(); static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, int y, int z); -static int try_place_macro(int itype, int ipos, /*int*/ CellInfo* macro); +static int try_place_macro(int itype, /*int*/ BelId ipos, /*int*/ CellInfo* macro); static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std::vector> &free_locations); @@ -2846,10 +2846,6 @@ static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, member_y = y + pl_macros[imacro]/*.members*/[imember].y_offset; member_z = z + pl_macros[imacro]/*.members*/[imember].z_offset; - volatile auto mx = member_x; - volatile auto my = member_y; - volatile auto mz = member_z; - // Check whether the location could accept block of this type // Then check whether the location could still accomodate more blocks // Also check whether the member position is valid, that is the member's location @@ -2872,7 +2868,7 @@ static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, } -static int try_place_macro(int itype, int ipos, /*int*/ CellInfo* imacro) { +static int try_place_macro(int itype, /*int*/ BelId ipos, /*int*/ CellInfo* imacro) { int x, y, z, member_x, member_y, member_z, imember; @@ -2883,18 +2879,17 @@ static int try_place_macro(int itype, int ipos, /*int*/ CellInfo* imacro) { int macro_placed = false; // Choose a random position for the head - auto bel = legal_pos[itype][ipos]; - auto loc = npnr_ctx->getBelLocation(bel); + auto loc = npnr_ctx->getBelLocation(ipos); x = loc.x; y = loc.y; z = loc.z; // If that location is occupied, do nothing. - if (!npnr_ctx->checkBelAvail(bel)) { + if (!npnr_ctx->checkBelAvail(ipos)) { return (macro_placed); } - if (!npnr_ctx->isValidBelForCell(imacro, bel)) + if (!npnr_ctx->isValidBelForCell(imacro, ipos)) return macro_placed; int macro_can_be_placed = check_macro_can_be_placed(imacro, itype, x, y, z); @@ -2912,7 +2907,7 @@ static int try_place_macro(int itype, int ipos, /*int*/ CellInfo* imacro) { auto iblk = pl_macros[imacro]/*.members*/[imember].blk_index; auto bel = grid[member_x][member_y][member_z]; - npnr_ctx->bindBel(bel, imacro->name, STRENGTH_WEAK); + npnr_ctx->bindBel(bel, iblk->name, STRENGTH_WEAK); // Could not ensure that the randomiser would not pick this location again // So, would have to do a lazy removal - whenever I come across a block that could not be placed, @@ -2928,8 +2923,9 @@ static int try_place_macro(int itype, int ipos, /*int*/ CellInfo* imacro) { static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std::vector> &free_locations) { int macro_placed; - int /*imacro,*/ itype, itry, ipos; + int /*imacro,*/ itype, itry /*, ipos*/; // ClusterBlockId blk_id; + BelId ipos; auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& device_ctx = g_vpr_ctx.device(); @@ -2961,7 +2957,7 @@ static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std for (itry = 0; itry < macros_max_num_tries && macro_placed == false; itry++) { // Choose a random position for the head - ipos = vtr::irand(free_locations[itype].size() - 1); + ipos = free_locations[itype][vtr::irand(free_locations[itype].size() - 1)]; // Try to place the macro macro_placed = try_place_macro(itype, ipos, imacro); @@ -2976,11 +2972,13 @@ static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std // if there are no legal positions, error out // Exhaustive placement of carry macros - for (ipos = 0; ipos < free_locations[itype].size() && macro_placed == false; ipos++) { + for (ipos : free_locations[itype]) { // Try to place the macro macro_placed = try_place_macro(itype, ipos, imacro); + if (macro_placed == true) break; + } // Exhausted all the legal placement position for this macro // If macro could not be placed after exhaustive placement, error out @@ -3115,27 +3113,39 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // * That is, this stores the number of entries in legal_pos[itype] that are worth considering // * as you look for a free location. // */ + std::vector> free_locations; // auto& device_ctx = g_vpr_ctx.device(); // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); - std::vector> free_locations(legal_pos.begin(), legal_pos.end()); + free_locations.resize(legal_pos.size()); + + // HACK HACK HACK + // Initially, populate free_locations with just z == 0 cells + // for carry chain placement + for (auto it = legal_pos.begin(); it != legal_pos.end(); it++) { + for (auto jt = it->begin(); jt != it->end(); jt++) { + auto loc = npnr_ctx->getBelLocation(*jt); + if (loc.z == 0) + free_locations[it - legal_pos.begin()].push_back(*jt); + } + } initial_placement_pl_macros(MAX_NUM_TRIES_TO_PLACE_MACROS_RANDOMLY, free_locations); // All the macros are placed, update the legal_pos[][] array - for (auto it = free_locations.begin(); it != free_locations.end(); it++) { - it->erase(remove_if(it->begin(), it->end(), [](BelId bel) { - auto cell_name = npnr_ctx->getBoundBelCell(bel); - return cell_name != IdString(); - }), it->end()); - } for (auto it = legal_pos.begin(); it != legal_pos.end(); it++) { it->erase(remove_if(it->begin(), it->end(), [](BelId bel) { auto cell_name = npnr_ctx->getBoundBelCell(bel); return cell_name != IdString(); }), it->end()); } + for (auto it = free_locations.begin(); it != free_locations.end(); it++) { + it->clear(); + const auto& src = legal_pos[it - free_locations.begin()]; + it->reserve(src.size()); + it->assign(src.begin(), src.end()); + } initial_placement_blocks(free_locations); From e29ae1dfc3c21fd8e97948999b172c6eda29f676 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 14:45:12 -0700 Subject: [PATCH 070/116] WIP --- common/placer_vpr.cc | 2 + common/vpr_place.cpp.inc | 146 +++++++++++++++++++-------------------- 2 files changed, 72 insertions(+), 76 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 5829272122..fdf7a49ce9 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -234,6 +234,8 @@ namespace vpr { int irand(int imax) { return npnr_ctx->rng(imax+1); } } + + #define OPEN -1 #include "vpr_types.h" #include "vpr_timing_place.cpp.inc" diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index e5ce0d588c..348655c3e4 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -225,9 +225,9 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, static float comp_bb_cost(e_cost_methods method); -static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, int y_to, int z_to*/ BelId bel_to); +static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, int x_to, int y_to, int z_to); -static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, int y_to, int z_to*/ BelId bel_to); +static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, int y_to, int z_to); static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, float rlim, @@ -276,12 +276,11 @@ static e_swap_result assess_swap(float delta_c, float t); static bool find_to(/*t_type_ptr type,*/ float rlim, int x_from, int y_from, - /*int *px_to, int *py_to, int *pz_to*/ - CellInfo* cell_from, BelId& bel_to); + int *px_to, int *py_to, int *pz_to, + CellInfo* cell_from); static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, int x_from, int y_from, - /*int *px_to, int *py_to, int *pz_to*/ - BelId &bel_to); + int *px_to, int *py_to, int *pz_to); static void get_non_updateable_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new); @@ -1181,7 +1180,7 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, } -static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, int y_to, int z_to*/ BelId bel_to) { +static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, int x_to, int y_to, int z_to) { /* Find all the blocks affected when b_from is swapped with b_to. * Returns abort_swap. */ @@ -1192,12 +1191,9 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to int abort_swap = false; // auto& place_ctx = g_vpr_ctx.mutable_placement(); -// -// x_from = place_ctx.block_locs[b_from].x; -// y_from = place_ctx.block_locs[b_from].y; -// z_from = place_ctx.block_locs[b_from].z; auto bel_from = b_from->bel; + auto bel_to = g_vpr_ctx.device().grid[x_to][y_to][z_to]; // b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; auto b_to_id = npnr_ctx->getBoundBelCell(bel_to); @@ -1289,43 +1285,48 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to } -static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, int y_to, int z_to*/ BelId bel_to) { +static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, int y_to, int z_to) { /* Finds and set ups the affected_blocks array. * Returns abort_swap. */ -// int imacro, imember; -// int x_swap_offset, y_swap_offset, z_swap_offset, x_from, y_from, z_from; -// ClusterBlockId curr_b_from; -// int curr_x_from, curr_y_from, curr_z_from, curr_x_to, curr_y_to, curr_z_to; + int /*imacro,*/ imember; + int x_swap_offset, y_swap_offset, z_swap_offset, x_from, y_from, z_from; + /*ClusterBlockId*/ CellInfo* curr_b_from; + int curr_x_from, curr_y_from, curr_z_from, curr_x_to, curr_y_to, curr_z_to; int abort_swap = false; // auto& place_ctx = g_vpr_ctx.placement(); -// auto& device_ctx = g_vpr_ctx.device(); -// auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& device_ctx = g_vpr_ctx.device(); + auto& cluster_ctx = g_vpr_ctx.clustering(); + + auto loc_from = npnr_ctx->getBelLocation(b_from->bel); + x_from = loc_from.x; + y_from = loc_from.y; + z_from = loc_from.z; + -// x_from = place_ctx.block_locs[b_from].x; -// y_from = place_ctx.block_locs[b_from].y; -// z_from = place_ctx.block_locs[b_from].z; -// // get_imacro_from_iblk(&imacro, b_from, pl_macros, num_pl_macros); // if ( imacro != -1) { + if (pl_macros.count(b_from)) { // // b_from is part of a macro, I need to swap the whole macro +// auto imacro = b_from; // // // Record down the relative position of the swap // x_swap_offset = x_to - x_from; // y_swap_offset = y_to - y_from; // z_swap_offset = z_to - z_from; // -// for (imember = 0; imember < pl_macros[imacro].num_blocks && abort_swap == false; imember++) { +// for (imember = 0; imember < pl_macros[imacro].size() && abort_swap == false; imember++) { // // // Gets the new from and to info for every block in the macro // // cannot use the old from and to info -// curr_b_from = pl_macros[imacro].members[imember].blk_index; +// curr_b_from = pl_macros[imacro]/*.members*/[imember].blk_index; // -// curr_x_from = place_ctx.block_locs[curr_b_from].x; -// curr_y_from = place_ctx.block_locs[curr_b_from].y; -// curr_z_from = place_ctx.block_locs[curr_b_from].z; +// auto loc_from = npnr_ctx->getBelLocation(curr_b_from->bel); +// curr_x_from = loc_from.x /*place_ctx.block_locs[curr_b_from].x*/; +// curr_y_from = loc_from.y /*place_ctx.block_locs[curr_b_from].y*/; +// curr_z_from = loc_from.z /*place_ctx.block_locs[curr_b_from].z*/; // // curr_x_to = curr_x_from + x_swap_offset; // curr_y_to = curr_y_from + y_swap_offset; @@ -1342,17 +1343,18 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, /*int x_to, // || curr_y_to < 1 || curr_y_to >= int(device_ctx.grid.height()) // || curr_z_to < 0 // || device_ctx.grid[curr_x_to][curr_y_to].type != cluster_ctx.clb_nlist.block_type(curr_b_from)) { -// abort_swap = true; + abort_swap = true; // } else { // abort_swap = setup_blocks_affected(curr_b_from, curr_x_to, curr_y_to, curr_z_to); // } // } // Finish going through all the blocks in the macro -// -// } else { + + } else { // This is not a macro - I could use the from and to info from before - abort_swap = setup_blocks_affected(b_from, /*x_to, y_to, z_to*/ bel_to); -// } // Finish handling cases for blocks in macro and otherwise + abort_swap = setup_blocks_affected(b_from, x_to, y_to, z_to); + + } // Finish handling cases for blocks in macro and otherwise return (abort_swap); @@ -1395,17 +1397,16 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin int y_from = loc.y /*place_ctx.block_locs[b_from].y*/; int z_from = loc.z /*place_ctx.block_locs[b_from].z*/; -// int x_to = OPEN; -// int y_to = OPEN; -// int z_to = OPEN; + int x_to = OPEN; + int y_to = OPEN; + int z_to = OPEN; // auto cluster_from_type = cluster_ctx.clb_nlist.block_type(b_from); // auto grid_from_type = g_vpr_ctx.device().grid[x_from][y_from].type; // VTR_ASSERT(cluster_from_type == grid_from_type); - BelId bel_to; - if (!find_to(/*cluster_ctx.clb_nlist.block_type(b_from),*/ rlim, x_from, y_from, /*&x_to, &y_to, &z_to*/ - b_from, bel_to)) + if (!find_to(/*cluster_ctx.clb_nlist.block_type(b_from),*/ rlim, x_from, y_from, &x_to, &y_to, &z_to, + b_from)) return REJECTED; #if 0 @@ -1430,7 +1431,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin // * of the blocks. Abort the swap if the to_block is part of a * // * macro (not supported yet). */ - bool abort_swap = find_affected_blocks(b_from, /*x_to, y_to, z_to*/ bel_to); + bool abort_swap = find_affected_blocks(b_from, x_to, y_to, z_to); if (abort_swap == false) { @@ -1751,8 +1752,8 @@ static void update_td_delta_costs(/*const ClusterNetId*/ NetInfo *net, const /*C static bool find_to(/*t_type_ptr type,*/ float rlim, int x_from, int y_from, - /*int *px_to, int *py_to, int *pz_to*/ - CellInfo* cell_from, BelId& bel_to) { + int *px_to, int *py_to, int *pz_to, + CellInfo* cell_from) { /* Returns the point to which I want to swap, properly range limited. * rlim must always be between 1 and device_ctx.grid.width() - 2 (inclusive) for this routine @@ -1764,6 +1765,7 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, int active_area; bool is_legal; // int itype; + BelId bel_to; auto& grid = g_vpr_ctx.device().grid; // auto& place_ctx = g_vpr_ctx.placement(); @@ -1791,8 +1793,6 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, num_tries = 0; // itype = type->index; - int px_to, py_to; - do { /* Until legal */ is_legal = true; @@ -1804,35 +1804,24 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, num_tries++; } - find_to_location(type, rlim, x_from, y_from/*, - px_to, py_to, pz_to*/, - bel_to); + find_to_location(type, rlim, x_from, y_from, + px_to, py_to, pz_to); - if (bel_to == BelId()) { + if (px_to < 0) { is_legal = false; continue; } + bel_to = grid[*px_to][*py_to][*pz_to]; - auto loc = npnr_ctx->getBelLocation(bel_to); - px_to = loc.x; - py_to = loc.y; - - if((x_from == px_to) && (y_from == py_to)) { + if((x_from == *px_to) && (y_from == *py_to)) { is_legal = false; - } else if(px_to > max_x || px_to < min_x || py_to > max_y || py_to < min_y) { + } else if(*px_to > max_x || *px_to < min_x || *py_to > max_y || *py_to < min_y) { is_legal = false; } else if(type != npnr_ctx->belTypeToId(npnr_ctx->getBelType(bel_to))) { is_legal = false; } else { /* Find z_to and test to validate that the "to" block is *not* fixed */ - //*pz_to = 0; - //if (grid[*px_to][*py_to].type->capacity > 1) { - // *pz_to = vtr::irand(grid[*px_to][*py_to].type->capacity - 1); - //} - //ClusterBlockId b_to = place_ctx.grid_blocks[*px_to][*py_to].blocks[*pz_to]; - //if ((b_to != EMPTY_BLOCK_ID) && (place_ctx.block_locs[b_to].is_fixed == true)) { - // is_legal = false; - //} + // *pz_to already set by find_to_location if (!npnr_ctx->isValidBelForCell(cell_from, bel_to)) { is_legal = false; @@ -1851,11 +1840,11 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, } } - VTR_ASSERT(px_to >= 0 && px_to < int(grid.width())); - VTR_ASSERT(py_to >= 0 && py_to < int(grid.height())); + VTR_ASSERT(*px_to >= 0 && *px_to < int(grid.width())); + VTR_ASSERT(*py_to >= 0 && *py_to < int(grid.height())); } while (is_legal == false); - if (px_to < 0 || px_to > int(grid.width() - 1) || py_to < 0 || py_to > int(grid.height() - 1)) { + if (*px_to < 0 || *px_to > int(grid.width() - 1) || *py_to < 0 || *py_to > int(grid.height() - 1)) { vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in routine find_to: (x_to,y_to) = (%d,%d)\n", px_to, py_to); } @@ -1864,9 +1853,8 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, } static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, - int x_from, int y_from/*, - int *px_to, int *py_to, int *pz_to*/, - BelId &bel_to) { + int x_from, int y_from, + int *px_to, int *py_to, int *pz_to) { auto& device_ctx = g_vpr_ctx.device(); auto& grid = device_ctx.grid; @@ -1883,21 +1871,27 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, int min_y = std::max(0, y_from - rly); int max_y = std::min(grid.height() - 1, y_from + rly); -// *pz_to = 0; + *pz_to = 0; if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || legal_pos[itype].size() < active_area) { int ipos = npnr_ctx->rng(legal_pos[itype].size()); - bel_to = legal_pos[itype][ipos]; + auto bel = legal_pos[itype][ipos]; + auto loc = npnr_ctx->getBelLocation(bel); + *px_to = loc.x; + *py_to = loc.y; + *pz_to = loc.z; } else { int x_rel = npnr_ctx->rng(std::max(0, max_x - min_x)+1); // +1 because rng() uses % int y_rel = npnr_ctx->rng(std::max(0, max_y - min_y)+1); // +1 because rng() uses % - int px_to = min_x + x_rel; - int py_to = min_y + y_rel; - if (!grid[px_to][py_to].empty()) { - int pz_to = npnr_ctx->rng(grid[px_to][py_to].size()); - bel_to = grid[px_to][py_to][pz_to]; + *px_to = min_x + x_rel; + *py_to = min_y + y_rel; + if (!grid[*px_to][*py_to].empty()) { + *pz_to = npnr_ctx->rng(grid[*px_to][*py_to].size()); + } + else { + *px_to = -1; + *py_to = -1; + *pz_to = -1; } - else - bel_to = BelId(); } } From 512a28fe7ce9e69ac4ecdfa48bd8fd83936f48ba Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 19:55:16 -0700 Subject: [PATCH 071/116] Shuffle free_locations, fix bugs, cleanup --- common/vpr_place.cpp.inc | 137 +++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 71 deletions(-) diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index 348655c3e4..e49a24bcc7 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -94,7 +94,7 @@ large enough to be on the order of timing costs for normal constraints. */ /* Cost of a net, and a temporary cost of a net used during move assessment. */ static std::vector net_cost, temp_net_cost; -static std::vector> legal_pos; +static std::vector> legal_pos; //static int *num_legal_pos = nullptr; /* [0..num_legal_pos-1] */ /* [0...cluster_ctx.clb_nlist.nets().size()-1] * @@ -207,18 +207,13 @@ static void load_legal_placements(); static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, int y, int z); -static int try_place_macro(int itype, /*int*/ BelId ipos, /*int*/ CellInfo* macro); +static int try_place_macro(int itype, /*int*/ Loc ipos, /*int*/ CellInfo* macro); -static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std::vector> &free_locations); +static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std::vector> &free_locations); -static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type pad_loc_type*/ - std::vector> &free_locations); - -static void initial_placement_location(/*int * free_locations, ClusterBlockId blk_id, - int *pipos, int *px, int *py, int *pz*/ - std::vector> &free_locations, - CellInfo *cell, - BelId& bel); +static void initial_placement_blocks(/*int * */ std::vector> &free_locations /*, enum e_pad_loc_type pad_loc_type*/); +static void initial_placement_location(/*int **/ std::vector> &free_locations, /*ClusterBlockId*/ CellInfo* blk_id, + /*int *pipos,*/ int *px, int *py, int *pz); static void initial_placement(/*enum e_pad_loc_type pad_loc_type, const char *pad_loc_file*/); @@ -291,8 +286,8 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_affected_nets); -static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin*/ - CellInfo* blk, BelId bel_from); +static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const ClusterBlockId*/ CellInfo *blk, /*const ClusterPinId blk_pin*/ + BelId bel_from); static void update_td_delta_costs(/*const ClusterNetId*/ NetInfo *net, const /*ClusterPinId*/ PortInfo &pin, float& delta_timing_cost, float& delta_delay_cost, CellInfo* blk); @@ -1807,11 +1802,15 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, find_to_location(type, rlim, x_from, y_from, px_to, py_to, pz_to); - if (px_to < 0) { + if (*px_to < 0) { is_legal = false; continue; } bel_to = grid[*px_to][*py_to][*pz_to]; + if (bel_to == BelId()) { + is_legal = false; + continue; + } if((x_from == *px_to) && (y_from == *py_to)) { is_legal = false; @@ -1845,7 +1844,7 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, } while (is_legal == false); if (*px_to < 0 || *px_to > int(grid.width() - 1) || *py_to < 0 || *py_to > int(grid.height() - 1)) { - vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in routine find_to: (x_to,y_to) = (%d,%d)\n", px_to, py_to); + vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__,"in routine find_to: (x_to,y_to) = (%d,%d)\n", *px_to, *py_to); } VTR_ASSERT(type == npnr_ctx->belTypeToId(npnr_ctx->getBelType(bel_to))); @@ -1874,11 +1873,9 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, *pz_to = 0; if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || legal_pos[itype].size() < active_area) { int ipos = npnr_ctx->rng(legal_pos[itype].size()); - auto bel = legal_pos[itype][ipos]; - auto loc = npnr_ctx->getBelLocation(bel); - *px_to = loc.x; - *py_to = loc.y; - *pz_to = loc.z; + *px_to = legal_pos[itype][ipos].x; + *py_to = legal_pos[itype][ipos].y; + *pz_to = legal_pos[itype][ipos].z; } else { int x_rel = npnr_ctx->rng(std::max(0, max_x - min_x)+1); // +1 because rng() uses % int y_rel = npnr_ctx->rng(std::max(0, max_y - min_y)+1); // +1 because rng() uses % @@ -2807,7 +2804,7 @@ static void load_legal_placements() { int itype = type.index; if (itype >= legal_pos.size()) legal_pos.resize(itype+1); - legal_pos[itype].push_back(bel); + legal_pos[itype].push_back(npnr_ctx->getBelLocation(bel)); } } @@ -2862,7 +2859,7 @@ static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, } -static int try_place_macro(int itype, /*int*/ BelId ipos, /*int*/ CellInfo* imacro) { +static int try_place_macro(int itype, /*int*/ Loc ipos, /*int*/ CellInfo* imacro) { int x, y, z, member_x, member_y, member_z, imember; @@ -2873,17 +2870,16 @@ static int try_place_macro(int itype, /*int*/ BelId ipos, /*int*/ CellInfo* imac int macro_placed = false; // Choose a random position for the head - auto loc = npnr_ctx->getBelLocation(ipos); - x = loc.x; - y = loc.y; - z = loc.z; + x = ipos.x; + y = ipos.y; + z = ipos.z; // If that location is occupied, do nothing. - if (!npnr_ctx->checkBelAvail(ipos)) { + if (!npnr_ctx->checkBelAvail(grid[x][y][z])) { return (macro_placed); } - if (!npnr_ctx->isValidBelForCell(imacro, ipos)) + if (!npnr_ctx->isValidBelForCell(imacro, grid[x][y][z])) return macro_placed; int macro_can_be_placed = check_macro_can_be_placed(imacro, itype, x, y, z); @@ -2914,12 +2910,12 @@ static int try_place_macro(int itype, /*int*/ BelId ipos, /*int*/ CellInfo* imac return (macro_placed); } -static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std::vector> &free_locations) { +static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std::vector> &free_locations) { int macro_placed; int /*imacro,*/ itype, itry /*, ipos*/; // ClusterBlockId blk_id; - BelId ipos; + Loc ipos; auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& device_ctx = g_vpr_ctx.device(); @@ -2966,7 +2962,7 @@ static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std // if there are no legal positions, error out // Exhaustive placement of carry macros - for (ipos : free_locations[itype]) { + for (auto ipos : free_locations[itype]) { // Try to place the macro macro_placed = try_place_macro(itype, ipos, imacro); @@ -2995,20 +2991,21 @@ static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std /* Place blocks that are NOT a part of any macro. * We'll randomly place each block in the clustered netlist, one by one. */ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type pad_loc_type*/ - std::vector>& free_locations) { -// int itype, ipos, x, y, z; + std::vector>& free_locations) { + int itype, /*ipos,*/ x, y, z; auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); -// auto& device_ctx = g_vpr_ctx.device(); -// - for (auto& blk_id : cluster_ctx.clb_nlist.blocks()) { - auto cell = blk_id.second.get(); + auto& device_ctx = g_vpr_ctx.device(); + auto& grid = device_ctx.grid; + + for (auto& b : cluster_ctx.clb_nlist.blocks()) { + auto blk_id = b.second.get(); // if (place_ctx.block_locs[blk_id].x != -1) { // -1 is a sentinel for an empty block // // block placed. // continue; // } - if (cell->bel != BelId()) + if (blk_id->bel != BelId()) continue; // /* Don't do IOs if the user specifies IOs; we'll read those locations later. */ @@ -3020,7 +3017,7 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // * Choose one randomly and put blk_id there. Then we don't want to pick // * that location again, so remove it from the free_locations array. // */ -// itype = cluster_ctx.clb_nlist.block_type(blk_id)->index; + itype = cluster_ctx.clb_nlist.block_type(blk_id).index; // if (free_locations[itype] <= 0) { // vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, // "Initial placement failed.\n" @@ -3028,12 +3025,11 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id), device_ctx.block_types[itype].name, itype); // } // - BelId bel; - initial_placement_location(free_locations, /*blk_id, &ipos, &x, &y, &z*/ cell, bel); + initial_placement_location(free_locations, blk_id, /*&ipos,*/ &x, &y, &z); // // // Make sure that the position is EMPTY_BLOCK before placing the block down // VTR_ASSERT(place_ctx.grid_blocks[x][y].blocks[z] == EMPTY_BLOCK_ID); - VTR_ASSERT(npnr_ctx->checkBelAvail(bel)); + VTR_ASSERT(npnr_ctx->checkBelAvail(grid[x][y][z])); // place_ctx.grid_blocks[x][y].blocks[z] = blk_id; // place_ctx.grid_blocks[x][y].usage++; @@ -3047,10 +3043,10 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // place_ctx.block_locs[blk_id].is_fixed = true; // } - if (npnr_ctx->isIO(cell)) - npnr_ctx->bindBel(bel, cell->name, /*STRENGTH_LOCKED*/STRENGTH_STRONG); + if (npnr_ctx->isIO(blk_id)) + npnr_ctx->bindBel(grid[x][y][z], blk_id->name, /*STRENGTH_LOCKED*/STRENGTH_STRONG); else - npnr_ctx->bindBel(bel, cell->name, STRENGTH_WEAK); + npnr_ctx->bindBel(grid[x][y][z], blk_id->name, STRENGTH_WEAK); //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel)); // /* Ensure randomizer doesn't pick this location again, since it's occupied. Could shift all the @@ -3060,34 +3056,31 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // legal_pos[itype][ipos] = legal_pos[itype][free_locations[itype] - 1]; /* overwrite used block position */ // free_locations[itype]--; - free_locations.at(cell->type.index).pop_back(); + free_locations.at(itype).pop_back(); // } } } -static void initial_placement_location(/*int * free_locations, ClusterBlockId blk_id, - int *pipos, int *px, int *py, int *pz*/ - std::vector> &free_locations, - CellInfo *cell, - BelId &bel) { +static void initial_placement_location(/*int **/ std::vector> &free_locations, /*ClusterBlockId*/ CellInfo *blk_id, + /*int *pipos,*/ int *px, int *py, int *pz) { -// auto& cluster_ctx = g_vpr_ctx.clustering(); -// -// int itype = cluster_ctx.clb_nlist.block_type(blk_id)->index; -// -// *pipos = vtr::irand(free_locations[itype] - 1); -// *px_to = legal_pos[itype][*pipos].x; -// *py_to = legal_pos[itype][*pipos].y; -// *pz_to = legal_pos[itype][*pipos].z; + auto& cluster_ctx = g_vpr_ctx.clustering(); + auto& grid = g_vpr_ctx.device().grid; + + int itype = cluster_ctx.clb_nlist.block_type(blk_id).index; - auto it = free_locations.at(cell->type.index).rbegin(); - auto ie = free_locations.at(cell->type.index).rend(); + auto it = free_locations.at(itype).rbegin(); + auto ie = free_locations.at(itype).rend(); for (; it != ie; ++it) { - if (!npnr_ctx->isValidBelForCell(cell, *it)) + *px = it->x; + *py = it->y; + *pz = it->z; + + if (!npnr_ctx->isValidBelForCell(blk_id, grid[*px][*py][*pz])) continue; - bel = *it; - std::swap(*it, free_locations.at(cell->type.index).back()); + + std::swap(*it, free_locations.at(itype).back()); return; } throw; @@ -3107,10 +3100,11 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // * That is, this stores the number of entries in legal_pos[itype] that are worth considering // * as you look for a free location. // */ - std::vector> free_locations; -// auto& device_ctx = g_vpr_ctx.device(); + std::vector> free_locations; + auto& device_ctx = g_vpr_ctx.device(); // auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& place_ctx = g_vpr_ctx.mutable_placement(); + auto& grid = device_ctx.grid; free_locations.resize(legal_pos.size()); @@ -3119,8 +3113,7 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // for carry chain placement for (auto it = legal_pos.begin(); it != legal_pos.end(); it++) { for (auto jt = it->begin(); jt != it->end(); jt++) { - auto loc = npnr_ctx->getBelLocation(*jt); - if (loc.z == 0) + if (jt->z == 0) free_locations[it - legal_pos.begin()].push_back(*jt); } } @@ -3129,16 +3122,18 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // All the macros are placed, update the legal_pos[][] array for (auto it = legal_pos.begin(); it != legal_pos.end(); it++) { - it->erase(remove_if(it->begin(), it->end(), [](BelId bel) { - auto cell_name = npnr_ctx->getBoundBelCell(bel); + it->erase(remove_if(it->begin(), it->end(), [&grid](const Loc& loc) { + auto cell_name = npnr_ctx->getBoundBelCell(grid[loc.x][loc.y][loc.z]); return cell_name != IdString(); }), it->end()); } for (auto it = free_locations.begin(); it != free_locations.end(); it++) { - it->clear(); const auto& src = legal_pos[it - free_locations.begin()]; + if (src.empty()) continue; + it->clear(); it->reserve(src.size()); it->assign(src.begin(), src.end()); + npnr_ctx->shuffle(*it); } initial_placement_blocks(free_locations); From 7fd70fb28b0a76efd1107e5fb34abe8b1077c0a2 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 20:37:56 -0700 Subject: [PATCH 072/116] Fix macro init --- common/placer_vpr.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index fdf7a49ce9..cb6753fc7a 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -195,7 +195,7 @@ namespace vpr { net = it->second.net; }; - if (!entry.empty()) { + if (entry.size() > 1) { log_info("Cell %s is a carry with open CIN and %d dependents\n", cell->name.c_str(npnr_ctx), entry.size()); macros.emplace(cell, std::move(entry)); } From 6cda74eae9a98df93ef1e4ff9ce22e5bfaded5ba Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 20:38:13 -0700 Subject: [PATCH 073/116] Fix filtering free_locations --- common/vpr_place.cpp.inc | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index e49a24bcc7..1141db76e7 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -2978,7 +2978,7 @@ static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std "Initial placement failed.\n" "Could not place macro length %d with head block %s (#%zu); not enough free locations of type %s (#%d).\n" "Please manually size the FPGA because VPR can't do this yet.\n", - pl_macros[imacro].size(), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id), imacro->type.c_str(npnr_ctx), itype); + pl_macros[imacro].size(), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id->udata), imacro->type.c_str(npnr_ctx), itype); } } else { @@ -3044,7 +3044,7 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // } if (npnr_ctx->isIO(blk_id)) - npnr_ctx->bindBel(grid[x][y][z], blk_id->name, /*STRENGTH_LOCKED*/STRENGTH_STRONG); + npnr_ctx->bindBel(grid[x][y][z], blk_id->name, STRENGTH_USER); else npnr_ctx->bindBel(grid[x][y][z], blk_id->name, STRENGTH_WEAK); //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel)); @@ -3124,15 +3124,21 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, for (auto it = legal_pos.begin(); it != legal_pos.end(); it++) { it->erase(remove_if(it->begin(), it->end(), [&grid](const Loc& loc) { auto cell_name = npnr_ctx->getBoundBelCell(grid[loc.x][loc.y][loc.z]); - return cell_name != IdString(); + if (cell_name == IdString()) return false; + auto cell = npnr_ctx->cells.at(cell_name).get(); + return cell->belStrength > STRENGTH_WEAK; }), it->end()); } for (auto it = free_locations.begin(); it != free_locations.end(); it++) { - const auto& src = legal_pos[it - free_locations.begin()]; + const auto& src = legal_pos.at(it - free_locations.begin()); if (src.empty()) continue; it->clear(); it->reserve(src.size()); - it->assign(src.begin(), src.end()); + for (auto& loc : src) { + auto cell_name = npnr_ctx->getBoundBelCell(grid[loc.x][loc.y][loc.z]); + if (cell_name == IdString()) + it->push_back(loc); + } npnr_ctx->shuffle(*it); } From 6e13b03744d0acc33dbacf03d30e8448794b3c85 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 23:04:16 -0700 Subject: [PATCH 074/116] Port over carry legalisation from place_legaliser.cc --- common/placer_vpr.cc | 285 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 241 insertions(+), 44 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index cb6753fc7a..b024540a35 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -41,6 +41,17 @@ #include "place_legaliser.h" #include "timing.h" #include "util.h" +#include "cells.h" +#include "design_utils.h" + +namespace NEXTPNR_NAMESPACE { + struct CellChain + { + std::vector cells; + float mid_x = 0, mid_y = 0; + }; + std::vector all_chains; +} namespace vpr { using namespace NEXTPNR_NAMESPACE; @@ -153,53 +164,15 @@ namespace vpr { // place_macro.cpp int alloc_and_load_placement_macros(/*t_direct_inf* directs, int num_directs, t_pl_macro ** */ std::unordered_map ¯os) { - auto id_lc = npnr_ctx->id("ICESTORM_LC"); - auto id_cin = npnr_ctx->id("CIN"); - auto id_cout = npnr_ctx->id("COUT"); - - for (auto &cell_entry : npnr_ctx->cells) { - auto cell = cell_entry.second.get(); - if (cell->type != id_lc) continue; - - auto it = cell->ports.find(id_cin); - if (it == cell->ports.end()) continue; - auto net = it->second.net; - it = cell->ports.find(id_cout); - if (net) { - // If CIN is driven, check if it's by COUT - auto driver = net->driver; - if (driver.port == id_cout) break; - } - // Check that COUT is driven - if (it == cell->ports.end()) continue; - net = it->second.net; - + for (auto &chain : all_chains) { t_pl_macro entry; - entry.emplace_back(cell, 0, 0, 0); - - while (net) { - NPNR_ASSERT(net->users.size() > 0); - CellInfo* sink_cell = nullptr; - for (const auto& load : net->users) { - if (sink_cell && load.cell != sink_cell) { - sink_cell = nullptr; - break; - } - sink_cell = load.cell; - } - if (!sink_cell) break; + for (int z = 0; z < int(chain.cells.size()); z++) + entry.emplace_back(chain.cells.at(z), 0, z / 8, z % 8); + macros.emplace(chain.cells.front(), std::move(entry)); + } - entry.emplace_back(sink_cell, 0, entry.size() / 8, entry.size() % 8); - auto it = sink_cell->ports.find(id_cout); - if (it == sink_cell->ports.end()) break; - net = it->second.net; - }; + assign_budget(npnr_ctx); - if (entry.size() > 1) { - log_info("Cell %s is a carry with open CIN and %d dependents\n", cell->name.c_str(npnr_ctx), entry.size()); - macros.emplace(cell, std::move(entry)); - } - } return macros.size(); } @@ -266,6 +239,8 @@ class VPRPlacer for (auto& c : grid._bels) c.resize(max_y+1); + find_carries(); + int32_t cell_idx = 0; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); @@ -330,6 +305,228 @@ class VPRPlacer return true; } + // Generic chain finder + template + std::vector find_chains(const Context *ctx, F1 cell_type_predicate, F2 get_previous, F3 get_next, + size_t min_length = 2) + { + std::set chained; + std::vector chains; + for (auto cell : sorted(ctx->cells)) { + if (chained.find(cell.first) != chained.end()) + continue; + CellInfo *ci = cell.second; + if (cell_type_predicate(ctx, ci)) { + CellInfo *start = ci; + CellInfo *prev_start = ci; + while (prev_start != nullptr) { + start = prev_start; + prev_start = get_previous(ctx, start); + } + CellChain chain; + CellInfo *end = start; + while (end != nullptr) { + chain.cells.push_back(end); + end = get_next(ctx, end); + } + if (chain.cells.size() >= min_length) { + chains.push_back(chain); + for (auto c : chain.cells) + chained.insert(c->name); + } + } + } + return chains; + } + + // Insert a logic cell to legalise a CIN->fabric connection + CellInfo *make_carry_feed_in(CellInfo *cin_cell, PortInfo &cin_port) + { + NPNR_ASSERT(cin_port.net != nullptr); + std::unique_ptr lc = create_ice_cell(ctx, ctx->id("ICESTORM_LC")); + lc->params[ctx->id("CARRY_ENABLE")] = "1"; + lc->params[ctx->id("CIN_CONST")] = "1"; + lc->params[ctx->id("CIN_SET")] = "1"; + lc->ports.at(ctx->id("I1")).net = cin_port.net; + cin_port.net->users.erase(std::remove_if(cin_port.net->users.begin(), cin_port.net->users.end(), + [cin_cell, cin_port](const PortRef &usr) { + return usr.cell == cin_cell && usr.port == cin_port.name; + })); + + PortRef i1_ref; + i1_ref.cell = lc.get(); + i1_ref.port = ctx->id("I1"); + lc->ports.at(ctx->id("I1")).net->users.push_back(i1_ref); + + std::unique_ptr out_net(new NetInfo()); + out_net->name = ctx->id(lc->name.str(ctx) + "$O"); + + PortRef drv_ref; + drv_ref.port = ctx->id("COUT"); + drv_ref.cell = lc.get(); + out_net->driver = drv_ref; + lc->ports.at(ctx->id("COUT")).net = out_net.get(); + + PortRef usr_ref; + usr_ref.port = cin_port.name; + usr_ref.cell = cin_cell; + out_net->users.push_back(usr_ref); + cin_cell->ports.at(cin_port.name).net = out_net.get(); + + IdString out_net_name = out_net->name; + NPNR_ASSERT(ctx->nets.find(out_net_name) == ctx->nets.end()); + ctx->nets[out_net_name] = std::move(out_net); + + IdString name = lc->name; + ctx->assignCellInfo(lc.get()); + ctx->cells[lc->name] = std::move(lc); + //createdCells.insert(name); + return ctx->cells[name].get(); + } + + // Insert a logic cell to legalise a COUT->fabric connection + CellInfo *make_carry_pass_out(PortInfo &cout_port) + { + NPNR_ASSERT(cout_port.net != nullptr); + std::unique_ptr lc = create_ice_cell(ctx, ctx->id("ICESTORM_LC")); + lc->params[ctx->id("LUT_INIT")] = "65280"; // 0xff00: O = I3 + lc->params[ctx->id("CARRY_ENABLE")] = "1"; + lc->ports.at(ctx->id("O")).net = cout_port.net; + std::unique_ptr co_i3_net(new NetInfo()); + co_i3_net->name = ctx->id(lc->name.str(ctx) + "$I3"); + co_i3_net->driver = cout_port.net->driver; + PortRef i3_r; + i3_r.port = ctx->id("I3"); + i3_r.cell = lc.get(); + co_i3_net->users.push_back(i3_r); + PortRef o_r; + o_r.port = ctx->id("O"); + o_r.cell = lc.get(); + cout_port.net->driver = o_r; + lc->ports.at(ctx->id("I3")).net = co_i3_net.get(); + cout_port.net = co_i3_net.get(); + + IdString co_i3_name = co_i3_net->name; + NPNR_ASSERT(ctx->nets.find(co_i3_name) == ctx->nets.end()); + ctx->nets[co_i3_name] = std::move(co_i3_net); + IdString name = lc->name; + ctx->assignCellInfo(lc.get()); + ctx->cells[lc->name] = std::move(lc); + //createdCells.insert(name); + return ctx->cells[name].get(); + } + + // Split a carry chain into multiple legal chains + std::vector split_carry_chain(CellChain &carryc) + { + bool start_of_chain = true; + std::vector chains; + std::vector tile; + const int max_length = (ctx->chip_info->height - 2) * 8 - 2; + auto curr_cell = carryc.cells.begin(); + while (curr_cell != carryc.cells.end()) { + CellInfo *cell = *curr_cell; + if (tile.size() >= 8) { + tile.clear(); + } + if (start_of_chain) { + tile.clear(); + chains.emplace_back(); + start_of_chain = false; + if (cell->ports.at(ctx->id("CIN")).net) { + // CIN is not constant and not part of a chain. Must feed in from fabric + CellInfo *feedin = make_carry_feed_in(cell, cell->ports.at(ctx->id("CIN"))); + chains.back().cells.push_back(feedin); + tile.push_back(feedin); + } + } + tile.push_back(cell); + chains.back().cells.push_back(cell); + bool split_chain = (!ctx->logicCellsCompatible(tile)) || (int(chains.back().cells.size()) > max_length); + if (split_chain) { + CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT"))); + tile.pop_back(); + chains.back().cells.back() = passout; + start_of_chain = true; + } else { + NetInfo *carry_net = cell->ports.at(ctx->id("COUT")).net; + bool at_end = (curr_cell == carryc.cells.end() - 1); + if (carry_net != nullptr && (carry_net->users.size() > 1 || at_end)) { + if (carry_net->users.size() > 2 || + (net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), false) != + net_only_drives(ctx, carry_net, is_lc, ctx->id("CIN"), false)) || + (at_end && !net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), true))) { + CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT"))); + chains.back().cells.push_back(passout); + tile.push_back(passout); + start_of_chain = true; + } + } + ++curr_cell; + } + } + return chains; + } + + bool find_carries() + { + std::vector carry_chains = + find_chains(ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); }, + [](const Context *ctx, const + + CellInfo *cell) { + CellInfo *carry_prev = + net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT")); + if (carry_prev != nullptr) + return carry_prev; + /*CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc, + ctx->id("COUT")); if (i3_prev != nullptr) return i3_prev;*/ + return (CellInfo *)nullptr; + }, + [](const Context *ctx, const CellInfo *cell) { + CellInfo *carry_next = net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, + ctx->id("CIN"), false); + if (carry_next != nullptr) + return carry_next; + /*CellInfo *i3_next = + net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"), + false); if (i3_next != nullptr) return i3_next;*/ + return (CellInfo *)nullptr; + }); + std::unordered_set chained; + for (auto &base_chain : carry_chains) { + for (auto c : base_chain.cells) + chained.insert(c->name); + } + // Any cells not in chains, but with carry enabled, must also be put in a single-carry chain + // for correct processing + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (chained.find(cell.first) == chained.end() && is_lc(ctx, ci) && + bool_or_default(ci->params, ctx->id("CARRY_ENABLE"))) { + CellChain sChain; + sChain.cells.push_back(ci); + chained.insert(cell.first); + carry_chains.push_back(sChain); + } + } + + // Find midpoints for all chains, before we start tearing them up + for (auto &base_chain : carry_chains) { + /*if (ctx->verbose)*/ { + log_info("Found carry chain: \n"); + for (auto entry : base_chain.cells) + log_info(" %s\n", entry->name.c_str(ctx)); + log_info("\n"); + } + std::vector split_chains = split_carry_chain(base_chain); + for (auto &chain : split_chains) { + //get_chain_midpoint(ctx, chain, chain.mid_x, chain.mid_y); + all_chains.push_back(chain); + } + } + } + private: Context *ctx; }; From e1e29693aac547d58811bc8e1b2c22177cf9af72 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 23:21:05 -0700 Subject: [PATCH 075/116] Refactor place_legaliser for prepare_and_find_carries() --- ice40/place_legaliser.cc | 27 ++++++++++++++++++++++++--- ice40/place_legaliser.h | 1 + 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/ice40/place_legaliser.cc b/ice40/place_legaliser.cc index 2aefb839ac..e906b4d3fd 100644 --- a/ice40/place_legaliser.cc +++ b/ice40/place_legaliser.cc @@ -131,6 +131,15 @@ class PlacementLegaliser return legalised_carries && replaced_cells; } + std::vector> prepare_and_find_carries() + { + std::vector> ret; + std::vector all_chains = prepare_carries(false /* midpoint */); + for (auto &c : all_chains) + ret.emplace_back(c.cells); + return ret; + } + private: void init_logic_cells() { @@ -153,7 +162,7 @@ class PlacementLegaliser } } - bool legalise_carries() + std::vector prepare_carries(bool midpoint=true) { std::vector carry_chains = find_chains(ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); }, @@ -195,7 +204,6 @@ class PlacementLegaliser carry_chains.push_back(sChain); } } - bool success = true; // Find midpoints for all chains, before we start tearing them up std::vector all_chains; for (auto &base_chain : carry_chains) { @@ -207,10 +215,17 @@ class PlacementLegaliser } std::vector split_chains = split_carry_chain(base_chain); for (auto &chain : split_chains) { - get_chain_midpoint(ctx, chain, chain.mid_x, chain.mid_y); + if (midpoint) get_chain_midpoint(ctx, chain, chain.mid_x, chain.mid_y); all_chains.push_back(chain); } } + return all_chains; + } + + bool legalise_carries() + { + bool success = true; + std::vector all_chains = prepare_carries(); // Actual chain placement for (auto &chain : all_chains) { if (ctx->verbose) @@ -514,4 +529,10 @@ bool legalise_design(Context *ctx) return lg.legalise(); } +std::vector> prepare_and_find_carries(Context *ctx) +{ + PlacementLegaliser lg(ctx); + return lg.prepare_and_find_carries(); +} + NEXTPNR_NAMESPACE_END diff --git a/ice40/place_legaliser.h b/ice40/place_legaliser.h index 5f4df6aac8..f02df680e6 100644 --- a/ice40/place_legaliser.h +++ b/ice40/place_legaliser.h @@ -25,6 +25,7 @@ NEXTPNR_NAMESPACE_BEGIN bool legalise_design(Context *ctx); +std::vector> prepare_and_find_carries(Context *ctx); NEXTPNR_NAMESPACE_END From cead10c9fa3a9fc8fa2a6e3462f6294c28e002bc Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 23:23:59 -0700 Subject: [PATCH 076/116] Use new prepare_and_find_carries() function --- common/placer_vpr.cc | 243 ++----------------------------------------- 1 file changed, 7 insertions(+), 236 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index b024540a35..6cdea00717 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -40,17 +40,10 @@ #include "place_common.h" #include "place_legaliser.h" #include "timing.h" -#include "util.h" -#include "cells.h" -#include "design_utils.h" +#include "place_legaliser.h" namespace NEXTPNR_NAMESPACE { - struct CellChain - { - std::vector cells; - float mid_x = 0, mid_y = 0; - }; - std::vector all_chains; + std::vector> carries; } namespace vpr { @@ -164,11 +157,11 @@ namespace vpr { // place_macro.cpp int alloc_and_load_placement_macros(/*t_direct_inf* directs, int num_directs, t_pl_macro ** */ std::unordered_map ¯os) { - for (auto &chain : all_chains) { + for (auto &chain : carries) { t_pl_macro entry; - for (int z = 0; z < int(chain.cells.size()); z++) - entry.emplace_back(chain.cells.at(z), 0, z / 8, z % 8); - macros.emplace(chain.cells.front(), std::move(entry)); + for (int z = 0; z < int(chain.size()); z++) + entry.emplace_back(chain.at(z), 0, z / 8, z % 8); + macros.emplace(chain.front(), std::move(entry)); } assign_budget(npnr_ctx); @@ -239,7 +232,7 @@ class VPRPlacer for (auto& c : grid._bels) c.resize(max_y+1); - find_carries(); + carries = prepare_and_find_carries(ctx); int32_t cell_idx = 0; for (auto &cell : ctx->cells) { @@ -305,228 +298,6 @@ class VPRPlacer return true; } - // Generic chain finder - template - std::vector find_chains(const Context *ctx, F1 cell_type_predicate, F2 get_previous, F3 get_next, - size_t min_length = 2) - { - std::set chained; - std::vector chains; - for (auto cell : sorted(ctx->cells)) { - if (chained.find(cell.first) != chained.end()) - continue; - CellInfo *ci = cell.second; - if (cell_type_predicate(ctx, ci)) { - CellInfo *start = ci; - CellInfo *prev_start = ci; - while (prev_start != nullptr) { - start = prev_start; - prev_start = get_previous(ctx, start); - } - CellChain chain; - CellInfo *end = start; - while (end != nullptr) { - chain.cells.push_back(end); - end = get_next(ctx, end); - } - if (chain.cells.size() >= min_length) { - chains.push_back(chain); - for (auto c : chain.cells) - chained.insert(c->name); - } - } - } - return chains; - } - - // Insert a logic cell to legalise a CIN->fabric connection - CellInfo *make_carry_feed_in(CellInfo *cin_cell, PortInfo &cin_port) - { - NPNR_ASSERT(cin_port.net != nullptr); - std::unique_ptr lc = create_ice_cell(ctx, ctx->id("ICESTORM_LC")); - lc->params[ctx->id("CARRY_ENABLE")] = "1"; - lc->params[ctx->id("CIN_CONST")] = "1"; - lc->params[ctx->id("CIN_SET")] = "1"; - lc->ports.at(ctx->id("I1")).net = cin_port.net; - cin_port.net->users.erase(std::remove_if(cin_port.net->users.begin(), cin_port.net->users.end(), - [cin_cell, cin_port](const PortRef &usr) { - return usr.cell == cin_cell && usr.port == cin_port.name; - })); - - PortRef i1_ref; - i1_ref.cell = lc.get(); - i1_ref.port = ctx->id("I1"); - lc->ports.at(ctx->id("I1")).net->users.push_back(i1_ref); - - std::unique_ptr out_net(new NetInfo()); - out_net->name = ctx->id(lc->name.str(ctx) + "$O"); - - PortRef drv_ref; - drv_ref.port = ctx->id("COUT"); - drv_ref.cell = lc.get(); - out_net->driver = drv_ref; - lc->ports.at(ctx->id("COUT")).net = out_net.get(); - - PortRef usr_ref; - usr_ref.port = cin_port.name; - usr_ref.cell = cin_cell; - out_net->users.push_back(usr_ref); - cin_cell->ports.at(cin_port.name).net = out_net.get(); - - IdString out_net_name = out_net->name; - NPNR_ASSERT(ctx->nets.find(out_net_name) == ctx->nets.end()); - ctx->nets[out_net_name] = std::move(out_net); - - IdString name = lc->name; - ctx->assignCellInfo(lc.get()); - ctx->cells[lc->name] = std::move(lc); - //createdCells.insert(name); - return ctx->cells[name].get(); - } - - // Insert a logic cell to legalise a COUT->fabric connection - CellInfo *make_carry_pass_out(PortInfo &cout_port) - { - NPNR_ASSERT(cout_port.net != nullptr); - std::unique_ptr lc = create_ice_cell(ctx, ctx->id("ICESTORM_LC")); - lc->params[ctx->id("LUT_INIT")] = "65280"; // 0xff00: O = I3 - lc->params[ctx->id("CARRY_ENABLE")] = "1"; - lc->ports.at(ctx->id("O")).net = cout_port.net; - std::unique_ptr co_i3_net(new NetInfo()); - co_i3_net->name = ctx->id(lc->name.str(ctx) + "$I3"); - co_i3_net->driver = cout_port.net->driver; - PortRef i3_r; - i3_r.port = ctx->id("I3"); - i3_r.cell = lc.get(); - co_i3_net->users.push_back(i3_r); - PortRef o_r; - o_r.port = ctx->id("O"); - o_r.cell = lc.get(); - cout_port.net->driver = o_r; - lc->ports.at(ctx->id("I3")).net = co_i3_net.get(); - cout_port.net = co_i3_net.get(); - - IdString co_i3_name = co_i3_net->name; - NPNR_ASSERT(ctx->nets.find(co_i3_name) == ctx->nets.end()); - ctx->nets[co_i3_name] = std::move(co_i3_net); - IdString name = lc->name; - ctx->assignCellInfo(lc.get()); - ctx->cells[lc->name] = std::move(lc); - //createdCells.insert(name); - return ctx->cells[name].get(); - } - - // Split a carry chain into multiple legal chains - std::vector split_carry_chain(CellChain &carryc) - { - bool start_of_chain = true; - std::vector chains; - std::vector tile; - const int max_length = (ctx->chip_info->height - 2) * 8 - 2; - auto curr_cell = carryc.cells.begin(); - while (curr_cell != carryc.cells.end()) { - CellInfo *cell = *curr_cell; - if (tile.size() >= 8) { - tile.clear(); - } - if (start_of_chain) { - tile.clear(); - chains.emplace_back(); - start_of_chain = false; - if (cell->ports.at(ctx->id("CIN")).net) { - // CIN is not constant and not part of a chain. Must feed in from fabric - CellInfo *feedin = make_carry_feed_in(cell, cell->ports.at(ctx->id("CIN"))); - chains.back().cells.push_back(feedin); - tile.push_back(feedin); - } - } - tile.push_back(cell); - chains.back().cells.push_back(cell); - bool split_chain = (!ctx->logicCellsCompatible(tile)) || (int(chains.back().cells.size()) > max_length); - if (split_chain) { - CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT"))); - tile.pop_back(); - chains.back().cells.back() = passout; - start_of_chain = true; - } else { - NetInfo *carry_net = cell->ports.at(ctx->id("COUT")).net; - bool at_end = (curr_cell == carryc.cells.end() - 1); - if (carry_net != nullptr && (carry_net->users.size() > 1 || at_end)) { - if (carry_net->users.size() > 2 || - (net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), false) != - net_only_drives(ctx, carry_net, is_lc, ctx->id("CIN"), false)) || - (at_end && !net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), true))) { - CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT"))); - chains.back().cells.push_back(passout); - tile.push_back(passout); - start_of_chain = true; - } - } - ++curr_cell; - } - } - return chains; - } - - bool find_carries() - { - std::vector carry_chains = - find_chains(ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); }, - [](const Context *ctx, const - - CellInfo *cell) { - CellInfo *carry_prev = - net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT")); - if (carry_prev != nullptr) - return carry_prev; - /*CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc, - ctx->id("COUT")); if (i3_prev != nullptr) return i3_prev;*/ - return (CellInfo *)nullptr; - }, - [](const Context *ctx, const CellInfo *cell) { - CellInfo *carry_next = net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, - ctx->id("CIN"), false); - if (carry_next != nullptr) - return carry_next; - /*CellInfo *i3_next = - net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"), - false); if (i3_next != nullptr) return i3_next;*/ - return (CellInfo *)nullptr; - }); - std::unordered_set chained; - for (auto &base_chain : carry_chains) { - for (auto c : base_chain.cells) - chained.insert(c->name); - } - // Any cells not in chains, but with carry enabled, must also be put in a single-carry chain - // for correct processing - for (auto cell : sorted(ctx->cells)) { - CellInfo *ci = cell.second; - if (chained.find(cell.first) == chained.end() && is_lc(ctx, ci) && - bool_or_default(ci->params, ctx->id("CARRY_ENABLE"))) { - CellChain sChain; - sChain.cells.push_back(ci); - chained.insert(cell.first); - carry_chains.push_back(sChain); - } - } - - // Find midpoints for all chains, before we start tearing them up - for (auto &base_chain : carry_chains) { - /*if (ctx->verbose)*/ { - log_info("Found carry chain: \n"); - for (auto entry : base_chain.cells) - log_info(" %s\n", entry->name.c_str(ctx)); - log_info("\n"); - } - std::vector split_chains = split_carry_chain(base_chain); - for (auto &chain : split_chains) { - //get_chain_midpoint(ctx, chain, chain.mid_x, chain.mid_y); - all_chains.push_back(chain); - } - } - } - private: Context *ctx; }; From 43ea78f29cab06320c3ecd7664ef2b1359d6031a Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 23:47:55 -0700 Subject: [PATCH 077/116] Add support for get_imacro_from_iblk() --- common/placer_vpr.cc | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 6cdea00717..2d62051971 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -53,8 +53,8 @@ namespace vpr { std::vector npnr_cells; struct DeviceGrid { - inline int width() const { return _bels.size(); } - inline int height() const { return _bels.front().size(); } + inline size_t width() const { return _bels.size(); } + inline size_t height() const { return _bels.front().size(); } inline const std::vector>& operator[](size_t x) { return _bels.at(x); } std::vector>> _bels; }; @@ -159,15 +159,27 @@ namespace vpr { { for (auto &chain : carries) { t_pl_macro entry; - for (int z = 0; z < int(chain.size()); z++) - entry.emplace_back(chain.at(z), 0, z / 8, z % 8); - macros.emplace(chain.front(), std::move(entry)); + auto head = chain.front(); + for (int z = 0; z < int(chain.size()); z++) { + auto cell = chain.at(z); + entry.emplace_back(cell, 0, z / 8, z % 8); + cell->attrs.emplace(npnr_ctx->id("carry_head"), head->name.str(npnr_ctx)); + } + macros.emplace(head, std::move(entry)); } assign_budget(npnr_ctx); return macros.size(); } + + void get_imacro_from_iblk(/*int **/ CellInfo **imacro, /*ClusterBlockId*/ CellInfo *iblk /*, t_pl_macro *macros, int num_macros*/) { + auto it = iblk->attrs.find(npnr_ctx->id("carry_head")); + if (it != iblk->attrs.end()) + *imacro = npnr_ctx->cells.at(npnr_ctx->id(it->second)).get(); + else + *imacro = nullptr; + } #define VTR_ASSERT NPNR_ASSERT #define VTR_ASSERT_SAFE NPNR_ASSERT From bdf9b126ed438fd6871cc307e9a75911655c2e4d Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sun, 22 Jul 2018 23:48:04 -0700 Subject: [PATCH 078/116] Preparing for macro swapping --- common/vpr_place.cpp.inc | 45 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index 1141db76e7..ddadeafcb6 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -1181,6 +1181,7 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, int x_to, * Returns abort_swap. */ // int imoved_blk, imacro; + CellInfo *imacro; // int x_from, y_from, z_from; /*ClusterBlockId*/ CellInfo* b_to; int abort_swap = false; @@ -1223,13 +1224,13 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, int x_to, } else if (b_to != /*INVALID_BLOCK_ID*/ NULL) { -// // Does not allow a swap with a macro yet -// get_imacro_from_iblk(&imacro, b_to, pl_macros, num_pl_macros); -// if (imacro != -1) { -// abort_swap = true; -// return (abort_swap); -// } -// + // Does not allow a swap with a macro yet + get_imacro_from_iblk(&imacro, b_to /*, pl_macros, num_pl_macros*/); + if (imacro != nullptr) { + abort_swap = true; + return (abort_swap); + } + // // Swap the block, dont swap the nets yet // place_ctx.block_locs[b_to].x = x_from; // place_ctx.block_locs[b_to].y = y_from; @@ -1286,6 +1287,7 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i * Returns abort_swap. */ int /*imacro,*/ imember; + CellInfo *imacro; int x_swap_offset, y_swap_offset, z_swap_offset, x_from, y_from, z_from; /*ClusterBlockId*/ CellInfo* curr_b_from; int curr_x_from, curr_y_from, curr_z_from, curr_x_to, curr_y_to, curr_z_to; @@ -1293,26 +1295,23 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i // auto& place_ctx = g_vpr_ctx.placement(); auto& device_ctx = g_vpr_ctx.device(); - auto& cluster_ctx = g_vpr_ctx.clustering(); +// auto& cluster_ctx = g_vpr_ctx.clustering(); auto loc_from = npnr_ctx->getBelLocation(b_from->bel); x_from = loc_from.x; y_from = loc_from.y; z_from = loc_from.z; - -// get_imacro_from_iblk(&imacro, b_from, pl_macros, num_pl_macros); -// if ( imacro != -1) { - if (pl_macros.count(b_from)) { + get_imacro_from_iblk(&imacro, b_from /*, pl_macros, num_pl_macros*/); + if ( imacro != nullptr) { // // b_from is part of a macro, I need to swap the whole macro -// auto imacro = b_from; // // // Record down the relative position of the swap // x_swap_offset = x_to - x_from; // y_swap_offset = y_to - y_from; // z_swap_offset = z_to - z_from; // -// for (imember = 0; imember < pl_macros[imacro].size() && abort_swap == false; imember++) { +// for (imember = 0; imember < int(pl_macros[imacro].size()) && abort_swap == false; imember++) { // // // Gets the new from and to info for every block in the macro // // cannot use the old from and to info @@ -1336,8 +1335,8 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i // //(neccessarily) translationally invariant for an arbitrary macro // if ( curr_x_to < 1 || curr_x_to >= int(device_ctx.grid.width()) // || curr_y_to < 1 || curr_y_to >= int(device_ctx.grid.height()) -// || curr_z_to < 0 -// || device_ctx.grid[curr_x_to][curr_y_to].type != cluster_ctx.clb_nlist.block_type(curr_b_from)) { +// || curr_z_to < 0 || curr_z_to >= int(device_ctx.grid[curr_x_to][curr_y_to].size()) +// || npnr_ctx->belTypeToId(npnr_ctx->getBelType(device_ctx.grid[curr_x_to][curr_y_to][curr_z_to])) != curr_b_from->type) { abort_swap = true; // } else { // abort_swap = setup_blocks_affected(curr_b_from, curr_x_to, curr_y_to, curr_z_to); @@ -1871,7 +1870,7 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, int max_y = std::min(grid.height() - 1, y_from + rly); *pz_to = 0; - if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || legal_pos[itype].size() < active_area) { + if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || int(legal_pos[itype].size()) < active_area) { int ipos = npnr_ctx->rng(legal_pos[itype].size()); *px_to = legal_pos[itype][ipos].x; *py_to = legal_pos[itype][ipos].y; @@ -2802,7 +2801,7 @@ static void load_legal_placements() { auto belType = npnr_ctx->getBelType(bel); auto type = npnr_ctx->belTypeToId(belType); int itype = type.index; - if (itype >= legal_pos.size()) + if (itype >= int(legal_pos.size())) legal_pos.resize(itype+1); legal_pos[itype].push_back(npnr_ctx->getBelLocation(bel)); } @@ -2832,7 +2831,7 @@ static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, int macro_can_be_placed = true; // Check whether all the members can be placed - for (imember = 0; imember < pl_macros[imacro].size(); imember++) { + for (imember = 0; imember < int(pl_macros[imacro].size()); imember++) { member_x = x + pl_macros[imacro]/*.members*/[imember].x_offset; member_y = y + pl_macros[imacro]/*.members*/[imember].y_offset; member_z = z + pl_macros[imacro]/*.members*/[imember].z_offset; @@ -2888,7 +2887,7 @@ static int try_place_macro(int itype, /*int*/ Loc ipos, /*int*/ CellInfo* imacro // Place down the macro macro_placed = true; - for (imember = 0; imember < pl_macros[imacro].size(); imember++) { + for (imember = 0; imember < int(pl_macros[imacro].size()); imember++) { member_x = x + pl_macros[imacro]/*.members*/[imember].x_offset; member_y = y + pl_macros[imacro]/*.members*/[imember].y_offset; @@ -2897,7 +2896,7 @@ static int try_place_macro(int itype, /*int*/ Loc ipos, /*int*/ CellInfo* imacro auto iblk = pl_macros[imacro]/*.members*/[imember].blk_index; auto bel = grid[member_x][member_y][member_z]; - npnr_ctx->bindBel(bel, iblk->name, STRENGTH_WEAK); + npnr_ctx->bindBel(bel, iblk->name, STRENGTH_STRONG); // Could not ensure that the randomiser would not pick this location again // So, would have to do a lazy removal - whenever I come across a block that could not be placed, @@ -2940,7 +2939,7 @@ static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std "Initial placement failed.\n" "Could not place macro length %d with head block %s (#%zu); not enough free locations of type %s (#%d).\n" "VPR cannot auto-size for your circuit, please resize the FPGA manually.\n", - pl_macros[imacro].size(), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id->udata), cluster_ctx.clb_nlist.block_type(blk_id).c_str(npnr_ctx), itype); + int(pl_macros[imacro].size()), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id->udata), cluster_ctx.clb_nlist.block_type(blk_id).c_str(npnr_ctx), itype); } // Try to place the macro first, if can be placed - place them, otherwise try again @@ -2978,7 +2977,7 @@ static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std "Initial placement failed.\n" "Could not place macro length %d with head block %s (#%zu); not enough free locations of type %s (#%d).\n" "Please manually size the FPGA because VPR can't do this yet.\n", - pl_macros[imacro].size(), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id->udata), imacro->type.c_str(npnr_ctx), itype); + int(pl_macros[imacro].size()), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id->udata), imacro->type.c_str(npnr_ctx), itype); } } else { From f5a59f389f039242bee616eeb055f5b140a8735c Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 23 Jul 2018 01:52:16 -0700 Subject: [PATCH 079/116] assign_budget() after prepare_and_find_carries() --- common/placer_vpr.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 2d62051971..d523d726f8 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -167,9 +167,6 @@ namespace vpr { } macros.emplace(head, std::move(entry)); } - - assign_budget(npnr_ctx); - return macros.size(); } @@ -245,6 +242,7 @@ class VPRPlacer c.resize(max_y+1); carries = prepare_and_find_carries(ctx); + assign_budget(ctx); int32_t cell_idx = 0; for (auto &cell : ctx->cells) { From ac210786fc48f11d17a15c74871d6029888f217b Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 23 Jul 2018 01:59:26 -0700 Subject: [PATCH 080/116] WIP -- completes placement but fails validity check --- common/vpr_place.cpp.inc | 183 +++++++++++++++++++-------------------- 1 file changed, 89 insertions(+), 94 deletions(-) diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index ddadeafcb6..437f58212c 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -1226,7 +1226,7 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, int x_to, // Does not allow a swap with a macro yet get_imacro_from_iblk(&imacro, b_to /*, pl_macros, num_pl_macros*/); - if (imacro != nullptr) { + if (imacro) { abort_swap = true; return (abort_swap); } @@ -1303,46 +1303,55 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i z_from = loc_from.z; get_imacro_from_iblk(&imacro, b_from /*, pl_macros, num_pl_macros*/); - if ( imacro != nullptr) { -// // b_from is part of a macro, I need to swap the whole macro -// -// // Record down the relative position of the swap -// x_swap_offset = x_to - x_from; -// y_swap_offset = y_to - y_from; -// z_swap_offset = z_to - z_from; -// -// for (imember = 0; imember < int(pl_macros[imacro].size()) && abort_swap == false; imember++) { -// -// // Gets the new from and to info for every block in the macro -// // cannot use the old from and to info -// curr_b_from = pl_macros[imacro]/*.members*/[imember].blk_index; -// -// auto loc_from = npnr_ctx->getBelLocation(curr_b_from->bel); -// curr_x_from = loc_from.x /*place_ctx.block_locs[curr_b_from].x*/; -// curr_y_from = loc_from.y /*place_ctx.block_locs[curr_b_from].y*/; -// curr_z_from = loc_from.z /*place_ctx.block_locs[curr_b_from].z*/; -// -// curr_x_to = curr_x_from + x_swap_offset; -// curr_y_to = curr_y_from + y_swap_offset; -// curr_z_to = curr_z_from + z_swap_offset; -// -// //Make sure that the swap_to location is valid -// //It must be: -// // * chip, and -// // * match the correct block type -// // -// //Note that we need to explicitly check that the types match, since the device floorplan is not -// //(neccessarily) translationally invariant for an arbitrary macro -// if ( curr_x_to < 1 || curr_x_to >= int(device_ctx.grid.width()) -// || curr_y_to < 1 || curr_y_to >= int(device_ctx.grid.height()) -// || curr_z_to < 0 || curr_z_to >= int(device_ctx.grid[curr_x_to][curr_y_to].size()) -// || npnr_ctx->belTypeToId(npnr_ctx->getBelType(device_ctx.grid[curr_x_to][curr_y_to][curr_z_to])) != curr_b_from->type) { + if (imacro) { + // b_from is part of a macro, I need to swap the whole macro + + // Record down the relative position of the swap + x_swap_offset = x_to - x_from; + y_swap_offset = y_to - y_from; + z_swap_offset = z_to - z_from; + + // HACK HACK HACK + // Do not allow overlapping swaps + if (x_swap_offset == 0 + && (y_swap_offset < int(pl_macros[imacro].size() / 8) + || (y_swap_offset == int(pl_macros[imacro].size() / 8) && z_swap_offset < int(pl_macros[imacro].size() % 8)))) { + abort_swap = true; + } + + for (imember = 0; imember < int(pl_macros[imacro].size()) && abort_swap == false; imember++) { + + // Gets the new from and to info for every block in the macro + // cannot use the old from and to info + curr_b_from = pl_macros[imacro]/*.members*/[imember].blk_index; + + auto loc_from = npnr_ctx->getBelLocation(curr_b_from->bel); + curr_x_from = loc_from.x /*place_ctx.block_locs[curr_b_from].x*/; + curr_y_from = loc_from.y /*place_ctx.block_locs[curr_b_from].y*/; + curr_z_from = loc_from.z /*place_ctx.block_locs[curr_b_from].z*/; + + curr_x_to = curr_x_from + x_swap_offset; + curr_y_to = curr_y_from + y_swap_offset; + curr_z_to = curr_z_from + z_swap_offset; + + //Make sure that the swap_to location is valid + //It must be: + // * chip, and + // * match the correct block type + // + //Note that we need to explicitly check that the types match, since the device floorplan is not + //(neccessarily) translationally invariant for an arbitrary macro + if ( curr_x_to < 1 || curr_x_to >= int(device_ctx.grid.width()) + || curr_y_to < 1 || curr_y_to >= int(device_ctx.grid.height()) + || curr_z_to < 0 || curr_z_to >= int(device_ctx.grid[curr_x_to][curr_y_to].size()) + || npnr_ctx->belTypeToId(npnr_ctx->getBelType(device_ctx.grid[curr_x_to][curr_y_to][curr_z_to])) != curr_b_from->type + || !npnr_ctx->isValidBelForCell(pl_macros[imacro][imember].blk_index, device_ctx.grid[curr_x_to][curr_y_to][curr_z_to])) { abort_swap = true; -// } else { -// abort_swap = setup_blocks_affected(curr_b_from, curr_x_to, curr_y_to, curr_z_to); -// } -// } // Finish going through all the blocks in the macro + } else { + abort_swap = setup_blocks_affected(curr_b_from, curr_x_to, curr_y_to, curr_z_to); + } + } // Finish going through all the blocks in the macro } else { // This is not a macro - I could use the from and to info from before @@ -1479,32 +1488,6 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin // No need to update anything, as we've already done the swap // in setup_blocks_affected -// /* Update clb data structures since we kept the move. */ -// /* Swap physical location */ -// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { -// -// x_to = blocks_affected.moved_blocks[iblk].xnew; -// y_to = blocks_affected.moved_blocks[iblk].ynew; -// z_to = blocks_affected.moved_blocks[iblk].znew; -// -// x_from = blocks_affected.moved_blocks[iblk].xold; -// y_from = blocks_affected.moved_blocks[iblk].yold; -// z_from = blocks_affected.moved_blocks[iblk].zold; -// -// b_from = blocks_affected.moved_blocks[iblk].block_num; -// -// place_ctx.grid_blocks[x_to][y_to].blocks[z_to] = b_from; -// -// if (blocks_affected.moved_blocks[iblk].swapped_to_was_empty) { -// place_ctx.grid_blocks[x_to][y_to].usage++; -// } -// if (blocks_affected.moved_blocks[iblk].swapped_from_is_empty) { -// place_ctx.grid_blocks[x_from][y_from].usage--; -// place_ctx.grid_blocks[x_from][y_from].blocks[z_from] = EMPTY_BLOCK_ID; -// } -// -// } // Finish updating clb for all blocks - } else { /* Move was rejected. */ /* Reset the net cost function flags first. */ @@ -1513,52 +1496,56 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin bb_updated_before[net_id->udata] = NOT_UPDATED_YET; } -// /* Restore the place_ctx.block_locs data structures to their state before the move. */ + /* Restore the place_ctx.block_locs data structures to their state before the move. */ // Since we already swapped in setup_blocks_affected(), unswap here; // to prevent npnr assertions firing, unplace all bels first for (const auto &b : blocks_affected) { - auto blk = b.first; - npnr_ctx->unbindBel(blk->bel); + auto b_from = b.first; + npnr_ctx->unbindBel(b_from->bel); } for (const auto &b : blocks_affected) { - auto blk = b.first; + auto b_from = b.first; auto bel = b.second; -// b_from = blocks_affected.moved_blocks[iblk].block_num; -// -// place_ctx.block_locs[b_from].x = blocks_affected.moved_blocks[iblk].xold; -// place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; -// place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; - - npnr_ctx->bindBel(bel, blk->name, STRENGTH_WEAK); - //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel)); + npnr_ctx->bindBel(bel, b_from->name, STRENGTH_WEAK); } } -// /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ + /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ blocks_affected.clear(); ts_nets_to_update.clear(); #if 0 //Check that each accepted swap yields a valid placement - check_place(*bb_cost, *timing_cost, place_algorithm, *delay_cost); + check_place(*bb_cost, *timing_cost, /*place_algorithm,*/ *delay_cost); #endif return (keep_switch); } else { + /* Restore the place_ctx.block_locs data structures to their state before the move. */ + + // Since we already swapped in setup_blocks_affected(), unswap here; + // to prevent npnr assertions firing, unplace all bels first + for (const auto &b : blocks_affected) { + auto b_from = b.first; + npnr_ctx->unbindBel(b_from->bel); + } + for (const auto &b : blocks_affected) { + auto b_from = b.first; + auto bel = b.second; + + npnr_ctx->bindBel(bel, b_from->name, STRENGTH_WEAK); + } + + /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ + blocks_affected.clear(); + +#if 0 + //Check that each accepted swap yields a valid placement + check_place(*bb_cost, *timing_cost, /*place_algorithm,*/ *delay_cost); +#endif -// /* Restore the place_ctx.block_locs data structures to their state before the move. */ -// for (int iblk = 0; iblk < blocks_affected.num_moved_blocks; iblk++) { -// b_from = blocks_affected.moved_blocks[iblk].block_num; -// -// place_ctx.block_locs[b_from].x = blocks_affected.moved_blocks[iblk].xold; -// place_ctx.block_locs[b_from].y = blocks_affected.moved_blocks[iblk].yold; -// place_ctx.block_locs[b_from].z = blocks_affected.moved_blocks[iblk].zold; -// } -// -// /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ -// blocks_affected.num_moved_blocks = 0; return ABORTED; } @@ -1801,10 +1788,18 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, find_to_location(type, rlim, x_from, y_from, px_to, py_to, pz_to); - if (*px_to < 0) { + if (*pz_to < 0) { is_legal = false; continue; } + // HACK HACK HACK + // Macro heads must start at z = 0 + else if (*pz_to > 0) { + CellInfo *imacro; + get_imacro_from_iblk(&imacro, cell_from /*, pl_macros, num_pl_macros*/); + if (imacro) *pz_to = 0; + } + bel_to = grid[*px_to][*py_to][*pz_to]; if (bel_to == BelId()) { is_legal = false; @@ -1987,7 +1982,7 @@ static float comp_td_point_to_point_delay(/*ClusterNetId*/ NetInfo* net_id, int return (delay_source_to_sink); } -////Recompute all point to point delays, updating point_to_point_delay_cost +//Recompute all point to point delays, updating point_to_point_delay_cost static void comp_td_point_to_point_delays() { auto& cluster_ctx = g_vpr_ctx.clustering(); @@ -2896,7 +2891,7 @@ static int try_place_macro(int itype, /*int*/ Loc ipos, /*int*/ CellInfo* imacro auto iblk = pl_macros[imacro]/*.members*/[imember].blk_index; auto bel = grid[member_x][member_y][member_z]; - npnr_ctx->bindBel(bel, iblk->name, STRENGTH_STRONG); + npnr_ctx->bindBel(bel, iblk->name, STRENGTH_WEAK); // Could not ensure that the randomiser would not pick this location again // So, would have to do a lazy removal - whenever I come across a block that could not be placed, @@ -3108,7 +3103,7 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, free_locations.resize(legal_pos.size()); // HACK HACK HACK - // Initially, populate free_locations with just z == 0 cells + // Initially, populate free_locations with just z == 0 cells // for carry chain placement for (auto it = legal_pos.begin(); it != legal_pos.end(); it++) { for (auto jt = it->begin(); jt != it->end(); jt++) { From 0659b93522df5c55e9f4aa8fa56fbde7e084b613 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 23 Jul 2018 02:26:26 -0700 Subject: [PATCH 081/116] Fix criticality computation for VPR to return [0,1] --- common/placer_vpr.cc | 51 +++++++++++++++++++++++++++++---- common/vpr_place.cpp.inc | 6 ++-- common/vpr_timing_place.cpp.inc | 4 +-- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index d523d726f8..9d0aabac3e 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -109,7 +109,46 @@ namespace vpr { const float timing_tradeoff = 0.5; } placer_opts; struct SetupTimingInfo { - void update() { update_budget(npnr_ctx); } + delay_t worst_slack; + delay_t best_slack; + float slack_spread = 0; + void update() { + update_budget(npnr_ctx); + + worst_slack = std::numeric_limits::max(); + best_slack = std::numeric_limits::min(); + + // Compute the delay for every pin on every net + for (auto &n : npnr_ctx->nets) { + auto net = n.second.get(); + + int driver_x, driver_y; + bool driver_gb; + CellInfo *driver_cell = net->driver.cell; + if (!driver_cell) + continue; + if (driver_cell->bel == BelId()) + continue; + npnr_ctx->estimatePosition(driver_cell->bel, driver_x, driver_y, driver_gb); + WireId drv_wire = npnr_ctx->getWireBelPin(driver_cell->bel, npnr_ctx->portPinFromId(net->driver.port)); + if (driver_gb) + continue; + for (auto& load : net->users) { + if (load.cell == nullptr) + continue; + CellInfo *load_cell = load.cell; + if (load_cell->bel == BelId()) + continue; + WireId user_wire = npnr_ctx->getWireBelPin(load_cell->bel, npnr_ctx->portPinFromId(load.port)); + delay_t raw_wl = npnr_ctx->estimateDelay(drv_wire, user_wire); + delay_t slack = load.budget - raw_wl; + worst_slack = std::min(worst_slack, slack); + best_slack = std::max(best_slack, slack); + } + } + + slack_spread = best_slack - worst_slack; + } }; static SetupTimingInfo timing_info; struct t_pl_macro_member { @@ -122,7 +161,7 @@ namespace vpr { typedef std::vector t_pl_macro; // timing_util.cpp - float calculate_clb_net_pin_criticality(/*timing_info, pin_lookup,*/ const PortRef& load, const NetInfo* net) + float calculate_clb_net_pin_criticality(const SetupTimingInfo& timing_info, /*const ClusteredPinAtomPinsLookup& pin_lookup,*/ const PortRef& load, const NetInfo* net) { NPNR_ASSERT(npnr_ctx->timing_driven); @@ -145,10 +184,10 @@ namespace vpr { return 0; WireId user_wire = npnr_ctx->getWireBelPin(load_cell->bel, npnr_ctx->portPinFromId(load.port)); delay_t raw_wl = npnr_ctx->estimateDelay(drv_wire, user_wire); - float slack = npnr_ctx->getDelayNS(load.budget) - npnr_ctx->getDelayNS(raw_wl); - if (slack <= 0) - return 1 - slack; - return 1/std::max(1, slack); + delay_t slack = load.budget - raw_wl; + if (timing_info.slack_spread == 0) + return 1; + return (slack - timing_info.worst_slack) / timing_info.slack_spread; #else return 1; #endif diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index 437f58212c..be17c37cf4 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -425,7 +425,7 @@ void try_place(t_placer_opts placer_opts, // timing_info->set_warn_unconstrained(false); //Don't warn again about unconstrained nodes again during placement //Initial slack estimates - load_criticalities(/**timing_info,*/ crit_exponent /*, netlist_pin_lookup*/); + load_criticalities(timing_info, crit_exponent /*, netlist_pin_lookup*/); // critical_path = timing_info->least_slack_critical_path(); // @@ -902,7 +902,7 @@ static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, //Per-temperature timing update timing_info.update(); - load_criticalities(/*timing_info,*/ crit_exponent /*, netlist_pin_lookup*/); + load_criticalities(timing_info, crit_exponent /*, netlist_pin_lookup*/); #ifdef ENABLE_CLASSIC_VPR_STA load_timing_graph_net_delays(point_to_point_delay_cost); @@ -988,7 +988,7 @@ static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, */ //Inner loop timing update timing_info.update(); - load_criticalities(/*timing_info,*/ crit_exponent /*, netlist_pin_lookup*/); + load_criticalities(timing_info, crit_exponent /*, netlist_pin_lookup*/); #ifdef ENABLE_CLASSIC_VPR_STA load_timing_graph_net_delays(point_to_point_delay_cost); diff --git a/common/vpr_timing_place.cpp.inc b/common/vpr_timing_place.cpp.inc index 12c8d9105f..dbe5d8b3f0 100644 --- a/common/vpr_timing_place.cpp.inc +++ b/common/vpr_timing_place.cpp.inc @@ -53,7 +53,7 @@ static void free_crit(/*vtr::t_chunk *chunk_list_ptr*/){ } /**************************************/ -void load_criticalities(/*SetupTimingInfo& timing_info,*/ float crit_exponent /*, const ClusteredPinAtomPinsLookup& pin_lookup*/) { +void load_criticalities(SetupTimingInfo& timing_info, float crit_exponent /*, const ClusteredPinAtomPinsLookup& pin_lookup*/) { /* Performs a 1-to-1 mapping from criticality to f_timing_place_crit. For every pin on every net (or, equivalently, for every tedge ending in that pin), f_timing_place_crit = criticality^(criticality exponent) */ @@ -68,7 +68,7 @@ void load_criticalities(/*SetupTimingInfo& timing_info,*/ float crit_exponent /* for (const auto& clb_pin : cluster_ctx.clb_nlist.net_sinks(net_id)) { //int ipin = cluster_ctx.clb_nlist.pin_net_index(clb_pin); - float clb_pin_crit = calculate_clb_net_pin_criticality(/*timing_info, pin_lookup,*/ clb_pin, net_id); + float clb_pin_crit = calculate_clb_net_pin_criticality(timing_info, /*pin_lookup,*/ clb_pin, net_id); /* The placer likes a great deal of contrast between criticalities. Since path criticality varies much more than timing, we "sharpen" timing From 44c009c5e20f851bb84bd418c787f6d746512387 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 23 Jul 2018 02:46:29 -0700 Subject: [PATCH 082/116] When swapping macros, check if to cell is compatible with from bel --- common/vpr_place.cpp.inc | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index be17c37cf4..ec8bd688a1 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -1344,13 +1344,29 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i if ( curr_x_to < 1 || curr_x_to >= int(device_ctx.grid.width()) || curr_y_to < 1 || curr_y_to >= int(device_ctx.grid.height()) || curr_z_to < 0 || curr_z_to >= int(device_ctx.grid[curr_x_to][curr_y_to].size()) - || npnr_ctx->belTypeToId(npnr_ctx->getBelType(device_ctx.grid[curr_x_to][curr_y_to][curr_z_to])) != curr_b_from->type - || !npnr_ctx->isValidBelForCell(pl_macros[imacro][imember].blk_index, device_ctx.grid[curr_x_to][curr_y_to][curr_z_to])) { + || npnr_ctx->belTypeToId(npnr_ctx->getBelType(device_ctx.grid[curr_x_to][curr_y_to][curr_z_to])) != curr_b_from->type) { abort_swap = true; } else { - abort_swap = setup_blocks_affected(curr_b_from, curr_x_to, curr_y_to, curr_z_to); - } + auto bel_to = device_ctx.grid[curr_x_to][curr_y_to][curr_z_to]; + if (!npnr_ctx->isValidBelForCell(pl_macros[imacro][imember].blk_index, bel_to)) { + abort_swap = true; + } + else { + auto cell_name = npnr_ctx->getBoundBelCell(bel_to); + if (cell_name != IdString()) { + auto cell_to = npnr_ctx->cells[cell_name].get(); + if (cell_to->belStrength > STRENGTH_WEAK) { + abort_swap = true; + } + else if (!npnr_ctx->isValidBelForCell(cell_to, pl_macros[imacro][imember].blk_index->bel)) { + abort_swap = true; + } + } + } + if (!abort_swap) + abort_swap = setup_blocks_affected(curr_b_from, curr_x_to, curr_y_to, curr_z_to); + } } // Finish going through all the blocks in the macro } else { // This is not a macro - I could use the from and to info from before From 24a34c7dd4d007d89e9b19a31c30255337c8e3ff Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 23 Jul 2018 02:58:35 -0700 Subject: [PATCH 083/116] Disable all slack redistribution... --- common/placer_vpr.cc | 4 +++- common/router1.cc | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 9d0aabac3e..0e0505c8f8 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -113,6 +113,7 @@ namespace vpr { delay_t best_slack; float slack_spread = 0; void update() { +#if 0 update_budget(npnr_ctx); worst_slack = std::numeric_limits::max(); @@ -149,6 +150,7 @@ namespace vpr { slack_spread = best_slack - worst_slack; } +#endif }; static SetupTimingInfo timing_info; struct t_pl_macro_member { @@ -165,7 +167,7 @@ namespace vpr { { NPNR_ASSERT(npnr_ctx->timing_driven); -#if 1 +#if 0 int driver_x, driver_y; bool driver_gb; CellInfo *driver_cell = net->driver.cell; diff --git a/common/router1.cc b/common/router1.cc index a17995ba2f..7276a23ba0 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -761,7 +761,9 @@ bool router1(Context *ctx) total_delay += ctx->getWireDelay(last).maxDelay(); return total_delay; }; - //update_budget(ctx, actual_delay); +#if 0 + update_budget(ctx, actual_delay); +#endif } bool printNets = ctx->verbose && (jobQueue.size() < 10); From d92f244286601190ce6f802681db0f51ec7d9b5b Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 23 Jul 2018 07:14:43 -0700 Subject: [PATCH 084/116] Fix criticality computation --- common/placer_vpr.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 0e0505c8f8..fb1325d818 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -149,8 +149,8 @@ namespace vpr { } slack_spread = best_slack - worst_slack; - } #endif + } }; static SetupTimingInfo timing_info; struct t_pl_macro_member { @@ -189,7 +189,7 @@ namespace vpr { delay_t slack = load.budget - raw_wl; if (timing_info.slack_spread == 0) return 1; - return (slack - timing_info.worst_slack) / timing_info.slack_spread; + return 1 - ((slack - timing_info.worst_slack) / timing_info.slack_spread); #else return 1; #endif From e6a6e4748af08985dfa25ec3cc0607cc9667cf02 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 23 Jul 2018 18:21:04 -0700 Subject: [PATCH 085/116] Enable slack redist, fix compile issue --- common/placer_vpr.cc | 12 ++++++------ common/router1.cc | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index b0d3d71098..2b0e666107 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -113,7 +113,7 @@ namespace vpr { delay_t best_slack; float slack_spread = 0; void update() { -#if 0 +#if 1 update_budget(npnr_ctx); worst_slack = std::numeric_limits::max(); @@ -131,7 +131,7 @@ namespace vpr { if (driver_cell->bel == BelId()) continue; npnr_ctx->estimatePosition(driver_cell->bel, driver_x, driver_y, driver_gb); - WireId drv_wire = npnr_ctx->getBelWirePin(driver_cell->bel, npnr_ctx->portPinFromId(net->driver.port)); + WireId drv_wire = npnr_ctx->getBelPinWire(driver_cell->bel, npnr_ctx->portPinFromId(net->driver.port)); if (driver_gb) continue; for (auto& load : net->users) { @@ -140,7 +140,7 @@ namespace vpr { CellInfo *load_cell = load.cell; if (load_cell->bel == BelId()) continue; - WireId user_wire = npnr_ctx->getBelWirePin(load_cell->bel, npnr_ctx->portPinFromId(load.port)); + WireId user_wire = npnr_ctx->getBelPinWire(load_cell->bel, npnr_ctx->portPinFromId(load.port)); delay_t raw_wl = npnr_ctx->estimateDelay(drv_wire, user_wire); delay_t slack = load.budget - raw_wl; worst_slack = std::min(worst_slack, slack); @@ -167,7 +167,7 @@ namespace vpr { { NPNR_ASSERT(npnr_ctx->timing_driven); -#if 0 +#if 1 int driver_x, driver_y; bool driver_gb; CellInfo *driver_cell = net->driver.cell; @@ -176,7 +176,7 @@ namespace vpr { if (driver_cell->bel == BelId()) return 0; npnr_ctx->estimatePosition(driver_cell->bel, driver_x, driver_y, driver_gb); - WireId drv_wire = npnr_ctx->getBelWirePin(driver_cell->bel, npnr_ctx->portPinFromId(net->driver.port)); + WireId drv_wire = npnr_ctx->getBelPinWire(driver_cell->bel, npnr_ctx->portPinFromId(net->driver.port)); if (driver_gb) return 0; if (load.cell == nullptr) @@ -184,7 +184,7 @@ namespace vpr { CellInfo *load_cell = load.cell; if (load_cell->bel == BelId()) return 0; - WireId user_wire = npnr_ctx->getBelWirePin(load_cell->bel, npnr_ctx->portPinFromId(load.port)); + WireId user_wire = npnr_ctx->getBelPinWire(load_cell->bel, npnr_ctx->portPinFromId(load.port)); delay_t raw_wl = npnr_ctx->estimateDelay(drv_wire, user_wire); delay_t slack = load.budget - raw_wl; if (timing_info.slack_spread == 0) diff --git a/common/router1.cc b/common/router1.cc index 0f0a94ee77..ecbdbc0d3b 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -617,6 +617,7 @@ bool router1(Context *ctx) if (ctx->verbose) log_info("routing queue contains %d jobs.\n", int(jobQueue.size())); } else { +#if 1 static auto actual_delay = [](Context *ctx, WireId src, WireId dst) { delay_t total_delay = 0; WireId last = dst; @@ -643,7 +644,6 @@ bool router1(Context *ctx) total_delay += ctx->getWireDelay(last).maxDelay(); return total_delay; }; -#if 0 update_budget(ctx, actual_delay); #endif } From fc6a63ed063e04c6ca992e3d41a6d383742118b5 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Thu, 26 Jul 2018 23:17:08 -0700 Subject: [PATCH 086/116] Fixes for master --- common/placer_vpr.cc | 8 +++----- common/vpr_place.cpp.inc | 2 ++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 2b0e666107..da25f7d2be 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -123,14 +123,13 @@ namespace vpr { for (auto &n : npnr_ctx->nets) { auto net = n.second.get(); - int driver_x, driver_y; bool driver_gb; CellInfo *driver_cell = net->driver.cell; if (!driver_cell) continue; if (driver_cell->bel == BelId()) continue; - npnr_ctx->estimatePosition(driver_cell->bel, driver_x, driver_y, driver_gb); + driver_gb = npnr_ctx->getBelGlobalBuf(driver_cell->bel); WireId drv_wire = npnr_ctx->getBelPinWire(driver_cell->bel, npnr_ctx->portPinFromId(net->driver.port)); if (driver_gb) continue; @@ -168,14 +167,13 @@ namespace vpr { NPNR_ASSERT(npnr_ctx->timing_driven); #if 1 - int driver_x, driver_y; bool driver_gb; CellInfo *driver_cell = net->driver.cell; if (!driver_cell) return 0; if (driver_cell->bel == BelId()) return 0; - npnr_ctx->estimatePosition(driver_cell->bel, driver_x, driver_y, driver_gb); + driver_gb = npnr_ctx->getBelGlobalBuf(driver_cell->bel); WireId drv_wire = npnr_ctx->getBelPinWire(driver_cell->bel, npnr_ctx->portPinFromId(net->driver.port)); if (driver_gb) return 0; @@ -283,7 +281,6 @@ class VPRPlacer c.resize(max_y+1); carries = prepare_and_find_carries(ctx); - assign_budget(ctx); int32_t cell_idx = 0; for (auto &cell : ctx->cells) { @@ -346,6 +343,7 @@ class VPRPlacer } } } + compute_fmax(ctx, true /* print_fmax */); return true; } diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index 3ad26d60f7..5f057b254b 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -3170,6 +3170,8 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // } //#endif // free(free_locations); + + assign_budget(npnr_ctx); } //static void free_fast_cost_update() { From f2a65980e86913f4cbafcf08cdc47d01604dfdf8 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 27 Jul 2018 19:57:26 -0700 Subject: [PATCH 087/116] Fix slack computation --- common/placer_vpr.cc | 17 ++++++++--------- common/vpr_place.cpp.inc | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index da25f7d2be..606493bdf7 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -110,14 +110,12 @@ namespace vpr { } placer_opts; struct SetupTimingInfo { delay_t worst_slack; - delay_t best_slack; - float slack_spread = 0; + delay_t max_req; void update() { #if 1 - update_budget(npnr_ctx); + update_budget(npnr_ctx); worst_slack = std::numeric_limits::max(); - best_slack = std::numeric_limits::min(); // Compute the delay for every pin on every net for (auto &n : npnr_ctx->nets) { @@ -143,11 +141,10 @@ namespace vpr { delay_t raw_wl = npnr_ctx->estimateDelay(drv_wire, user_wire); delay_t slack = load.budget - raw_wl; worst_slack = std::min(worst_slack, slack); - best_slack = std::max(best_slack, slack); } } - slack_spread = best_slack - worst_slack; + max_req = delay_t(1.0e12 / npnr_ctx->target_freq); #endif } }; @@ -185,9 +182,11 @@ namespace vpr { WireId user_wire = npnr_ctx->getBelPinWire(load_cell->bel, npnr_ctx->portPinFromId(load.port)); delay_t raw_wl = npnr_ctx->estimateDelay(drv_wire, user_wire); delay_t slack = load.budget - raw_wl; - if (timing_info.slack_spread == 0) - return 1; - return 1 - ((slack - timing_info.worst_slack) / timing_info.slack_spread); + delay_t shift = timing_info.worst_slack < 0 ? -timing_info.worst_slack : 0; + float crit = 1 - (float(slack + shift) / (timing_info.max_req + shift)); + crit = std::max(0., crit); + crit = std::min(1., crit); + return crit; #else return 1; #endif diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index 5f057b254b..2ac2909af1 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -421,7 +421,7 @@ void try_place(t_placer_opts placer_opts, // placement_delay_calc = std::make_shared(atom_ctx.nlist, atom_ctx.lookup, point_to_point_delay_cost); // timing_info = make_setup_timing_info(placement_delay_calc); // -// timing_info->update(); + timing_info.update(); // timing_info->set_warn_unconstrained(false); //Don't warn again about unconstrained nodes again during placement //Initial slack estimates From 58a380daf100f75290cfbf7ac70b3599d009434a Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 27 Jul 2018 20:59:42 -0700 Subject: [PATCH 088/116] Restore .members of t_pl_macro --- common/placer_vpr.cc | 6 ++++-- common/vpr_place.cpp.inc | 38 +++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 606493bdf7..a1aa90c39b 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -156,7 +156,9 @@ namespace vpr { int y_offset; int z_offset; }; - typedef std::vector t_pl_macro; + struct t_pl_macro { + std::vector members; + }; // timing_util.cpp float calculate_clb_net_pin_criticality(const SetupTimingInfo& timing_info, /*const ClusteredPinAtomPinsLookup& pin_lookup,*/ const PortRef& load, const NetInfo* net) @@ -200,7 +202,7 @@ namespace vpr { auto head = chain.front(); for (int z = 0; z < int(chain.size()); z++) { auto cell = chain.at(z); - entry.emplace_back(cell, 0, z / 8, z % 8); + entry.members.emplace_back(cell, 0, z / 8, z % 8); cell->attrs.emplace(npnr_ctx->id("carry_head"), head->name.str(npnr_ctx)); } macros.emplace(head, std::move(entry)); diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index 2ac2909af1..f73b32042d 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -1314,16 +1314,16 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i // HACK HACK HACK // Do not allow overlapping swaps if (x_swap_offset == 0 - && (y_swap_offset < int(pl_macros[imacro].size() / 8) - || (y_swap_offset == int(pl_macros[imacro].size() / 8) && z_swap_offset < int(pl_macros[imacro].size() % 8)))) { + && (y_swap_offset < int(pl_macros[imacro].members.size() / 8) + || (y_swap_offset == int(pl_macros[imacro].members.size() / 8) && z_swap_offset < int(pl_macros[imacro].members.size() % 8)))) { abort_swap = true; } - for (imember = 0; imember < int(pl_macros[imacro].size()) && abort_swap == false; imember++) { + for (imember = 0; imember < int(pl_macros[imacro].members.size()) && abort_swap == false; imember++) { // Gets the new from and to info for every block in the macro // cannot use the old from and to info - curr_b_from = pl_macros[imacro]/*.members*/[imember].blk_index; + curr_b_from = pl_macros[imacro].members[imember].blk_index; auto loc_from = npnr_ctx->getBelLocation(curr_b_from->bel); curr_x_from = loc_from.x /*place_ctx.block_locs[curr_b_from].x*/; @@ -1348,7 +1348,7 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i abort_swap = true; } else { auto bel_to = device_ctx.grid[curr_x_to][curr_y_to][curr_z_to]; - if (!npnr_ctx->isValidBelForCell(pl_macros[imacro][imember].blk_index, bel_to)) { + if (!npnr_ctx->isValidBelForCell(pl_macros[imacro].members[imember].blk_index, bel_to)) { abort_swap = true; } else { @@ -1358,7 +1358,7 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i if (cell_to->belStrength > STRENGTH_WEAK) { abort_swap = true; } - else if (!npnr_ctx->isValidBelForCell(cell_to, pl_macros[imacro][imember].blk_index->bel)) { + else if (!npnr_ctx->isValidBelForCell(cell_to, pl_macros[imacro].members[imember].blk_index->bel)) { abort_swap = true; } } @@ -2842,10 +2842,10 @@ static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, int macro_can_be_placed = true; // Check whether all the members can be placed - for (imember = 0; imember < int(pl_macros[imacro].size()); imember++) { - member_x = x + pl_macros[imacro]/*.members*/[imember].x_offset; - member_y = y + pl_macros[imacro]/*.members*/[imember].y_offset; - member_z = z + pl_macros[imacro]/*.members*/[imember].z_offset; + for (imember = 0; imember < int(pl_macros[imacro].members.size()); imember++) { + member_x = x + pl_macros[imacro].members[imember].x_offset; + member_y = y + pl_macros[imacro].members[imember].y_offset; + member_z = z + pl_macros[imacro].members[imember].z_offset; // Check whether the location could accept block of this type // Then check whether the location could still accomodate more blocks @@ -2855,7 +2855,7 @@ static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, && member_z < device_ctx.grid[member_x][member_y].size() && npnr_ctx->belTypeToId(npnr_ctx->getBelType(device_ctx.grid[member_x][member_y][member_z])).index == itype && npnr_ctx->checkBelAvail(device_ctx.grid[member_x][member_y][member_z]) - && npnr_ctx->isValidBelForCell(pl_macros[imacro][imember].blk_index, device_ctx.grid[member_x][member_y][member_z])) { + && npnr_ctx->isValidBelForCell(pl_macros[imacro].members[imember].blk_index, device_ctx.grid[member_x][member_y][member_z])) { // Can still accomodate blocks here, check the next position continue; } else { @@ -2898,13 +2898,13 @@ static int try_place_macro(int itype, /*int*/ Loc ipos, /*int*/ CellInfo* imacro // Place down the macro macro_placed = true; - for (imember = 0; imember < int(pl_macros[imacro].size()); imember++) { + for (imember = 0; imember < int(pl_macros[imacro].members.size()); imember++) { - member_x = x + pl_macros[imacro]/*.members*/[imember].x_offset; - member_y = y + pl_macros[imacro]/*.members*/[imember].y_offset; - member_z = z + pl_macros[imacro]/*.members*/[imember].z_offset; + member_x = x + pl_macros[imacro].members[imember].x_offset; + member_y = y + pl_macros[imacro].members[imember].y_offset; + member_z = z + pl_macros[imacro].members[imember].z_offset; - auto iblk = pl_macros[imacro]/*.members*/[imember].blk_index; + auto iblk = pl_macros[imacro].members[imember].blk_index; auto bel = grid[member_x][member_y][member_z]; npnr_ctx->bindBel(bel, iblk->name, STRENGTH_WEAK); @@ -2945,12 +2945,12 @@ static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std // Assume that all the blocks in the macro are of the same type itype = cluster_ctx.clb_nlist.block_type(blk_id).index; - if (free_locations[itype].size() < pl_macros[imacro].size()) { + if (free_locations[itype].size() < pl_macros[imacro].members.size()) { vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, "Initial placement failed.\n" "Could not place macro length %d with head block %s (#%zu); not enough free locations of type %s (#%d).\n" "VPR cannot auto-size for your circuit, please resize the FPGA manually.\n", - int(pl_macros[imacro].size()), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id->udata), cluster_ctx.clb_nlist.block_type(blk_id).c_str(npnr_ctx), itype); + int(pl_macros[imacro].members.size()), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id->udata), cluster_ctx.clb_nlist.block_type(blk_id).c_str(npnr_ctx), itype); } // Try to place the macro first, if can be placed - place them, otherwise try again @@ -2988,7 +2988,7 @@ static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std "Initial placement failed.\n" "Could not place macro length %d with head block %s (#%zu); not enough free locations of type %s (#%d).\n" "Please manually size the FPGA because VPR can't do this yet.\n", - int(pl_macros[imacro].size()), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id->udata), imacro->type.c_str(npnr_ctx), itype); + int(pl_macros[imacro].members.size()), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id->udata), imacro->type.c_str(npnr_ctx), itype); } } else { From 5339609349a9d291063e9eb89b62c55be2c31b17 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 27 Jul 2018 21:05:34 -0700 Subject: [PATCH 089/116] Revert to vtr::irand and vtr::frand --- common/placer_vpr.cc | 1 + common/vpr_place.cpp.inc | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index a1aa90c39b..325a17577a 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -248,6 +248,7 @@ namespace vpr { } int irand(int imax) { return npnr_ctx->rng(imax+1); } + float frand() { return npnr_ctx->rng() / float(0x3fffffff); } } #define OPEN -1 diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index f73b32042d..db4758b833 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -1591,7 +1591,7 @@ static /*ClusterBlockId*/ CellInfo* pick_from_block() { // it is inefficient to sample randomly from that container. // Instead, I've created a (global) randomly-accessible container // called npnr_cells that contains the same contents - auto b_from = npnr_cells.at(npnr_ctx->rng(npnr_cells.size())); + auto b_from = npnr_cells.at(vtr::irand(npnr_cells.size() - 1)); //Record it as tried tried_from_blocks.insert(b_from); @@ -1882,17 +1882,19 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, *pz_to = 0; if (int(grid.width() / 4) < rlx || int(grid.height() / 4) < rly || int(legal_pos[itype].size()) < active_area) { - int ipos = npnr_ctx->rng(legal_pos[itype].size()); + int ipos = vtr::irand(legal_pos[itype].size() - 1); *px_to = legal_pos[itype][ipos].x; *py_to = legal_pos[itype][ipos].y; *pz_to = legal_pos[itype][ipos].z; } else { - int x_rel = npnr_ctx->rng(std::max(0, max_x - min_x)+1); // +1 because rng() uses % - int y_rel = npnr_ctx->rng(std::max(0, max_y - min_y)+1); // +1 because rng() uses % + int x_rel = vtr::irand(std::max(0, max_x - min_x)); + int y_rel = vtr::irand(std::max(0, max_y - min_y)); *px_to = min_x + x_rel; *py_to = min_y + y_rel; + // Instead of computing z_to in the outer find_to function, + // do it here so we have a fully valid location if (!grid[*px_to][*py_to].empty()) { - *pz_to = npnr_ctx->rng(grid[*px_to][*py_to].size()); + *pz_to = vtr::irand(grid[*px_to][*py_to].size() - 1); } else { *px_to = -1; @@ -1912,7 +1914,7 @@ static e_swap_result assess_swap(float delta_c, float t) { if (delta_c <= 0) { /* Reduce variation in final solution due to round off */ - fnum = npnr_ctx->rng() / float(0x3fffffff); + fnum = vtr::frand(); accept = ACCEPTED; return (accept); @@ -1921,7 +1923,7 @@ static e_swap_result assess_swap(float delta_c, float t) { if (t == 0.) return (REJECTED); - fnum = npnr_ctx->rng() / float(0x3fffffff); + fnum = vtr::frand(); prob_fac = exp(-delta_c / t); if (prob_fac > fnum) { accept = ACCEPTED; From e002b8fffd1a7f45c35865e87b7e41eec6c71819 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 27 Jul 2018 21:25:49 -0700 Subject: [PATCH 090/116] For macros swaps, use a two pass approach to determine validity --- common/vpr_place.cpp.inc | 73 ++++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index db4758b833..f4fee0ea60 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -1311,15 +1311,10 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i y_swap_offset = y_to - y_from; z_swap_offset = z_to - z_from; - // HACK HACK HACK - // Do not allow overlapping swaps - if (x_swap_offset == 0 - && (y_swap_offset < int(pl_macros[imacro].members.size() / 8) - || (y_swap_offset == int(pl_macros[imacro].members.size() / 8) && z_swap_offset < int(pl_macros[imacro].members.size() % 8)))) { - abort_swap = true; - } - - for (imember = 0; imember < int(pl_macros[imacro].members.size()) && abort_swap == false; imember++) { + // Split existing for loop into two passes + // -- first checks validity of entire swap + // -- second calls setup_blocks_affected + for (imember = 0; imember < int(pl_macros[imacro].members.size()) && abort_swap == false; imember++) { // Gets the new from and to info for every block in the macro // cannot use the old from and to info @@ -1361,12 +1356,70 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i else if (!npnr_ctx->isValidBelForCell(cell_to, pl_macros[imacro].members[imember].blk_index->bel)) { abort_swap = true; } + + // Does not allow a swap with a macro yet + CellInfo *imacro; + get_imacro_from_iblk(&imacro, cell_to /*, pl_macros, num_pl_macros*/); + if (imacro) { + abort_swap = true; + } } } + //if (!abort_swap) + // abort_swap = setup_blocks_affected(curr_b_from, curr_x_to, curr_y_to, curr_z_to); + } + } + + for (imember = 0; imember < int(pl_macros[imacro].members.size()) && abort_swap == false; imember++) { + + // Gets the new from and to info for every block in the macro + // cannot use the old from and to info + curr_b_from = pl_macros[imacro].members[imember].blk_index; + + auto loc_from = npnr_ctx->getBelLocation(curr_b_from->bel); + curr_x_from = loc_from.x /*place_ctx.block_locs[curr_b_from].x*/; + curr_y_from = loc_from.y /*place_ctx.block_locs[curr_b_from].y*/; + curr_z_from = loc_from.z /*place_ctx.block_locs[curr_b_from].z*/; + + curr_x_to = curr_x_from + x_swap_offset; + curr_y_to = curr_y_from + y_swap_offset; + curr_z_to = curr_z_from + z_swap_offset; + +// //Make sure that the swap_to location is valid +// //It must be: +// // * chip, and +// // * match the correct block type +// // +// //Note that we need to explicitly check that the types match, since the device floorplan is not +// //(neccessarily) translationally invariant for an arbitrary macro +// if ( curr_x_to < 1 || curr_x_to >= int(device_ctx.grid.width()) +// || curr_y_to < 1 || curr_y_to >= int(device_ctx.grid.height()) +// || curr_z_to < 0 || curr_z_to >= int(device_ctx.grid[curr_x_to][curr_y_to].size()) +// || npnr_ctx->belTypeToId(npnr_ctx->getBelType(device_ctx.grid[curr_x_to][curr_y_to][curr_z_to])) != curr_b_from->type) { +// abort_swap = true; +// } else { +// auto bel_to = device_ctx.grid[curr_x_to][curr_y_to][curr_z_to]; +// if (!npnr_ctx->isValidBelForCell(pl_macros[imacro].members[imember].blk_index, bel_to)) { +// abort_swap = true; +// } +// else { +// auto cell_name = npnr_ctx->getBoundBelCell(bel_to); +// if (cell_name != IdString()) { +// auto cell_to = npnr_ctx->cells[cell_name].get(); +// if (cell_to->belStrength > STRENGTH_WEAK) { +// abort_swap = true; +// } +// else if (!npnr_ctx->isValidBelForCell(cell_to, pl_macros[imacro].members[imember].blk_index->bel)) { +// abort_swap = true; +// } +// } +// } + if (!abort_swap) abort_swap = setup_blocks_affected(curr_b_from, curr_x_to, curr_y_to, curr_z_to); - } + NPNR_ASSERT(!abort_swap); +// } } // Finish going through all the blocks in the macro } else { // This is not a macro - I could use the from and to info from before From 5f4fdd315c1a9639db190359abc6b43c8de626cf Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 27 Jul 2018 22:28:48 -0700 Subject: [PATCH 091/116] HACK to force macros swaps to take same Z value --- common/vpr_place.cpp.inc | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index f4fee0ea60..61a14c661f 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -1310,6 +1310,7 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i x_swap_offset = x_to - x_from; y_swap_offset = y_to - y_from; z_swap_offset = z_to - z_from; + NPNR_ASSERT(z_swap_offset == 0); // Split existing for loop into two passes // -- first checks validity of entire swap @@ -1862,11 +1863,17 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, continue; } // HACK HACK HACK - // Macro heads must start at z = 0 - else if (*pz_to > 0) { + // Since nextpnr operates with logic cells (not clusters) + // ensure that carry chains are swapped to identical z + // values, as find_affected_blocks() will take care of + // the rest + else { CellInfo *imacro; get_imacro_from_iblk(&imacro, cell_from /*, pl_macros, num_pl_macros*/); - if (imacro) *pz_to = 0; + if (imacro) { + auto loc_from = npnr_ctx->getBelLocation(cell_from->bel); + *pz_to = loc_from.z; + } } bel_to = grid[*px_to][*py_to][*pz_to]; From c0abd12c05638195a03e4279796bd6ec19033c0c Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 27 Jul 2018 22:30:58 -0700 Subject: [PATCH 092/116] Print out CPD, and cope with illegal Z when finding swaps --- common/vpr_place.cpp.inc | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index 61a14c661f..7a87169c74 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -346,7 +346,7 @@ void try_place(t_placer_opts placer_opts, delay_cost, new_delay_cost, place_delay_value, inverse_prev_bb_cost, inverse_prev_timing_cost, oldt, crit_exponent, first_rlim, final_rlim , inverse_delta_rlim; -// tatum::TimingPathInfo critical_path; + tatum::TimingPathInfo critical_path; // float sTNS = NAN; // float sWNS = NAN; @@ -427,7 +427,7 @@ void try_place(t_placer_opts placer_opts, //Initial slack estimates load_criticalities(timing_info, crit_exponent /*, netlist_pin_lookup*/); -// critical_path = timing_info->least_slack_critical_path(); + critical_path = timing_info.least_slack_critical_path(); // // //Write out the initial timing echo file // if(isEchoFileEnabled(E_ECHO_INITIAL_PLACEMENT_TIMING_GRAPH)) { @@ -655,10 +655,11 @@ void try_place(t_placer_opts placer_opts, update_t(&t, rlim, success_rat /*, annealing_sched*/); // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// critical_path = timing_info->least_slack_critical_path(); + if (placer_opts.enable_timing_computations) { + critical_path = timing_info.least_slack_critical_path(); // sTNS = timing_info->setup_total_negative_slack(); // sWNS = timing_info->setup_worst_negative_slack(); -// } + } vtr::printf_info("%7.3f " "%7.4f %10.4f %-10.5g %-10.5g " @@ -667,7 +668,7 @@ void try_place(t_placer_opts placer_opts, "%9d %6.3f\n", oldt, stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, - place_delay_value, /*1e9*critical_path.delay()*/ 0., /*1e9*sTNS*/ 0., /*1e9*sWNS*/ 0., + place_delay_value, /*1e9**/critical_path.delay(), /*1e9*sTNS*/ 0., /*1e9*sWNS*/ 0., success_rat, std_dev, rlim, crit_exponent, tot_iter, t / oldt); @@ -744,10 +745,11 @@ void try_place(t_placer_opts placer_opts, std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// critical_path = timing_info->least_slack_critical_path(); + if (placer_opts.enable_timing_computations) { + critical_path = timing_info->least_slack_critical_path(); // sTNS = timing_info->setup_total_negative_slack(); // sWNS = timing_info->setup_worst_negative_slack(); -// } + } vtr::printf_info("%7.3f " "%7.4f %10.4f %-10.5g %-10.5g " @@ -756,7 +758,7 @@ void try_place(t_placer_opts placer_opts, "%9d %6.3f\n", t, stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, - place_delay_value, /*1e9*critical_path.delay()*/0., /*1e9*sTNS*/0., /*1e9*sWNS*/0., + place_delay_value, /*1e9**/critical_path.delay(), /*1e9*sTNS*/0., /*1e9*sWNS*/0., success_rat, std_dev, rlim, crit_exponent, tot_iter, 0.); @@ -1872,6 +1874,10 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, get_imacro_from_iblk(&imacro, cell_from /*, pl_macros, num_pl_macros*/); if (imacro) { auto loc_from = npnr_ctx->getBelLocation(cell_from->bel); + if (loc_from.z >= grid[*px_to][*py_to].size()) { + is_legal = false; + continue; + } *pz_to = loc_from.z; } } From e3e32bcf83adcd3d77385a77a8fbda059efe3b50 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 27 Jul 2018 22:31:21 -0700 Subject: [PATCH 093/116] compute_fmax to return minimum slack --- common/timing.cc | 3 ++- common/timing.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index f720b77211..37d124270f 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -225,7 +225,7 @@ void update_budget(Context *ctx) } } -void compute_fmax(Context *ctx, bool print_fmax, bool print_path) +delay_t compute_fmax(Context *ctx, bool print_fmax, bool print_path) { delay_t default_slack = delay_t(1.0e12 / ctx->target_freq); PortRefList crit_path; @@ -267,6 +267,7 @@ void compute_fmax(Context *ctx, bool print_fmax, bool print_path) } if (print_fmax) log_info("estimated Fmax = %.2f MHz\n", 1e6 / (default_slack - min_slack)); + return min_slack; } NEXTPNR_NAMESPACE_END diff --git a/common/timing.h b/common/timing.h index a1e12ab3ac..a7c50b4ad4 100644 --- a/common/timing.h +++ b/common/timing.h @@ -30,7 +30,7 @@ void assign_budget(Context *ctx); // Evenly redistribute the total path slack amongst all sinks on each path void update_budget(Context *ctx); -void compute_fmax(Context *ctx, bool print_fmax = false, bool print_path = false); +delay_t compute_fmax(Context *ctx, bool print_fmax = false, bool print_path = false); NEXTPNR_NAMESPACE_END From 43e557af5c5c60dcbfc7b0fd69d95463cfa1bc66 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 27 Jul 2018 22:31:36 -0700 Subject: [PATCH 094/116] Add stubs for tatum::TimingPathInfo --- common/placer_vpr.cc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 325a17577a..d843bca67d 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -108,6 +108,15 @@ namespace vpr { const float td_place_exp_last = 8.0; const float timing_tradeoff = 0.5; } placer_opts; + + namespace tatum { + struct TimingPathInfo { + TimingPathInfo(float delay=0) : _delay(delay) {} + float delay() { return _delay; } + float _delay; + }; + }; + struct SetupTimingInfo { delay_t worst_slack; delay_t max_req; @@ -147,6 +156,12 @@ namespace vpr { max_req = delay_t(1.0e12 / npnr_ctx->target_freq); #endif } + tatum::TimingPathInfo least_slack_critical_path() + { + delay_t default_slack = delay_t(1.0e12 / npnr_ctx->target_freq); + delay_t min_slack = compute_fmax(npnr_ctx); + return tatum::TimingPathInfo(npnr_ctx->getDelayNS(default_slack - min_slack)); + } }; static SetupTimingInfo timing_info; struct t_pl_macro_member { From fd334cd756131f905a20842403ee231aaf6e6f04 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 27 Jul 2018 22:32:04 -0700 Subject: [PATCH 095/116] Typo --- common/vpr_place.cpp.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index 7a87169c74..9111fa8c13 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -746,7 +746,7 @@ void try_place(t_placer_opts placer_opts, // if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { if (placer_opts.enable_timing_computations) { - critical_path = timing_info->least_slack_critical_path(); + critical_path = timing_info.least_slack_critical_path(); // sTNS = timing_info->setup_total_negative_slack(); // sWNS = timing_info->setup_worst_negative_slack(); } From 15474e7cd6bf1a17f7d13ad26c0af2c0151acca5 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 27 Jul 2018 23:10:40 -0700 Subject: [PATCH 096/116] Fix warning --- common/vpr_place.cpp.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/vpr_place.cpp.inc b/common/vpr_place.cpp.inc index 9111fa8c13..7e88f9a2e4 100644 --- a/common/vpr_place.cpp.inc +++ b/common/vpr_place.cpp.inc @@ -1874,7 +1874,7 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, get_imacro_from_iblk(&imacro, cell_from /*, pl_macros, num_pl_macros*/); if (imacro) { auto loc_from = npnr_ctx->getBelLocation(cell_from->bel); - if (loc_from.z >= grid[*px_to][*py_to].size()) { + if (size_t(loc_from.z) >= grid[*px_to][*py_to].size()) { is_legal = false; continue; } From cf9bfaf63f26e2470e005bcafd9e2ecae171af3f Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 28 Jul 2018 12:15:17 -0700 Subject: [PATCH 097/116] Move VPR code from common/ to vpr/ --- CMakeLists.txt | 2 +- common/placer_vpr.cc | 31 ++++++++++--------- {common => vpr/base}/vpr_types.h | 0 .../vpr_place.cpp.inc => vpr/place/place.cpp | 0 .../place/timing_place.cpp | 0 5 files changed, 18 insertions(+), 15 deletions(-) rename {common => vpr/base}/vpr_types.h (100%) rename common/vpr_place.cpp.inc => vpr/place/place.cpp (100%) rename common/vpr_timing_place.cpp.inc => vpr/place/timing_place.cpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 55e577636d..3c39413767 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,7 +157,7 @@ if (BUILD_PYTHON) endif () endif() -include_directories(common/ json/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) +include_directories(common/ json/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS} .) aux_source_directory(common/ COMMON_SRC_FILES) aux_source_directory(json/ JSON_PARSER_FILES) set(COMMON_FILES ${COMMON_SRC_FILES} ${JSON_PARSER_FILES}) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index d843bca67d..3135f12091 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -52,18 +52,21 @@ namespace vpr { static Context* npnr_ctx = NULL; std::vector npnr_cells; + // base/device_grid.h struct DeviceGrid { inline size_t width() const { return _bels.size(); } inline size_t height() const { return _bels.front().size(); } inline const std::vector>& operator[](size_t x) { return _bels.at(x); } std::vector>> _bels; }; + // base/netlist_fwd.h enum PinType { DRIVER = PortType::PORT_OUT, SINK = PortType::PORT_IN, }; - static struct { + // base/globals.h + static struct VprContext { struct t_clustering { struct { std::unordered_map>& nets() const { return npnr_ctx->nets; } @@ -97,9 +100,11 @@ namespace vpr { t_device& operator()() { return *this; } } device; } g_vpr_ctx; + // base/vpr_types.h static struct t_annealing_sched { const float inner_num = 10; } annealing_sched; + // base/vpr_types.h static struct t_placer_opts { bool enable_timing_computations; const int inner_loop_recompute_divider = 0; @@ -108,7 +113,7 @@ namespace vpr { const float td_place_exp_last = 8.0; const float timing_tradeoff = 0.5; } placer_opts; - + // libtatum namespace tatum { struct TimingPathInfo { TimingPathInfo(float delay=0) : _delay(delay) {} @@ -116,12 +121,11 @@ namespace vpr { float _delay; }; }; - + // timing/timing_info.h struct SetupTimingInfo { delay_t worst_slack; delay_t max_req; void update() { -#if 1 update_budget(npnr_ctx); worst_slack = std::numeric_limits::max(); @@ -154,7 +158,6 @@ namespace vpr { } max_req = delay_t(1.0e12 / npnr_ctx->target_freq); -#endif } tatum::TimingPathInfo least_slack_critical_path() { @@ -164,6 +167,7 @@ namespace vpr { } }; static SetupTimingInfo timing_info; + // place/place_macro.h struct t_pl_macro_member { t_pl_macro_member(CellInfo* blk_index, int x_offset, int y_offset, int z_offset) : blk_index(blk_index), x_offset(x_offset), y_offset(y_offset), z_offset(z_offset) {} CellInfo* blk_index; @@ -171,16 +175,16 @@ namespace vpr { int y_offset; int z_offset; }; + // place/place_macro.h struct t_pl_macro { std::vector members; }; - // timing_util.cpp + // timing/timing_util.h float calculate_clb_net_pin_criticality(const SetupTimingInfo& timing_info, /*const ClusteredPinAtomPinsLookup& pin_lookup,*/ const PortRef& load, const NetInfo* net) { NPNR_ASSERT(npnr_ctx->timing_driven); -#if 1 bool driver_gb; CellInfo *driver_cell = net->driver.cell; if (!driver_cell) @@ -204,12 +208,9 @@ namespace vpr { crit = std::max(0., crit); crit = std::min(1., crit); return crit; -#else - return 1; -#endif } - // place_macro.cpp + // place/place_macro.cpp int alloc_and_load_placement_macros(/*t_direct_inf* directs, int num_directs, t_pl_macro ** */ std::unordered_map ¯os) { for (auto &chain : carries) { @@ -225,6 +226,7 @@ namespace vpr { return macros.size(); } + // place/place_macro.cpp void get_imacro_from_iblk(/*int **/ CellInfo **imacro, /*ClusterBlockId*/ CellInfo *iblk /*, t_pl_macro *macros, int num_macros*/) { auto it = iblk->attrs.find(npnr_ctx->id("carry_head")); if (it != iblk->attrs.end()) @@ -266,11 +268,12 @@ namespace vpr { float frand() { return npnr_ctx->rng() / float(0x3fffffff); } } + // libarchfpga #define OPEN -1 - #include "vpr_types.h" - #include "vpr_timing_place.cpp.inc" - #include "vpr_place.cpp.inc" + #include "vpr/base/vpr_types.h" + #include "vpr/place/timing_place.cpp" + #include "vpr/place/place.cpp" } NEXTPNR_NAMESPACE_BEGIN diff --git a/common/vpr_types.h b/vpr/base/vpr_types.h similarity index 100% rename from common/vpr_types.h rename to vpr/base/vpr_types.h diff --git a/common/vpr_place.cpp.inc b/vpr/place/place.cpp similarity index 100% rename from common/vpr_place.cpp.inc rename to vpr/place/place.cpp diff --git a/common/vpr_timing_place.cpp.inc b/vpr/place/timing_place.cpp similarity index 100% rename from common/vpr_timing_place.cpp.inc rename to vpr/place/timing_place.cpp From 0fa65a8e03a38dbff94f88a8c2d9596bfbcf3a31 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 28 Jul 2018 12:31:37 -0700 Subject: [PATCH 098/116] Put some more things back in the right place --- common/placer_vpr.cc | 31 +++++----- vpr/base/vpr_types.h | 134 ++++++++++++++++++++--------------------- vpr/place/place.cpp | 138 ++++++++++++++++++++----------------------- 3 files changed, 144 insertions(+), 159 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 3135f12091..f09e41ccfe 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -100,19 +100,6 @@ namespace vpr { t_device& operator()() { return *this; } } device; } g_vpr_ctx; - // base/vpr_types.h - static struct t_annealing_sched { - const float inner_num = 10; - } annealing_sched; - // base/vpr_types.h - static struct t_placer_opts { - bool enable_timing_computations; - const int inner_loop_recompute_divider = 0; - const int recompute_crit_iter = 1; - const float td_place_exp_first = 1.0; - const float td_place_exp_last = 8.0; - const float timing_tradeoff = 0.5; - } placer_opts; // libtatum namespace tatum { struct TimingPathInfo { @@ -271,7 +258,6 @@ namespace vpr { // libarchfpga #define OPEN -1 - #include "vpr/base/vpr_types.h" #include "vpr/place/timing_place.cpp" #include "vpr/place/place.cpp" } @@ -335,15 +321,26 @@ class VPRPlacer NetInfo *ni = net.second.get(); ni->udata = net_idx++; } - - vpr::placer_opts.enable_timing_computations = ctx->timing_driven; } bool place() { log_break(); - vpr::try_place(vpr::placer_opts, vpr::annealing_sched); + vpr::t_placer_opts placer_opts; + placer_opts.place_algorithm = vpr::PATH_TIMING_DRIVEN_PLACE; + placer_opts.enable_timing_computations = ctx->timing_driven; + placer_opts.inner_loop_recompute_divider = 0; + placer_opts.recompute_crit_iter = 1; + placer_opts.td_place_exp_first = 1.0; + placer_opts.td_place_exp_last = 8.0; + placer_opts.timing_tradeoff = 0.5; + + vpr::t_annealing_sched annealing_sched; + annealing_sched.type = vpr::AUTO_SCHED; + annealing_sched.inner_num = 10; + + vpr::try_place(placer_opts, annealing_sched); // Final post-pacement validitiy check for (auto bel : ctx->getBels()) { diff --git a/vpr/base/vpr_types.h b/vpr/base/vpr_types.h index 246863b5e3..2d21699173 100644 --- a/vpr/base/vpr_types.h +++ b/vpr/base/vpr_types.h @@ -551,17 +551,17 @@ // std::string SDCFile; // std::string slack_definition; //}; -// -///*************************************************************************** -// * Placement and routing data types -// ****************************************************************************/ -// -///* Timing data structures end */ -//enum sched_type { -// AUTO_SCHED, USER_SCHED -//}; -///* Annealing schedule */ -// + +/*************************************************************************** + * Placement and routing data types + ****************************************************************************/ + +/* Timing data structures end */ +enum sched_type { + AUTO_SCHED, USER_SCHED +}; +/* Annealing schedule */ + //enum pic_type { // NO_PICTURE, PLACEMENT, ROUTING //}; @@ -743,65 +743,65 @@ struct t_bb { // std::string device_layout; // std::string hmetis_input_file; //}; -// -///* Annealing schedule information for the placer. The schedule type * -// * is either USER_SCHED or AUTO_SCHED. Inner_num is multiplied by * -// * num_blocks^4/3 to find the number of moves per temperature. The * -// * remaining information is used only for USER_SCHED, and have the * -// * obvious meanings. */ -//struct t_annealing_sched { -// enum sched_type type; -// float inner_num; -// float init_t; -// float alpha_t; -// float exit_t; -//}; -// -///* Various options for the placer. * -// * place_algorithm: BOUNDING_BOX_PLACE or PATH_TIMING_DRIVEN_PLACE * -// * timing_tradeoff: When TIMING_DRIVEN_PLACE mode, what is the tradeoff * -// * timing driven and BOUNDING_BOX_PLACE. * -// * place_cost_exp: Power to which denominator is raised for linear_cong. * -// * place_chan_width: The channel width assumed if only one placement is * -// * performed. * -// * pad_loc_type: Are pins FREE, fixed randomly, or fixed from a file. * -// * pad_loc_file: File to read pin locations form if pad_loc_type * -// * is USER. * -// * place_freq: Should the placement be skipped, done once, or done for each * -// * channel width in the binary search. * -// * recompute_crit_iter: how many temperature stages pass before we recompute * -// * criticalities based on average point to point delay * -// * enable_timing_computations: in bounding_box mode, normally, timing * -// * information is not produced, this causes the information * -// * to be computed. in *_TIMING_DRIVEN modes, this has no effect* -// * inner_loop_crit_divider: (move_lim/inner_loop_crit_divider) determines how* -// * many inner_loop iterations pass before a recompute of * -// * criticalities is done. * -// * td_place_exp_first: exponent that is used on the timing_driven criticlity * -// * it is the value that the exponent starts at. * -// * td_place_exp_last: value that the criticality exponent will be at the end * -// * doPlacement: true if placement is supposed to be done in the CAD flow, false otherwise */ -//enum e_place_algorithm { -// BOUNDING_BOX_PLACE, PATH_TIMING_DRIVEN_PLACE -//}; -// -//struct t_placer_opts { -// enum e_place_algorithm place_algorithm; -// float timing_tradeoff; -// float place_cost_exp; -// int place_chan_width; + +/* Annealing schedule information for the placer. The schedule type * + * is either USER_SCHED or AUTO_SCHED. Inner_num is multiplied by * + * num_blocks^4/3 to find the number of moves per temperature. The * + * remaining information is used only for USER_SCHED, and have the * + * obvious meanings. */ +struct t_annealing_sched { + enum sched_type type; + float inner_num; + float init_t; + float alpha_t; + float exit_t; +}; + +/* Various options for the placer. * + * place_algorithm: BOUNDING_BOX_PLACE or PATH_TIMING_DRIVEN_PLACE * + * timing_tradeoff: When TIMING_DRIVEN_PLACE mode, what is the tradeoff * + * timing driven and BOUNDING_BOX_PLACE. * + * place_cost_exp: Power to which denominator is raised for linear_cong. * + * place_chan_width: The channel width assumed if only one placement is * + * performed. * + * pad_loc_type: Are pins FREE, fixed randomly, or fixed from a file. * + * pad_loc_file: File to read pin locations form if pad_loc_type * + * is USER. * + * place_freq: Should the placement be skipped, done once, or done for each * + * channel width in the binary search. * + * recompute_crit_iter: how many temperature stages pass before we recompute * + * criticalities based on average point to point delay * + * enable_timing_computations: in bounding_box mode, normally, timing * + * information is not produced, this causes the information * + * to be computed. in *_TIMING_DRIVEN modes, this has no effect* + * inner_loop_crit_divider: (move_lim/inner_loop_crit_divider) determines how* + * many inner_loop iterations pass before a recompute of * + * criticalities is done. * + * td_place_exp_first: exponent that is used on the timing_driven criticlity * + * it is the value that the exponent starts at. * + * td_place_exp_last: value that the criticality exponent will be at the end * + * doPlacement: true if placement is supposed to be done in the CAD flow, false otherwise */ +enum e_place_algorithm { + BOUNDING_BOX_PLACE, PATH_TIMING_DRIVEN_PLACE +}; + +struct t_placer_opts { + enum e_place_algorithm place_algorithm; + float timing_tradeoff; + float place_cost_exp; + int place_chan_width; // enum e_pad_loc_type pad_loc_type; -// std::string pad_loc_file; + std::string pad_loc_file; // enum pfreq place_freq; -// int recompute_crit_iter; -// bool enable_timing_computations; -// int inner_loop_recompute_divider; -// float td_place_exp_first; -// int seed; -// float td_place_exp_last; + int recompute_crit_iter; + bool enable_timing_computations; + int inner_loop_recompute_divider; + float td_place_exp_first; + int seed; + float td_place_exp_last; // e_stage_action doPlacement; -//}; -// +}; + ///* All the parameters controlling the router's operation are in this * // * structure. * // * first_iter_pres_fac: Present sharing penalty factor used for the * diff --git a/vpr/place/place.cpp b/vpr/place/place.cpp index 7e88f9a2e4..01f4aa64a0 100644 --- a/vpr/place/place.cpp +++ b/vpr/place/place.cpp @@ -9,8 +9,8 @@ //#include "vtr_util.h" //#include "vtr_random.h" //#include "vtr_matrix.h" -// -//#include "vpr_types.h" + +#include "vpr/base/vpr_types.h" //#include "vpr_error.h" //#include "vpr_utils.h" // @@ -226,30 +226,30 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, float rlim, - /*enum e_place_algorithm place_algorithm,*/ float timing_tradeoff, + enum e_place_algorithm place_algorithm, float timing_tradeoff, float inverse_prev_bb_cost, float inverse_prev_timing_cost, float *delay_cost); static /*ClusterBlockId*/ CellInfo* pick_from_block(); static void check_place(float bb_cost, float timing_cost, - /*enum e_place_algorithm place_algorithm,*/ + enum e_place_algorithm place_algorithm, float delay_cost); static float starting_t(float *cost_ptr, float *bb_cost_ptr, float *timing_cost_ptr, - /*t_annealing_sched annealing_sched,*/ int max_moves, float rlim, - /*enum e_place_algorithm place_algorithm,*/ float timing_tradeoff, + t_annealing_sched annealing_sched, int max_moves, float rlim, + enum e_place_algorithm place_algorithm, float timing_tradeoff, float inverse_prev_bb_cost, float inverse_prev_timing_cost, float *delay_cost_ptr); -static void update_t(float *t, float rlim, float success_rat/*, - t_annealing_sched annealing_sched*/); +static void update_t(float *t, float rlim, float success_rat, + t_annealing_sched annealing_sched); static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid); -static int exit_crit(float t, float cost /*, - t_annealing_sched annealing_sched*/); +static int exit_crit(float t, float cost, + t_annealing_sched annealing_sched); static int count_connections(); @@ -282,7 +282,7 @@ static void get_non_updateable_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coo static void update_bb(/*ClusterNetId*/ NetInfo* net_id, t_bb *bb_coord_new, t_bb *bb_edge_new, int xold, int yold, int xnew, int ynew); -static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorithm,*/ float& bb_delta_c, float& timing_delta_c, float& delay_delta_c); +static int find_affected_nets_and_update_costs(e_place_algorithm place_algorithm, float& bb_delta_c, float& timing_delta_c, float& delay_delta_c); static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_affected_nets); @@ -373,8 +373,8 @@ void try_place(t_placer_opts placer_opts, num_swap_aborted = 0; num_ts_called = 0; - if (/*placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE - ||*/ placer_opts.enable_timing_computations) { + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + || placer_opts.enable_timing_computations) { /*do this before the initial placement to avoid messing up the initial placement */ alloc_lookups_and_criticalities(/*chan_width_dist, router_opts, det_routing_arch, segment_inf, directs, num_directs*/); @@ -399,7 +399,7 @@ void try_place(t_placer_opts placer_opts, /* Gets initial cost and loads bounding boxes. */ - if (/*placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE ||*/ placer_opts.enable_timing_computations) { + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE || placer_opts.enable_timing_computations) { bb_cost = comp_bb_cost(NORMAL); crit_exponent = placer_opts.td_place_exp_first; /*this will be modified when rlim starts to change */ @@ -482,7 +482,7 @@ void try_place(t_placer_opts placer_opts, } //Sanity check that initial placement is legal - check_place(bb_cost, timing_cost, /*placer_opts.place_algorithm,*/ delay_cost); + check_place(bb_cost, timing_cost, placer_opts.place_algorithm, delay_cost); //Initial pacement statistics vtr::printf_info("Initial placement cost: %g bb_cost: %g td_cost: %g delay_cost: %g\n", @@ -563,18 +563,17 @@ void try_place(t_placer_opts placer_opts, inverse_delta_rlim = 1 / (first_rlim - final_rlim); t = starting_t(&cost, &bb_cost, &timing_cost, - /*annealing_sched,*/ move_lim, rlim, - /*placer_opts.place_algorithm,*/ placer_opts.timing_tradeoff, + annealing_sched, move_lim, rlim, + placer_opts.place_algorithm, placer_opts.timing_tradeoff, inverse_prev_bb_cost, inverse_prev_timing_cost, &delay_cost); tot_iter = 0; moves_since_cost_recompute = 0; /* Outer loop of the simmulated annealing begins */ - while (exit_crit(t, cost /*, annealing_sched*/) == 0) { + while (exit_crit(t, cost, annealing_sched) == 0) { -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - if (placer_opts.enable_timing_computations) { + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { cost = 1; } @@ -613,8 +612,7 @@ void try_place(t_placer_opts placer_opts, } bb_cost = new_bb_cost; -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - if (placer_opts.enable_timing_computations) { + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { comp_td_costs(&new_timing_cost, &new_delay_cost); if (fabs(new_timing_cost - timing_cost) > timing_cost * ERROR_TOL) { vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, @@ -629,8 +627,7 @@ void try_place(t_placer_opts placer_opts, timing_cost = new_timing_cost; } -// if (placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { - else { + if (placer_opts.place_algorithm == BOUNDING_BOX_PLACE) { cost = new_bb_cost; } moves_since_cost_recompute = 0; @@ -652,10 +649,9 @@ void try_place(t_placer_opts placer_opts, std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); oldt = t; /* for finding and printing alpha. */ - update_t(&t, rlim, success_rat /*, annealing_sched*/); + update_t(&t, rlim, success_rat, annealing_sched); -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - if (placer_opts.enable_timing_computations) { + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { critical_path = timing_info.least_slack_critical_path(); // sTNS = timing_info->setup_total_negative_slack(); // sWNS = timing_info->setup_worst_negative_slack(); @@ -689,8 +685,7 @@ void try_place(t_placer_opts placer_opts, // update_screen(ScreenUpdatePriority::MINOR, msg, PLACEMENT, timing_info); update_rlim(&rlim, success_rat, device_ctx.grid); -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - if (placer_opts.enable_timing_computations) { + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { crit_exponent = (1 - (rlim - final_rlim) * inverse_delta_rlim) * (placer_opts.td_place_exp_last - placer_opts.td_place_exp_first) + placer_opts.td_place_exp_first; @@ -744,8 +739,7 @@ void try_place(t_placer_opts placer_opts, std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - if (placer_opts.enable_timing_computations) { + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { critical_path = timing_info.least_slack_critical_path(); // sTNS = timing_info->setup_total_negative_slack(); // sWNS = timing_info->setup_worst_negative_slack(); @@ -772,7 +766,7 @@ void try_place(t_placer_opts placer_opts, } #endif - check_place(bb_cost, timing_cost, /*placer_opts.place_algorithm,*/ delay_cost); + check_place(bb_cost, timing_cost, placer_opts.place_algorithm, delay_cost); //Some stats vtr::printf_info("\n"); @@ -860,8 +854,8 @@ void try_place(t_placer_opts placer_opts, vtr::printf_info("\tSwaps aborted : %*d (%4.1f %%)\n", num_swap_print_digits, num_swap_aborted, 100*abort_rate); free_placement_structs(placer_opts); - if (/*placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE - ||*/ placer_opts.enable_timing_computations) { + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + || placer_opts.enable_timing_computations) { #ifdef ENABLE_CLASSIC_VPR_STA free_timing_graph(slacks); @@ -886,8 +880,7 @@ static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, #endif SetupTimingInfo& timing_info) { -// if (placer_opts.place_algorithm != PATH_TIMING_DRIVEN_PLACE) - if (!placer_opts.enable_timing_computations) + if (placer_opts.place_algorithm != PATH_TIMING_DRIVEN_PLACE) return; /*at each temperature change we update these values to be used */ @@ -951,7 +944,7 @@ static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, /* Inner loop begins */ for (inner_iter = 0; inner_iter < move_lim; inner_iter++) { e_swap_result swap_result = try_swap(t, cost, bb_cost, timing_cost, rlim, - /*placer_opts.place_algorithm,*/ placer_opts.timing_tradeoff, + placer_opts.place_algorithm, placer_opts.timing_tradeoff, inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost); if (swap_result == ACCEPTED) { @@ -972,8 +965,7 @@ static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, } -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - if (placer_opts.enable_timing_computations) { + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { /* Do we want to re-timing analyze the circuit to get updated slack and criticality values? * We do this only once in a while, since it is expensive. @@ -1065,14 +1057,14 @@ static void update_rlim(float *rlim, float success_rat, const DeviceGrid& grid) } /* Update the temperature according to the annealing schedule selected. */ -static void update_t(float *t, float rlim, float success_rat /*, - t_annealing_sched annealing_sched*/) { +static void update_t(float *t, float rlim, float success_rat, + t_annealing_sched annealing_sched) { /* float fac; */ -// if (annealing_sched.type == USER_SCHED) { -// *t = annealing_sched.alpha_t * (*t); -// } else { /* AUTO_SCHED */ + if (annealing_sched.type == USER_SCHED) { + *t = annealing_sched.alpha_t * (*t); + } else { /* AUTO_SCHED */ if (success_rat > 0.96) { *t = (*t) * 0.5; } else if (success_rat > 0.8) { @@ -1082,21 +1074,21 @@ static void update_t(float *t, float rlim, float success_rat /*, } else { *t = (*t) * 0.8; } -// } + } } -static int exit_crit(float t, float cost/*, - t_annealing_sched annealing_sched*/) { +static int exit_crit(float t, float cost, + t_annealing_sched annealing_sched) { /* Return 1 when the exit criterion is met. */ -// if (annealing_sched.type == USER_SCHED) { -// if (t < annealing_sched.exit_t) { -// return (1); -// } else { -// return (0); -// } -// } + if (annealing_sched.type == USER_SCHED) { + if (t < annealing_sched.exit_t) { + return (1); + } else { + return (0); + } + } auto& cluster_ctx = g_vpr_ctx.clustering(); @@ -1115,8 +1107,8 @@ static int exit_crit(float t, float cost/*, static float starting_t(float *cost_ptr, float *bb_cost_ptr, float *timing_cost_ptr, - /*t_annealing_sched annealing_sched,*/ int max_moves, float rlim, - /*enum e_place_algorithm place_algorithm,*/ float timing_tradeoff, + t_annealing_sched annealing_sched, int max_moves, float rlim, + enum e_place_algorithm place_algorithm, float timing_tradeoff, float inverse_prev_bb_cost, float inverse_prev_timing_cost, float *delay_cost_ptr) { @@ -1125,8 +1117,8 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, int i, num_accepted, move_lim; double std_dev, av, sum_of_squares; /* Double important to avoid round off */ -// if (annealing_sched.type == USER_SCHED) -// return (annealing_sched.init_t); + if (annealing_sched.type == USER_SCHED) + return (annealing_sched.init_t); auto& cluster_ctx = g_vpr_ctx.clustering(); @@ -1140,7 +1132,7 @@ static float starting_t(float *cost_ptr, float *bb_cost_ptr, for (i = 0; i < move_lim; i++) { e_swap_result swap_result = try_swap(HUGE_POSITIVE_FLOAT, cost_ptr, bb_cost_ptr, timing_cost_ptr, rlim, - /*place_algorithm,*/ timing_tradeoff, + place_algorithm, timing_tradeoff, inverse_prev_bb_cost, inverse_prev_timing_cost, delay_cost_ptr); if (swap_result == ACCEPTED) { @@ -1437,7 +1429,7 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timing_cost, float rlim, - /*enum e_place_algorithm place_algorithm,*/ float timing_tradeoff, + enum e_place_algorithm place_algorithm, float timing_tradeoff, float inverse_prev_bb_cost, float inverse_prev_timing_cost, float *delay_cost) { @@ -1511,10 +1503,9 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin if (abort_swap == false) { // Find all the nets affected by this swap and update thier bounding box - /*int num_nets_affected =*/ find_affected_nets_and_update_costs(/*place_algorithm,*/ bb_delta_c, timing_delta_c, delay_delta_c); + /*int num_nets_affected =*/ find_affected_nets_and_update_costs(place_algorithm, bb_delta_c, timing_delta_c, delay_delta_c); -// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - if (placer_opts.enable_timing_computations) { + if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { /*in this case we redefine delta_c as a combination of timing and bb. * *additionally, we normalize all values, therefore delta_c is in * *relation to 1*/ @@ -1532,8 +1523,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin *cost = *cost + delta_c; *bb_cost = *bb_cost + bb_delta_c; -// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - if (placer_opts.enable_timing_computations) { + if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { /*update the point_to_point_timing_cost and point_to_point_delay_cost * values from the temporary values */ *timing_cost = *timing_cost + timing_delta_c; @@ -1668,7 +1658,7 @@ static /*ClusterBlockId*/ CellInfo* pick_from_block() { //and updates their bounding box. // //Returns the number of affected nets. -static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorithm,*/ float& bb_delta_c, float& timing_delta_c, float& delay_delta_c) { +static int find_affected_nets_and_update_costs(e_place_algorithm place_algorithm, float& bb_delta_c, float& timing_delta_c, float& delay_delta_c) { VTR_ASSERT_SAFE(bb_delta_c == 0.); VTR_ASSERT_SAFE(timing_delta_c == 0.); VTR_ASSERT_SAFE(delay_delta_c == 0.); @@ -1701,8 +1691,7 @@ static int find_affected_nets_and_update_costs(/*e_place_algorithm place_algorit //once per net, not once per pin. update_net_bb(net_id, /*iblk, blk, blk_pin,*/ blk, bel); -// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - if (placer_opts.enable_timing_computations) { + if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { //Determine the change in timing costs if required update_td_delta_costs(net_id, blk_pin, timing_delta_c, delay_delta_c, blk); } @@ -2231,8 +2220,8 @@ static void free_placement_structs(t_placer_opts placer_opts) { // free_legal_placements(); // free_fast_cost_update(); - if (/*placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE - ||*/ placer_opts.enable_timing_computations) { + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + || placer_opts.enable_timing_computations) { for (const auto& net : cluster_ctx.clb_nlist.nets()) { auto net_id = net.second.get(); @@ -2298,8 +2287,8 @@ static void alloc_and_load_placement_structs( // max_pins_per_clb = max(max_pins_per_clb, device_ctx.block_types[i].num_pins); // } // - if (/*placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE - ||*/ placer_opts.enable_timing_computations) { + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + || placer_opts.enable_timing_computations) { /* Allocate structures associated with timing driven placement */ /* [0..cluster_ctx.clb_nlist.nets().size()-1][1..num_pins-1] */ point_to_point_delay_cost.resize(num_nets); @@ -3343,7 +3332,7 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, //} static void check_place(float bb_cost, float timing_cost, - /*enum e_place_algorithm place_algorithm,*/ + enum e_place_algorithm place_algorithm, float delay_cost) { // /* Checks that the placement has not confused our data structures. * @@ -3369,8 +3358,7 @@ static void check_place(float bb_cost, float timing_cost, error++; } -// if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - if (placer_opts.enable_timing_computations) { + if (place_algorithm == PATH_TIMING_DRIVEN_PLACE) { comp_td_costs(&timing_cost_check, &delay_cost_check); //vtr::printf_info("timing_cost recomputed from scratch: %g\n", timing_cost_check); if (fabs(timing_cost_check - timing_cost) > timing_cost * ERROR_TOL) { From da2740fe886118c5c4d09445ff6e222da4c4adb2 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 28 Jul 2018 13:42:32 -0700 Subject: [PATCH 099/116] Cleanup, add support for sTNS and sWNS --- common/placer_vpr.cc | 28 +++++---- vpr/place/place.cpp | 140 +++++++++++++++++++++---------------------- 2 files changed, 87 insertions(+), 81 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index f09e41ccfe..b075f0568f 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -110,12 +110,16 @@ namespace vpr { }; // timing/timing_info.h struct SetupTimingInfo { - delay_t worst_slack; + delay_t sWNS, sTNS; delay_t max_req; + delay_t worst_path_slack; void update() { update_budget(npnr_ctx); - worst_slack = std::numeric_limits::max(); + sWNS = std::numeric_limits::max(); + sTNS = 0; + max_req = delay_t(1.0e12 / npnr_ctx->target_freq); + worst_path_slack = timing_analysis(npnr_ctx); // Compute the delay for every pin on every net for (auto &n : npnr_ctx->nets) { @@ -140,20 +144,22 @@ namespace vpr { WireId user_wire = npnr_ctx->getBelPinWire(load_cell->bel, npnr_ctx->portPinFromId(load.port)); delay_t raw_wl = npnr_ctx->estimateDelay(drv_wire, user_wire); delay_t slack = load.budget - raw_wl; - worst_slack = std::min(worst_slack, slack); + sWNS = std::min(sWNS, slack); + if (slack < 0) + sTNS += slack; } } - - max_req = delay_t(1.0e12 / npnr_ctx->target_freq); } tatum::TimingPathInfo least_slack_critical_path() { - delay_t default_slack = delay_t(1.0e12 / npnr_ctx->target_freq); - delay_t min_slack = compute_fmax(npnr_ctx); - return tatum::TimingPathInfo(npnr_ctx->getDelayNS(default_slack - min_slack)); + return tatum::TimingPathInfo(npnr_ctx->getDelayNS(max_req - worst_path_slack)); } + float setup_total_negative_slack() { return npnr_ctx->getDelayNS(sTNS); } + float setup_worst_negative_slack() { return npnr_ctx->getDelayNS(sWNS); } }; - static SetupTimingInfo timing_info; + std::unique_ptr make_setup_timing_info(/*std::shared_ptr delay_calculator*/) { + return std::unique_ptr(new SetupTimingInfo); + } // place/place_macro.h struct t_pl_macro_member { t_pl_macro_member(CellInfo* blk_index, int x_offset, int y_offset, int z_offset) : blk_index(blk_index), x_offset(x_offset), y_offset(y_offset), z_offset(z_offset) {} @@ -190,7 +196,7 @@ namespace vpr { WireId user_wire = npnr_ctx->getBelPinWire(load_cell->bel, npnr_ctx->portPinFromId(load.port)); delay_t raw_wl = npnr_ctx->estimateDelay(drv_wire, user_wire); delay_t slack = load.budget - raw_wl; - delay_t shift = timing_info.worst_slack < 0 ? -timing_info.worst_slack : 0; + delay_t shift = std::min(timing_info.sWNS, 0); float crit = 1 - (float(slack + shift) / (timing_info.max_req + shift)); crit = std::max(0., crit); crit = std::min(1., crit); @@ -360,7 +366,7 @@ class VPRPlacer } } } - compute_fmax(ctx, true /* print_fmax */); + timing_analysis(ctx, true /* print_fmax */); return true; } diff --git a/vpr/place/place.cpp b/vpr/place/place.cpp index 01f4aa64a0..e3f78a8ae9 100644 --- a/vpr/place/place.cpp +++ b/vpr/place/place.cpp @@ -347,8 +347,8 @@ void try_place(t_placer_opts placer_opts, oldt, crit_exponent, first_rlim, final_rlim , inverse_delta_rlim; tatum::TimingPathInfo critical_path; -// float sTNS = NAN; -// float sWNS = NAN; + float sTNS = NAN; + float sWNS = NAN; double std_dev; char msg[vtr::bufsize]; @@ -360,7 +360,7 @@ void try_place(t_placer_opts placer_opts, auto& device_ctx = g_vpr_ctx.device(); auto& cluster_ctx = g_vpr_ctx.clustering(); -// std::shared_ptr timing_info; + std::shared_ptr timing_info; // std::shared_ptr placement_delay_calc; // // /* Allocated here because it goes into timing critical code where each memory allocation is expensive */ @@ -419,15 +419,15 @@ void try_place(t_placer_opts placer_opts, // */ // auto& atom_ctx = g_vpr_ctx.atom(); // placement_delay_calc = std::make_shared(atom_ctx.nlist, atom_ctx.lookup, point_to_point_delay_cost); -// timing_info = make_setup_timing_info(placement_delay_calc); + timing_info = make_setup_timing_info(/*placement_delay_calc*/); // - timing_info.update(); + timing_info->update(); // timing_info->set_warn_unconstrained(false); //Don't warn again about unconstrained nodes again during placement //Initial slack estimates - load_criticalities(timing_info, crit_exponent /*, netlist_pin_lookup*/); + load_criticalities(*timing_info, crit_exponent /*, netlist_pin_lookup*/); - critical_path = timing_info.least_slack_critical_path(); + critical_path = timing_info->least_slack_critical_path(); // // //Write out the initial timing echo file // if(isEchoFileEnabled(E_ECHO_INITIAL_PLACEMENT_TIMING_GRAPH)) { @@ -487,18 +487,18 @@ void try_place(t_placer_opts placer_opts, //Initial pacement statistics vtr::printf_info("Initial placement cost: %g bb_cost: %g td_cost: %g delay_cost: %g\n", cost, bb_cost, timing_cost, delay_cost); -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { -// vtr::printf_info("Initial placement estimated Critical Path Delay (CPD): %g ns\n", -// 1e9*critical_path.delay()); -// vtr::printf_info("Initial placement estimated setup Total Negative Slack (sTNS): %g ns\n", -// 1e9*timing_info->setup_total_negative_slack()); -// vtr::printf_info("Initial placement estimated setup Worst Negative Slack (sWNS): %g ns\n", -// 1e9*timing_info->setup_worst_negative_slack()); -// vtr::printf_info("\n"); -// + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { + vtr::printf_info("Initial placement estimated Critical Path Delay (CPD): %g ns\n", + /*1e9**/critical_path.delay()); + vtr::printf_info("Initial placement estimated setup Total Negative Slack (sTNS): %g ns\n", + /*1e9**/timing_info->setup_total_negative_slack()); + vtr::printf_info("Initial placement estimated setup Worst Negative Slack (sWNS): %g ns\n", + /*1e9**/timing_info->setup_worst_negative_slack()); + vtr::printf_info("\n"); + // vtr::printf_info("Initial placement estimated setup slack histogram:\n"); // print_histogram(create_setup_slack_histogram(*timing_info->setup_analyzer())); -// } + } vtr::printf_info("\n"); //Table header @@ -585,7 +585,7 @@ void try_place(t_placer_opts placer_opts, slacks, timing_inf, #endif - /***/timing_info); + *timing_info); placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, move_lim, crit_exponent, inner_recompute_limit, &stats, @@ -595,7 +595,7 @@ void try_place(t_placer_opts placer_opts, timing_inf, #endif /*netlist_pin_lookup,*/ - /***/timing_info); + *timing_info); /* Lines below prevent too much round-off error from accumulating * * in the cost over many iterations. This round-off can lead to * @@ -652,9 +652,9 @@ void try_place(t_placer_opts placer_opts, update_t(&t, rlim, success_rat, annealing_sched); if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - critical_path = timing_info.least_slack_critical_path(); -// sTNS = timing_info->setup_total_negative_slack(); -// sWNS = timing_info->setup_worst_negative_slack(); + critical_path = timing_info->least_slack_critical_path(); + sTNS = timing_info->setup_total_negative_slack(); + sWNS = timing_info->setup_worst_negative_slack(); } vtr::printf_info("%7.3f " @@ -664,7 +664,7 @@ void try_place(t_placer_opts placer_opts, "%9d %6.3f\n", oldt, stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, - place_delay_value, /*1e9**/critical_path.delay(), /*1e9*sTNS*/ 0., /*1e9*sWNS*/ 0., + place_delay_value, /*1e9**/critical_path.delay(), /*1e9**/sTNS, /*1e9**/sWNS, success_rat, std_dev, rlim, crit_exponent, tot_iter, t / oldt); @@ -707,7 +707,7 @@ void try_place(t_placer_opts placer_opts, slacks, timing_inf, #endif - /***/timing_info); + *timing_info); t = 0; /* freeze out */ @@ -721,7 +721,7 @@ void try_place(t_placer_opts placer_opts, timing_inf, #endif /*netlist_pin_lookup,*/ - /***/timing_info); + *timing_info); tot_iter += move_lim; success_rat = ((float) stats.success_sum) / move_lim; @@ -740,9 +740,9 @@ void try_place(t_placer_opts placer_opts, std_dev = get_std_dev(stats.success_sum, stats.sum_of_squares, stats.av_cost); if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - critical_path = timing_info.least_slack_critical_path(); -// sTNS = timing_info->setup_total_negative_slack(); -// sWNS = timing_info->setup_worst_negative_slack(); + critical_path = timing_info->least_slack_critical_path(); + sTNS = timing_info->setup_total_negative_slack(); + sWNS = timing_info->setup_worst_negative_slack(); } vtr::printf_info("%7.3f " @@ -752,7 +752,7 @@ void try_place(t_placer_opts placer_opts, "%9d %6.3f\n", t, stats.av_cost, stats.av_bb_cost, stats.av_timing_cost, stats.av_delay_cost, - place_delay_value, /*1e9**/critical_path.delay(), /*1e9*sTNS*/0., /*1e9*sWNS*/0., + place_delay_value, /*1e9**/critical_path.delay(), /*1e9**/sTNS, /*1e9**/sWNS, success_rat, std_dev, rlim, crit_exponent, tot_iter, 0.); @@ -782,57 +782,57 @@ void try_place(t_placer_opts placer_opts, // } // comp_td_costs(&timing_cost, &delay_cost); /*computes point_to_point_delay_cost */ // } -// -// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE -// || placer_opts.enable_timing_computations) { -// -// //Final timing estimate + + if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE + || placer_opts.enable_timing_computations) { + + //Final timing estimate // VTR_ASSERT(timing_info); -// timing_info->update(); //Tatum -// critical_path = timing_info->least_slack_critical_path(); -// + timing_info->update(); //Tatum + critical_path = timing_info->least_slack_critical_path(); + // if(isEchoFileEnabled(E_ECHO_FINAL_PLACEMENT_TIMING_GRAPH)) { // auto& timing_ctx = g_vpr_ctx.timing(); // // tatum::write_echo(getEchoFileName(E_ECHO_FINAL_PLACEMENT_TIMING_GRAPH), // *timing_ctx.graph, *timing_ctx.constraints, *placement_delay_calc, timing_info->analyzer()); // } -// -//#ifdef ENABLE_CLASSIC_VPR_STA -// //Old VPR analyzer -// load_timing_graph_net_delays(point_to_point_delay_cost); -// do_timing_analysis(slacks, timing_inf, false, true); -//#endif -// -// -// /* Print critical path delay. */ -// vtr::printf_info("\n"); -// vtr::printf_info("Placement estimated critical path delay: %g ns", -// 1e9*critical_path.delay(), get_critical_path_delay()); -//#ifdef ENABLE_CLASSIC_VPR_STA -// vtr::printf_info(" (classic VPR STA %g ns)", get_critical_path_delay()); -//#endif -// vtr::printf("\n"); -// vtr::printf_info("Placement estimated setup Total Negative Slack (sTNS): %g ns\n", -// 1e9*timing_info->setup_total_negative_slack()); -// vtr::printf_info("Placement estimated setup Worst Negative Slack (sWNS): %g ns\n", -// 1e9*timing_info->setup_worst_negative_slack()); -// vtr::printf_info("\n"); -// + +#ifdef ENABLE_CLASSIC_VPR_STA + //Old VPR analyzer + load_timing_graph_net_delays(point_to_point_delay_cost); + do_timing_analysis(slacks, timing_inf, false, true); +#endif + + + /* Print critical path delay. */ + vtr::printf_info("\n"); + vtr::printf_info("Placement estimated critical path delay: %g ns", + 1e9*critical_path.delay() /*, get_critical_path_delay()*/); +#ifdef ENABLE_CLASSIC_VPR_STA + vtr::printf_info(" (classic VPR STA %g ns)", get_critical_path_delay()); +#endif + vtr::printf("\n"); + vtr::printf_info("Placement estimated setup Total Negative Slack (sTNS): %g ns\n", + 1e9*timing_info->setup_total_negative_slack()); + vtr::printf_info("Placement estimated setup Worst Negative Slack (sWNS): %g ns\n", + 1e9*timing_info->setup_worst_negative_slack()); + vtr::printf_info("\n"); + // vtr::printf_info("Placement estimated setup slack histogram:\n"); // print_histogram(create_setup_slack_histogram(*timing_info->setup_analyzer())); // vtr::printf_info("\n"); -// -//#ifdef ENABLE_CLASSIC_VPR_STA -// float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); -// if(cpd_diff_ns > ERROR_TOL) { -// print_classic_cpds(); -// print_tatum_cpds(timing_info->critical_paths()); -// -// vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); -// } -//#endif -// } + +#ifdef ENABLE_CLASSIC_VPR_STA + float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); + if(cpd_diff_ns > ERROR_TOL) { + print_classic_cpds(); + print_tatum_cpds(timing_info->critical_paths()); + + vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); + } +#endif + } sprintf(msg, "Placement. Cost: %g bb_cost: %g td_cost: %g Channel Factor: %d", cost, bb_cost, timing_cost, /*width_fac*/ -1); From a76a28670c014e55727ec0fb33d7cbba0653b2bc Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 28 Jul 2018 14:14:54 -0700 Subject: [PATCH 100/116] update_budget -> assign_budget --- common/placer_vpr.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index b075f0568f..e1242f0668 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -114,7 +114,7 @@ namespace vpr { delay_t max_req; delay_t worst_path_slack; void update() { - update_budget(npnr_ctx); + assign_budget(npnr_ctx, true /* quiet */); sWNS = std::numeric_limits::max(); sTNS = 0; From ef16f9944ffa54bb7992b58449cb560e8e4c98f3 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 28 Jul 2018 23:07:19 -0700 Subject: [PATCH 101/116] Support update_screen() --- common/placer_vpr.cc | 8 ++++++++ vpr/place/place.cpp | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index e1242f0668..92bbd5d39a 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -227,6 +227,12 @@ namespace vpr { else *imacro = nullptr; } + // draw/draw.h + void update_screen(/*ScreenUpdatePriority priority, const char *msg, enum pic_type pic_on_screen_val, + std::shared_ptr timing_info*/) + { + npnr_ctx->yield(); + } #define VTR_ASSERT NPNR_ASSERT #define VTR_ASSERT_SAFE NPNR_ASSERT @@ -332,6 +338,7 @@ class VPRPlacer bool place() { log_break(); + ctx->lock(); vpr::t_placer_opts placer_opts; placer_opts.place_algorithm = vpr::PATH_TIMING_DRIVEN_PLACE; @@ -367,6 +374,7 @@ class VPRPlacer } } timing_analysis(ctx, true /* print_fmax */); + ctx->unlock(); return true; } diff --git a/vpr/place/place.cpp b/vpr/place/place.cpp index e3f78a8ae9..9cdf016d39 100644 --- a/vpr/place/place.cpp +++ b/vpr/place/place.cpp @@ -536,8 +536,8 @@ void try_place(t_placer_opts placer_opts, sprintf(msg, "Initial Placement. Cost: %g BB Cost: %g TD Cost %g Delay Cost: %g \t Channel Factor: %d", cost, bb_cost, timing_cost, delay_cost, /*width_fac*/ -1); -// //Draw the initial placement -// update_screen(ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info); + //Draw the initial placement + update_screen(/*ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info*/); move_lim = (int) (annealing_sched.inner_num * pow(cluster_ctx.clb_nlist.blocks().size(), 1.3333)); @@ -682,7 +682,7 @@ void try_place(t_placer_opts placer_opts, sprintf(msg, "Cost: %g BB Cost %g TD Cost %g Temperature: %g", cost, bb_cost, timing_cost, t); -// update_screen(ScreenUpdatePriority::MINOR, msg, PLACEMENT, timing_info); + update_screen(/*ScreenUpdatePriority::MINOR, msg, PLACEMENT, timing_info*/); update_rlim(&rlim, success_rat, device_ctx.grid); if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { @@ -838,7 +838,7 @@ void try_place(t_placer_opts placer_opts, cost, bb_cost, timing_cost, /*width_fac*/ -1); vtr::printf_info("Placement cost: %g, bb_cost: %g, td_cost: %g, delay_cost: %g\n", cost, bb_cost, timing_cost, delay_cost); -// update_screen(ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info); + update_screen(/*ScreenUpdatePriority::MAJOR, msg, PLACEMENT, timing_info*/); // Print out swap statistics size_t total_swap_attempts = num_swap_rejected + num_swap_accepted + num_swap_aborted; From 58db6a8a3b0954862a1c28933b3d78298f42036f Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 28 Jul 2018 23:09:33 -0700 Subject: [PATCH 102/116] Comment out ENABLE_CLASSIC_VPR_STA --- vpr/place/place.cpp | 188 ++++++++++++++++++++++---------------------- 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/vpr/place/place.cpp b/vpr/place/place.cpp index 9cdf016d39..1ac7d0b670 100644 --- a/vpr/place/place.cpp +++ b/vpr/place/place.cpp @@ -306,10 +306,10 @@ static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, int * outer_crit_iter_count, float * inverse_prev_timing_cost, float * inverse_prev_bb_cost, /*const ClusteredPinAtomPinsLookup& netlist_pin_lookup,*/ -#ifdef ENABLE_CLASSIC_VPR_STA - t_slack* slacks, - t_timing_inf timing_inf, -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// t_slack* slacks, +// t_timing_inf timing_inf, +//#endif SetupTimingInfo& timing_info); static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, @@ -317,10 +317,10 @@ static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, float crit_exponent, int inner_recompute_limit, t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, float * delay_cost, -#ifdef ENABLE_CLASSIC_VPR_STA - t_slack* slacks, - t_timing_inf timing_inf, -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// t_slack* slacks, +// t_timing_inf timing_inf, +//#endif /*const ClusteredPinAtomPinsLookup& netlist_pin_lookup,*/ SetupTimingInfo& timing_info); @@ -329,9 +329,9 @@ void try_place(t_placer_opts placer_opts, t_annealing_sched annealing_sched/*, t_chan_width_dist chan_width_dist, t_router_opts router_opts, t_det_routing_arch *det_routing_arch, t_segment_inf * segment_inf, -#ifdef ENABLE_CLASSIC_VPR_STA - t_timing_inf timing_inf, -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// t_timing_inf timing_inf, +//#endif t_direct_inf *directs, int num_directs*/) { // /* Does almost all the work of placing a circuit. Width_fac gives the * @@ -378,9 +378,9 @@ void try_place(t_placer_opts placer_opts, /*do this before the initial placement to avoid messing up the initial placement */ alloc_lookups_and_criticalities(/*chan_width_dist, router_opts, det_routing_arch, segment_inf, directs, num_directs*/); -#ifdef ENABLE_CLASSIC_VPR_STA - slacks = alloc_and_load_timing_graph(timing_inf); -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// slacks = alloc_and_load_timing_graph(timing_inf); +//#endif } // width_fac = placer_opts.place_chan_width; @@ -414,13 +414,13 @@ void try_place(t_placer_opts placer_opts, //Update the point-to-point delays from the initial placement comp_td_point_to_point_delays(); -// /* -// * Initialize timing analysis -// */ + /* + * Initialize timing analysis + */ // auto& atom_ctx = g_vpr_ctx.atom(); // placement_delay_calc = std::make_shared(atom_ctx.nlist, atom_ctx.lookup, point_to_point_delay_cost); timing_info = make_setup_timing_info(/*placement_delay_calc*/); -// + timing_info->update(); // timing_info->set_warn_unconstrained(false); //Don't warn again about unconstrained nodes again during placement @@ -428,7 +428,7 @@ void try_place(t_placer_opts placer_opts, load_criticalities(*timing_info, crit_exponent /*, netlist_pin_lookup*/); critical_path = timing_info->least_slack_critical_path(); -// + // //Write out the initial timing echo file // if(isEchoFileEnabled(E_ECHO_INITIAL_PLACEMENT_TIMING_GRAPH)) { // auto& timing_ctx = g_vpr_ctx.timing(); @@ -437,18 +437,18 @@ void try_place(t_placer_opts placer_opts, // *timing_ctx.graph, *timing_ctx.constraints, *placement_delay_calc, timing_info->analyzer()); // } -#ifdef ENABLE_CLASSIC_VPR_STA - load_timing_graph_net_delays(point_to_point_delay_cost); - do_timing_analysis(slacks, timing_inf, false, true); - - float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); - if(cpd_diff_ns > ERROR_TOL) { - print_classic_cpds(); - print_tatum_cpds(timing_info->critical_paths()); - - vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); - } -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// load_timing_graph_net_delays(point_to_point_delay_cost); +// do_timing_analysis(slacks, timing_inf, false, true); +// +// float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); +// if(cpd_diff_ns > ERROR_TOL) { +// print_classic_cpds(); +// print_tatum_cpds(timing_info->critical_paths()); +// +// vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); +// } +//#endif /*now we can properly compute costs */ comp_td_costs(&timing_cost, &delay_cost); /*also updates values in point_to_point_delay_cost */ @@ -581,19 +581,19 @@ void try_place(t_placer_opts placer_opts, crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, /*netlist_pin_lookup,*/ -#ifdef ENABLE_CLASSIC_VPR_STA - slacks, - timing_inf, -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// slacks, +// timing_inf, +//#endif *timing_info); placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, move_lim, crit_exponent, inner_recompute_limit, &stats, &cost, &bb_cost, &timing_cost, &delay_cost, -#ifdef ENABLE_CLASSIC_VPR_STA - slacks, - timing_inf, -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// slacks, +// timing_inf, +//#endif /*netlist_pin_lookup,*/ *timing_info); @@ -668,17 +668,17 @@ void try_place(t_placer_opts placer_opts, success_rat, std_dev, rlim, crit_exponent, tot_iter, t / oldt); -#ifdef ENABLE_CLASSIC_VPR_STA - if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { - float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); - if(cpd_diff_ns > ERROR_TOL) { - print_classic_cpds(); - print_tatum_cpds(timing_info->critical_paths()); - - vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); - } - } -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE) { +// float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); +// if(cpd_diff_ns > ERROR_TOL) { +// print_classic_cpds(); +// print_tatum_cpds(timing_info->critical_paths()); +// +// vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); +// } +// } +//#endif sprintf(msg, "Cost: %g BB Cost %g TD Cost %g Temperature: %g", cost, bb_cost, timing_cost, t); @@ -703,10 +703,10 @@ void try_place(t_placer_opts placer_opts, crit_exponent, bb_cost, &place_delay_value, &timing_cost, &delay_cost, &outer_crit_iter_count, &inverse_prev_timing_cost, &inverse_prev_bb_cost, /*netlist_pin_lookup,*/ -#ifdef ENABLE_CLASSIC_VPR_STA - slacks, - timing_inf, -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// slacks, +// timing_inf, +//#endif *timing_info); t = 0; /* freeze out */ @@ -716,10 +716,10 @@ void try_place(t_placer_opts placer_opts, placement_inner_loop(t, rlim, placer_opts, inverse_prev_bb_cost, inverse_prev_timing_cost, move_lim, crit_exponent, inner_recompute_limit, &stats, &cost, &bb_cost, &timing_cost, &delay_cost, -#ifdef ENABLE_CLASSIC_VPR_STA - slacks, - timing_inf, -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// slacks, +// timing_inf, +//#endif /*netlist_pin_lookup,*/ *timing_info); @@ -798,20 +798,20 @@ void try_place(t_placer_opts placer_opts, // *timing_ctx.graph, *timing_ctx.constraints, *placement_delay_calc, timing_info->analyzer()); // } -#ifdef ENABLE_CLASSIC_VPR_STA - //Old VPR analyzer - load_timing_graph_net_delays(point_to_point_delay_cost); - do_timing_analysis(slacks, timing_inf, false, true); -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// //Old VPR analyzer +// load_timing_graph_net_delays(point_to_point_delay_cost); +// do_timing_analysis(slacks, timing_inf, false, true); +//#endif /* Print critical path delay. */ vtr::printf_info("\n"); vtr::printf_info("Placement estimated critical path delay: %g ns", 1e9*critical_path.delay() /*, get_critical_path_delay()*/); -#ifdef ENABLE_CLASSIC_VPR_STA - vtr::printf_info(" (classic VPR STA %g ns)", get_critical_path_delay()); -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// vtr::printf_info(" (classic VPR STA %g ns)", get_critical_path_delay()); +//#endif vtr::printf("\n"); vtr::printf_info("Placement estimated setup Total Negative Slack (sTNS): %g ns\n", 1e9*timing_info->setup_total_negative_slack()); @@ -823,15 +823,15 @@ void try_place(t_placer_opts placer_opts, // print_histogram(create_setup_slack_histogram(*timing_info->setup_analyzer())); // vtr::printf_info("\n"); -#ifdef ENABLE_CLASSIC_VPR_STA - float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); - if(cpd_diff_ns > ERROR_TOL) { - print_classic_cpds(); - print_tatum_cpds(timing_info->critical_paths()); - - vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); - } -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// float cpd_diff_ns = std::abs(get_critical_path_delay() - 1e9*critical_path.delay()); +// if(cpd_diff_ns > ERROR_TOL) { +// print_classic_cpds(); +// print_tatum_cpds(timing_info->critical_paths()); +// +// vpr_throw(VPR_ERROR_TIMING, __FILE__, __LINE__, "Classic VPR and Tatum critical paths do not match (%g and %g respectively)", get_critical_path_delay(), 1e9*critical_path.delay()); +// } +//#endif } sprintf(msg, "Placement. Cost: %g bb_cost: %g td_cost: %g Channel Factor: %d", @@ -857,9 +857,9 @@ void try_place(t_placer_opts placer_opts, if (placer_opts.place_algorithm == PATH_TIMING_DRIVEN_PLACE || placer_opts.enable_timing_computations) { -#ifdef ENABLE_CLASSIC_VPR_STA - free_timing_graph(slacks); -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// free_timing_graph(slacks); +//#endif free_lookups_and_criticalities(); } @@ -874,10 +874,10 @@ static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, int * outer_crit_iter_count, float * inverse_prev_timing_cost, float * inverse_prev_bb_cost, /*const ClusteredPinAtomPinsLookup& netlist_pin_lookup,*/ -#ifdef ENABLE_CLASSIC_VPR_STA - t_slack* slacks, - t_timing_inf timing_inf, -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// t_slack* slacks, +// t_timing_inf timing_inf, +//#endif SetupTimingInfo& timing_info) { if (placer_opts.place_algorithm != PATH_TIMING_DRIVEN_PLACE) @@ -899,10 +899,10 @@ static void outer_loop_recompute_criticalities(t_placer_opts placer_opts, timing_info.update(); load_criticalities(timing_info, crit_exponent /*, netlist_pin_lookup*/); -#ifdef ENABLE_CLASSIC_VPR_STA - load_timing_graph_net_delays(point_to_point_delay_cost); - do_timing_analysis(slacks, timing_inf, false, true); -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// load_timing_graph_net_delays(point_to_point_delay_cost); +// do_timing_analysis(slacks, timing_inf, false, true); +//#endif /*recompute costs from scratch, based on new criticalities */ comp_td_costs(timing_cost, delay_cost); @@ -923,10 +923,10 @@ static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, float crit_exponent, int inner_recompute_limit, t_placer_statistics *stats, float * cost, float * bb_cost, float * timing_cost, float * delay_cost, -#ifdef ENABLE_CLASSIC_VPR_STA - t_slack* slacks, - t_timing_inf timing_inf, -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// t_slack* slacks, +// t_timing_inf timing_inf, +//#endif /*const ClusteredPinAtomPinsLookup& netlist_pin_lookup,*/ SetupTimingInfo& timing_info) { @@ -984,10 +984,10 @@ static void placement_inner_loop(float t, float rlim, t_placer_opts placer_opts, timing_info.update(); load_criticalities(timing_info, crit_exponent /*, netlist_pin_lookup*/); -#ifdef ENABLE_CLASSIC_VPR_STA - load_timing_graph_net_delays(point_to_point_delay_cost); - do_timing_analysis(slacks, timing_inf, false, true); -#endif +//#ifdef ENABLE_CLASSIC_VPR_STA +// load_timing_graph_net_delays(point_to_point_delay_cost); +// do_timing_analysis(slacks, timing_inf, false, true); +//#endif comp_td_costs(timing_cost, delay_cost); } From cc2107a5df2eda7fedb616b9649774eb9e0bc7ad Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Sat, 28 Jul 2018 23:12:01 -0700 Subject: [PATCH 103/116] Cleanup --- vpr/place/place.cpp | 59 +-------------------------------------------- 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/vpr/place/place.cpp b/vpr/place/place.cpp index 1ac7d0b670..9cddcfd10f 100644 --- a/vpr/place/place.cpp +++ b/vpr/place/place.cpp @@ -787,7 +787,7 @@ void try_place(t_placer_opts placer_opts, || placer_opts.enable_timing_computations) { //Final timing estimate -// VTR_ASSERT(timing_info); + VTR_ASSERT(timing_info); timing_info->update(); //Tatum critical_path = timing_info->least_slack_critical_path(); @@ -1192,28 +1192,10 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, int x_to, // Check whether the to_location is empty if (b_to == NULL) { -// // Swap the block, dont swap the nets yet -// place_ctx.block_locs[b_from].x = x_to; -// place_ctx.block_locs[b_from].y = y_to; -// place_ctx.block_locs[b_from].z = z_to; - npnr_ctx->unbindBel(bel_from); npnr_ctx->bindBel(bel_to, b_from->name, STRENGTH_WEAK); //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel_to)); -// // Sets up the blocks moved -// imoved_blk = blocks_affected.num_moved_blocks; -// blocks_affected.moved_blocks[imoved_blk].block_num = b_from; -// blocks_affected.moved_blocks[imoved_blk].xold = x_from; -// blocks_affected.moved_blocks[imoved_blk].xnew = x_to; -// blocks_affected.moved_blocks[imoved_blk].yold = y_from; -// blocks_affected.moved_blocks[imoved_blk].ynew = y_to; -// blocks_affected.moved_blocks[imoved_blk].zold = z_from; -// blocks_affected.moved_blocks[imoved_blk].znew = z_to; -// blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = true; -// blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = true; -// blocks_affected.num_moved_blocks ++; - blocks_affected.emplace_back(b_from, bel_from); } else if (b_to != /*INVALID_BLOCK_ID*/ NULL) { @@ -1225,46 +1207,10 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, int x_to, return (abort_swap); } -// // Swap the block, dont swap the nets yet -// place_ctx.block_locs[b_to].x = x_from; -// place_ctx.block_locs[b_to].y = y_from; -// place_ctx.block_locs[b_to].z = z_from; -// -// place_ctx.block_locs[b_from].x = x_to; -// place_ctx.block_locs[b_from].y = y_to; -// place_ctx.block_locs[b_from].z = z_to; - npnr_ctx->unbindBel(bel_to); npnr_ctx->unbindBel(bel_from); npnr_ctx->bindBel(bel_to, b_from->name, STRENGTH_WEAK); npnr_ctx->bindBel(bel_from, b_to->name, STRENGTH_WEAK); - //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel_to)); - //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel_from)); - -// // Sets up the blocks moved -// imoved_blk = blocks_affected.num_moved_blocks; -// blocks_affected.moved_blocks[imoved_blk].block_num = b_from; -// blocks_affected.moved_blocks[imoved_blk].xold = x_from; -// blocks_affected.moved_blocks[imoved_blk].xnew = x_to; -// blocks_affected.moved_blocks[imoved_blk].yold = y_from; -// blocks_affected.moved_blocks[imoved_blk].ynew = y_to; -// blocks_affected.moved_blocks[imoved_blk].zold = z_from; -// blocks_affected.moved_blocks[imoved_blk].znew = z_to; -// blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = false; -// blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = false; -// blocks_affected.num_moved_blocks ++; -// -// imoved_blk = blocks_affected.num_moved_blocks; -// blocks_affected.moved_blocks[imoved_blk].block_num = b_to; -// blocks_affected.moved_blocks[imoved_blk].xold = x_to; -// blocks_affected.moved_blocks[imoved_blk].xnew = x_from; -// blocks_affected.moved_blocks[imoved_blk].yold = y_to; -// blocks_affected.moved_blocks[imoved_blk].ynew = y_from; -// blocks_affected.moved_blocks[imoved_blk].zold = z_to; -// blocks_affected.moved_blocks[imoved_blk].znew = z_from; -// blocks_affected.moved_blocks[imoved_blk].swapped_to_was_empty = false; -// blocks_affected.moved_blocks[imoved_blk].swapped_from_is_empty = false; -// blocks_affected.num_moved_blocks ++; blocks_affected.emplace_back(b_from, bel_from); blocks_affected.emplace_back(b_to, bel_to); @@ -2038,10 +1984,7 @@ static float comp_td_point_to_point_delay(/*ClusterNetId*/ NetInfo* net_id, int // * In particular this aproach does not accurately capture the effect of fast // * carry-chain connections. // */ -// delay_source_to_sink = get_delta_delay(delta_x, delta_y); - delay_source_to_sink = npnr_ctx->getDelayNS(npnr_ctx->estimateDelay(drv_wire, user_wire)); - NPNR_ASSERT(delay_source_to_sink >= 0); // if (delay_source_to_sink < 0) { // vpr_throw(VPR_ERROR_PLACE, __FILE__, __LINE__, From b67ae0a56e6f3d6513cc737e0e9c2477031915e5 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Thu, 2 Aug 2018 23:29:24 -0700 Subject: [PATCH 104/116] Refactor into place_macro.{cpp,h}, speedup pl_macros, cleanup --- common/placer_vpr.cc | 58 ++---- vpr/place/place.cpp | 62 +++--- vpr/place/place_macro.cpp | 418 ++++++++++++++++++++++++++++++++++++++ vpr/place/place_macro.h | 168 +++++++++++++++ 4 files changed, 628 insertions(+), 78 deletions(-) create mode 100644 vpr/place/place_macro.cpp create mode 100644 vpr/place/place_macro.h diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 92bbd5d39a..2713024121 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -42,10 +42,6 @@ #include "timing.h" #include "place_legaliser.h" -namespace NEXTPNR_NAMESPACE { - std::vector> carries; -} - namespace vpr { using namespace NEXTPNR_NAMESPACE; @@ -65,6 +61,8 @@ namespace vpr { DRIVER = PortType::PORT_OUT, SINK = PortType::PORT_IN, }; + // base/clustered_netlist_fwd.h + typedef size_t ClusterBlockId; // base/globals.h static struct VprContext { struct t_clustering { @@ -160,19 +158,6 @@ namespace vpr { std::unique_ptr make_setup_timing_info(/*std::shared_ptr delay_calculator*/) { return std::unique_ptr(new SetupTimingInfo); } - // place/place_macro.h - struct t_pl_macro_member { - t_pl_macro_member(CellInfo* blk_index, int x_offset, int y_offset, int z_offset) : blk_index(blk_index), x_offset(x_offset), y_offset(y_offset), z_offset(z_offset) {} - CellInfo* blk_index; - int x_offset; - int y_offset; - int z_offset; - }; - // place/place_macro.h - struct t_pl_macro { - std::vector members; - }; - // timing/timing_util.h float calculate_clb_net_pin_criticality(const SetupTimingInfo& timing_info, /*const ClusteredPinAtomPinsLookup& pin_lookup,*/ const PortRef& load, const NetInfo* net) { @@ -202,31 +187,6 @@ namespace vpr { crit = std::min(1., crit); return crit; } - - // place/place_macro.cpp - int alloc_and_load_placement_macros(/*t_direct_inf* directs, int num_directs, t_pl_macro ** */ std::unordered_map ¯os) - { - for (auto &chain : carries) { - t_pl_macro entry; - auto head = chain.front(); - for (int z = 0; z < int(chain.size()); z++) { - auto cell = chain.at(z); - entry.members.emplace_back(cell, 0, z / 8, z % 8); - cell->attrs.emplace(npnr_ctx->id("carry_head"), head->name.str(npnr_ctx)); - } - macros.emplace(head, std::move(entry)); - } - return macros.size(); - } - - // place/place_macro.cpp - void get_imacro_from_iblk(/*int **/ CellInfo **imacro, /*ClusterBlockId*/ CellInfo *iblk /*, t_pl_macro *macros, int num_macros*/) { - auto it = iblk->attrs.find(npnr_ctx->id("carry_head")); - if (it != iblk->attrs.end()) - *imacro = npnr_ctx->cells.at(npnr_ctx->id(it->second)).get(); - else - *imacro = nullptr; - } // draw/draw.h void update_screen(/*ScreenUpdatePriority priority, const char *msg, enum pic_type pic_on_screen_val, std::shared_ptr timing_info*/) @@ -272,6 +232,7 @@ namespace vpr { #include "vpr/place/timing_place.cpp" #include "vpr/place/place.cpp" + #include "vpr/place/place_macro.cpp" } NEXTPNR_NAMESPACE_BEGIN @@ -298,7 +259,18 @@ class VPRPlacer for (auto& c : grid._bels) c.resize(max_y+1); - carries = prepare_and_find_carries(ctx); + auto carries = prepare_and_find_carries(ctx); + for (auto &chain : carries) { + auto head = chain.front(); + for (int z = 1; z < int(chain.size()); z++) { + auto cell = chain.at(z); + cell->constr_parent = head; + cell->constr_x = 0; + cell->constr_y = z / 8; + cell->constr_z = z % 8; + } + head->constr_children.assign(chain.begin() + 1, chain.end()); + } int32_t cell_idx = 0; for (auto &cell : ctx->cells) { diff --git a/vpr/place/place.cpp b/vpr/place/place.cpp index 9cddcfd10f..e410a24f01 100644 --- a/vpr/place/place.cpp +++ b/vpr/place/place.cpp @@ -26,7 +26,7 @@ //#include "read_xml_arch_file.h" //#include "echo_files.h" //#include "vpr_utils.h" -//#include "place_macro.h" +#include "vpr/place/place_macro.h" //#include "histogram.h" //#include "place_util.h" // @@ -159,7 +159,7 @@ static std::vector ts_nets_to_update; /* The pl_macros array stores all the carry chains placement macros. * * [0...num_pl_macros-1] */ -static std::unordered_map pl_macros; +static std::vector pl_macros; /* These file-scoped variables keep track of the number of swaps * * rejected, accepted or aborted. The total number of swap attempts * @@ -205,9 +205,9 @@ static void load_legal_placements(); //static void free_legal_placements(); -static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, int y, int z); +static int check_macro_can_be_placed(int imacro, int itype, int x, int y, int z); -static int try_place_macro(int itype, /*int*/ Loc ipos, /*int*/ CellInfo* macro); +static int try_place_macro(int itype, /*int*/ Loc ipos, int macro); static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std::vector> &free_locations); @@ -1174,10 +1174,9 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, int x_to, /* Find all the blocks affected when b_from is swapped with b_to. * Returns abort_swap. */ -// int imoved_blk, imacro; - CellInfo *imacro; + int /*imoved_blk,*/ imacro; // int x_from, y_from, z_from; - /*ClusterBlockId*/ CellInfo* b_to; + /*ClusterBlockId*/ CellInfo *b_to; int abort_swap = false; // auto& place_ctx = g_vpr_ctx.mutable_placement(); @@ -1187,10 +1186,10 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, int x_to, // b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; auto b_to_id = npnr_ctx->getBoundBelCell(bel_to); - b_to = (b_to_id == IdString() ? NULL : npnr_ctx->cells[b_to_id].get()); + b_to = (b_to_id == IdString() ? nullptr : npnr_ctx->cells[b_to_id].get()); // Check whether the to_location is empty - if (b_to == NULL) { + if (b_to == /*EMPTY_BLOCK_ID*/ nullptr) { npnr_ctx->unbindBel(bel_from); npnr_ctx->bindBel(bel_to, b_from->name, STRENGTH_WEAK); @@ -1198,11 +1197,11 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, int x_to, blocks_affected.emplace_back(b_from, bel_from); - } else if (b_to != /*INVALID_BLOCK_ID*/ NULL) { + } else /*if (b_to != INVALID_BLOCK_ID)*/ { // Does not allow a swap with a macro yet - get_imacro_from_iblk(&imacro, b_to /*, pl_macros, num_pl_macros*/); - if (imacro) { + get_imacro_from_iblk(&imacro, b_to->udata, pl_macros /*, num_pl_macros*/); + if (imacro != -1) { abort_swap = true; return (abort_swap); } @@ -1226,8 +1225,7 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i /* Finds and set ups the affected_blocks array. * Returns abort_swap. */ - int /*imacro,*/ imember; - CellInfo *imacro; + int imacro, imember; int x_swap_offset, y_swap_offset, z_swap_offset, x_from, y_from, z_from; /*ClusterBlockId*/ CellInfo* curr_b_from; int curr_x_from, curr_y_from, curr_z_from, curr_x_to, curr_y_to, curr_z_to; @@ -1242,8 +1240,8 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i y_from = loc_from.y; z_from = loc_from.z; - get_imacro_from_iblk(&imacro, b_from /*, pl_macros, num_pl_macros*/); - if (imacro) { + get_imacro_from_iblk(&imacro, b_from->udata, pl_macros /*, num_pl_macros*/); + if (imacro != -1) { // b_from is part of a macro, I need to swap the whole macro // Record down the relative position of the swap @@ -1299,9 +1297,8 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i } // Does not allow a swap with a macro yet - CellInfo *imacro; - get_imacro_from_iblk(&imacro, cell_to /*, pl_macros, num_pl_macros*/); - if (imacro) { + get_imacro_from_iblk(&imacro, cell_to->udata, pl_macros /*, num_pl_macros*/); + if (imacro != -1) { abort_swap = true; } } @@ -1805,9 +1802,9 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, // values, as find_affected_blocks() will take care of // the rest else { - CellInfo *imacro; - get_imacro_from_iblk(&imacro, cell_from /*, pl_macros, num_pl_macros*/); - if (imacro) { + int imacro; + get_imacro_from_iblk(&imacro, cell_from->udata, pl_macros /*, num_pl_macros*/); + if (imacro != -1) { auto loc_from = npnr_ctx->getBelLocation(cell_from->bel); if (size_t(loc_from.z) >= grid[*px_to][*py_to].size()) { is_legal = false; @@ -2830,7 +2827,7 @@ static void load_legal_placements() { -static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, int y, int z) { +static int check_macro_can_be_placed(int imacro, int itype, int x, int y, int z) { int imember; size_t member_x, member_y, member_z; @@ -2869,7 +2866,7 @@ static int check_macro_can_be_placed(/*int*/ CellInfo* imacro, int itype, int x, } -static int try_place_macro(int itype, /*int*/ Loc ipos, /*int*/ CellInfo* imacro) { +static int try_place_macro(int itype, /*int*/ Loc ipos, int imacro) { int x, y, z, member_x, member_y, member_z, imember; @@ -2889,7 +2886,7 @@ static int try_place_macro(int itype, /*int*/ Loc ipos, /*int*/ CellInfo* imacro return (macro_placed); } - if (!npnr_ctx->isValidBelForCell(imacro, grid[x][y][z])) + if (!npnr_ctx->isValidBelForCell(pl_macros[imacro].members[0].blk_index, grid[x][y][z])) return macro_placed; int macro_can_be_placed = check_macro_can_be_placed(imacro, itype, x, y, z); @@ -2923,25 +2920,20 @@ static int try_place_macro(int itype, /*int*/ Loc ipos, /*int*/ CellInfo* imacro static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std::vector> &free_locations) { int macro_placed; - int /*imacro,*/ itype, itry /*, ipos*/; -// ClusterBlockId blk_id; + int imacro, itype, itry /*, ipos*/; + /*ClusterBlockId*/ CellInfo *blk_id; Loc ipos; auto& cluster_ctx = g_vpr_ctx.clustering(); // auto& device_ctx = g_vpr_ctx.device(); /* Macros are harder to place. Do them first */ -// for (imacro = 0; imacro < num_pl_macros; imacro++) { - for (auto &b : cluster_ctx.clb_nlist.blocks()) { - auto imacro = b.second.get(); - - // If not part of macro, ignore - if (!pl_macros.count(imacro)) continue; + for (imacro = 0; imacro < int(pl_macros.size()); imacro++) { // Every macro are not placed in the beginnning macro_placed = false; - auto blk_id = imacro; + blk_id = pl_macros[imacro].members[0].blk_index; // Assume that all the blocks in the macro are of the same type itype = cluster_ctx.clb_nlist.block_type(blk_id).index; @@ -2988,7 +2980,7 @@ static void initial_placement_pl_macros(int macros_max_num_tries, /*int * */ std "Initial placement failed.\n" "Could not place macro length %d with head block %s (#%zu); not enough free locations of type %s (#%d).\n" "Please manually size the FPGA because VPR can't do this yet.\n", - int(pl_macros[imacro].members.size()), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id->udata), imacro->type.c_str(npnr_ctx), itype); + int(pl_macros[imacro].members.size()), cluster_ctx.clb_nlist.block_name(blk_id).c_str(), size_t(blk_id->udata), blk_id->type.c_str(npnr_ctx), itype); } } else { diff --git a/vpr/place/place_macro.cpp b/vpr/place/place_macro.cpp new file mode 100644 index 0000000000..e5ce95d44e --- /dev/null +++ b/vpr/place/place_macro.cpp @@ -0,0 +1,418 @@ +//#include +//#include +//#include +//#include +//#include +//using namespace std; + +//#include "vtr_assert.h" +//#include "vtr_memory.h" +//#include "vtr_util.h" +// +//#include "vpr_types.h" +//#include "vpr_error.h" +//#include "physical_types.h" +//#include "globals.h" +//#include "place.h" +//#include "read_xml_arch_file.h" +#include "place_macro.h" +//#include "vpr_utils.h" +//#include "echo_files.h" + + +///******************** File-scope variables declarations **********************/ +// +///* f_idirect_from_blk_pin array allow us to quickly find pins that could be in a * +// * direct connection. Values stored is the index of the possible direct connection * +// * as specified in the arch file, OPEN (-1) is stored for pins that could not be * +// * part of a direct chain conneciton. * +// * [0...device_ctx.num_block_types-1][0...num_pins-1] */ +//static int ** f_idirect_from_blk_pin = nullptr; +// +///* f_direct_type_from_blk_pin array stores the value SOURCE if the pin is the * +// * from_pin, SINK if the pin is the to_pin in the direct connection as specified in * +// * the arch file, OPEN (-1) is stored for pins that could not be part of a direct * +// * chain conneciton. * +// * [0...device_ctx.num_block_types-1][0...num_pins-1] */ +//static int ** f_direct_type_from_blk_pin = nullptr; + +/* f_imacro_from_blk_pin maps a blk_num to the corresponding macro index. * + * If the block is not part of a macro, the value OPEN (-1) is stored. * + * [0...cluster_ctx.clb_nlist.blocks().size()-1] */ +static /*vtr::vector_map*/ std::vector f_imacro_from_iblk; + + +///******************** Subroutine declarations ********************************/ +// +//static void find_all_the_macro (int * num_of_macro, std::vector &pl_macro_member_blk_num_of_this_blk, +// std::vector &pl_macro_idirect, std::vector &pl_macro_num_members, std::vector> &pl_macro_member_blk_num); + +static void alloc_and_load_imacro_from_iblk(/*t_pl_macro **/ std::vector ¯os /*, int num_macros*/); + +//static void write_place_macros(std::string filename, const t_pl_macro* macros, int num_macros); +// +//static bool is_constant_clb_net(ClusterNetId clb_net); +// +//static bool net_is_driven_by_direct(ClusterNetId clb_net); +// +//static void validate_macros(t_pl_macro* macros, int num_macro); +///******************** Subroutine definitions *********************************/ +// +// +//static void find_all_the_macro (int * num_of_macro, std::vector &pl_macro_member_blk_num_of_this_blk, +// std::vector &pl_macro_idirect, std::vector &pl_macro_num_members, std::vector> &pl_macro_member_blk_num) { +// +// /* Compute required size: * +// * Go through all the pins with possible direct connections in * +// * f_idirect_from_blk_pin. Count the number of heads (which is the same * +// * as the number macros) and also the length of each macro * +// * Head - blocks with to_pin OPEN and from_pin connected * +// * Tail - blocks with to_pin connected and from_pin OPEN */ +// +// int from_iblk_pin, to_iblk_pin, from_idirect, to_idirect, +// from_src_or_sink, to_src_or_sink; +// ClusterNetId to_net_id, from_net_id, next_net_id, curr_net_id; +// ClusterBlockId next_blk_id; +// int num_blk_pins, num_macro; +// int imember; +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// num_macro = 0; +// for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { +// num_blk_pins = cluster_ctx.clb_nlist.block_type(blk_id)->num_pins; +// for (to_iblk_pin = 0; to_iblk_pin < num_blk_pins; to_iblk_pin++) { +// +// to_net_id = cluster_ctx.clb_nlist.block_net(blk_id, to_iblk_pin); +// to_idirect = f_idirect_from_blk_pin[cluster_ctx.clb_nlist.block_type(blk_id)->index][to_iblk_pin]; +// to_src_or_sink = f_direct_type_from_blk_pin[cluster_ctx.clb_nlist.block_type(blk_id)->index][to_iblk_pin]; +// +// // Identify potential macro head blocks (i.e. start of a macro) +// // +// // The input SINK (to_pin) of a potential HEAD macro will have either: +// // * no connection to any net (OPEN), or +// // * a connection to a constant net (e.g. gnd/vcc) which is not driven by a direct +// // +// // Note that the restriction that constant nets are not driven from another direct ensures that +// // blocks in the middle of a chain with internal constant signals are not detected has potential +// // head blocks. +// if (to_src_or_sink == SINK && to_idirect != OPEN +// && (to_net_id == ClusterNetId::INVALID() +// || (is_constant_clb_net(to_net_id) +// && !net_is_driven_by_direct(to_net_id)))) { +// +// for (from_iblk_pin = 0; from_iblk_pin < num_blk_pins; from_iblk_pin++) { +// from_net_id = cluster_ctx.clb_nlist.block_net(blk_id, from_iblk_pin; +// from_idirect = f_idirect_from_blk_pin[cluster_ctx.clb_nlist.block_type(blk_id)->index][from_iblk_pin]; +// from_src_or_sink = f_direct_type_from_blk_pin[cluster_ctx.clb_nlist.block_type(blk_id)->index][from_iblk_pin]; +// +// // Confirm whether this is a head macro +// // +// // The output SOURCE (from_pin) of a true head macro will: +// // * drive another block with the same direct connection +// if (from_src_or_sink == SOURCE && to_idirect == from_idirect && from_net_id != ClusterNetId::INVALID()) { +// +// // Mark down that this is the first block in the macro +// pl_macro_member_blk_num_of_this_blk[0] = blk_id; +// pl_macro_idirect[num_macro] = to_idirect; +// +// // Increment the num_member count. +// pl_macro_num_members[num_macro]++; +// +// // Also find out how many members are in the macros, +// // there are at least 2 members - 1 head and 1 tail. +// +// // Initialize the variables +// next_net_id = from_net_id; +// next_blk_id = blk_id; +// +// // Start finding the other members +// while (next_net_id != ClusterNetId::INVALID()) { +// curr_net_id = next_net_id; +// +// // Assume that carry chains only has 1 sink - direct connection +// VTR_ASSERT(cluster_ctx.clb_nlist.net_sinks(curr_net_id).size() == 1); +// next_blk_id = cluster_ctx.clb_nlist.net_pin_block(curr_net_id, 1); +// +// // Assume that the from_iblk_pin index is the same for the next block +// VTR_ASSERT(f_idirect_from_blk_pin[cluster_ctx.clb_nlist.block_type(next_blk_id)->index][from_iblk_pin] == from_idirect +// && f_direct_type_from_blk_pin[cluster_ctx.clb_nlist.block_type(next_blk_id)->index][from_iblk_pin] == SOURCE); +// next_net_id = cluster_ctx.clb_nlist.block_net(next_blk_id, from_iblk_pin); +// +// // Mark down this block as a member of the macro +// imember = pl_macro_num_members[num_macro]; +// pl_macro_member_blk_num_of_this_blk[imember] = next_blk_id; +// +// // Increment the num_member count. +// pl_macro_num_members[num_macro]++; +// +// } // Found all the members of this macro at this point +// +// // Allocate the second dimension of the blk_num array since I now know the size +// pl_macro_member_blk_num[num_macro].resize(pl_macro_num_members[num_macro]); +// // Copy the data from the temporary array to the newly allocated array. +// for (imember = 0; imember < pl_macro_num_members[num_macro]; imember++) +// pl_macro_member_blk_num[num_macro][imember] = pl_macro_member_blk_num_of_this_blk[imember]; +// +// // Increment the macro count +// num_macro ++; +// +// } // Do nothing if the from_pins does not have same possible direct connection. +// } // Finish going through all the pins for from_pins. +// } // Do nothing if the to_pins does not have same possible direct connection. +// } // Finish going through all the pins for to_pins. +// } // Finish going through all blocks. +// +// // Now, all the data is readily stored in the temporary data structures. +// *num_of_macro = num_macro; +//} + + +int alloc_and_load_placement_macros(/*t_direct_inf* directs, int num_segments,*/ /*t_pl_macro ***/ std::vector ¯os) { +// +// /* This function allocates and loads the macros placement macros * +// * and returns the total number of macros in 2 steps. * +// * 1) Allocate temporary data structure for maximum possible * +// * size and loops through all the blocks storing the data * +// * relevant to the carry chains. At the same time, also count * +// * the amount of memory required for the actual variables. * +// * 2) Allocate the actual variables with the exact amount of * +// * memory. Then loads the data from the temporary data * +// * structures before freeing them. * +// * * +// * For pl_macro_member_blk_num, allocate for the first dimension * +// * only at first. Allocate for the second dimemsion when I know * +// * the size. Otherwise, the array is going to be of size * +// * cluster_ctx.clb_nlist.blocks().size()^2 (There are big * +// * benckmarks VPR that have cluster_ctx.clb_nlist.blocks().size() * +// * in the 100k's range). * +// * * +// * The placement macro array is freed by the caller(s). */ + + /* Declaration of local variables */ + int imacro, imember /*, num_macro*/; + auto& cluster_ctx = g_vpr_ctx.clustering(); + +// /* Allocate maximum memory for temporary variables. */ +// std::vector pl_macro_idirect(cluster_ctx.clb_nlist.blocks().size()); +// std::vector pl_macro_num_members(cluster_ctx.clb_nlist.blocks().size()); +// std::vector> pl_macro_member_blk_num(cluster_ctx.clb_nlist.blocks().size()); +// std::vector pl_macro_member_blk_num_of_this_blk(cluster_ctx.clb_nlist.blocks().size()); +// +// t_pl_macro * macro = nullptr; +// +// /* Sets up the required variables. */ +// alloc_and_load_idirect_from_blk_pin(directs, num_directs, +// &f_idirect_from_blk_pin, &f_direct_type_from_blk_pin); +// +// +// /* Compute required size: * +// * Go through all the pins with possible direct connections in * +// * f_idirect_from_blk_pin. Count the number of heads (which is the same * +// * as the number macros) and also the length of each macro * +// * Head - blocks with to_pin OPEN and from_pin connected * +// * Tail - blocks with to_pin connected and from_pin OPEN */ +// num_macro = 0; +// find_all_the_macro (&num_macro, pl_macro_member_blk_num_of_this_blk, +// pl_macro_idirect, pl_macro_num_members, pl_macro_member_blk_num); +// +// /* Allocate the memories for the macro. */ +// macro = (t_pl_macro *) vtr::malloc (num_macro * sizeof(t_pl_macro)); + + /* Allocate the memories for the chaim members. * + * Load the values from the temporary data structures. */ + for (auto &b : cluster_ctx.clb_nlist.blocks()) { + auto head = b.second.get(); + const auto& children = head->constr_children; + if (children.empty()) continue; + imacro = macros.size(); + + macros.emplace_back(); + macros[imacro].members.resize(children.size()+1); + macros[imacro].members[0].x_offset = 0; + macros[imacro].members[0].y_offset = 0; + macros[imacro].members[0].z_offset = 0; + macros[imacro].members[0].blk_index = head; + + /* Load the values for each member of the macro */ + for (imember = 1; imember <= int(children.size()); imember++) { + macros[imacro].members[imember].x_offset = children[imember-1]->constr_x; + macros[imacro].members[imember].y_offset = children[imember-1]->constr_y; + macros[imacro].members[imember].z_offset = children[imember-1]->constr_z; + macros[imacro].members[imember].blk_index = children[imember-1]; + } + } + +// /* Returns the pointer to the macro by reference. */ +// *macros = macro; +// +// if(isEchoFileEnabled(E_ECHO_PLACE_MACROS)) { +// write_place_macros(getEchoFileName(E_ECHO_PLACE_MACROS), *macros, num_macro); +// } +// +// validate_macros(*macros, num_macro); + + return macros.size(); +} + +void get_imacro_from_iblk(int *imacro, ClusterBlockId iblk, std::vector ¯os /*, int num_macros*/) { + + /* This mapping is needed for fast lookup's whether the block with index * + * iblk belongs to a placement macro or not. * + * * + * The array f_imacro_from_iblk is used for the mapping for speed reason * + * [0...cluster_ctx.clb_nlist.blocks().size()-1] */ + + /* If the array is not allocated and loaded, allocate it. */ + if (f_imacro_from_iblk.size() == 0) { + alloc_and_load_imacro_from_iblk(macros /*, num_macros*/); + } + + /* Return the imacro for the block. */ + *imacro = f_imacro_from_iblk[iblk]; + +} + +/* Allocates and loads imacro_from_iblk array. */ +static void alloc_and_load_imacro_from_iblk(/*t_pl_macro **/ std::vector ¯os /*, int num_macros*/) { + int imacro, imember; + auto& cluster_ctx = g_vpr_ctx.clustering(); + + f_imacro_from_iblk.resize(cluster_ctx.clb_nlist.blocks().size(), OPEN); + + /* Allocate and initialize the values to OPEN (-1). */ + //for (const auto& b : cluster_ctx.clb_nlist.blocks()) { + // auto blk_id = b.second.get(); + // f_imacro_from_iblk.insert(blk_id, OPEN); + //} + + /* Load the values */ + for (imacro = 0; imacro < int(macros.size()); imacro++) { + for (imember = 0; imember < int(macros[imacro].members.size()); imember++) { + /*ClusterBlockId*/ auto blk_id = macros[imacro].members[imember].blk_index; + f_imacro_from_iblk[blk_id->udata] = imacro; + } + } +} + +//void free_placement_macros_structs() { +// +// /* This function frees up all the static data structures used. */ +// +// // This frees up the two arrays and set the pointers to NULL +// auto& device_ctx = g_vpr_ctx.device(); +// int itype; +// if ( f_idirect_from_blk_pin != nullptr ) { +// for (itype = 1; itype < device_ctx.num_block_types; itype++) { +// free(f_idirect_from_blk_pin[itype]); +// } +// free(f_idirect_from_blk_pin); +// f_idirect_from_blk_pin = nullptr; +// } +// +// if ( f_direct_type_from_blk_pin != nullptr ) { +// for (itype = 1; itype < device_ctx.num_block_types; itype++) { +// free(f_direct_type_from_blk_pin[itype]); +// } +// free(f_direct_type_from_blk_pin); +// f_direct_type_from_blk_pin = nullptr; +// } +//} +// +//static void write_place_macros(std::string filename, const t_pl_macro *macros, int num_macros) { +// +// FILE* f = vtr::fopen(filename.c_str(), "w"); +// +// fprintf(f, "#Identified Placement macros\n"); +// fprintf(f, "Num_Macros: %d\n", num_macros); +// for (int imacro = 0; imacro < num_macros; ++imacro) { +// const t_pl_macro* macro = ¯os[imacro]; +// fprintf(f, "Macro_Id: %d, Num_Blocks: %d\n", imacro, macro->num_blocks); +// fprintf(f, "------------------------------------------------------\n"); +// for (int imember = 0; imember < macro->num_blocks; ++imember) { +// const t_pl_macro_member* macro_memb = ¯o->members[imember]; +// fprintf(f, "Block_Id: %zu, x_offset: %d, y_offset: %d, z_offset: %d\n", +// size_t(macro_memb->blk_index), +// macro_memb->x_offset, +// macro_memb->y_offset, +// macro_memb->z_offset); +// } +// fprintf(f, "\n"); +// } +// +// fprintf(f, "\n"); +// +// fprintf(f, "#Macro-related direct connections\n"); +// fprintf(f, "type type_pin is_direct direct_type\n"); +// fprintf(f, "------------------------------------------\n"); +// auto& device_ctx = g_vpr_ctx.device(); +// for (int itype = 0; itype < device_ctx.num_block_types; ++itype) { +// t_type_descriptor* type = &device_ctx.block_types[itype]; +// +// for (int ipin = 0; ipin < type->num_pins; ++ipin) { +// if (f_idirect_from_blk_pin[itype][ipin] != OPEN) { +// if (f_direct_type_from_blk_pin[itype][ipin] == SOURCE) { +// fprintf(f, "%-9s %-9d true SOURCE \n", type->name, ipin); +// } else { +// VTR_ASSERT(f_direct_type_from_blk_pin[itype][ipin] == SINK); +// fprintf(f, "%-9s %-9d true SINK \n", type->name, ipin); +// } +// } else { +// VTR_ASSERT(f_direct_type_from_blk_pin[itype][ipin] == OPEN); +// } +// } +// +// } +// +// fclose(f); +//} +// +//static bool is_constant_clb_net(ClusterNetId clb_net) { +// auto& atom_ctx = g_vpr_ctx.atom(); +// AtomNetId atom_net = atom_ctx.lookup.atom_net(clb_net); +// +// return atom_ctx.nlist.net_is_constant(atom_net); +//} +// +//static bool net_is_driven_by_direct(ClusterNetId clb_net) { +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// ClusterBlockId block_id = cluster_ctx.clb_nlist.net_driver_block(clb_net); +// int pin_index = cluster_ctx.clb_nlist.net_pin_physical_index(clb_net, 0); +// +// auto direct = f_idirect_from_blk_pin[cluster_ctx.clb_nlist.block_type(block_id)->index][pin_index]; +// +// return direct != OPEN; +//} +// +//static void validate_macros(t_pl_macro* macros, int num_macros) { +// //Perform sanity checks on macros +// auto& cluster_ctx = g_vpr_ctx.clustering(); +// +// //Verify that blocks only appear in a single macro +// std::multimap block_to_macro; +// for (int imacro = 0; imacro < num_macros; ++imacro) { +// for (int imember = 0; imember < macros[imacro].num_blocks; ++imember) { +// ClusterBlockId iblk = macros[imacro].members[imember].blk_index; +// +// block_to_macro.emplace(iblk, imacro); +// } +// } +// +// for (auto blk_id : cluster_ctx.clb_nlist.blocks()) { +// auto range = block_to_macro.equal_range(blk_id); +// +// int blk_macro_cnt = std::distance(range.first, range.second); +// if (blk_macro_cnt > 1) { +// std::stringstream msg; +// msg << "Block #" << size_t(blk_id) << " '" << cluster_ctx.clb_nlist.block_name(blk_id) << "'" +// << " appears in " << blk_macro_cnt << " placement macros (should appear in at most one). Related Macros:\n"; +// +// for (auto iter = range.first; iter != range.second; ++iter) { +// int imacro = iter->second; +// msg << " Macro #: " << imacro << "\n"; +// } +// +// VPR_THROW(VPR_ERROR_PLACE, msg.str().c_str()); +// } +// } +//} diff --git a/vpr/place/place_macro.h b/vpr/place/place_macro.h new file mode 100644 index 0000000000..d5f4e9a96c --- /dev/null +++ b/vpr/place/place_macro.h @@ -0,0 +1,168 @@ +/**************************************************************************************** + Y.G.THIEN + 29 AUG 2012 + + This file contains functions related to placement macros. The term "placement macros" + refers to a structure that contains information on blocks that need special treatment + during placement and possibly routing. + + An example of placement macros is a carry chain. Blocks in a carry chain have to be + placed in a specific orientation or relative placement so that the carry_in's and the + carry_out's are properly aligned. With that, the carry chains would be able to use the + direct connections specified in the arch file. Direct connections with the pin's + fc_value 0 would be treated specially in routing where the whole carry chain would be + treated as a unit and regular routing would not be used to connect the carry_in's and + carry_out's. Floorplanning constraints may also be an example of placement macros. + + The function alloc_and_load_placement_macros allocates and loads the placement + macros in the following steps: + (1) First, go through all the block types and mark down the pins that could possibly + be part of a placement macros. + (2) Then, go through the netlist of all the pins marked in (1) to find out all the + heads of the placement macros using criteria depending on the type of placement + macros. For carry chains, the heads of the placement macros are blocks with + carry_in's not connected to any nets (OPEN) while the carry_out's connected to the + netlist with only 1 SINK. + (3) Traverse from the heads to the tails of the placement macros and load the + information in the t_pl_macro data structure. Similar to (2), tails are identified + with criteria depending on the type of placement macros. For carry chains, the + tails are blocks with carry_out's not connected to any nets (OPEN) while the + carry_in's is connected to the netlist which has only 1 SINK. + + The only placement macros supported at the moment are the carry chains with limited + functionality. + + Current support for placement macros are: + (1) The arch parser for direct connections is working. The specifications of the direct + connections are specified in sample_adder_arch.xml and also in the + VPR_User_Manual.doc + (2) The placement macros allocator and loader is working. + (3) The initial placement of placement macros that respects the restrictions of the + placement macros is working. + (4) The post-placement legality check for placement macros is working. + + Current limitations on placement macros are: + (1) One block could only be a part of a carry chain. In the future, if a block is part + of multiple placement macros, we should load 1 huge placement macro instead of + multiple placement macros that contain the same block. + (2) Bus direct connections (direct connections with multiple bits) are supported. + However, a 2-bit carry chain when loaded would become 2 1-bit carry chains. + And because of (1), only 1 1-bit carry chain would be loaded. In the future, + placement macros with multiple-bit connections or multiple 1-bit connections + should be allowed. + (3) Placement macros that span longer or wider than the chip would cause an error. + In the future, we *might* expand the size of the chip to accommodate such + placement macros that are crucial. + + In order for the carry chain support to work, two changes are required in the + arch file. + (1) For carry chain support, added in a new child in called . + specifies a list of available direct connections on the FPGA chip + that are necessary for direct carry chain connections. These direct connections + would be treated specially in routing if the fc_value for the pins is specified + as 0. Note that only direct connections that has fc_value 0 could be used as a + carry chain. + + A may have 0 or more children called . For each , + there are the following fields: + 1) name: This specifies the name given to this particular direct connection. + 2) from_pin: This specifies the SOURCEs for this direct connection. The format + could be as following: + a) type_name.port_name, for all the pins in this port. + b) type_name.port_name [end_pin_index:start_pin_index], for a + single pin, the end_pin_index and start_pin_index could be + the same. + 3) to_pin: This specifies the SINKs for this direct connection. The format is + the same as from_pin. + Note that the width of the from_pin and to_pin has to match. + 4) x_offset: This specifies the x direction that this connection is going from + SOURCEs to SINKs. + 5) y_offset: This specifies the y direction that this connection is going from + SOURCEs to SINKs. + Note that the x_offset and y_offset could not both be 0. + 6) z_offset: This specifies the z sublocations that all the blocks in this + direct connection to be at. + + The example of a direct connection specification below shows a possible carry chain + connection going north on the FPGA chip: + _______________________________________________________________________________ + | | + | | + | | + |_______________________________________________________________________________| + A corresponding arch file that has this direct connection is sample_adder_arch.xml + A corresponding blif file that uses this direct connection is adder.blif + + (2) As mentioned in (1), carry chain connections using the directs would only be + recognized if the pin's fc_value is 0. In order to achieve this, pin-based fc_value + is required. Hence, the new tag replaces both and tags. + + A tag may have 0 or more children called . For each , there are the + following fields: + 1) in_type: This specifies the default fc_type for input pins. They could + be "frac", "abs" or "full". + 2) in_val: This specifies the default fc_value for input pins. + 3) out_type: This specifies the default fc_type for output pins. They could + be "frac", "abs" or "full". + 4) out_val: This specifies the default fc_value for output pins. + + As for the children, there are the following fields: + 1) name: This specifies the name of the port/pin that the fc_type and fc_value + apply to. The name have to be in the format "port_name" or + "port_name [end_pin_index:start_pin_index]" where port_name is the name + of the port it apply to while end_pin_index and start_pin_index could + be specified to apply the fc_type and fc_value that follows to part of + a bus (multi-pin) port. + 2) fc_type: This specifies the fc_type that would be applied to the specified pins. + 3) fc_val: This specifies the fc_value that would be applied to the specified pins. + + The example of a pin-based fc_value specification below shows that the fc_values for + the cout and the cin ports are 0: + _______________________________________________________________________________ + | | + | | + | | + | | + |_______________________________________________________________________________| + A corresponding arch file that has this direct connection is sample_adder_arch.xml + A corresponding blif file that uses this direct connection is adder.blif + +****************************************************************************************/ + + +#ifndef PLACE_MACRO_H +#define PLACE_MACRO_H + +/* These are the placement macro structure. + * It is in the form of array of structs instead of + * structs of arrays for cache efficiency. + * Could have more data members for other macro type. + * blk_index: The cluster_ctx.blocks index of this block. + * x_offset: The x_offset of the previous block to this cluster_ctx.blocks. + * y_offset: The y_offset of the previous block to this cluster_ctx.blocks. + */ +struct t_pl_macro_member{ + /*ClusterBlockId*/ CellInfo *blk_index; + int x_offset; + int y_offset; + int z_offset; +}; + +/* num_blocks: The number of blocks this macro contains. + * members: An array of blocks in this macro [0:num_macro-1]. + * idirect: The direct index as specified in the arch file + */ +struct t_pl_macro { + //int num_blocks; + /*t_pl_macro_member**/ std::vector members; +}; + + +/* These are the function declarations. */ +int alloc_and_load_placement_macros(/*t_direct_inf* directs, int num_segments,*/ /*t_pl_macro ***/ std::vector &chains); +void get_imacro_from_iblk(int *imacro, ClusterBlockId iblk, /*t_pl_macro **/ std::vector ¯os /*, int num_macros*/); +//void free_placement_macros_structs(); + +#endif From 96bfac28d47e9812d00234d5ddbe8bf4bb112b71 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 6 Aug 2018 18:29:28 -0700 Subject: [PATCH 105/116] Add print_fmax argument back to timing_analysis() --- common/timing.cc | 10 +++++++--- common/timing.h | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index c00e1ba50c..a3a468b155 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -196,7 +196,7 @@ void assign_budget(Context *ctx, bool quiet) log_info("Checksum: 0x%08x\n", ctx->checksum()); } -void timing_analysis(Context *ctx, bool print_histogram, bool print_path) +delay_t timing_analysis(Context *ctx, bool print_fmax, bool print_histogram, bool print_path) { PortRefVector crit_path; DelayFrequency slack_histogram; @@ -242,8 +242,10 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path) } } - delay_t default_slack = delay_t(1.0e12 / ctx->target_freq); - log_info("estimated Fmax = %.2f MHz\n", 1e6 / (default_slack - min_slack)); + if (print_fmax) { + delay_t default_slack = delay_t(1.0e12 / ctx->target_freq); + log_info("estimated Fmax = %.2f MHz\n", 1e6 / (default_slack - min_slack)); + } if (print_histogram && slack_histogram.size() > 0) { constexpr unsigned num_bins = 20; @@ -269,6 +271,8 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path) std::string(bins[i] * bar_width / max_freq, '*').c_str(), (bins[i] * bar_width) % max_freq > 0 ? '+' : ' '); } + + return min_slack; } NEXTPNR_NAMESPACE_END diff --git a/common/timing.h b/common/timing.h index cfb71ae0a9..9530e8effa 100644 --- a/common/timing.h +++ b/common/timing.h @@ -29,7 +29,7 @@ void assign_budget(Context *ctx, bool quiet = false); // Perform timing analysis and print out the fmax, and optionally the // critical path -void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_path = false); +delay_t timing_analysis(Context *ctx, bool print_fmax = false, bool print_histogram = true, bool print_path = false); NEXTPNR_NAMESPACE_END From 43334fa7ad56a27f5332320f2dbff8b526b7c36e Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 6 Aug 2018 18:29:54 -0700 Subject: [PATCH 106/116] Update call to timing_analysis from router1 --- common/router1.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/router1.cc b/common/router1.cc index 0733a61ec2..6f5c565d24 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -937,7 +937,7 @@ bool router1(Context *ctx, const Router1Cfg &cfg) #ifndef NDEBUG ctx->check(); #endif - timing_analysis(ctx, true /* slack_histogram */, true /* print_path */); + timing_analysis(ctx, true /* print_fmax */, true /* print_histogram */, true /* print_path */); ctx->unlock(); return true; } catch (log_execution_error_exception) { From 3f0879907a73cd85043d21756d723841f621a19d Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 6 Aug 2018 18:39:58 -0700 Subject: [PATCH 107/116] Fix merge with upstream --- common/placer_vpr.cc | 41 +++++++++++++++-------------------------- ice40/arch.cc | 6 ++---- vpr/place/place.cpp | 39 +++++++++++++++++---------------------- 3 files changed, 34 insertions(+), 52 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 2713024121..19fb0419ad 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -38,9 +38,8 @@ #include #include "log.h" #include "place_common.h" -#include "place_legaliser.h" #include "timing.h" -#include "place_legaliser.h" +#include "util.h" namespace vpr { using namespace NEXTPNR_NAMESPACE; @@ -117,7 +116,7 @@ namespace vpr { sWNS = std::numeric_limits::max(); sTNS = 0; max_req = delay_t(1.0e12 / npnr_ctx->target_freq); - worst_path_slack = timing_analysis(npnr_ctx); + worst_path_slack = timing_analysis(npnr_ctx, false /* print_fmax */, false /* print_histogram */); // Compute the delay for every pin on every net for (auto &n : npnr_ctx->nets) { @@ -130,18 +129,16 @@ namespace vpr { if (driver_cell->bel == BelId()) continue; driver_gb = npnr_ctx->getBelGlobalBuf(driver_cell->bel); - WireId drv_wire = npnr_ctx->getBelPinWire(driver_cell->bel, npnr_ctx->portPinFromId(net->driver.port)); if (driver_gb) continue; for (auto& load : net->users) { - if (load.cell == nullptr) + if (!load.cell) continue; CellInfo *load_cell = load.cell; if (load_cell->bel == BelId()) continue; - WireId user_wire = npnr_ctx->getBelPinWire(load_cell->bel, npnr_ctx->portPinFromId(load.port)); - delay_t raw_wl = npnr_ctx->estimateDelay(drv_wire, user_wire); - delay_t slack = load.budget - raw_wl; + auto net_delay = npnr_ctx->getNetinfoRouteDelay(net, load); + auto slack = load.budget - net_delay; sWNS = std::min(sWNS, slack); if (slack < 0) sTNS += slack; @@ -259,19 +256,6 @@ class VPRPlacer for (auto& c : grid._bels) c.resize(max_y+1); - auto carries = prepare_and_find_carries(ctx); - for (auto &chain : carries) { - auto head = chain.front(); - for (int z = 1; z < int(chain.size()); z++) { - auto cell = chain.at(z); - cell->constr_parent = head; - cell->constr_x = 0; - cell->constr_y = z / 8; - cell->constr_z = z % 8; - } - head->constr_children.assign(chain.begin() + 1, chain.end()); - } - int32_t cell_idx = 0; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); @@ -297,7 +281,7 @@ class VPRPlacer loc_name.c_str(), ctx->belTypeToId(bel_type).c_str(ctx), ci->name.c_str(ctx), ci->type.c_str(ctx)); } - ctx->bindBel(bel, ci->name, STRENGTH_USER); + ctx->bindBel(bel, ci, STRENGTH_USER); } } int32_t net_idx = 0; @@ -328,12 +312,13 @@ class VPRPlacer vpr::try_place(placer_opts, annealing_sched); // Final post-pacement validitiy check + ctx->yield(); for (auto bel : ctx->getBels()) { - IdString cell = ctx->getBoundBelCell(bel); + CellInfo *cell = ctx->getBoundBelCell(bel); if (!ctx->isBelLocationValid(bel)) { std::string cell_text = "no cell"; - if (cell != IdString()) - cell_text = std::string("cell '") + cell.str(ctx) + "'"; + if (cell != nullptr) + cell_text = std::string("cell '") + ctx->nameOf(cell) + "'"; if (ctx->force) { log_warning("post-placement validity check failed for Bel '%s' " "(%s)\n", @@ -345,7 +330,11 @@ class VPRPlacer } } } - timing_analysis(ctx, true /* print_fmax */); + for (auto cell : sorted(ctx->cells)) + if (get_constraints_distance(ctx, cell.second) != 0) + log_error("constraint satisfaction check failed for cell '%s' at Bel '%s'\n", cell.first.c_str(ctx), + ctx->getBelName(cell.second->bel).c_str(ctx)); + timing_analysis(ctx); ctx->unlock(); return true; } diff --git a/ice40/arch.cc b/ice40/arch.cc index 209edba9e3..eadc8e9291 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -708,11 +708,11 @@ bool Arch::place_vpr() bool is_reset = net->is_reset, is_cen = net->is_enable; NPNR_ASSERT(!is_reset || !is_cen); if (is_reset) { - ctx->bindBel(gb_reset.back(), cell->name, STRENGTH_WEAK); + ctx->bindBel(gb_reset.back(), cell, STRENGTH_WEAK); gb_reset.pop_back(); } else if (is_cen) { - ctx->bindBel(gb_cen.back(), cell->name, STRENGTH_WEAK); + ctx->bindBel(gb_cen.back(), cell, STRENGTH_WEAK); gb_cen.pop_back(); } } @@ -721,8 +721,6 @@ bool Arch::place_vpr() return placer_vpr(ctx); } -bool Arch::route() { return router1(getCtx()); } - // ----------------------------------------------------------------------- bool Arch::route() diff --git a/vpr/place/place.cpp b/vpr/place/place.cpp index e410a24f01..2b48c0bb75 100644 --- a/vpr/place/place.cpp +++ b/vpr/place/place.cpp @@ -1184,15 +1184,13 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, int x_to, auto bel_from = b_from->bel; auto bel_to = g_vpr_ctx.device().grid[x_to][y_to][z_to]; -// b_to = place_ctx.grid_blocks[x_to][y_to].blocks[z_to]; - auto b_to_id = npnr_ctx->getBoundBelCell(bel_to); - b_to = (b_to_id == IdString() ? nullptr : npnr_ctx->cells[b_to_id].get()); + b_to = npnr_ctx->getBoundBelCell(bel_to); // Check whether the to_location is empty if (b_to == /*EMPTY_BLOCK_ID*/ nullptr) { npnr_ctx->unbindBel(bel_from); - npnr_ctx->bindBel(bel_to, b_from->name, STRENGTH_WEAK); + npnr_ctx->bindBel(bel_to, b_from, STRENGTH_WEAK); //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel_to)); blocks_affected.emplace_back(b_from, bel_from); @@ -1208,8 +1206,8 @@ static int setup_blocks_affected(/*ClusterBlockId*/ CellInfo* b_from, int x_to, npnr_ctx->unbindBel(bel_to); npnr_ctx->unbindBel(bel_from); - npnr_ctx->bindBel(bel_to, b_from->name, STRENGTH_WEAK); - npnr_ctx->bindBel(bel_from, b_to->name, STRENGTH_WEAK); + npnr_ctx->bindBel(bel_to, b_from, STRENGTH_WEAK); + npnr_ctx->bindBel(bel_from, b_to, STRENGTH_WEAK); blocks_affected.emplace_back(b_from, bel_from); blocks_affected.emplace_back(b_to, bel_to); @@ -1286,9 +1284,8 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i abort_swap = true; } else { - auto cell_name = npnr_ctx->getBoundBelCell(bel_to); - if (cell_name != IdString()) { - auto cell_to = npnr_ctx->cells[cell_name].get(); + auto cell_to = npnr_ctx->getBoundBelCell(bel_to); + if (cell_to) { if (cell_to->belStrength > STRENGTH_WEAK) { abort_swap = true; } @@ -1513,7 +1510,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin auto b_from = b.first; auto bel = b.second; - npnr_ctx->bindBel(bel, b_from->name, STRENGTH_WEAK); + npnr_ctx->bindBel(bel, b_from, STRENGTH_WEAK); } } @@ -1540,7 +1537,7 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin auto b_from = b.first; auto bel = b.second; - npnr_ctx->bindBel(bel, b_from->name, STRENGTH_WEAK); + npnr_ctx->bindBel(bel, b_from, STRENGTH_WEAK); } /* Resets the num_moved_blocks, but do not free blocks_moved array. Defensive Coding */ @@ -1834,9 +1831,8 @@ static bool find_to(/*t_type_ptr type,*/ float rlim, is_legal = false; } else { - auto cell_name = npnr_ctx->getBoundBelCell(bel_to); - if (cell_name != IdString()) { - auto cell_to = npnr_ctx->cells[cell_name].get(); + auto cell_to = npnr_ctx->getBoundBelCell(bel_to); + if (cell_to) { if (cell_to->belStrength > STRENGTH_WEAK) { is_legal = false; } @@ -2904,7 +2900,7 @@ static int try_place_macro(int itype, /*int*/ Loc ipos, int imacro) { auto iblk = pl_macros[imacro].members[imember].blk_index; auto bel = grid[member_x][member_y][member_z]; - npnr_ctx->bindBel(bel, iblk->name, STRENGTH_WEAK); + npnr_ctx->bindBel(bel, iblk, STRENGTH_WEAK); // Could not ensure that the randomiser would not pick this location again // So, would have to do a lazy removal - whenever I come across a block that could not be placed, @@ -3046,9 +3042,9 @@ static void initial_placement_blocks(/*int * free_locations, enum e_pad_loc_type // } if (npnr_ctx->isIO(blk_id)) - npnr_ctx->bindBel(grid[x][y][z], blk_id->name, STRENGTH_USER); + npnr_ctx->bindBel(grid[x][y][z], blk_id, STRENGTH_USER); else - npnr_ctx->bindBel(grid[x][y][z], blk_id->name, STRENGTH_WEAK); + npnr_ctx->bindBel(grid[x][y][z], blk_id, STRENGTH_WEAK); //NPNR_ASSERT(npnr_ctx->isBelLocationValid(bel)); // /* Ensure randomizer doesn't pick this location again, since it's occupied. Could shift all the @@ -3125,9 +3121,8 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, // All the macros are placed, update the legal_pos[][] array for (auto it = legal_pos.begin(); it != legal_pos.end(); it++) { it->erase(remove_if(it->begin(), it->end(), [&grid](const Loc& loc) { - auto cell_name = npnr_ctx->getBoundBelCell(grid[loc.x][loc.y][loc.z]); - if (cell_name == IdString()) return false; - auto cell = npnr_ctx->cells.at(cell_name).get(); + auto cell = npnr_ctx->getBoundBelCell(grid[loc.x][loc.y][loc.z]); + if (!cell) return false; return cell->belStrength > STRENGTH_WEAK; }), it->end()); } @@ -3137,8 +3132,8 @@ static void initial_placement(/*enum e_pad_loc_type pad_loc_type, it->clear(); it->reserve(src.size()); for (auto& loc : src) { - auto cell_name = npnr_ctx->getBoundBelCell(grid[loc.x][loc.y][loc.z]); - if (cell_name == IdString()) + auto cell = npnr_ctx->getBoundBelCell(grid[loc.x][loc.y][loc.z]); + if (!cell) it->push_back(loc); } npnr_ctx->shuffle(*it); From 4653a2bf9ed224aae1fa679d0e37813fd6d63933 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 6 Aug 2018 18:48:36 -0700 Subject: [PATCH 108/116] assign_budget() to return min_slack too --- common/timing.cc | 4 +++- common/timing.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index a3a468b155..f75a8d5d89 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -152,7 +152,7 @@ struct Timing } }; -void assign_budget(Context *ctx, bool quiet) +delay_t assign_budget(Context *ctx, bool quiet) { if (!quiet) { log_break(); @@ -194,6 +194,8 @@ void assign_budget(Context *ctx, bool quiet) if (!quiet) log_info("Checksum: 0x%08x\n", ctx->checksum()); + + return timing.min_slack; } delay_t timing_analysis(Context *ctx, bool print_fmax, bool print_histogram, bool print_path) diff --git a/common/timing.h b/common/timing.h index 9530e8effa..0f0fb181b2 100644 --- a/common/timing.h +++ b/common/timing.h @@ -25,7 +25,7 @@ NEXTPNR_NAMESPACE_BEGIN // Evenly redistribute the total path slack amongst all sinks on each path -void assign_budget(Context *ctx, bool quiet = false); +delay_t assign_budget(Context *ctx, bool quiet = false); // Perform timing analysis and print out the fmax, and optionally the // critical path From e220f8147cff7be3938bb10a6fc99ced7c2afbe5 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 6 Aug 2018 18:48:51 -0700 Subject: [PATCH 109/116] Run assign_budget() or timing_analysis(); not both --- common/placer_vpr.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 19fb0419ad..589361682e 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -111,12 +111,14 @@ namespace vpr { delay_t max_req; delay_t worst_path_slack; void update() { - assign_budget(npnr_ctx, true /* quiet */); + if (npnr_ctx->slack_redist_iter > 0) + worst_path_slack = assign_budget(npnr_ctx, true /* quiet */); + else + worst_path_slack = timing_analysis(npnr_ctx, false /* print_fmax */, false /* print_histogram */); sWNS = std::numeric_limits::max(); sTNS = 0; max_req = delay_t(1.0e12 / npnr_ctx->target_freq); - worst_path_slack = timing_analysis(npnr_ctx, false /* print_fmax */, false /* print_histogram */); // Compute the delay for every pin on every net for (auto &n : npnr_ctx->nets) { From 9f31c30d4c815af2266d673b2f35850f0e8a3a62 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 6 Aug 2018 19:03:46 -0700 Subject: [PATCH 110/116] Port over macro checking in VPR's check_place() --- vpr/place/place.cpp | 64 +++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/vpr/place/place.cpp b/vpr/place/place.cpp index 2b48c0bb75..605f1f6f26 100644 --- a/vpr/place/place.cpp +++ b/vpr/place/place.cpp @@ -1661,8 +1661,8 @@ static void record_affected_net(/*const ClusterNetId*/ NetInfo *net, int& num_af } } -static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const ClusterBlockId blk, const ClusterPinId blk_pin*/ - CellInfo* blk, BelId bel_from) { +static void update_net_bb(/*const ClusterNetId*/ NetInfo* net, /*int iblk, const ClusterBlockId*/ CellInfo* blk, /*const ClusterPinId blk_pin*/ + BelId bel_from) { // auto& cluster_ctx = g_vpr_ctx.clustering(); if (net->users.size() < SMALL_NET) { @@ -3273,11 +3273,11 @@ static void check_place(float bb_cost, float timing_cost, // // vtr::vector bdone; int error = 0; -// ClusterBlockId bnum, head_iblk, member_iblk; + /*ClusterBlockId*/ CellInfo* /*bnum,*/ head_iblk, *member_iblk; float bb_cost_check; // int usage_check; float timing_cost_check, delay_cost_check; -// int imacro, imember, member_x, member_y, member_z; + int imacro, imember, member_x, member_y, member_z; bb_cost_check = comp_bb_cost(CHECK); //vtr::printf_info("bb_cost recomputed from scratch: %g\n", bb_cost_check); @@ -3359,31 +3359,33 @@ static void check_place(float bb_cost, float timing_cost, // error++; // } // bdone.clear(); -// -// /* Check the pl_macro placement are legal - blocks are in the proper relative position. */ -// for (imacro = 0; imacro < num_pl_macros; imacro++) { -// -// head_iblk = pl_macros[imacro].members[0].blk_index; -// -// for (imember = 0; imember < pl_macros[imacro].num_blocks; imember++) { -// -// member_iblk = pl_macros[imacro].members[imember].blk_index; -// -// // Compute the suppossed member's x,y,z location -// member_x = place_ctx.block_locs[head_iblk].x + pl_macros[imacro].members[imember].x_offset; -// member_y = place_ctx.block_locs[head_iblk].y + pl_macros[imacro].members[imember].y_offset; -// member_z = place_ctx.block_locs[head_iblk].z + pl_macros[imacro].members[imember].z_offset; -// -// // Check the place_ctx.block_locs data structure first -// if (place_ctx.block_locs[member_iblk].x != member_x -// || place_ctx.block_locs[member_iblk].y != member_y -// || place_ctx.block_locs[member_iblk].z != member_z) { -// vtr::printf_error(__FILE__, __LINE__, -// "Block %zu in pl_macro #%d is not placed in the proper orientation.\n", -// size_t(member_iblk), imacro); -// error++; -// } -// + + /* Check the pl_macro placement are legal - blocks are in the proper relative position. */ + for (imacro = 0; imacro < int(pl_macros.size()); imacro++) { + + head_iblk = pl_macros[imacro].members[0].blk_index; + + for (imember = 0; imember < int(pl_macros[imacro].members.size()); imember++) { + + member_iblk = pl_macros[imacro].members[imember].blk_index; + + // Compute the suppossed member's x,y,z location + auto head_loc = npnr_ctx->getBelLocation(head_iblk->bel); + member_x = head_loc.x + pl_macros[imacro].members[imember].x_offset; + member_y = head_loc.y + pl_macros[imacro].members[imember].y_offset; + member_z = head_loc.z + pl_macros[imacro].members[imember].z_offset; + + // Check the place_ctx.block_locs data structure first + auto member_loc = npnr_ctx->getBelLocation(member_iblk->bel); + if (member_loc.x != member_x + || member_loc.y != member_y + || member_loc.z != member_z) { + vtr::printf_error(__FILE__, __LINE__, + "Block %zu in pl_macro #%d is not placed in the proper orientation.\n", + size_t(member_iblk), imacro); + error++; + } + // // Then check the place_ctx.grid data structure // if (place_ctx.grid_blocks[member_x][member_y].blocks[member_z] != member_iblk) { // vtr::printf_error(__FILE__, __LINE__, @@ -3391,8 +3393,8 @@ static void check_place(float bb_cost, float timing_cost, // size_t(member_iblk), imacro); // error++; // } -// } // Finish going through all the members -// } // Finish going through all the macros + } // Finish going through all the members + } // Finish going through all the macros if (error == 0) { vtr::printf_info("\n"); From 032b6ae305278c78a3d4597467437f3b95d62658 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 6 Aug 2018 19:47:51 -0700 Subject: [PATCH 111/116] Query context for (x,y,z) location to Bel --- common/placer_vpr.cc | 36 +++++++++++++++++------------------- vpr/place/place.cpp | 4 +++- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 589361682e..adf8ec1c44 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -49,10 +49,23 @@ namespace vpr { // base/device_grid.h struct DeviceGrid { - inline size_t width() const { return _bels.size(); } - inline size_t height() const { return _bels.front().size(); } - inline const std::vector>& operator[](size_t x) { return _bels.at(x); } - std::vector>> _bels; + inline size_t width() const { return npnr_ctx->chip_info->width; } + inline size_t height() const { return npnr_ctx->chip_info->height; } + + struct BelXY { + BelXY(BelRange&& range) : range(range) {} + BelRange range; + inline BelId operator[](int z) { auto it = range.begin(); for (; z > 0; --z, ++it); return *it; } + inline size_t size() { size_t n = 0; for (auto it = range.begin(); it != range.end(); ++it, ++n); return n; } + }; + + struct BelX { + BelX(int x) : x(x) {} + int x; + inline BelXY operator[](int y) { return BelXY(npnr_ctx->getBelsByTile(x,y)); } + }; + + inline BelX operator[](int x) { return BelX(x); } }; // base/netlist_fwd.h enum PinType @@ -242,21 +255,6 @@ class VPRPlacer VPRPlacer(Context *ctx) : ctx(ctx) { vpr::npnr_ctx = ctx; - int max_y = 0; - auto &grid = vpr::g_vpr_ctx.device.grid; - for (auto bel : ctx->getBels()) { - auto loc = ctx->getBelLocation(bel); - if (loc.x >= int(grid._bels.size())) - grid._bels.resize(loc.x+1); - max_y = std::max(loc.y, max_y); - if (max_y >= int(grid._bels[loc.x].size())) - grid._bels[loc.x].resize(max_y+1); - if (loc.z >= int(grid._bels[loc.x][loc.y].size())) - grid._bels[loc.x][loc.y].resize(loc.z+1); - grid._bels[loc.x][loc.y][loc.z] = bel; - } - for (auto& c : grid._bels) - c.resize(max_y+1); int32_t cell_idx = 0; for (auto &cell : ctx->cells) { diff --git a/vpr/place/place.cpp b/vpr/place/place.cpp index 605f1f6f26..74f3e23b03 100644 --- a/vpr/place/place.cpp +++ b/vpr/place/place.cpp @@ -1422,6 +1422,8 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin vtr::printf_info( "swap [%d][%d][%d] %s \"%s\" <=> [%d][%d][%d] %s \"%s\"\n", x_from, y_from, z_from, grid[x_from][y_from].type->name, (b_from != -1 ? cluster_ctx.blocks[b_from].name : ""), x_to, y_to, z_to, grid[x_to][y_to].type->name, (b_to != -1 ? cluster_ctx.blocks[b_to].name : "")); +#else + (void) z_from; #endif // /* Make the switch in order to make computing the new bounding * @@ -1887,7 +1889,7 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, *py_to = min_y + y_rel; // Instead of computing z_to in the outer find_to function, // do it here so we have a fully valid location - if (!grid[*px_to][*py_to].empty()) { + if (!grid[*px_to][*py_to].size() > 0) { *pz_to = vtr::irand(grid[*px_to][*py_to].size() - 1); } else { From d707d3faceeaff7523bcd3b5548e30536d75bdf2 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 6 Aug 2018 19:59:07 -0700 Subject: [PATCH 112/116] Fix check --- vpr/place/place.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vpr/place/place.cpp b/vpr/place/place.cpp index 74f3e23b03..1da0fa5dd8 100644 --- a/vpr/place/place.cpp +++ b/vpr/place/place.cpp @@ -1889,7 +1889,7 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, *py_to = min_y + y_rel; // Instead of computing z_to in the outer find_to function, // do it here so we have a fully valid location - if (!grid[*px_to][*py_to].size() > 0) { + if (grid[*px_to][*py_to].size() > 0) { *pz_to = vtr::irand(grid[*px_to][*py_to].size() - 1); } else { From 5f7953f5c16d74669c64349ea62be765dbfd5810 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 6 Aug 2018 21:07:37 -0700 Subject: [PATCH 113/116] Fix clobbering --- vpr/place/place.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vpr/place/place.cpp b/vpr/place/place.cpp index 1da0fa5dd8..0955762462 100644 --- a/vpr/place/place.cpp +++ b/vpr/place/place.cpp @@ -1294,8 +1294,9 @@ static int find_affected_blocks(/*ClusterBlockId*/ CellInfo* b_from, int x_to, i } // Does not allow a swap with a macro yet - get_imacro_from_iblk(&imacro, cell_to->udata, pl_macros /*, num_pl_macros*/); - if (imacro != -1) { + int jmacro; + get_imacro_from_iblk(&jmacro, cell_to->udata, pl_macros /*, num_pl_macros*/); + if (jmacro != -1) { abort_swap = true; } } From e4c3bc2e5f730a708842cb4578478445c03ddfe1 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 6 Aug 2018 22:31:07 -0700 Subject: [PATCH 114/116] Revert "Query context for (x,y,z) location to Bel" This reverts commit 032b6ae305278c78a3d4597467437f3b95d62658. Conflicts: vpr/place/place.cpp --- common/placer_vpr.cc | 36 +++++++++++++++++++----------------- vpr/place/place.cpp | 4 +--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index adf8ec1c44..589361682e 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -49,23 +49,10 @@ namespace vpr { // base/device_grid.h struct DeviceGrid { - inline size_t width() const { return npnr_ctx->chip_info->width; } - inline size_t height() const { return npnr_ctx->chip_info->height; } - - struct BelXY { - BelXY(BelRange&& range) : range(range) {} - BelRange range; - inline BelId operator[](int z) { auto it = range.begin(); for (; z > 0; --z, ++it); return *it; } - inline size_t size() { size_t n = 0; for (auto it = range.begin(); it != range.end(); ++it, ++n); return n; } - }; - - struct BelX { - BelX(int x) : x(x) {} - int x; - inline BelXY operator[](int y) { return BelXY(npnr_ctx->getBelsByTile(x,y)); } - }; - - inline BelX operator[](int x) { return BelX(x); } + inline size_t width() const { return _bels.size(); } + inline size_t height() const { return _bels.front().size(); } + inline const std::vector>& operator[](size_t x) { return _bels.at(x); } + std::vector>> _bels; }; // base/netlist_fwd.h enum PinType @@ -255,6 +242,21 @@ class VPRPlacer VPRPlacer(Context *ctx) : ctx(ctx) { vpr::npnr_ctx = ctx; + int max_y = 0; + auto &grid = vpr::g_vpr_ctx.device.grid; + for (auto bel : ctx->getBels()) { + auto loc = ctx->getBelLocation(bel); + if (loc.x >= int(grid._bels.size())) + grid._bels.resize(loc.x+1); + max_y = std::max(loc.y, max_y); + if (max_y >= int(grid._bels[loc.x].size())) + grid._bels[loc.x].resize(max_y+1); + if (loc.z >= int(grid._bels[loc.x][loc.y].size())) + grid._bels[loc.x][loc.y].resize(loc.z+1); + grid._bels[loc.x][loc.y][loc.z] = bel; + } + for (auto& c : grid._bels) + c.resize(max_y+1); int32_t cell_idx = 0; for (auto &cell : ctx->cells) { diff --git a/vpr/place/place.cpp b/vpr/place/place.cpp index 0955762462..08f1d9eaed 100644 --- a/vpr/place/place.cpp +++ b/vpr/place/place.cpp @@ -1423,8 +1423,6 @@ static e_swap_result try_swap(float t, float *cost, float *bb_cost, float *timin vtr::printf_info( "swap [%d][%d][%d] %s \"%s\" <=> [%d][%d][%d] %s \"%s\"\n", x_from, y_from, z_from, grid[x_from][y_from].type->name, (b_from != -1 ? cluster_ctx.blocks[b_from].name : ""), x_to, y_to, z_to, grid[x_to][y_to].type->name, (b_to != -1 ? cluster_ctx.blocks[b_to].name : "")); -#else - (void) z_from; #endif // /* Make the switch in order to make computing the new bounding * @@ -1890,7 +1888,7 @@ static void find_to_location(/*t_type_ptr*/ IdString type, float rlim, *py_to = min_y + y_rel; // Instead of computing z_to in the outer find_to function, // do it here so we have a fully valid location - if (grid[*px_to][*py_to].size() > 0) { + if (!grid[*px_to][*py_to].empty()) { *pz_to = vtr::irand(grid[*px_to][*py_to].size() - 1); } else { From 2245c5f58fb0971507bee03db5dada37d5584d35 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Mon, 6 Aug 2018 22:32:32 -0700 Subject: [PATCH 115/116] Use chip_info for width and height --- common/placer_vpr.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/placer_vpr.cc b/common/placer_vpr.cc index 589361682e..c08361fa10 100644 --- a/common/placer_vpr.cc +++ b/common/placer_vpr.cc @@ -49,8 +49,8 @@ namespace vpr { // base/device_grid.h struct DeviceGrid { - inline size_t width() const { return _bels.size(); } - inline size_t height() const { return _bels.front().size(); } + inline size_t width() const { return npnr_ctx->chip_info->width; } + inline size_t height() const { return npnr_ctx->chip_info->height; } inline const std::vector>& operator[](size_t x) { return _bels.at(x); } std::vector>> _bels; }; From 50e8aac87cadc6d2c0745f0a92466c347b54b281 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Fri, 10 Aug 2018 08:43:54 -0700 Subject: [PATCH 116/116] Add VTR attribution --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e9f197cdb0..0d13069d41 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ nextpnr -- a portable FPGA place and route tool =============================================== +### *This `vpr_place` branch contains code adapted from the [Verilog-To-Routing](https://verilogtorouting.org/) project.* + nextpnr aims to be a vendor neutral, timing driven, FOSS FPGA place and route tool.