blob: a4152b4634018a53bb80fdef72ab43d082d3ec4a [file] [log] [blame]
/*
* 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 ****/