blob: bdc7943e7ceefceeeb92628e2b8920a38dc2ae87 [file] [log] [blame]
/*
Author(s): Oleg Petelin
Last revised: May, 2015
************ NEW SWITCH BLOCK HIGH-LEVEL DESCRIPTION ************
The new switch block description format allows a much finer level of control over the switch blocks generated by VPR.
Whereas a user previously only had the choice of 'wilton', 'universal', 'subset', or 'full' switch blocks, the new
format allows a user to specify a small set of mathematical formulas that describe the switch block connections. This format
allows the specification of all the switch blocks previously available in VPR as well as a great number of new switch block patterns.
The new switch block description format is loosely based on chapter 7 of Lemieux' and Lewis' "Design of Interconnection Networks
for Programmable Logic" book (2004).
************ FILES AND THEIR PURPOSE ************
The overall flow of parsing and building the new switch blocks involves reading switch block descriptions from the VPR
architecture file and then building the switch blocks according to these descriptions in build_rr_graph (rr_graph.c).
This functionality is split across different files which are described below:
* read_xml_arch_file.c (under libarchfpga):
* calls ProcessSwitchblocks which fills s_arch-->switchblocks with info about each user-defined switch blocks
* calls functions from parse_switchblocks.c to build switch block data structures (reads-in permutation functions
and wire segment source-point/dest-point connection info)
* ProcessSegments has been modified to give a string name to each segment type (in t_segment_inf).
* parse_switchblocks.c (under libarchfpga):
* provides functions to help load switch block data structures during XML parsing of VPR architecture file
* provides functions to evaluate switch block permutation formulas and return a numeric result
* SetupVPR.c:
* SetupRoutingArch copies switch block information from s_arch to s_det_routing_arch
* physical_types.h (under libarchfpga):
* defines classes, structs and typedefs for parsing switch blocks from XML architecture file
* vpr_types.h
* defines classes, structs and typedefs for building switch blocks
* build_switchblocks.c (this file):
* builds t_sb_connection_map sparse array containing target connections for each track in each horizontal/vertical channel segment.
'compute_track_connections' is the most important function here -- it computes the set of track segments that a given track at
(x, y, from_side, to_side, from_track) should connect to.
* rr_graph.c:
* calls alloc_and_load_switchblock_permutations (from build_switchblocks.c) which creates the t_sb_connection_map
* rr_graph2.c:
* get_track_to_chan_seg is called within get_track_to_tracks which looks at t_sb_connection_map to create the edges that
connect a source track segment into a destination channel segment
************ DESCRIPTION OF SWITCH BLOCK SPECIFICATION ************
Some terminology:
* Wire segment type: a named collection of wires. Assigned a particular length and fraction of total channel width in VPR's architecture file
* Wire segment group: a number assigned to a particular wire segment's sub-segment (sub-segments separated by switch blocks). Used to define
a collection of wire segments within a channel segment that all go in the same direction and have the same start point.
* Wire segment point: a number assigned to a specific switch block along a wire segment
Ex: for a length-4 wire segment (belonging to some named segment type):
wire segment point 0-------1-------2-------3-------0
wire segment group 0 1 2 3
The new switch block format allows a user to specify mathematical permutation functions that prescribe how a set of from_type/from_group/from_point wire segments in one channel segment (the source set) should connect to a set of to_type/to_group/to_point wire segments in an adjacent channel segment (the destination set). This provides for an abstract but very flexible way of specifying different switch block patterns.
An example from the XML VPR architecture file is given below. The 'wireconn' entries define ordered source/destination sets of wire segments that should be connected with the specified permutation functions. The wireconn entries essentially "re-index" the channel so that a permutation function of 't/2' means that the t'th wire segment in the source wireconn set should connect to the [(t/2)%W]'th wire segment in the destination set where W is the size, or effective channel width, of the destination set (note that permutation functions are implicitly modulo W so that all functions evaluate to a number that indexes into the destination set).
<switch_block type="custom"/> <-- backwards-compatible with VPR's previous wilton/subset/universal/full specification
...
...
<switchblocklist> <-- XML node that defines custom switch blocks
<switchblock name="my_switchblock" type="unidir"> <-- switch block name/type. type is either unidirectional or bidirectional
<switchblock_location type="EVERYWHERE"/> <-- location to implement switch block (EVERYWHERE/CORE/PERIMETER/CORNER/FRINGE)
<switchfuncs> <-- list of permutation functions
<func type="lr" formula="t+1"/> <-- left-to-right switch block connection
<func type="lt" formula="W-t"/> <-- formula supports different operators; discussed below
<func type="lb" formula="W+t-1"/>
<func type="rt" formula="W+t-1"/>
<func type="br" formula="W-t-2"/>
<func type="bt" formula="t+1"/>
<func type="rl" formula="t+1"/>
<func type="tl" formula="W-t"/>
<func type="bl" formula="W+t-1"/>
<func type="tr" formula="W+t-1"/>
<func type="rb" formula="W-t-2"/>
<func type="tb" formula="t+1"/>
</switchfuncs>
<wireconn FT="my_seg" TT="my_seg" FP="0,1,2,3" TP="0"/> <-- from_type/to_type/from_point/to_point
<wireconn FT="another_seg" TT="my_seg" FP="0,1" TP="0"/> <-- subset of wire segments to connect
</switchblock>
<switchblock name="another_switch_block" type="unidir">
... switch block description ...
</switchblock>
</switchblocklist>
In the above 'wireconn' entries, FT means 'From Type', TT means 'To Type', FP means 'From Point' and TP means 'To Point'. Where 'type' and 'point' is terminology defined earlier on.
Allowed symmbols and operators for the switch block permutation functions are described below (recall
that formulas are evaluated in 'parse_switchblocks.c'):
* "+" -- addition
* "-" -- subtraction
* "/" -- division
* "*" -- multiplication
* "t" -- index of the wire segment in the source set
* "W" -- size of the destination set
*/
/* TODO TODO TODO!! in t_seg_details VPR referes to 'group' as a collection of L tracks that are consecutive and
all with different start points. I'm referring to 'group' as the collection of track segments
in a channel going in the same direction and having the same start point (going by literature,
this seems to be the more appropriate definition of 'group') */
#include <cstring>
#include <cassert>
#include <algorithm>
#include <iterator>
#include "build_switchblocks.h"
#include "physical_types.h"
#include "parse_switchblocks.h"
#include "util.h"
#include <malloc.h>
#include <iostream>
#include <forward_list>
using namespace std;
/************ Defines ************/
/* if defined, switch block patterns are loaded by first computing a row of switch blocks and then
stamping out the row throughout the FPGA */
//#define FAST_SB_COMPUTATION
/* REF_X/REF_Y set a reference coordinate; some look-up structures in this file are computed relative to
this reference */
#define REF_X 1 //constexpr int REX_X = 1; <-- basically C++11 defines; more type-safe
#define REF_Y 1
/************ Classes ************/
/* contains info about a track segment type */
class Track_Info{
public:
int length; /* the length of this type of track segment in tiles */
int num_tracks; /* total number of tracks in a channel segment (basically W) */
int start; /* the track index at which this type starts in the channel segment (0..W-1) */
void set(int len, int tracks, int st){
length = len; num_tracks = tracks; start = st;
}
Track_Info(){
this->set(0, 0, 0);
}
Track_Info(int len, int tracks, int st){
this->set(len, tracks, st);
}
};
/************ Typedefs ************/
/* Used to get info about a given track type based on the name */
typedef map< string, Track_Info > t_track_type_sizes;
/************ Function Declarations ************/
/* Counts the number of tracks in each track type in the specified channel */
static void count_track_type_sizes(INP t_seg_details *channel, INP int nodes_per_chan,
INOUTP t_track_type_sizes *track_type_sizes);
#ifdef FAST_SB_COMPUTATION
/* over all connected wire types (i,j), compute the maximum least common multiple of their track lengths,
ie max(LCM(L_i, L_J)) */
static int get_max_lcm(INP vector<t_switchblock_inf> *switchblocks, INP t_track_type_sizes *track_type_sizes);
/* compute all the switchblocks around the perimeter of the FPGA for the given switchblock and wireconn */
static void compute_perimeter_switchblocks(INP t_chan_details *chan_details_x, INP t_chan_details *chan_details_y,
INP vector<t_switchblock_inf> *switchblocks, INP int nx, INP int ny, INP int nodes_per_chan,
INP t_track_type_sizes *track_type_sizes, INP e_directionality directionality,
INOUTP t_sb_connection_map *sb_conns);
/* computes a horizontal line of switchblocks of size sb_row_size (or of nx-2, whichever is smaller), starting
at coordinate (1,1) */
static void compute_switchblock_row(INP int sb_row_size, INP t_chan_details *chan_details_x, INP t_chan_details *chan_details_y,
INP vector<t_switchblock_inf> *switchblocks, INP int nx, INP int ny, INP int nodes_per_chan,
INP t_track_type_sizes *track_type_sizes, INP e_directionality directionality,
INOUTP t_sb_connection_map *sb_row);
/* stamp out a line of horizontal switchblocks starting at coordinates (ref_x, ref_y) and
continuing on for sb_row_size */
static void stampout_switchblocks_from_row( INP int sb_row_size,
INP int nodes_per_chan,
INP int nx, INP int ny, INP t_track_type_sizes *track_type_sizes,
INP e_directionality directionality, INP t_sb_connection_map *sb_row,
INOUTP t_sb_connection_map *sb_conns );
/* compute greatest common denominator of x and y */
static int gcd(INP int x, INP int y);
/* compute least common multiple of x and y */
static int lcm(INP int x, INP int y);
#endif //FAST_SB_COMPUTATION
/* Compute the track(s) that the track at (x, y, from_side, to_side, from_track) should connect to.
sb_conns is updated with the result */
static void compute_track_connections(INP int x_coord, INP int y_coord, INP enum e_side from_side,
INP enum e_side to_side, INP int from_track, INP t_chan_details * chan_details_x,
INP t_chan_details * chan_details_y, INP t_switchblock_inf *sb,
INP int nx, INP int ny, INP int nodes_per_chan,
INP t_track_type_sizes *track_type_sizes, INP e_directionality directionality,
INOUTP t_sb_connection_map *sb_conns);
/* ... sb_conn represents the 'coordinates' of the desired switch block connections */
static void compute_track_wireconn_connections(INP int nx, INP int ny, INP e_directionality directionality, INP int nodes_per_chan,
INP t_chan_details *from_chan_details, INP t_chan_details *to_chan_details, INP Switchblock_Lookup sb_conn,
INP int from_x, INP int from_y, INP int to_x, INP int to_y, INP t_rr_type from_chan_type, INP t_rr_type to_chan_type,
INP t_track_type_sizes *track_type_sizes, INP t_switchblock_inf *sb,
INP t_wireconn_inf *wireconn_ptr, INOUTP t_sb_connection_map *sb_conns);
/* returns the track indices belonging to the types in 'track_type_vec' and wirepoint in 'points' at the given channel segment */
static void get_wirepoint_tracks( INP e_directionality directionality, INP int nx, INP int ny, INP int nodes_per_chan, t_seg_details *chan_details,
INP t_rr_type chan_type, INP int x, INP int y, INP e_side side, INP vector<string> *track_type_vec, INP vector<int> *points,
INP t_track_type_sizes *track_type_sizes, INP bool is_dest, INOUTP vector<int> *tracks);
static t_rr_type index_into_correct_chan(INP int tile_x, INP int tile_y, INP enum e_side side,
INP t_chan_details *chan_details_x, INP t_chan_details *chan_details_y,
OUTP int *chan_x, OUTP int *chan_y,
OUTP t_chan_details **chan_details);
/* checks whether the specified coordinates are out of bounds */
static bool coords_out_of_bounds(INP int nx, INP int ny, INP int x_coord, INP int y_coord,
e_rr_type chan_type);
/* returns seg coordinate based on a channel's x/y coordinates */
static int get_seg_coordinate(INP t_rr_type chan_type, INP int x_coord, INP int y_coord);
/* returns the group number of the specified track */
static int get_track_group(INP int nx, INP int ny, INP e_rr_type chan_type,
INP t_seg_details &track_details, int seg_coord);
int get_track_segment_length(INP int nx, INP int ny, INP e_rr_type chan_type, INP t_seg_details &track_details);
/* Returns the wirepoint of the track specified by track_details at a segment coordinate
of seg_coord, and connection to the sb_side of the switchblock */
static int get_wirepoint_of_track(INP int nx, INP int ny, INP e_rr_type chan_type,
INP t_seg_details &track_details, INP int seg_coord, INP e_side sb_side);
/* returns true if the coordinates x/y do not correspond to the location specified by 'location' */
static bool sb_not_here(INP int nx, INP int ny, INP int x, INP int y, INP e_sb_location location);
/* checks if the specified coordinates represent a corner of the FPGA */
static bool is_corner(INP int nx, INP int ny, INP int x, INP int y);
/* checks if the specified coordinates correspond to one of the perimeter switchblocks */
static bool is_perimeter(INP int nx, INP int ny, INP int x, INP int y);
/* checks if the specified coordinates correspond to the core of the FPGA (i.e. not perimeter) */
static bool is_core(INP int nx, INP int ny, INP int x, INP int y);
/* adjusts a negative destination track calculated from a permutation formula */
static int adjust_formula_result(INP int dest_track, INP int dest_W);
t_sb_connection_map* alloc_switchblock_permutations(int nx, int ny, int channel_width);
/************ Function Definitions ************/
/* allocate and build the switchblock permutation map */
t_sb_connection_map * alloc_and_load_switchblock_permutations( INP t_chan_details * chan_details_x,
INP t_chan_details * chan_details_y, INP int nx, INP int ny,
INP vector<t_switchblock_inf> switchblocks,
INP s_chan_width *nodes_per_chan, INP e_directionality directionality){
/* get a single number for channel width */
int channel_width = nodes_per_chan->max;
if (nodes_per_chan->max != nodes_per_chan->x_min || nodes_per_chan->max != nodes_per_chan->y_min){
vpr_throw(VPR_ERROR_ARCH, __FILE__, __LINE__, "Custom switch blocks currently support consistent channel widths only.");
}
/* sparse array that will contain switch block connections */
//t_sb_connection_map *sb_conns = new t_sb_connection_map;
t_sb_connection_map *sb_conns = alloc_switchblock_permutations(nx, ny, channel_width);
/* We assume that x & y channels have the same ratios of track types. i.e., looking at a single
channel is representative of all channels in the FPGA -- as of 3/9/2013 this is true in VPR */
t_track_type_sizes track_type_sizes;
count_track_type_sizes(chan_details_x[0][0], channel_width, &track_type_sizes);
#ifdef FAST_SB_COMPUTATION
/******** fast switch block computation method; computes a row of switchblocks then stamps it out everywhere ********/
/* figure out max(lcm(L_i, L_j)) for all wire lengths belonging to wire types i & j */
int max_lcm = get_max_lcm(&switchblocks, &track_type_sizes);
t_sb_connection_map sb_row;
/* compute the perimeter switchblocks. unfortunately we can't just compute corners and stamp out the rest because
for a unidirectional architecture corners AND perimeter switchblocks require special treatment */
compute_perimeter_switchblocks( chan_details_x, chan_details_y, &switchblocks,
nx, ny, channel_width, &track_type_sizes, directionality, sb_conns);
/* compute the switchblock row */
compute_switchblock_row( max_lcm, chan_details_x, chan_details_y, &switchblocks,
nx, ny, channel_width, &track_type_sizes, directionality, &sb_row );
/* stamp-out the switchblock row throughout the rest of the FPGA */
stampout_switchblocks_from_row( max_lcm, channel_width,
nx, ny, &track_type_sizes, directionality, &sb_row, sb_conns );
#else
/******** slow switch block computation method; computes switchblocks at each coordinate ********/
/* iterate over all the switchblocks specified in the architecture */
for (int i_sb = 0; i_sb < (int)switchblocks.size(); i_sb++){
t_switchblock_inf sb = switchblocks[i_sb];
/* verify that switchblock type matches specified directionality -- currently we have to stay consistent */
if (directionality != sb.directionality){
vpr_throw(VPR_ERROR_ARCH, __FILE__, __LINE__, "alloc_and_load_switchblock_connections: Switchblock %s does not match directionality of architecture\n", sb.name.c_str());
}
/* Iterate over the x,y coordinates spanning the FPGA. Currently, the FPGA size is set as
(0..nx+1) by (0..ny+1), so we iterate over 0..nx and 0..ny. */
for (int x_coord = 0; x_coord <= nx+1; x_coord++){
for (int y_coord = 0; y_coord <= ny+1; y_coord++){
if (sb_not_here(nx, ny, x_coord, y_coord, sb.location)){
continue;
}
/* Iterate over each track in channel */
for (int from_track = 0; from_track < channel_width; from_track++){
/* now we iterate over all the potential side1->side2 connections */
for ( e_side from_side = (e_side) 0; from_side < 4; from_side = (e_side)(from_side + 1) ){
for ( e_side to_side = (e_side) 0; to_side < 4; to_side = (e_side)(to_side + 1) ){
/* Fill appropriate entry of the sb_conns map with vector specifying the tracks
the current track will connect to */
compute_track_connections(x_coord, y_coord, from_side, to_side, from_track,
chan_details_x, chan_details_y, &sb, nx, ny, channel_width,
&track_type_sizes, directionality, sb_conns);
}
}
}
}
}
}
#endif
return sb_conns;
}
/* allocate switch block matrix */
t_sb_connection_map* alloc_switchblock_permutations(int nx, int ny, int channel_width){
t_sb_connection_map *sb_conns = new t_sb_connection_map;
forward_list< t_to_track_inf > tracks;
vector < forward_list< t_to_track_inf >> from_track_vec(channel_width, tracks);
vector < vector< forward_list < t_to_track_inf >>> to_side_vec(4, from_track_vec);
vector < vector< vector < forward_list < t_to_track_inf >>>> from_side_vec(4, to_side_vec);
vector < vector< vector < vector < forward_list < t_to_track_inf >>>>> y_vec(ny+1, from_side_vec);
sb_conns->assign(nx+1, y_vec);
//(*sb_conns) = (t_sb_connection_map) alloc_matrix5(0, nx, 0, ny, 0, 3, 0, 3, 0, channel_width-1, sizeof(forward_list<t_to_track_inf>));
return sb_conns;
}
/* deallocates switch block connections sparse array */
void free_switchblock_permutations(INOUTP t_sb_connection_map *sb_conns, int nx, int ny){
sb_conns->clear();
delete sb_conns;
//free_matrix5((void*)(*sb_conns), 0, nx, 0, ny, 0, 3, 0, 3, 0, sizeof(forward_list<t_to_track_inf>));
sb_conns = NULL;
/* the switch block unordered_map can get quite large and it doesn't seem like the program
is interested in releasing the memory back to the OS after the map is cleared.
calling malloc_trim forces the program to give unused heap space back to the OS.
this significantly reduces memory usage during the routing stage when running multiple
large benchmark circuits in parallel. */
malloc_trim(0);
return;
}
#ifdef FAST_SB_COMPUTATION
/* over all connected wire types (i,j), compute the maximum least common multiple of their track lengths,
ie max(LCM(L_i, L_J)) */
static int get_max_lcm( INP vector<t_switchblock_inf> *switchblocks, INP t_track_type_sizes *track_type_sizes){
int max_lcm = -1;
int num_sb = (int)switchblocks->size();
/* over each switchblock */
for (int isb = 0; isb < num_sb; isb++){
t_switchblock_inf *sb = &(switchblocks->at(isb));
int num_wireconns = (int)sb->wireconns.size();
/* over each wireconn */
for (int iwc = 0; iwc < num_wireconns; iwc++){
t_wireconn_inf *wc = &(sb->wireconns[iwc]);
int num_from_types = (int)wc->from_type.size();
int num_to_types = (int)wc->to_type.size();
/* over each from type */
for (int ifrom = 0; ifrom < num_from_types; ifrom++){
/* over each to type */
for (int ito = 0; ito < num_to_types; ito++){
if ((*track_type_sizes).find(wc->from_type[ifrom]) != (*track_type_sizes).end() &&
(*track_type_sizes).find(wc->to_type[ito]) != (*track_type_sizes).end()) {
// the condition can fail if freq of a seg is 0 (so it is in wc, but not in track_type_size)
int length1 = track_type_sizes->at(wc->from_type[ifrom]).length;
int length2 = track_type_sizes->at(wc->to_type[ito]).length;
int current_lcm = lcm(length1, length2);
if (current_lcm > max_lcm){
max_lcm = current_lcm;
}
}
}
}
}
}
return max_lcm;
}
/* computes a horizontal row of switchblocks of size sb_row_size (or of nx-2, whichever is smaller), starting
at coordinate (1,1) */
static void compute_switchblock_row(INP int sb_row_size, INP t_chan_details *chan_details_x, INP t_chan_details *chan_details_y,
INP vector<t_switchblock_inf> *switchblocks, INP int nx, INP int ny, INP int nodes_per_chan,
INP t_track_type_sizes *track_type_sizes, INP e_directionality directionality,
INOUTP t_sb_connection_map *sb_row){
int y = 1;
for (int isb = 0; isb < (int)switchblocks->size(); isb++){
t_switchblock_inf *sb = &(switchblocks->at(isb));
for (int x = 1; x < 1 + sb_row_size; x++){
if (sb_not_here(nx, ny, x, y, sb->location)){
continue;
}
/* Iterate over each track in channel */
for (int from_track = 0; from_track < nodes_per_chan; from_track++){
/* now we iterate over all the potential side1->side2 connections */
for ( e_side from_side = (e_side) 0; from_side < 4; from_side = (e_side)(from_side + 1) ){
for ( e_side to_side = (e_side) 0; to_side < 4; to_side = (e_side)(to_side + 1) ){
/* Fill appropriate entry of the sb_conns map with vector specifying the tracks
the current track will connect to */
compute_track_connections(x, y, from_side, to_side, from_track,
chan_details_x, chan_details_y, sb, nx, ny, nodes_per_chan,
track_type_sizes, directionality, sb_row);
}
}
}
}
}
}
/* stamp out a row of horizontal switchblocks throughout the FPGA starting at coordinates (ref_x, ref_y) and
continuing on for sb_row_size */
static void stampout_switchblocks_from_row( INP int sb_row_size,
INP int nodes_per_chan,
INP int nx, INP int ny, INP t_track_type_sizes *track_type_sizes,
INP e_directionality directionality, INP t_sb_connection_map *sb_row,
INOUTP t_sb_connection_map *sb_conns ){
/* over all x coordinates that may need stamping out */
for (int x = 1; x < nx; x++){
/* over all y coordinates that may need stamping out */
for (int y = 1; y < ny; y++){
/* perimeter has been precomputed */
if ( is_perimeter(nx, ny, x, y) ){
continue;
}
/* over each source side */
for (int from_side = 0; from_side < 4; from_side ++){
/* over each destination side */
for (int to_side = 0; to_side < 4; to_side ++){
/* can't connect a side to itself */
if (from_side == to_side){
continue;
}
/* over each source track */
for (int itrack = 0; itrack < nodes_per_chan; itrack++){
/* get the total x+y distance of the current switchblock from the reference switchblock */
int distance = (x - REF_X) + (y - REF_Y);
if (distance < 0){
distance = sb_row_size - ((-1*distance)%sb_row_size);
}
/* figure out the coordinates of the switchblock we want to copy */
int copy_y = 1;
int copy_x = 1 + (distance % sb_row_size); //TODO: based on what? explain staggering pattern
/* create the indices to key into the switchblock permutation map */
Switchblock_Lookup my_key(x, y, (e_side)from_side, (e_side)to_side, itrack);
Switchblock_Lookup copy_key(copy_x, copy_y, (e_side)from_side, (e_side)to_side, itrack);
if ( sb_row->count(copy_key) == 0 ){
continue;
}
(*sb_conns)[my_key] = sb_row->at(copy_key);
}
}
}
}
}
}
/* compute all the switchblocks around the perimeter of the FPGA for the given switchblock and wireconn */
static void compute_perimeter_switchblocks(INP t_chan_details *chan_details_x, INP t_chan_details *chan_details_y,
INP vector<t_switchblock_inf> *switchblocks, INP int nx, INP int ny, INP int nodes_per_chan,
INP t_track_type_sizes *track_type_sizes, INP e_directionality directionality,
INOUTP t_sb_connection_map *sb_conns){
int x, y;
for (int isb = 0; isb < (int)switchblocks->size(); isb++){
/* along left and right edge */
x = 0;
t_switchblock_inf *sb = &(switchblocks->at(isb));
for (int i = 0; i < 2; i++){ //TODO: can use i+=nx to make more explicit what the ranges of the loop are
for (y = 0; y <= ny+1; y++){
if (sb_not_here(nx, ny, x, y, sb->location)){
continue;
}
/* Iterate over each track in channel */
for (int from_track = 0; from_track < nodes_per_chan; from_track++){
/* now we iterate over all the potential side1->side2 connections */
for ( e_side from_side = (e_side) 0; from_side < 4; from_side = (e_side)(from_side + 1) ){
for ( e_side to_side = (e_side) 0; to_side < 4; to_side = (e_side)(to_side + 1) ){
/* Fill appropriate entry of the sb_conns map with vector specifying the tracks
the current track will connect to */
compute_track_connections(x, y, from_side, to_side, from_track,
chan_details_x, chan_details_y, sb, nx, ny, nodes_per_chan,
track_type_sizes, directionality, sb_conns);
}
}
}
}
x = nx;
}
}
for (int isb = 0; isb < (int)switchblocks->size(); isb++){
/* along bottom and top edge */
y = 0;
t_switchblock_inf *sb = &(switchblocks->at(isb));
for (int i = 0; i < 2; i++){
for (x = 0; x <= nx+1; x++){
if (sb_not_here(nx, ny, x, y, sb->location)){
continue;
}
/* Iterate over each track in channel */
for (int from_track = 0; from_track < nodes_per_chan; from_track++){
/* now we iterate over all the potential side1->side2 connections */
for ( e_side from_side = (e_side) 0; from_side < 4; from_side = (e_side)(from_side + 1) ){
for ( e_side to_side = (e_side) 0; to_side < 4; to_side = (e_side)(to_side + 1) ){
/* Fill appropriate entry of the sb_conns map with vector specifying the tracks
the current track will connect to */
compute_track_connections(x, y, from_side, to_side, from_track,
chan_details_x, chan_details_y, sb, nx, ny, nodes_per_chan,
track_type_sizes, directionality, sb_conns);
}
}
}
}
y = ny;
}
}
}
/* compute greatest common denominator of x and y */
static int gcd(INP int x, INP int y){
int result;
if (y == 0){
result = x;
} else {
result = gcd(y, x % y);
}
return result;
}
/* compute least common multiple of x and y */
static int lcm(INP int x, INP int y){
int result;
result = x / gcd(x,y) * y;
return result;
}
#endif //FAST_SB_COMPUTATION
/* returns true if the coordinates x/y do not correspond to the location specified by 'location' */
static bool sb_not_here(INP int nx, INP int ny, INP int x, INP int y, INP e_sb_location location){
bool sb_not_here = true;
switch( location ){
case E_EVERYWHERE:
sb_not_here = false;
break;
case E_PERIMETER:
if (is_perimeter(nx, ny, x, y)){
sb_not_here = false;
}
break;
case E_CORNER:
if (is_corner(nx, ny, x, y)){
sb_not_here = false;
}
break;
case E_CORE:
if (is_core(nx, ny, x, y)){
sb_not_here = false;
}
break;
case E_FRINGE:
if (is_perimeter(nx, ny, x, y) && !is_corner(nx, ny, x, y)){
sb_not_here = false;
}
break;
default:
vpr_throw(VPR_ERROR_ARCH, __FILE__, __LINE__, "sb_not_here: unrecognized location enum: %d\n", location);
break;
}
return sb_not_here;
}
/* checks if the specified coordinates represent a corner of the FPGA */
static bool is_corner(INP int nx, INP int ny, INP int x, INP int y){
bool is_corner = false;
if ((x == 0 && y == 0) ||
(x == 0 && y == ny) ||
(x == nx && y == 0) ||
(x == nx && y == ny)){
is_corner = true;
}
return is_corner;
}
/* checks if the specified coordinates correspond to one of the perimeter switchblocks */
static bool is_perimeter(INP int nx, INP int ny, INP int x, INP int y){
bool is_perimeter = false;
if (x == 0 || x == nx ||
y == 0 || y == ny){
is_perimeter = true;
}
return is_perimeter;
}
/* checks if the specified coordinates correspond to the core of the FPGA (i.e. not perimeter) */
static bool is_core(INP int nx, INP int ny, INP int x, INP int y){
bool is_core = !is_perimeter(nx, ny, x, y);
return is_core;
}
/* Counts the number of tracks in each track type in the specified channel */
static void count_track_type_sizes(INP t_seg_details *channel, INP int nodes_per_chan,
INOUTP t_track_type_sizes *track_type_sizes){
string track_type;
const char * new_type;
int new_length, length;
int new_start, start;
int num_tracks = 0;
Track_Info track_info;
track_type = channel[0].type_name_ptr;
length = channel[0].length;
start = 0;
for (int itrack = 0; itrack < nodes_per_chan; itrack++){
new_type = channel[itrack].type_name_ptr;
new_length = channel[itrack].length;
new_start = itrack;
if (strcmp(new_type, track_type.c_str()) != 0){
track_info.set(length, num_tracks, start);
(*track_type_sizes)[track_type] = track_info;
track_type = new_type;
length = new_length;
start = new_start;
num_tracks = 0;
}
num_tracks++;
}
track_info.set(length, num_tracks, start);
(*track_type_sizes)[track_type] = track_info;
return;
}
/* returns the track indices belonging to the types in 'track_type_vec' and wirepoint in 'points' at the given channel segment */
static void get_wirepoint_tracks( INP e_directionality directionality, INP int nx, INP int ny, INP int nodes_per_chan, t_seg_details *chan_details,
INP t_rr_type chan_type, INP int x, INP int y, INP e_side side, INP vector<string> *track_type_vec, INP vector<int> *points,
INP t_track_type_sizes *track_type_sizes, INP bool is_dest, INOUTP vector<int> *tracks){
tracks->clear();
int num_types = (int)track_type_vec->size();
int seg_coord = x;
if (chan_type == CHANY){
seg_coord = y;
}
for (int itype = 0; itype < num_types; itype++){
string track_type = track_type_vec->at(itype);
if ((*track_type_sizes).find(track_type) == (*track_type_sizes).end()) {
// track_type_sizes may not contain track_type if its seg freq is 0
continue;
}
/* get the number of tracks of given type */
int num_type_tracks = track_type_sizes->at(track_type).num_tracks;
/* get the last track belonging to this type */
int first_type_track = track_type_sizes->at(track_type).start;
int last_type_track = first_type_track + num_type_tracks - 1;
/* walk through each track segment of specified type and check whether it matches one
of the specified wirepoints */
for (int itrack = first_type_track; itrack <= last_type_track; itrack++){
e_direction seg_direction = chan_details[itrack].direction;
if (side == TOP || side == RIGHT){
if (seg_direction == DEC_DIRECTION && is_dest){
continue;
}
if (seg_direction == INC_DIRECTION && !is_dest){
continue;
}
} else {
assert(side == LEFT || side == BOTTOM);
if (seg_direction == DEC_DIRECTION && !is_dest){
continue;
}
if (seg_direction == INC_DIRECTION && is_dest){
continue;
}
}
int track_wirepoint = get_wirepoint_of_track(nx, ny, chan_type, chan_details[itrack], seg_coord, side);
/* check if this track belongs to one of the specified wirepoints; add it to our 'tracks' vector if so */
if ( find( points->begin(), points->end(), track_wirepoint ) != points->end() ){
tracks->push_back( itrack );
}
}
}
sort(tracks->begin(), tracks->end());
}
/* Compute the track(s) that the track at (x, y, from_side, to_side, from_track) should connect to.
sb_conns is updated with the result */
static void compute_track_connections(INP int x_coord, INP int y_coord, INP enum e_side from_side,
INP enum e_side to_side, INP int from_track, INP t_chan_details * chan_details_x,
INP t_chan_details * chan_details_y, INP t_switchblock_inf *sb,
INP int nx, INP int ny, INP int nodes_per_chan,
INP t_track_type_sizes *track_type_sizes, INP e_directionality directionality,
INOUTP t_sb_connection_map *sb_conns){
t_chan_details *from_chan_details = NULL; /* details for source channel */
t_chan_details *to_chan_details = NULL ; /* details for destination channel */
int from_x, from_y; /* index into source channel */
int to_x, to_y; /* index into destination channel */
t_rr_type from_chan_type, to_chan_type; /* the type of channel - i.e. CHANX or CHANY */
from_x = from_y = to_x = to_y = UNDEFINED;
Connect_SB_Sides side_conn(from_side, to_side); /* for indexing into this switchblock's permutation funcs */
Switchblock_Lookup sb_conn(x_coord, y_coord, from_side, to_side, from_track); /* for indexing into FPGA's switchblock map */
/* can't connect a switchblock side to itself */
if (from_side == to_side){
return;
}
/* check that the permutation map has an entry for this side combination */
if (sb->permutation_map.count(side_conn) == 0){
/* the specified switchblock does not have any permutation funcs for this side1->side2 connection */
return;
}
/* find the correct channel, and the coordinates to index into it for both the source and
destination channels. also return the channel type (ie chanx/chany) into which we are
indexing */
from_chan_type = index_into_correct_chan(x_coord, y_coord, from_side, chan_details_x, chan_details_y,
&from_x, &from_y, &from_chan_details);
to_chan_type = index_into_correct_chan(x_coord, y_coord, to_side, chan_details_x, chan_details_y,
&to_x, &to_y, &to_chan_details);
/* make sure from_x/y and to_x/y aren't out of bounds */
if (coords_out_of_bounds(nx, ny, to_x, to_y, to_chan_type) ||
coords_out_of_bounds(nx, ny, from_x, from_y, from_chan_type)){
return;
}
/* iterate over all the wire connections specified for this switch block */
for (int iconn = 0; iconn < (int)sb->wireconns.size(); iconn++){
/* pointer to a connection specification between two wire types/groups */
t_wireconn_inf *wireconn_ptr = &sb->wireconns[iconn];
/* compute the destination track segments to which the source track segment should connect based on the
current wireconn */
compute_track_wireconn_connections(nx, ny, directionality, nodes_per_chan, from_chan_details, to_chan_details,
sb_conn, from_x, from_y, to_x, to_y, from_chan_type, to_chan_type, track_type_sizes,
sb, wireconn_ptr, sb_conns);
}
from_chan_details = NULL;
to_chan_details = NULL;
return;
}
/* computes the destination track segments that a source track segment at the coordinate 'sb_conn' (in
channel segment with coordinate from_x/from_y) should connect to based on the specified 'wireconn_ptr'.
wireconn_ptr defines the source and destination sets of track segments (based on track segment type & wirepoint
as defined at the top of this file), and the indices of tracks to connect to are relative to these sets */
static void compute_track_wireconn_connections(INP int nx, INP int ny, INP e_directionality directionality, INP int nodes_per_chan,
INP t_chan_details *from_chan_details, INP t_chan_details *to_chan_details, INP Switchblock_Lookup sb_conn,
INP int from_x, INP int from_y, INP int to_x, INP int to_y, INP t_rr_type from_chan_type, INP t_rr_type to_chan_type,
INP t_track_type_sizes *track_type_sizes, INP t_switchblock_inf *sb,
INP t_wireconn_inf *wireconn_ptr, INOUTP t_sb_connection_map *sb_conns){
e_direction from_track_direction = from_chan_details[from_x][from_y][sb_conn.from_track].direction;
if ( from_track_direction == INC_DIRECTION ){
/* if this is a unidirectional track headed in the increasing direction (relative to coordinate system)
then switch block source side should be BOTTOM or LEFT */
if (sb_conn.from_side == TOP || sb_conn.from_side == RIGHT){
return;
}
} else if ( from_track_direction == DEC_DIRECTION ){
/* a track heading in the decreasing direction can only connect from the TOP or RIGHT sides of a switch block */
if (sb_conn.from_side == BOTTOM || sb_conn.from_side == LEFT){
return;
}
} else {
assert( from_track_direction = BI_DIRECTION );
}
/* name of the source track type */
const char *track_type_name = from_chan_details[from_x][from_y][sb_conn.from_track].type_name_ptr;
/* names of track types we may be connecting from/to */
vector<string> *from_track_type = &(wireconn_ptr->from_type);
vector<string> *to_track_type = &(wireconn_ptr->to_type);
/* the 'seg' coordinate of the from channel */
int from_seg = get_seg_coordinate( from_chan_type, from_x, from_y );
/* the 'from' wirepoint and 'to' wirepoints at which the connection is made */
int from_wirepoint;
vector<int> to_wirepoint;
/* the current track corresponds to a single wirepoint */
from_wirepoint = get_wirepoint_of_track(nx, ny, from_chan_type, from_chan_details[from_x][from_y][sb_conn.from_track],
from_seg, sb_conn.from_side);
/* If we're at a switch block side where *all* wires start/terminate (i.e. around the FPGA perimeter) then
the source wirepoint is 0 no matter what */
if ((TOP==sb_conn.from_side && sb_conn.y_coord==0) ||
(RIGHT==sb_conn.from_side && sb_conn.x_coord==0) ||
(LEFT==sb_conn.from_side && sb_conn.x_coord==nx) ||
(BOTTOM==sb_conn.from_side && sb_conn.y_coord==ny)){
from_wirepoint = 0;
}
/* there may be multiple destination wirepoints */
to_wirepoint = wireconn_ptr->to_point;
/* vectors that will contain indices of the tracks belonging to the source/dest wire types/points */
vector<int> potential_src_tracks;
vector<int> potential_dest_tracks;
/* the index of the source/destination track within their own wirepoint set */
int src_track_in_sp, dest_track_in_sp; //XXX: what's 'sp'???
/* the effective destination channel width is the size of the destination track set */
int dest_W;
/* check that the current track has one of the types specified by the wire connection */
bool skip = true;
for (int itype = 0; itype < (int)from_track_type->size(); itype++){
if ( strcmp(from_track_type->at(itype).c_str(), track_type_name) == 0 ){
skip = false;
break;
}
}
if (skip){
return;
}
/* check that the current track has one of the source wire points specified by the wire connection */
skip = true;
for (int ipoint = 0; ipoint < (int)wireconn_ptr->from_point.size(); ipoint++){
if (from_wirepoint == wireconn_ptr->from_point[ipoint]){
skip = false;
break;
}
}
if (skip){
return;
}
/* now we need to find all tracks in the destination channel that correspond to the
type and point specified by the current wireconn_ptr. Must also have appropriate
directionality */
/* get the indices of tracks in the destination group, as well as the effective destination
channel width (which is the number of tracks in destination group) */
get_wirepoint_tracks(directionality, nx, ny, nodes_per_chan, to_chan_details[to_x][to_y], to_chan_type, to_x, to_y, sb_conn.to_side,
to_track_type, &to_wirepoint, track_type_sizes, true, &potential_dest_tracks);
if (0 == potential_dest_tracks.size()){
return;
}
dest_W = potential_dest_tracks.size();
/* get the index of the source track relative to all the tracks specified by the wireconn */
get_wirepoint_tracks(directionality, nx, ny, nodes_per_chan, from_chan_details[from_x][from_y], from_chan_type, from_x, from_y, sb_conn.from_side,
from_track_type, &(wireconn_ptr->from_point), track_type_sizes, false, &potential_src_tracks);
if (0 == potential_src_tracks.size()){
return;
}
vector<int>::iterator it = find(potential_src_tracks.begin(), potential_src_tracks.end(), sb_conn.from_track);
if (it == potential_src_tracks.end()){
vpr_throw(VPR_ERROR_ARCH, __FILE__, __LINE__, "Could not find switch block source track in vector of potential source tracks\n");
}
src_track_in_sp = it - potential_src_tracks.begin();
/* at this point the vectors 'potential_src_tracks' and 'potential_dest_tracks' contain the indices of the from_type/from_point
and to_type/to_point track segments. now its time to compute which destination track segments our specific track segment of interest
('from_track') should connect to */
/* get a reference to the string vector containing desired side1->side2 permutations */
Connect_SB_Sides side_conn(sb_conn.from_side, sb_conn.to_side);
vector<string> &permutations_ref = sb->permutation_map[side_conn];
/* iterate over the permutation functions specified in permutations_ref */
int to_track;
s_formula_data formula_data;
for (int iperm = 0; iperm < (int)permutations_ref.size(); iperm++){
/* Convert the symbolic permutation formula to a number */
formula_data.dest_W = dest_W;
if (0 == dest_W){
return;
}
formula_data.track = src_track_in_sp;
dest_track_in_sp = get_sb_formula_result(permutations_ref[iperm].c_str(), formula_data);
dest_track_in_sp = adjust_formula_result(dest_track_in_sp, dest_W);
if(dest_track_in_sp < 0){
vpr_throw(VPR_ERROR_ARCH, __FILE__, __LINE__, "Got a negative track from switch block formula %s", permutations_ref[iperm].c_str());
}
/* the resulting track number is the *index* of the destination track in it's own
set, so we need to convert that back to the absolute index of the track in the channel */
to_track = potential_dest_tracks[dest_track_in_sp];
/* create the struct containing information about the target track segment which will be added to the
sb connections map */
t_to_track_inf to_track_inf;
to_track_inf.to_track = to_track;
to_track_inf.switch_ind = to_chan_details[to_x][to_y][to_track].arch_wire_switch;
/* and now, finally, add this switchblock connection to the switchblock connections map */
//(*sb_conns)[sb_conn.x_coord][sb_conn.y_coord][sb_conn.from_side][sb_conn.to_side][sb_conn.from_track].push_back(to_track_inf);
(*sb_conns)[sb_conn.x_coord][sb_conn.y_coord][sb_conn.from_side][sb_conn.to_side][sb_conn.from_track].push_front(to_track_inf);
/* If bidir architecture, implement the reverse connection as well */
if (BI_DIRECTIONAL == directionality){
to_track_inf.to_track = sb_conn.from_track;
Switchblock_Lookup sb_conn_reverse(sb_conn.x_coord, sb_conn.y_coord, sb_conn.to_side, sb_conn.from_side, to_track);
//(*sb_conns)[sb_conn.x_coord][sb_conn.y_coord][sb_conn.to_side][sb_conn.from_side][to_track].push_back(to_track_inf);
(*sb_conns)[sb_conn.x_coord][sb_conn.y_coord][sb_conn.to_side][sb_conn.from_side][to_track].push_front(to_track_inf);
}
}
}
/* Here we find the correct channel (x or y), and the coordinates to index into it based on the
specified tile coordinates and the switchblock side. Also returns the type of channel
that we are indexing into (ie, CHANX or CHANY */
static t_rr_type index_into_correct_chan(INP int tile_x, INP int tile_y, INP enum e_side side,
INP t_chan_details *chan_details_x, INP t_chan_details *chan_details_y,
OUTP int *set_x, OUTP int *set_y,
OUTP t_chan_details **set_chan_details){
t_rr_type chan_type = CHANX;
/* here we use the VPR convention that a tile 'owns' the channels directly to the right
and above it */
switch (side){
case TOP:
/* this is y-channel belonging to tile above */
*set_chan_details = chan_details_y;
*set_x = tile_x;
*set_y = tile_y+1;
chan_type = CHANY;
break;
case RIGHT:
/* this is x-channel belonging to tile to the right */
*set_chan_details = chan_details_x;
*set_x = tile_x+1;
*set_y = tile_y;
chan_type = CHANX;
break;
case BOTTOM:
/* this is y-channel on the right of the tile */
*set_chan_details = chan_details_y;
*set_x = tile_x;
*set_y = tile_y;
chan_type = CHANY;
break;
case LEFT:
/* this is x-channel on top of the tile */
*set_chan_details = chan_details_x;
*set_x = tile_x;
*set_y = tile_y;
chan_type = CHANX;
break;
default:
vpr_throw(VPR_ERROR_ARCH, __FILE__, __LINE__, "index_into_correct_chan: unknown side specified: %d\n", side);
break;
}
return chan_type;
}
/* checks whether the specified coordinates are out of bounds */
static bool coords_out_of_bounds(INP int nx, INP int ny, INP int x_coord, INP int y_coord,
e_rr_type chan_type){
bool result = true;
if (CHANX == chan_type){
if (x_coord <=0 || x_coord >= nx+1 || /* there is no x-channel at x=0 */
y_coord < 0 || y_coord >= ny+1){
result = true;
} else {
result = false;
}
} else if (CHANY == chan_type){
if (x_coord < 0 || x_coord >= nx+1 ||
y_coord <= 0 || y_coord >= ny+1){ /* there is no y-channel at y=0 */
result = true;
} else {
result = false;
}
} else {
vpr_throw(VPR_ERROR_ARCH, __FILE__, __LINE__, "coords_out_of_bounds(): illegal channel type %d\n", chan_type);
}
return result;
}
/* We can look at a channel as either having an x and y coordinate, or as having a
'seg' and 'chan' coordinate. Here we return the seg coordinate based on the
channel type and channel x/y coordinates */
static int get_seg_coordinate(INP t_rr_type chan_type, INP int x_coord, INP int y_coord){
int seg = -1;
switch (chan_type){
case CHANX:
seg = x_coord;
break;
case CHANY:
seg = y_coord;
break;
default:
vpr_throw(VPR_ERROR_ARCH, __FILE__, __LINE__, "get_seg_coordinate: 'chan_type' %d is not a channel\n", chan_type);
break;
}
return seg;
}
/* returns the group number of the specified track */
static int get_track_group(INP int nx, INP int ny, INP e_rr_type chan_type,
INP t_seg_details &track_details, int seg_coord){
/* We get track group by comparing the track's seg_coord to the seg_start of the track.
The offset between seg_start (or seg_end) and seg_coord is the group number
Cases:
seg starts at bottom but does not extend all the way to the top -- look at seg_end
seg starts > bottom and does not extend all the way to top -- look at seg_start
seg starts > bottom but terminates all the way at the top -- look at seg_start
seg starts at bottom and extends all the way to the top -- look at seg end
*/
int group;
int seg_start = track_details.seg_start;
int seg_end = track_details.seg_end;
int track_length = get_track_segment_length(nx, ny, chan_type, track_details);
int min_seg;
/* determine the minimum and maximum values that the 'seg' coordinate
of a track can take */
min_seg = 1;
if (seg_start != min_seg){
group = seg_coord - seg_start;
} else {
group = (track_length-1) - (seg_end - seg_coord);
}
return group;
}
/* returns track segment length based on either:
1) the track length specified in the segment details variable for this track (if this track segment doesn't span entire FPGA)
2) the seg_start and seg_end coordinates in the segment details for this track (if this track segment spans entire FPGA, as might happen for very long wires)
Computing the track segment length in this way help to classify short vs long track segments according to wirepoint. */
int get_track_segment_length(INP int nx, INP int ny, INP e_rr_type chan_type, INP t_seg_details &track_details){
int track_length;
int min_seg = 1;
int max_seg = nx;
if (chan_type == CHANY){
max_seg = ny;
}
int seg_start = track_details.seg_start;
int seg_end = track_details.seg_end;
if (seg_start == min_seg && seg_end == max_seg){
track_length = seg_end - seg_start + 1;
} else {
track_length = track_details.length;
}
return track_length;
}
/* Returns the wirepoint of the track specified by track_details at a segment coordinate
of seg_coord, and connection to the sb_side of the switchblock */
static int get_wirepoint_of_track(INP int nx, INP int ny, INP e_rr_type chan_type,
INP t_seg_details &track_details, INP int seg_coord, INP e_side sb_side){
/* this function calculates the wirepoint of a given track by first calculating
the group of the specified track. For instance, for a track with L=4:
wirepoint: 0-------1-------2-------3-------0
group: 0 1 2 3
So knowing the track's group and which switchblock side it connects to is
enough to calculate the wirepoint
*/
int wirepoint;
/* get the minimum and maximum segment coordinate which a track in this channel type can take */
int min_seg = 1;
int max_seg = nx;
if (chan_type == CHANY){
max_seg = ny;
}
/* check whether the current seg_coord/sb_side coordinate specifies a perimeter switch block side at which all track segments terminate/start.
in this case only segments with wirepoints = 0 can exist */
bool perimeter_connection = false;
if ((seg_coord == min_seg && (sb_side == RIGHT || sb_side == TOP)) ||
(seg_coord == max_seg && (sb_side == LEFT || sb_side == BOTTOM))){
perimeter_connection = true;
}
if (perimeter_connection){
wirepoint = 0;
} else {
int track_length = get_track_segment_length(nx, ny, chan_type, track_details);
int group = get_track_group(nx, ny, chan_type, track_details, seg_coord);
if (LEFT == sb_side || BOTTOM == sb_side){
wirepoint = (group + 1) % track_length;
} else {
assert(RIGHT == sb_side || TOP == sb_side);
wirepoint = group;
}
}
return wirepoint;
}
/* adjusts a negative destination track calculated from a permutation formula */
static int adjust_formula_result(INP int dest_track, INP int dest_W){
int result = dest_track;
if (dest_track < 0){
int mult = (-1*dest_track) / dest_W + 1;
result = dest_track + mult*dest_W;
}
result = (result + dest_W) % dest_W;
return result;
}