| /* |
| * Author: Oleg Petelin |
| * Date: July 2014 |
| * |
| * The connection block metrics quantify certain qualities of the connection block. |
| * There are basically two orthogonal classes of metrics. One which is related to |
| * what groups of wires a pin connects to (groups being defined by wire start points). |
| * And a second class which is related to the quality of the switch pattern that the pins |
| * make into the channel in general (specifically, how much the switches of the pins overlap |
| * with eachother). Again, these two classes of metrics are fairly orthogonal, so it is |
| * possible to adjust one while keeping the other relatively constant. |
| * |
| * The code below currently works for the input/output connection blocks of bidirectional |
| * architectures and input connection blocks of unidirectional architectures. |
| * This code provides functions for calculating the connection block metrics, as well |
| * as for adjusting the actual connection block so that a certain value of a specified |
| * metric is achieved (this is done using an annealer). |
| */ |
| |
| #include <stdio.h> |
| #include <math.h> |
| #include <string.h> |
| #include <ctime> |
| #include <utility> |
| #include <cmath> |
| |
| #include <string> |
| #include <iostream> |
| #include <fstream> |
| #include <sstream> |
| #include <algorithm> |
| |
| #include <map> |
| #include <iterator> |
| |
| #include "vtr_random.h" |
| #include "vtr_assert.h" |
| #include "vtr_log.h" |
| #include "vtr_math.h" |
| |
| #include "vpr_types.h" |
| #include "vpr_error.h" |
| #include "vpr_utils.h" |
| |
| #include "cb_metrics.h" |
| |
| /* TODO: move to libarchfpga/include/util.h after ODIN II has been converted to use g++ */ |
| /* a generic function for determining if a given map key exists */ |
| template<typename F, typename T> |
| bool map_has_key(const F key, const std::map<F, T>* my_map) { |
| bool exists; |
| typename std::map<F, T>::const_iterator it = my_map->find(key); |
| if (my_map->end() != it) { |
| exists = true; |
| } else { |
| exists = false; |
| } |
| return exists; |
| } |
| /* a generic function for determining if a given set element exists */ |
| template<typename T> |
| bool set_has_element(const T elem, const std::set<T>* my_set) { |
| bool exists; |
| typename std::set<T>::const_iterator it = my_set->find(elem); |
| if (my_set->end() != it) { |
| exists = true; |
| } else { |
| exists = false; |
| } |
| return exists; |
| } |
| |
| /**** Function Declarations ****/ |
| /* goes through each pin of pin_type and determines which side of the block it comes out on. results are stored in |
| * the 'pin_locations' 2d-vector */ |
| static void get_pin_locations(const t_physical_tile_type_ptr block_type, const e_pin_type pin_type, const int num_pin_type_pins, int***** tracks_connected_to_pin, t_2d_int_vec* pin_locations); |
| |
| /* Gets the maximum Fc value from the Fc_array of this pin type. Errors out if the pins of this pin_type don't all have the same Fc */ |
| static int get_max_Fc(const int* Fc_array, const t_physical_tile_type_ptr block_type, const e_pin_type pin_type); |
| |
| /* initializes the fields of the cb_metrics class */ |
| static void init_cb_structs(const t_physical_tile_type_ptr block_type, int***** tracks_connected_to_pin, const int num_segments, const t_segment_inf* segment_inf, const e_pin_type pin_type, const int num_pin_type_pins, const int nodes_per_chan, const int Fc, Conn_Block_Metrics* cb_metrics); |
| |
| /* given a set of tracks connected to a pin, we'd like to find which of these tracks are connected to a number of switches |
| * greater than 'criteria'. The resulting set of tracks is passed back in the 'result' vector */ |
| static void find_tracks_with_more_switches_than(const std::set<int>* pin_tracks, const t_vec_vec_set* track_to_pins, const int side, const bool both_sides, const int criteria, std::vector<int>* result); |
| /* given a pin on some side of a block, we'd like to find the set of tracks that is NOT connected to that pin on that side. This set of tracks |
| * is passed back in the 'result' vector */ |
| static void find_tracks_unconnected_to_pin(const std::set<int>* pin_tracks, const std::vector<std::set<int> >* track_to_pins, std::vector<int>* result); |
| |
| /* iterates through the elements of set 1 and returns the number of elements in set1 that are |
| * also in set2 (in terms of bit vectors, this looks for the number of positions where both bit vectors |
| * have a value of 1; values of 0 not counted... so, not quite true hamming proximity). Analogously, if we |
| * wanted the hamming distance of these two sets, (in terms of bit vectors, the number of bit positions that are |
| * different... i.e. the actual definition of hamming disatnce) that would be 2(set_size - returned_value) */ |
| static int hamming_proximity_of_two_sets(const std::set<int>* set1, const std::set<int>* set2); |
| /* returns the pin diversity metric of a block */ |
| static float get_pin_diversity(const int Fc, const int num_pin_type_pins, const Conn_Block_Metrics* cb_metrics); |
| /* Returns the wire homogeneity of a block's connection to tracks */ |
| static float get_wire_homogeneity(const int Fc, const int nodes_per_chan, const int num_pin_type_pins, const int exponent, const bool both_sides, const Conn_Block_Metrics* cb_metrics); |
| /* Returns the hamming proximity of a block's connection to tracks */ |
| static float get_hamming_proximity(const int Fc, const int num_pin_type_pins, const int exponent, const bool both_sides, const Conn_Block_Metrics* cb_metrics); |
| /* Returns Lemieux's cost function for sparse crossbars (see his 2001 book) applied here to the connection block */ |
| static float get_lemieux_cost_func(const int exponent, const bool both_sides, const Conn_Block_Metrics* cb_metrics); |
| |
| /* this annealer is used to adjust a desired wire or pin metric while keeping the other type of metric |
| * relatively constant */ |
| static bool annealer(const e_metric metric, const int nodes_per_chan, const t_physical_tile_type_ptr block_type, const e_pin_type pin_type, const int Fc, const int num_pin_type_pins, const float target_metric, const float target_metric_tolerance, int***** pin_to_track_connections, Conn_Block_Metrics* cb_metrics); |
| /* updates temperature based on current temperature and the annealer's outer loop iteration */ |
| static double update_temp(const double temp); |
| /* determines whether to accept or reject a proposed move based on the resulting delta of the cost and current temperature */ |
| static bool accept_move(const double del_cost, const double temp); |
| /* this function simply moves a switch from one track to another track (with an empty slot). The switch stays on the |
| * same pin as before. */ |
| static double try_move(const e_metric metric, const int nodes_per_chan, const float initial_orthogonal_metric, const float orthogonal_metric_tolerance, const t_physical_tile_type_ptr block_type, const e_pin_type pin_type, const int Fc, const int num_pin_type_pins, const double cost, const double temp, const float target_metric, int***** pin_to_track_connections, Conn_Block_Metrics* cb_metrics); |
| |
| static void print_switch_histogram(const int nodes_per_chan, const Conn_Block_Metrics* cb_metrics); |
| |
| /**** Function Definitions ****/ |
| |
| /* adjusts the connection block until the appropriate metric has hit its target value. the other orthogonal metric is kept constant |
| * within some tolerance */ |
| void adjust_cb_metric(const e_metric metric, const float target, const float target_tolerance, const t_physical_tile_type_ptr block_type, int***** pin_to_track_connections, const e_pin_type pin_type, const int* Fc_array, const t_chan_width* chan_width_inf, const int num_segments, const t_segment_inf* segment_inf) { |
| Conn_Block_Metrics cb_metrics; |
| |
| /* various error checks */ |
| if (metric >= NUM_METRICS) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Invalid CB metric type specified: %d\n", (int)metric); |
| } |
| if (block_type->num_pins == 0) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Trying to adjust CB metrics for a block with no pins!\n"); |
| } |
| if (block_type->height > 1 || block_type->width > 1) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Adjusting connection block metrics is currently intended for CLBs, which have height and width = 1\n"); |
| } |
| if (chan_width_inf->x_min != chan_width_inf->x_max || chan_width_inf->y_min != chan_width_inf->y_max |
| || chan_width_inf->x_max != chan_width_inf->y_max) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "This code currently assumes that channel width is uniform throughout the fpga"); |
| } |
| |
| int nodes_per_chan = chan_width_inf->x_min; |
| |
| /* get Fc */ |
| int Fc = get_max_Fc(Fc_array, block_type, pin_type); |
| |
| /* get the number of block pins that are of pin_type */ |
| int num_pin_type_pins = 0; |
| if (DRIVER == pin_type) { |
| num_pin_type_pins = block_type->num_drivers; |
| } else if (RECEIVER == pin_type) { |
| num_pin_type_pins = block_type->num_receivers; |
| } else { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Found unexpected pin type when adjusting CB wire metric: %d\n", pin_type); |
| } |
| |
| /* get initial values for metrics */ |
| get_conn_block_metrics(block_type, pin_to_track_connections, num_segments, segment_inf, pin_type, |
| Fc_array, chan_width_inf, &cb_metrics); |
| |
| /* now run the annealer to adjust the desired metric towards the target value */ |
| bool success = annealer(metric, nodes_per_chan, block_type, pin_type, Fc, num_pin_type_pins, target, |
| target_tolerance, pin_to_track_connections, &cb_metrics); |
| if (!success) { |
| VTR_LOG("Failed to adjust specified connection block metric\n"); |
| } |
| |
| print_switch_histogram(nodes_per_chan, &cb_metrics); |
| } |
| |
| /* calculates all the connection block metrics and returns them through the cb_metrics variable */ |
| void get_conn_block_metrics(const t_physical_tile_type_ptr block_type, int***** tracks_connected_to_pin, const int num_segments, const t_segment_inf* segment_inf, const e_pin_type pin_type, const int* Fc_array, const t_chan_width* chan_width_inf, Conn_Block_Metrics* cb_metrics) { |
| if (chan_width_inf->x_min != chan_width_inf->x_max || chan_width_inf->y_min != chan_width_inf->y_max |
| || chan_width_inf->x_max != chan_width_inf->y_max) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Currently this code assumes that channel width is uniform throughout the fpga"); |
| } |
| int nodes_per_chan = chan_width_inf->x_min; |
| |
| /* get Fc */ |
| int Fc = get_max_Fc(Fc_array, block_type, pin_type); |
| |
| /* a bit of error checking */ |
| if (0 == block_type->index) { |
| /* an empty block */ |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Cannot calculate CB metrics for empty blocks\n"); |
| } else if (0 == Fc) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Can not compute CB metrics for pins not connected to any tracks\n"); |
| } |
| |
| /* get the number of block pins that are of pin_type */ |
| int num_pin_type_pins = UNDEFINED; |
| if (DRIVER == pin_type) { |
| num_pin_type_pins = block_type->num_drivers; |
| } else if (RECEIVER == pin_type) { |
| num_pin_type_pins = block_type->num_receivers; |
| } else { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Found unexpected pin type when adjusting CB wire metric: %d\n", pin_type); |
| } |
| |
| /* initialize CB metrics structures */ |
| init_cb_structs(block_type, tracks_connected_to_pin, num_segments, segment_inf, pin_type, num_pin_type_pins, nodes_per_chan, |
| Fc, cb_metrics); |
| |
| /* check based on block type whether we should account for pins on both sides of a channel when computing the relevant CB metrics |
| * (i.e. from a block on the left and from a block on the right for a vertical channel, for instance) */ |
| bool both_sides = false; |
| if (0 == strcmp("clb", block_type->name) && DRIVER == pin_type) { |
| /* many CLBs are adjacent to eachother, so connections from one CLB |
| * will share the channel segment with its neighbor. We'd like to take this into |
| * account for the applicable metrics. */ |
| both_sides = true; |
| } else { |
| /* other blocks (i.e. IO, RAM, etc) are not as frequent as CLBs */ |
| both_sides = false; |
| } |
| |
| /* get the metrics */ |
| cb_metrics->wire_homogeneity = get_wire_homogeneity(Fc, nodes_per_chan, num_pin_type_pins, 2, both_sides, cb_metrics); |
| |
| cb_metrics->hamming_proximity = get_hamming_proximity(Fc, num_pin_type_pins, 2, both_sides, cb_metrics); |
| |
| cb_metrics->lemieux_cost_func = get_lemieux_cost_func(2, both_sides, cb_metrics); |
| |
| cb_metrics->pin_diversity = get_pin_diversity(Fc, num_pin_type_pins, cb_metrics); |
| } |
| |
| /* initializes the fields of the cb_metrics class */ |
| static void init_cb_structs(const t_physical_tile_type_ptr block_type, int***** tracks_connected_to_pin, const int num_segments, const t_segment_inf* segment_inf, const e_pin_type pin_type, const int num_pin_type_pins, const int nodes_per_chan, const int Fc, Conn_Block_Metrics* cb_metrics) { |
| /* can not calculate CB metrics for open pins */ |
| if (OPEN == pin_type) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Can not initialize CB metric structures for pins of OPEN type\n"); |
| } |
| |
| int num_wire_types = get_num_wire_types(num_segments, segment_inf); |
| cb_metrics->num_wire_types = num_wire_types; |
| |
| get_pin_locations(block_type, pin_type, num_pin_type_pins, tracks_connected_to_pin, &cb_metrics->pin_locations); |
| |
| /* allocate the multi-dimensional vectors used for conveniently calculating CB metrics */ |
| for (int iside = 0; iside < 4; iside++) { |
| cb_metrics->track_to_pins.push_back(std::vector<std::set<int> >()); |
| cb_metrics->pin_to_tracks.push_back(std::vector<std::set<int> >()); |
| cb_metrics->wire_types_used_count.push_back(std::vector<std::vector<int> >()); |
| for (int i = 0; i < nodes_per_chan; i++) { |
| cb_metrics->track_to_pins.at(iside).push_back(std::set<int>()); |
| } |
| for (int ipin = 0; ipin < (int)cb_metrics->pin_locations.at(iside).size(); ipin++) { |
| cb_metrics->pin_to_tracks.at(iside).push_back(std::set<int>()); |
| cb_metrics->wire_types_used_count.at(iside).push_back(std::vector<int>()); |
| for (int itype = 0; itype < num_wire_types; itype++) { |
| cb_metrics->wire_types_used_count.at(iside).at(ipin).push_back(0); |
| } |
| } |
| } |
| |
| /* set the values of the multi-dimensional vectors */ |
| std::set<int> counted_pins; |
| int track = 0; |
| /* over each side of the block */ |
| for (int iside = 0; iside < 4; iside++) { |
| /* over each height unit */ |
| for (int iheight = 0; iheight < block_type->height; iheight++) { |
| /* over each width unit */ |
| for (int iwidth = 0; iwidth < block_type->width; iwidth++) { |
| /* over each pin */ |
| for (int ipin = 0; ipin < (int)cb_metrics->pin_locations.at(iside).size(); ipin++) { |
| int pin = cb_metrics->pin_locations.at(iside).at(ipin); |
| |
| bool pin_counted = false; |
| if ((int)counted_pins.size() == num_pin_type_pins) { |
| /* Some blocks like io appear to have four sides, but only one * |
| * of those sides is actually used in practice. So here we try * |
| * not to count the unused pins. */ |
| break; |
| } |
| |
| /* now iterate over each track connected to this pin */ |
| for (int i = 0; i < Fc; i++) { |
| /* insert track into pin_to_tracks */ |
| track = tracks_connected_to_pin[pin][iwidth][iheight][iside][i]; |
| |
| if (-1 == track) { |
| /* this pin is not connected to any tracks */ |
| break; |
| } |
| |
| std::pair<std::set<int>::iterator, bool> result1 = cb_metrics->pin_to_tracks.at(iside).at(ipin).insert(track); |
| if (!result1.second) { |
| /* this track should not already be a part of the set */ |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Attempted to insert element into pin_to_tracks set which already exists there\n"); |
| } |
| |
| /* insert the current pin into the corresponding tracks_to_pin entry */ |
| std::pair<std::set<int>::iterator, bool> result2 = cb_metrics->track_to_pins.at(iside).at(track).insert(pin); |
| if (!result2.second) { |
| /* this pin should not already be a part of the set */ |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Attempted to insert element into track_to_pins set which already exists there\n"); |
| } |
| |
| /* keep track of how many of each wire type is used by the current pin */ |
| cb_metrics->wire_types_used_count.at(iside).at(ipin).at(track % num_wire_types)++; |
| pin_counted = true; |
| } |
| if (pin_counted) { |
| counted_pins.insert(pin); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /* wires may be grouped in a channel according to their start points. i.e. at a given channel segment with L=4, there are up to |
| * four 'types' of L=4 wires: those that start in this channel segment, those than end in this channel segment, and two types |
| * that are in between. here we return the number of wire types. |
| * the current connection block metrics code can only deal with channel segments that carry wires of only one length (i.e. L=4). |
| * this may be enhanced in the future. */ |
| int get_num_wire_types(const int num_segments, const t_segment_inf* segment_inf) { |
| int num_wire_types = 0; |
| |
| if (num_segments == 1) { |
| /* There are as many wire start points as the value of L */ |
| num_wire_types = segment_inf[0].length; |
| } else { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Currently, the connection block metrics code can only deal with channel segments that carry wires of only one lenght.\n"); |
| } |
| |
| return num_wire_types; |
| } |
| |
| /* Gets the maximum Fc value from the Fc_array of this pin type. Errors out if the pins of this pin_type don't all have the same Fc */ |
| int get_max_Fc(const int* Fc_array, const t_physical_tile_type_ptr block_type, const e_pin_type pin_type) { |
| int Fc = 0; |
| for (int ipin = 0; ipin < block_type->num_pins; ++ipin) { |
| int iclass = block_type->pin_class[ipin]; |
| if (Fc_array[ipin] > Fc && block_type->class_inf[iclass].type == pin_type) { |
| /* currently I'm assuming that the Fc for all pins are the same. Check this here. */ |
| if (Fc != 0 && Fc_array[ipin] != Fc) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Two pins of the same type have different Fc values. This is currently not allowed for CB metrics\n"); |
| } |
| Fc = Fc_array[ipin]; |
| } |
| } |
| return Fc; |
| } |
| |
| /* returns the pin diversity metric of a block */ |
| static float get_pin_diversity(const int Fc, const int num_pin_type_pins, const Conn_Block_Metrics* cb_metrics) { |
| float total_pin_diversity = 0; |
| float exp_factor = 3.3; |
| int num_wire_types = cb_metrics->num_wire_types; |
| |
| /* Determine the diversity of each pin. The concept of this function is that * |
| * a pin connecting to a wire class more than once returns diminishing gains. * |
| * This is modelled as an exponential function s.t. at large ratios of * |
| * connections/expected_connections we will always get (almost) the same * |
| * contribution to pin diversity. */ |
| float mean = (float)Fc / (float)(num_wire_types); |
| for (int iside = 0; iside < 4; iside++) { |
| for (int ipin = 0; ipin < (int)cb_metrics->pin_locations.at(iside).size(); ipin++) { |
| float pin_diversity = 0; |
| for (int i = 0; i < num_wire_types; i++) { |
| pin_diversity += (1 / (float)num_wire_types) * (1 - exp(-exp_factor * (float)cb_metrics->wire_types_used_count.at(iside).at(ipin).at(i) / mean)); |
| } |
| total_pin_diversity += pin_diversity; |
| } |
| } |
| total_pin_diversity /= num_pin_type_pins; |
| return total_pin_diversity; |
| } |
| |
| /* Returns Lemieux's cost function for sparse crossbars (see his 2001 book) applied here to the connection block */ |
| static float get_lemieux_cost_func(const int exponent, const bool both_sides, const Conn_Block_Metrics* cb_metrics) { |
| float lcf = 0; |
| |
| const t_2d_int_vec* pin_locations = &cb_metrics->pin_locations; |
| const t_vec_vec_set* pin_to_tracks = &cb_metrics->pin_to_tracks; |
| |
| /* may want to calculate LCF for two sides at once to simulate presence of neighboring blocks */ |
| int mult = (both_sides) ? 2 : 1; |
| |
| /* iterate over the sides */ |
| for (int iside = 0; iside < (4 / mult); iside++) { |
| /* how many pins do we need to iterate over? this depends on whether or not we take into |
| * account pins on adjacent sides of a channel */ |
| int num_pins = 0; |
| if (both_sides) { |
| num_pins = (int)pin_locations->at(iside).size() + (int)pin_locations->at(iside + 2).size(); |
| } else { |
| num_pins = (int)pin_locations->at(iside).size(); |
| } |
| |
| if (0 == num_pins) { |
| continue; |
| } |
| |
| float lcf_pins = 0; |
| /* for each pin... */ |
| int pin_comparisons = 0; |
| for (int ipin = 0; ipin < num_pins; ipin++) { |
| int pin_ind = ipin; |
| int pin_side = iside; |
| if (both_sides) { |
| if (ipin >= (int)pin_locations->at(iside).size()) { |
| pin_side += 2; |
| pin_ind = pin_ind % pin_locations->at(iside).size(); |
| } |
| } |
| float pin_lcf = 0; |
| /* ...compare it's track connections to every other pin that we haven't already compared it to */ |
| for (int icomp = ipin + 1; icomp < num_pins; icomp++) { |
| int comp_pin_ind = icomp; |
| int comp_side = iside; |
| if (both_sides) { |
| if (icomp >= (int)pin_locations->at(iside).size()) { |
| comp_side += 2; |
| comp_pin_ind = comp_pin_ind % pin_locations->at(iside).size(); |
| } |
| } |
| pin_comparisons++; |
| /* get the hamming proximity between the tracks of the two pins being compared */ |
| float pin_to_pin_lcf = (float)hamming_proximity_of_two_sets(&pin_to_tracks->at(pin_side).at(pin_ind), &pin_to_tracks->at(comp_side).at(comp_pin_ind)); |
| pin_to_pin_lcf = 2 * ((int)pin_to_tracks->at(pin_side).at(pin_ind).size() - pin_to_pin_lcf); |
| if (0 == pin_to_pin_lcf) { |
| pin_to_pin_lcf = 1; |
| } |
| pin_to_pin_lcf = pow(1.0 / pin_to_pin_lcf, exponent); |
| pin_lcf += pin_to_pin_lcf; |
| } |
| lcf_pins += pin_lcf; |
| } |
| lcf += lcf_pins / (0.5 * num_pins * (num_pins - 1)); |
| } |
| lcf /= (4.0 / (both_sides ? 2.0 : 1.0)); |
| |
| return lcf; |
| } |
| |
| /* Returns the hamming proximity of a block's connection to tracks */ |
| static float get_hamming_proximity(const int Fc, const int num_pin_type_pins, const int exponent, const bool both_sides, const Conn_Block_Metrics* cb_metrics) { |
| float hamming_proximity = 0; |
| |
| const t_2d_int_vec* pin_locations = &cb_metrics->pin_locations; |
| const t_vec_vec_set* pin_to_tracks = &cb_metrics->pin_to_tracks; |
| |
| /* may want to calculate HP for two sides at once to simulate presence of neighboring blocks */ |
| int mult = (both_sides) ? 2 : 1; |
| |
| /* iterate over the sides */ |
| for (int iside = 0; iside < (4 / mult); iside++) { |
| /* how many pins do we need to iterate over? this depends on whether or not we take into |
| * account pins on adjacent sides of a channel */ |
| int num_pins = 0; |
| if (both_sides) { |
| num_pins = (int)pin_locations->at(iside).size() + (int)pin_locations->at(iside + 2).size(); |
| } else { |
| num_pins = (int)pin_locations->at(iside).size(); |
| } |
| |
| if (0 == num_pins) { |
| continue; |
| } |
| |
| float hp_pins = 0; |
| /* for each pin... */ |
| int pin_comparisons = 0; |
| for (int ipin = 0; ipin < num_pins; ipin++) { |
| int pin_ind = ipin; |
| int pin_side = iside; |
| if (both_sides) { |
| if (ipin >= (int)pin_locations->at(iside).size()) { |
| pin_side += 2; |
| pin_ind = pin_ind % pin_locations->at(iside).size(); |
| } |
| } |
| float pin_hp = 0; |
| /* ...compare it's track connections to every other pin that we haven't already compared it to */ |
| for (int icomp = ipin + 1; icomp < num_pins; icomp++) { |
| int comp_pin_ind = icomp; |
| int comp_side = iside; |
| if (both_sides) { |
| if (icomp >= (int)pin_locations->at(iside).size()) { |
| comp_side += 2; |
| comp_pin_ind = comp_pin_ind % pin_locations->at(iside).size(); |
| } |
| } |
| pin_comparisons++; |
| /* get the hamming proximity between the tracks of the two pins being compared */ |
| float pin_to_pin_hp = (float)hamming_proximity_of_two_sets(&pin_to_tracks->at(pin_side).at(pin_ind), &pin_to_tracks->at(comp_side).at(comp_pin_ind)); |
| pin_to_pin_hp = pow(pin_to_pin_hp, exponent); |
| pin_hp += pin_to_pin_hp; |
| } |
| hp_pins += pin_hp; |
| } |
| hamming_proximity += hp_pins * 2.0 / (float)((num_pins - 1) * pow(Fc, exponent)); |
| } |
| hamming_proximity /= num_pin_type_pins; |
| |
| return hamming_proximity; |
| } |
| |
| /* iterates through the elements of set 1 and returns the number of elements in set1 that are |
| * also in set2 (in terms of bit vectors, this looks for the number of positions where both bit vectors |
| * have a value of 1; values of 0 not counted... so, not quite true hamming proximity). Analogously, if we |
| * wanted the hamming distance of these two sets, (in terms of bit vectors, the number of bit positions that are |
| * different... i.e. the actual definition of hamming disatnce) that would be 2(set_size - returned_value) */ |
| static int hamming_proximity_of_two_sets(const std::set<int>* set1, const std::set<int>* set2) { |
| int result = 0; |
| |
| std::set<int>::const_iterator it; |
| for (it = set1->begin(); it != set1->end(); it++) { |
| int element = *it; |
| if (set_has_element(element, set2)) { |
| result++; |
| } |
| } |
| return result; |
| } |
| |
| /* Returns the wire homogeneity of a block's connection to tracks */ |
| static float get_wire_homogeneity(const int Fc, const int nodes_per_chan, const int num_pin_type_pins, const int exponent, const bool both_sides, const Conn_Block_Metrics* cb_metrics) { |
| float total_wire_homogeneity = 0; |
| float wire_homogeneity[4]; |
| int counted_pins_per_side[4]; |
| |
| /* get the number of pins on each side */ |
| const t_2d_int_vec* pin_locations = &cb_metrics->pin_locations; |
| for (int iside = 0; iside < 4; iside++) { |
| counted_pins_per_side[iside] = (int)pin_locations->at(iside).size(); |
| } |
| |
| int unconnected_wires = 0; |
| int total_conns = 0; |
| int total_pins_on_side = 0; |
| float mean = 0; |
| float wire_homogeneity_temp = 0; |
| /* If 'both_sides' is true, then the metric is calculated as if there is a block on both sides of the |
| * channel. This is useful for frequently-occuring blocks like the CLB, which are packed together side by side */ |
| int mult = (both_sides) ? 2 : 1; |
| /* and now compute the wire homogeneity metric */ |
| /* sides must be ordered as TOP, RIGHT, BOTTOM, LEFT. see the e_side enum */ |
| for (int side = 0; side < (4 / mult); side++) { |
| mean = 0; |
| unconnected_wires = 0; |
| total_conns = total_pins_on_side = 0; |
| for (int i = 0; i < mult; i++) { |
| total_pins_on_side += counted_pins_per_side[side + mult * i]; |
| } |
| |
| if (total_pins_on_side == 0) { |
| continue; |
| } |
| |
| total_conns = total_pins_on_side * Fc; |
| unconnected_wires = (total_conns) ? std::max(0, nodes_per_chan - total_conns) : 0; |
| mean = (float)total_conns / (float)(nodes_per_chan - unconnected_wires); |
| wire_homogeneity[side] = 0; |
| for (int track = 0; track < nodes_per_chan; track++) { |
| wire_homogeneity_temp = 0; |
| for (int i = 0; i < mult; i++) { |
| if (counted_pins_per_side[side + i * mult] > 0) { |
| /* only include sides with connected pins */ |
| wire_homogeneity_temp += (float)cb_metrics->track_to_pins.at(side + i * mult).at(track).size(); |
| } |
| } |
| wire_homogeneity[side] += pow(fabs(wire_homogeneity_temp - mean), exponent); |
| } |
| float normalization = ((float)Fc * pow(((float)total_pins_on_side - mean), exponent) + (float)(nodes_per_chan - Fc) * pow(mean, exponent)) / (float)total_pins_on_side; |
| wire_homogeneity[side] -= unconnected_wires * mean; |
| wire_homogeneity[side] /= normalization; |
| total_wire_homogeneity += wire_homogeneity[side]; |
| } |
| total_wire_homogeneity /= num_pin_type_pins; |
| |
| return total_wire_homogeneity; |
| } |
| |
| /* goes through each pin of pin_type and determines which side of the block it comes out on. results are stored in |
| * the 'pin_locations' 2d-vector */ |
| static void get_pin_locations(const t_physical_tile_type_ptr block_type, const e_pin_type pin_type, const int num_pin_type_pins, int***** tracks_connected_to_pin, t_2d_int_vec* pin_locations) { |
| std::set<int> counted_pins; |
| |
| pin_locations->clear(); |
| /* over each side */ |
| for (int iside = 0; iside < 4; iside++) { |
| /* go through each pin of the block */ |
| for (int ipin = 0; ipin < block_type->num_pins; ipin++) { |
| /* if this pin is not of the correct type, skip it */ |
| e_pin_type this_pin_type = block_type->class_inf[block_type->pin_class[ipin]].type; |
| if (this_pin_type != pin_type) { |
| continue; |
| } |
| |
| //TODO: block_type->pin_loc indicates that there are pins on all sides of an I/O block, but this is not actually the case... |
| // In the future we should change pin_loc to indicate the correct pin locations |
| /* push back an empty vector for this side */ |
| pin_locations->push_back(std::vector<int>()); |
| for (int iwidth = 0; iwidth < block_type->width; iwidth++) { |
| for (int iheight = 0; iheight < block_type->height; iheight++) { |
| /* check if ipin is present at this side/width/height */ |
| if (-1 != tracks_connected_to_pin[ipin][iwidth][iheight][iside][0]) { |
| if ((int)counted_pins.size() == num_pin_type_pins) { |
| /* Some blocks like io appear to have four sides, but only one * |
| * of those sides is actually used in practice. So here we try * |
| * not to count the unused pins. */ |
| break; |
| } |
| /* if so, push it onto our pin_locations vector at the appropriate side */ |
| pin_locations->at(iside).push_back(ipin); |
| counted_pins.insert(ipin); |
| } |
| } |
| } |
| /* sort the vector at the current side in increasing order, for good measure */ |
| sort(pin_locations->at(iside).begin(), pin_locations->at(iside).end()); |
| } |
| } |
| /* now we have a vector of vectors [0..3][0..num_pins_on_this_side] specifying which pins are on which side */ |
| } |
| |
| /* given a set of tracks connected to a pin, we'd like to find which of these tracks are connected to a number of switches |
| * greater than 'criteria'. The resulting set of tracks is passed back in the 'result' vector */ |
| static void find_tracks_with_more_switches_than(const std::set<int>* pin_tracks, const t_vec_vec_set* track_to_pins, const int side, const bool both_sides, const int criteria, std::vector<int>* result) { |
| result->clear(); |
| |
| if (both_sides && side >= 2) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "when accounting for pins on both sides of a channel segment, the passed-in side should have index < 2. got %d\n", side); |
| } |
| |
| /* for each track connected to the pin */ |
| std::set<int>::const_iterator it; |
| for (it = pin_tracks->begin(); it != pin_tracks->end(); it++) { |
| int track = *it; |
| |
| int num_switches = 0; |
| if (both_sides) { |
| num_switches = (int)track_to_pins->at(side).at(track).size() + (int)track_to_pins->at(side + 2).at(track).size(); |
| } else { |
| num_switches = (int)track_to_pins->at(side).at(track).size(); |
| } |
| if (num_switches > criteria) { |
| result->push_back(track); |
| } |
| } |
| } |
| |
| /* given a pin on some side of a block, we'd like to find the set of tracks that is NOT connected to that pin on that side. This set of tracks |
| * is passed back in the 'result' vector */ |
| static void find_tracks_unconnected_to_pin(const std::set<int>* pin_tracks, const std::vector<std::set<int> >* track_to_pins, std::vector<int>* result) { |
| result->clear(); |
| /* for each track in the channel segment */ |
| for (int itrack = 0; itrack < (int)track_to_pins->size(); itrack++) { |
| /* check if this track is not connected to the pin */ |
| if (!set_has_element(itrack, pin_tracks)) { |
| result->push_back(itrack); |
| } |
| } |
| } |
| |
| /* this function simply moves a switch from one track to another track (with an empty slot). The switch stays on the |
| * same pin as before. */ |
| static double try_move(const e_metric metric, const int nodes_per_chan, const float initial_orthogonal_metric, const float orthogonal_metric_tolerance, const t_physical_tile_type_ptr block_type, const e_pin_type pin_type, const int Fc, const int num_pin_type_pins, const double cost, const double temp, const float target_metric, int***** pin_to_track_connections, Conn_Block_Metrics* cb_metrics) { |
| double new_cost = 0; |
| float new_orthogonal_metric = 0; |
| float new_metric = 0; |
| |
| /* will determine whether we should revert the attempted move at the end of this function */ |
| bool revert = false; |
| /* indicates whether or not we allow a track to be fully disconnected from all the pins of the connection block |
| * in the processs of trying a move (to allow this, preserve_tracks is set to false) */ |
| const bool preserve_tracks = true; |
| |
| t_vec_vec_set* pin_to_tracks = &cb_metrics->pin_to_tracks; |
| t_vec_vec_set* track_to_pins = &cb_metrics->track_to_pins; |
| t_3d_int_vec* wire_types_used_count = &cb_metrics->wire_types_used_count; |
| int num_wire_types = cb_metrics->num_wire_types; |
| |
| /* for the CLB block types it is appropriate to account for pins on both sides of a channel segment when |
| * calculating a CB metric (because CLBs are often found side by side) */ |
| bool both_sides = false; |
| if (0 == strcmp("clb", block_type->name) && DRIVER == pin_type) { |
| /* many CLBs are adjacent to eachother, so connections from one CLB |
| * will share the channel segment with its neighbor. We'd like to take this into |
| * account for the applicable metrics. */ |
| both_sides = true; |
| } else { |
| /* other blocks (i.e. IO, RAM, etc) are not as frequent as CLBs */ |
| both_sides = false; |
| } |
| |
| static std::vector<int> set_of_tracks; |
| /* the set_of_tracks vector is used to find sets of tracks satisfying some criteria that we want. we reserve memory for it, which |
| * should be preserved between calls of this function so that we don't have to allocate memory every time */ |
| set_of_tracks.reserve(nodes_per_chan); |
| set_of_tracks.clear(); |
| |
| /* choose a random side, random pin, and a random switch */ |
| int rand_side = vtr::irand(3); |
| int rand_pin_index = vtr::irand(cb_metrics->pin_locations.at(rand_side).size() - 1); |
| int rand_pin = cb_metrics->pin_locations.at(rand_side).at(rand_pin_index); |
| std::set<int>* tracks_connected_to_pin = &pin_to_tracks->at(rand_side).at(rand_pin_index); |
| |
| /* If the pin is unconnected, return. */ |
| if (0 == tracks_connected_to_pin->size()) { |
| new_cost = cost; |
| } else { |
| /* get an old track connection i.e. one that is connected to our pin. this track has to have a certain number of switches. |
| * for instance, if we want to avoid completely disconnecting a track, it should have > 1 switch so that when we move one |
| * switch from this track to another, this track will still be connected. */ |
| |
| /* find the set of tracks satisfying the 'number of switches' criteria mentioned above */ |
| int check_side = rand_side; |
| if (both_sides && check_side >= 2) { |
| check_side -= 2; /* will be checking this, along with (check_side + 2) */ |
| } |
| if (preserve_tracks) { |
| /* looking for tracks with 2 or more switches */ |
| find_tracks_with_more_switches_than(tracks_connected_to_pin, track_to_pins, check_side, both_sides, 1, &set_of_tracks); |
| } else { |
| /* looking for tracks with 1 or more switches */ |
| find_tracks_with_more_switches_than(tracks_connected_to_pin, track_to_pins, check_side, both_sides, 0, &set_of_tracks); |
| } |
| |
| if (set_of_tracks.size() == 0) { |
| new_cost = cost; |
| } else { |
| /* now choose a random track from the returned set of qualifying tracks */ |
| int old_track = vtr::irand(set_of_tracks.size() - 1); |
| old_track = set_of_tracks.at(old_track); |
| |
| /* next, get a new track connection i.e. one that is not already connected to our randomly chosen pin */ |
| find_tracks_unconnected_to_pin(tracks_connected_to_pin, &track_to_pins->at(rand_side), &set_of_tracks); |
| int new_track = vtr::irand(set_of_tracks.size() - 1); |
| new_track = set_of_tracks.at(new_track); |
| |
| /* move the rand_pin's connection from the old track to the new track and see what the new cost is */ |
| /* update CB metrics structures */ |
| pin_to_tracks->at(rand_side).at(rand_pin_index).erase(old_track); |
| pin_to_tracks->at(rand_side).at(rand_pin_index).insert(new_track); |
| |
| track_to_pins->at(rand_side).at(old_track).erase(rand_pin); |
| track_to_pins->at(rand_side).at(new_track).insert(rand_pin); |
| |
| wire_types_used_count->at(rand_side).at(rand_pin_index).at(old_track % num_wire_types)--; |
| wire_types_used_count->at(rand_side).at(rand_pin_index).at(new_track % num_wire_types)++; |
| |
| /* the orthogonal metric needs to stay within some tolerance of its initial value. here we get the |
| * orthogonal metric after the above move */ |
| if (metric < NUM_WIRE_METRICS) { |
| /* get the new pin diversity cost */ |
| new_orthogonal_metric = get_pin_diversity(Fc, num_pin_type_pins, cb_metrics); |
| } else { |
| /* get the new wire homogeneity cost */ |
| new_orthogonal_metric = get_wire_homogeneity(Fc, nodes_per_chan, num_pin_type_pins, 2, both_sides, cb_metrics); |
| } |
| |
| /* check if the orthogonal metric has remained within tolerance */ |
| if (new_orthogonal_metric >= initial_orthogonal_metric - orthogonal_metric_tolerance |
| && new_orthogonal_metric <= initial_orthogonal_metric + orthogonal_metric_tolerance) { |
| /* The orthogonal metric is within tolerance. Can proceed */ |
| |
| /* get the new metric */ |
| new_metric = 0; |
| |
| double delta_cost; |
| switch (metric) { |
| case WIRE_HOMOGENEITY: |
| new_metric = get_wire_homogeneity(Fc, nodes_per_chan, num_pin_type_pins, 2, both_sides, cb_metrics); |
| break; |
| case HAMMING_PROXIMITY: |
| new_metric = get_hamming_proximity(Fc, num_pin_type_pins, 2, both_sides, cb_metrics); |
| break; |
| case LEMIEUX_COST_FUNC: |
| new_metric = get_lemieux_cost_func(2, both_sides, cb_metrics); |
| break; |
| case PIN_DIVERSITY: |
| new_metric = get_pin_diversity(Fc, num_pin_type_pins, cb_metrics); |
| break; |
| default: |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "try_move: illegal CB metric being adjusted: %d\n", (int)metric); |
| break; |
| } |
| new_cost = fabs(target_metric - new_metric); |
| delta_cost = new_cost - cost; |
| if (!accept_move(delta_cost, temp)) { |
| revert = true; |
| } |
| } else { |
| /* the new orthogoanl metric changed too much. will undo the move made before */ |
| revert = true; |
| } |
| |
| if (revert) { |
| /* revert the attempted move */ |
| pin_to_tracks->at(rand_side).at(rand_pin_index).insert(old_track); |
| pin_to_tracks->at(rand_side).at(rand_pin_index).erase(new_track); |
| |
| track_to_pins->at(rand_side).at(old_track).insert(rand_pin); |
| track_to_pins->at(rand_side).at(new_track).erase(rand_pin); |
| |
| wire_types_used_count->at(rand_side).at(rand_pin_index).at(old_track % num_wire_types)++; |
| wire_types_used_count->at(rand_side).at(rand_pin_index).at(new_track % num_wire_types)--; |
| |
| new_cost = cost; |
| } else { |
| /* accept the attempted move */ |
| |
| /* need to update the actual pin-to-track mapping used by build_rr_graph */ |
| int track_index = 0; |
| for (track_index = 0; track_index < Fc; track_index++) { |
| if (pin_to_track_connections[rand_pin][0][0][rand_side][track_index] == old_track) { |
| break; |
| } |
| } |
| pin_to_track_connections[rand_pin][0][0][rand_side][track_index] = new_track; |
| |
| /* update metrics */ |
| switch (metric) { |
| case WIRE_HOMOGENEITY: |
| cb_metrics->wire_homogeneity = new_metric; |
| cb_metrics->pin_diversity = new_orthogonal_metric; |
| break; |
| case HAMMING_PROXIMITY: |
| cb_metrics->hamming_proximity = new_metric; |
| cb_metrics->pin_diversity = new_orthogonal_metric; |
| break; |
| case LEMIEUX_COST_FUNC: |
| cb_metrics->lemieux_cost_func = new_metric; |
| cb_metrics->pin_diversity = new_orthogonal_metric; |
| break; |
| case PIN_DIVERSITY: |
| cb_metrics->pin_diversity = new_metric; |
| cb_metrics->wire_homogeneity = new_orthogonal_metric; |
| break; |
| default: |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "try_move: illegal CB metric: %d\n", (int)metric); |
| break; |
| } |
| } |
| } |
| } |
| return new_cost; |
| } |
| |
| /* this annealer is used to adjust a desired wire or pin metric while keeping the other type of metric |
| * relatively constant */ |
| static bool annealer(const e_metric metric, const int nodes_per_chan, const t_physical_tile_type_ptr block_type, const e_pin_type pin_type, const int Fc, const int num_pin_type_pins, const float target_metric, const float target_metric_tolerance, int***** pin_to_track_connections, Conn_Block_Metrics* cb_metrics) { |
| bool success = false; |
| double temp = INITIAL_TEMP; |
| |
| /* the annealer adjusts a wire metric or a pin metric while keeping the other one relatively constant. what I'm |
| * calling the orthogonal metric is the metric we'd like to keep relatively constant within some tolerance */ |
| float initial_orthogonal_metric; |
| float orthogonal_metric_tolerance; |
| |
| /* get initial metrics and cost */ |
| double cost = 0; |
| orthogonal_metric_tolerance = 0.05; |
| if (metric < NUM_WIRE_METRICS) { |
| initial_orthogonal_metric = cb_metrics->pin_diversity; |
| |
| switch (metric) { |
| case WIRE_HOMOGENEITY: |
| cost = fabs(cb_metrics->wire_homogeneity - target_metric); |
| break; |
| case HAMMING_PROXIMITY: |
| cost = fabs(cb_metrics->hamming_proximity - target_metric); |
| break; |
| case LEMIEUX_COST_FUNC: |
| cost = fabs(cb_metrics->lemieux_cost_func - target_metric); |
| break; |
| default: |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "CB metrics annealer: illegal wire metric: %d\n", (int)metric); |
| break; |
| } |
| } else { |
| initial_orthogonal_metric = cb_metrics->wire_homogeneity; |
| |
| switch (metric) { |
| case PIN_DIVERSITY: |
| cost = fabs(cb_metrics->pin_diversity - target_metric); |
| break; |
| default: |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "CB metrics annealer: illegal pin metric: %d\n", (int)metric); |
| break; |
| } |
| } |
| |
| /* the main annealer loop */ |
| for (int i_outer = 0; i_outer < MAX_OUTER_ITERATIONS; i_outer++) { |
| for (int i_inner = 0; i_inner < MAX_INNER_ITERATIONS; i_inner++) { |
| double new_cost = 0; |
| new_cost = try_move(metric, nodes_per_chan, initial_orthogonal_metric, orthogonal_metric_tolerance, |
| block_type, pin_type, Fc, num_pin_type_pins, cost, temp, target_metric, pin_to_track_connections, cb_metrics); |
| |
| /* update the cost after trying the move */ |
| if (new_cost != cost) { |
| cost = new_cost; |
| } |
| } |
| |
| temp = update_temp(temp); |
| |
| /* stop if temperature has decreased to 0 */ |
| if (0 == temp) { |
| break; |
| } |
| |
| /* also break if the target metric is within its specified tolerance */ |
| if (cost <= target_metric_tolerance) { |
| break; |
| } |
| } |
| |
| if (cost <= target_metric_tolerance) { |
| success = true; |
| } else { |
| success = false; |
| } |
| |
| return success; |
| } |
| |
| /* updates temperature based on current temperature and the annealer's outer loop iteration */ |
| static double update_temp(const double temp) { |
| double new_temp; |
| double fac = TEMP_DECREASE_FAC; |
| double temp_threshold = LOWEST_TEMP; |
| |
| /* just decrease temp by a constant factor */ |
| new_temp = fac * temp; |
| |
| if (temp < temp_threshold) { |
| new_temp = 0; |
| } |
| |
| return new_temp; |
| } |
| |
| /* determines whether to accept or reject a proposed move based on the resulting delta of the cost and current temperature */ |
| static bool accept_move(const double del_cost, const double temp) { |
| bool accept = false; |
| |
| if (del_cost < 0) { |
| /* cost has decreased -- always accept */ |
| accept = true; |
| } else { |
| /* determine probabilistically whether or not to accept */ |
| double probability = pow(2.718, -(del_cost / temp)); |
| double rand_value = (double)vtr::frand(); |
| if (rand_value < probability) { |
| accept = true; |
| } else { |
| accept = false; |
| } |
| } |
| |
| return accept; |
| } |
| |
| static void print_switch_histogram(const int nodes_per_chan, const Conn_Block_Metrics* cb_metrics) { |
| /* key: number of switches; element: number of tracks with that switch count */ |
| std::map<int, int> switch_histogram; |
| |
| const t_vec_vec_set* track_to_pins = &cb_metrics->track_to_pins; |
| |
| for (int iside = 0; iside < 2; iside++) { |
| for (int itrack = 0; itrack < nodes_per_chan; itrack++) { |
| int num_switches = (int)track_to_pins->at(iside).at(itrack).size() + (int)track_to_pins->at(iside + 2).at(itrack).size(); |
| if (map_has_key(num_switches, &switch_histogram)) { |
| switch_histogram.at(num_switches)++; |
| } else { |
| switch_histogram[num_switches] = 1; |
| } |
| } |
| } |
| |
| VTR_LOG("\t===CB Metrics Switch Histogram===\n\t#switches ==> #num tracks carrying that number of switches\n"); |
| std::map<int, int>::const_iterator it; |
| for (it = switch_histogram.begin(); it != switch_histogram.end(); it++) { |
| VTR_LOG("\t%d ==> %d\n", it->first, it->second); |
| } |
| } |
| |
| /**** EXPERIMENTAL ****/ |
| |
| /* constructs a crossbar matrix from the connection block 'conn_block'. rows correspond to the pins, |
| * columns correspond to the wires. entry (i,j) is set to 1 if that pin and track are connected. the |
| * only pins accounted for here are the pins that actually have switches on the conn block side in question */ |
| static void get_xbar_matrix(const int***** conn_block, const t_physical_tile_type_ptr block_type, e_pin_type pin_type, const int side, const bool both_sides, const int nodes_per_chan, const int Fc, t_xbar_matrix* xbar_matrix) { |
| xbar_matrix->clear(); |
| if (both_sides && side >= 2) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "If analyzing both sides of a conn block, the initial side should have index < 2"); |
| } |
| |
| int num_pins = block_type->num_pins; |
| |
| /* if we're checking both sides of a channel, we're dealing with two CLBs instead of one */ |
| int pins_to_check = both_sides ? num_pins * 2 : num_pins; |
| |
| int checked_pins = 0; |
| for (int ipin = 0; ipin < pins_to_check; ipin++) { |
| int width = 0; /* CLBs have width/height of 0 */ |
| int height = 0; |
| |
| int pin = ipin; |
| int check_side = side; |
| if (pin >= num_pins) { |
| /* checking on the other side of the channel */ |
| pin %= num_pins; |
| check_side += 2; |
| } |
| |
| /* skip if specified pin isn't of 'pin_type' */ |
| int pin_class_index = block_type->pin_class[pin]; |
| if (pin_type != block_type->class_inf[pin_class_index].type) { |
| continue; |
| } |
| /* skip if specified pin doesn't connect to tracks on the specified side */ |
| if (conn_block[pin][width][height][check_side][0] == -1) { |
| continue; |
| } |
| |
| /* each pin corresponds to a row of the xbar matrix */ |
| xbar_matrix->push_back(std::vector<float>()); |
| |
| /* each wire in the channel corresponds to a column of the xbar matrix */ |
| for (int icol = 0; icol < nodes_per_chan; icol++) { |
| xbar_matrix->at(checked_pins).push_back(0.0); |
| } |
| /* set appropriate elements of the current xbar row to 1 (according to which tracks the pin connects to) */ |
| for (int iswitch = 0; iswitch < Fc; iswitch++) { |
| int set_col = conn_block[pin][width][height][check_side][iswitch]; |
| xbar_matrix->at(checked_pins).at(set_col) = 1.0; |
| } |
| |
| /* increment the number of pins for which we have created an xbar row */ |
| checked_pins++; |
| } |
| } |
| |
| /* returns a transposed version of the specified crossbar matrix */ |
| static t_xbar_matrix transpose_xbar(const t_xbar_matrix* xbar) { |
| t_xbar_matrix result; |
| |
| int new_cols = (int)xbar->size(); |
| int new_rows = (int)xbar->at(0).size(); |
| |
| for (int i = 0; i < new_rows; i++) { |
| result.push_back(std::vector<float>()); |
| for (int j = 0; j < new_cols; j++) { |
| result.at(i).push_back(xbar->at(j).at(i)); |
| } |
| } |
| return result; |
| } |
| |
| /* calculates the factorial of 'num'. result is a long double. some precision may be lost, but that's |
| * OK for my purposes */ |
| static long double factorial(const int num) { |
| long double result = 1; |
| if (num <= 1) { |
| result = 1; |
| } else { |
| for (int i = 2; i <= num; i++) { |
| result *= (long double)i; |
| } |
| } |
| return result; |
| } |
| |
| /* calculates n choose k. some precision may be lost, but for my purposes |
| * that doesn't really matter */ |
| static long double binomial_coefficient(const int n, const int k) { |
| if (n < k) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "calculating the binomial coefficient requires that n >= k"); |
| } |
| long double result; |
| result = factorial(n) / (factorial(k) * factorial(n - k)); |
| return result; |
| } |
| |
| static long double count_switch_configurations(const int level, const int signals_left, const int capacity_left, std::vector<int>* config, std::map<int, Wire_Counting>* count_map) { |
| long double result = 0; |
| |
| if (capacity_left < signals_left) { |
| printf("capacity left %d signals left %d level %d\n", capacity_left, signals_left, level); |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "the number of signals remaining should not be greater than the remaining capacity"); |
| } |
| |
| /* get the capacity of the current wire group (here, group is defined by the number of switches a wire carries) */ |
| std::map<int, Wire_Counting>::const_iterator it = count_map->begin(); |
| advance(it, level); |
| int my_capacity = it->second.num_wires; |
| |
| /* get the total capacity of the remaining wire groups */ |
| int downstream_capacity = capacity_left - my_capacity; |
| |
| /* get the maximum number of signals this wire group can carry */ |
| int can_take = std::min(my_capacity, signals_left); |
| |
| /* we cannot push more signals onto the other wire groups than they can take. to avoid this the minimum number of signals allowed |
| * to be carried by the current wire group may be above 0 */ |
| int start_fill = signals_left - downstream_capacity; |
| if (start_fill < 0) { |
| start_fill = 0; |
| } |
| |
| /* now, for each number of signals this wire group can carry... */ |
| for (int isigs = start_fill; isigs <= can_take; isigs++) { |
| config->at(level) = isigs; |
| if (level == (int)count_map->size() - 1) { |
| /* if this is the final wire group, we want to count the number of possible ways that we can |
| * configure the switches in the channel to achieve the wire usage specified by the current value of the config vector */ |
| long double num_configs = 1; |
| for (int i = 0; i < (int)config->size(); i++) { |
| it = count_map->begin(); |
| advance(it, i); |
| int num_switches_per_wire = it->first; |
| int num_wires_in_group = it->second.num_wires; |
| int num_wires_used_in_group = config->at(i); |
| num_configs *= binomial_coefficient(num_wires_in_group, num_wires_used_in_group) |
| * (long double)pow((long double)num_switches_per_wire, (long double)num_wires_used_in_group); |
| } |
| |
| /* add this info for the final wire group */ |
| it = count_map->begin(); |
| advance(it, level); |
| std::map<int, long double>* configs_used = &count_map->at(it->first).configs_used; |
| if (map_has_key(config->at(level), configs_used)) { |
| configs_used->at(config->at(level)) += num_configs; |
| } else { |
| (*configs_used)[config->at(level)] = num_configs; |
| } |
| result = num_configs; |
| } else { |
| /* recurse to the next wire group */ |
| long double num_configs; |
| num_configs = count_switch_configurations(level + 1, signals_left - isigs, downstream_capacity, config, count_map); |
| /* add this info for the current wire group */ |
| it = count_map->begin(); |
| advance(it, level); |
| std::map<int, long double>* configs_used = &count_map->at(it->first).configs_used; |
| if (map_has_key(config->at(level), configs_used)) { |
| configs_used->at(config->at(level)) += num_configs; |
| } else { |
| (*configs_used)[config->at(level)] = num_configs; |
| } |
| |
| result += num_configs; |
| } |
| } |
| |
| return result; |
| } |
| |
| /* 'normalizes' the given crossbar. normalization is done in a way such that an entry which was formerly equal to '1' |
| * will now equal to the probability of that switch being available */ |
| static void normalize_xbar(const float fraction_wires_used, t_xbar_matrix* xbar) { |
| int rows = (int)xbar->size(); /* rows correspond to pins */ |
| int cols = (int)xbar->at(0).size(); /* columns correspond to wires */ |
| |
| std::map<int, Wire_Counting> count_map; |
| |
| /* the number of signals that this channel can carry (each wire with switches contributes 1 to capacity) */ |
| int capacity = 0; |
| |
| /* create a map where the key is the number of switches, and the element is a class with a field |
| * that specifies how many wires carry this number of switches */ |
| for (int iwire = 0; iwire < cols; iwire++) { |
| int num_switches = 0; |
| for (int ipin = 0; ipin < rows; ipin++) { |
| if (1 == xbar->at(ipin).at(iwire)) { |
| num_switches++; |
| } |
| } |
| |
| if (0 == num_switches) { |
| continue; |
| } |
| capacity++; |
| if (map_has_key(num_switches, &count_map)) { |
| /* a map entry for this number of switches exists. increment the number of wires that use this number of switches */ |
| count_map.at(num_switches).num_wires++; |
| } else { |
| /* create a map entry. so far only 1 wire uses this number of switches */ |
| Wire_Counting dummy; |
| dummy.num_wires = 1; |
| count_map[num_switches] = dummy; |
| } |
| } |
| |
| /* now we have to count two things. |
| * 1) the total number of possible switch configurations in which 'wires_used' wires are occupied by a signal |
| * 2) in general, we have some number of 'wire groups' as defined above. each wire group is used a certain number of times in some |
| * specific configuration of switches. we want to determine, for each wire group, the number of switch configurations |
| * that leads to y wires of this group being used, for every feasable y. |
| * |
| * These two things should allow us to calculate the expectation of how many wires of each group are used on average. |
| * And this in turn allows us to set the probabilities of each wire in the xbar matrix being available/unavailable |
| */ |
| |
| /* the config vector represents some distribution of signals over available wires. i.e. x wires of type 0 get used, y wires of type 1, etc |
| * this vector is created here, but is updated inside the count_switch_configurations function */ |
| std::vector<int> config; |
| for (int i = 0; i < (int)count_map.size(); i++) { |
| config.push_back(0); |
| } |
| |
| /* the nuber of wires that are used */ |
| int wires_used = vtr::nint(fraction_wires_used * (float)capacity); |
| |
| long double total_configurations = count_switch_configurations(0, wires_used, capacity, &config, &count_map); |
| |
| /* next we need to calculate the expectation of the number of wires available for each wire group */ |
| std::map<int, Wire_Counting>::const_iterator it; |
| for (it = count_map.begin(); it != count_map.end(); it++) { |
| std::map<int, long double>* configs_used = &count_map.at(it->first).configs_used; |
| |
| float* expectation_available = &count_map.at(it->first).expectation_available; |
| (*expectation_available) = 0; |
| std::map<int, long double>::const_iterator it2; |
| for (it2 = configs_used->begin(); it2 != configs_used->end(); it2++) { |
| int used = it2->first; |
| long double num_configurations = it2->second; |
| long double probability = num_configurations / total_configurations; |
| (*expectation_available) += (float)probability * (float)used; |
| } |
| (*expectation_available) = (float)it->second.num_wires - (*expectation_available); |
| } |
| |
| /* and now we adjust the column values of the xbar matrix according to the expected availability of the corresponding wires |
| * (recall a column corresponds to a wire) */ |
| for (int iwire = 0; iwire < cols; iwire++) { |
| int num_switches = 0; |
| for (int ipin = 0; ipin < rows; ipin++) { |
| if (1 == xbar->at(ipin).at(iwire)) { |
| num_switches++; |
| } |
| } |
| |
| if (num_switches == 0) { |
| continue; |
| } |
| |
| float fraction_available = count_map.at(num_switches).expectation_available / count_map.at(num_switches).num_wires; |
| |
| for (int ipin = 0; ipin < rows; ipin++) { |
| if (1 == xbar->at(ipin).at(iwire)) { |
| xbar->at(ipin).at(iwire) = fraction_available; |
| //xbar->at(ipin).at(iwire) = 0.15; |
| } |
| } |
| } |
| } |
| |
| /* prints a crossbar matrix */ |
| static void print_xbar(t_xbar_matrix* xbar) { |
| int rows = (int)xbar->size(); |
| int cols = (int)xbar->at(0).size(); |
| |
| for (int irow = 0; irow < rows; irow++) { |
| VTR_LOG("\t\t|\t"); |
| for (int icol = 0; icol < cols; icol++) { |
| VTR_LOG("%.2f\t", xbar->at(irow).at(icol)); |
| } |
| VTR_LOG(" |\n"); |
| } |
| } |
| |
| static float probabilistic_or(const float a, const float b) { |
| float result; |
| result = a + b - a * b; /* assuming a & b are not mutually exclusive */ |
| return result; |
| } |
| |
| /* allocates an xbar with specified number of rows and columns where each element is set to 'num' */ |
| static void allocate_xbar(const int rows, const int cols, const float num, t_xbar_matrix* xbar) { |
| xbar->clear(); |
| for (int irow = 0; irow < rows; irow++) { |
| xbar->push_back(std::vector<float>()); |
| for (int icol = 0; icol < cols; icol++) { |
| xbar->at(irow).push_back(num); |
| } |
| } |
| } |
| |
| /* combines xbar1 followed by xbar2 into a compound xbar returned via the return value */ |
| static t_xbar_matrix combine_two_xbars(const t_xbar_matrix* xbar1, const t_xbar_matrix* xbar2) { |
| t_xbar_matrix xbar_out; |
| |
| if (xbar1->at(0).size() != xbar2->size()) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "xbar1 should have the same number of columns as xbar2 has rows"); |
| } |
| |
| /* get the dimensions of the combined xbar */ |
| int rows = (int)xbar1->size(); |
| int cols = (int)xbar2->at(0).size(); |
| |
| /* allocate xbar_out */ |
| allocate_xbar(rows, cols, 0.0, &xbar_out); |
| |
| /* the xbars are combined similar to matrix multiplication, except instead of adding the multiplied elements together, |
| * we treat the elements as probabilities and 'or' them together */ |
| |
| /* create xbar_out */ |
| int vec_length = (int)xbar2->size(); |
| for (int irow = 0; irow < rows; irow++) { |
| for (int icol = 0; icol < cols; icol++) { |
| /* combine irow'th row of xbar1 with icol'th column of xbar2 */ |
| float result = 0; |
| for (int ielem = 0; ielem < vec_length; ielem++) { |
| float product = xbar1->at(irow).at(ielem) * xbar2->at(ielem).at(icol); |
| result = probabilistic_or(product, result); |
| } |
| xbar_out.at(irow).at(icol) = result; |
| } |
| } |
| |
| return xbar_out; |
| } |
| |
| void analyze_conn_blocks(const int***** opin_cb, const int***** ipin_cb, const t_physical_tile_type_ptr block_type, const int* Fc_array_out, const int* Fc_array_in, const t_chan_width* chan_width_inf) { |
| if (0 != strcmp(block_type->name, "clb")) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "This code currently works for CLB blocks only"); |
| } |
| if (chan_width_inf->x_min != chan_width_inf->x_max || chan_width_inf->y_min != chan_width_inf->y_max |
| || chan_width_inf->x_max != chan_width_inf->y_max) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "This code currently assumes that channel width is uniform throughout the fpga"); |
| } |
| int nodes_per_chan = chan_width_inf->x_min; |
| |
| /* get Fc */ |
| int Fc_out = get_max_Fc(Fc_array_out, block_type, DRIVER); |
| int Fc_in = get_max_Fc(Fc_array_in, block_type, RECEIVER); |
| |
| /* get the basic input and output conn block crossbars */ |
| t_xbar_matrix output_xbar, input_xbar; |
| get_xbar_matrix(opin_cb, block_type, DRIVER, 0, true, nodes_per_chan, Fc_out, &output_xbar); |
| get_xbar_matrix(ipin_cb, block_type, RECEIVER, 0, false, nodes_per_chan, Fc_in, &input_xbar); |
| input_xbar = transpose_xbar(&input_xbar); |
| |
| /* 'normalize' the output_xbar such that each entry which was formerly equal to '1' will now |
| * equal to the probability of that corresponding switch being available for use */ |
| normalize_xbar(0.7, &output_xbar); |
| |
| /* combine the output and input CB crossbars */ |
| t_xbar_matrix compound_xbar; |
| compound_xbar = combine_two_xbars(&output_xbar, &input_xbar); |
| |
| /* and then combine that with a full crossbar */ |
| //t_xbar_matrix full_xbar; |
| //allocate_xbar((int)compound_xbar.size(), (int)compound_xbar.size(), 1.0, &full_xbar); |
| //compound_xbar = combine_two_xbars(&compound_xbar, &full_xbar); |
| |
| printf("output crossbar\n"); |
| print_xbar(&output_xbar); |
| printf("\n\ninput crossbar\n"); |
| print_xbar(&input_xbar); |
| printf("\n\ncompound crossbar\n"); |
| print_xbar(&compound_xbar); |
| |
| for (int irow = 0; irow < (int)compound_xbar.size(); irow++) { |
| float row_total = 0; |
| for (int icol = 0; icol < (int)compound_xbar.at(0).size(); icol++) { |
| row_total += compound_xbar.at(irow).at(icol); |
| } |
| printf("pin %d total %f\n", irow, row_total); |
| } |
| } |
| |
| /* make a poor cb pattern. */ |
| void make_poor_cb_pattern(const e_pin_type pin_type, const t_physical_tile_type_ptr block_type, const int* Fc_array, const t_chan_width* chan_width_inf, int***** cb) { |
| if (block_type->num_pins == 0) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Trying to adjust CB metrics for a block with no pins!\n"); |
| } |
| if (block_type->height > 1 || block_type->width > 1) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Adjusting connection block metrics is currently intended for CLBs, which have height and width = 1\n"); |
| } |
| if (chan_width_inf->x_min != chan_width_inf->x_max || chan_width_inf->y_min != chan_width_inf->y_max |
| || chan_width_inf->x_max != chan_width_inf->y_max) { |
| VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "This code currently assumes that channel width is uniform throughout the fpga"); |
| } |
| |
| int nodes_per_chan = chan_width_inf->x_min; |
| |
| /* get Fc */ |
| int Fc = get_max_Fc(Fc_array, block_type, pin_type); |
| |
| /* clear the existing CB */ |
| for (int ipin = 0; ipin < block_type->num_pins; ipin++) { |
| for (int iwidth = 0; iwidth < block_type->width; iwidth++) { |
| for (int iheight = 0; iheight < block_type->height; iheight++) { |
| for (int iside = 0; iside < 4; iside++) { |
| for (int ifc = 0; ifc < Fc; ifc++) { |
| cb[ipin][iwidth][iheight][iside][ifc] = -1; |
| } |
| } |
| } |
| } |
| } |
| |
| /* now set the CB to what would seem to be a bad switch pattern */ |
| for (int iside = 0; iside < 2; iside++) { |
| int track_counter = 0; |
| for (int ipin = 0; ipin < 2 * block_type->num_pins; ipin++) { |
| for (int iwidth = 0; iwidth < block_type->width; iwidth++) { |
| for (int iheight = 0; iheight < block_type->height; iheight++) { |
| int side = iside; |
| int pin = ipin; |
| if (ipin >= block_type->num_pins) { |
| side += 2; |
| pin %= block_type->num_pins; |
| } |
| |
| /* if this pin is not of the correct type, skip it */ |
| e_pin_type this_pin_type = block_type->class_inf[block_type->pin_class[pin]].type; |
| if (this_pin_type != pin_type || block_type->is_ignored_pin[pin]) { |
| continue; |
| } |
| |
| /* make sure this pin exists at this location */ |
| if (1 != block_type->pinloc[iwidth][iheight][side][pin]) { |
| continue; |
| } |
| |
| /* consecutive assignment */ |
| for (int iconn = 0; iconn < Fc; iconn++) { |
| cb[pin][iwidth][iheight][side][iconn] = track_counter % nodes_per_chan; |
| track_counter++; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /**** END EXPERIMENTAL ****/ |