blob: 0e12253f9c5e37f844b40aa32ec4d25813455785 [file] [log] [blame] [edit]
/*
Author(s): Oleg Petelin
Last revised: September, 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/dest 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
* s_det_routing_arch carries info about custom switch blocks
* build_switchblocks.c (this file):
* builds t_sb_connection_map sparse array containing target connections for each wire in each horizontal/vertical channel segment.
'compute_wire_connections' is the most important function here -- it computes the set of wire segments that a given wire at
(x, y, from_side, to_side, from_wire) 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_wire_to_chan_seg is called within get_wire_to_wires which looks at t_sb_connection_map to create the edges that
connect a source wire segment into a destination channel segment
************ DESCRIPTION OF SWITCH BLOCK SPECIFICATION ************
Some terminology:
* Wire segment type: a named group of wires defined in the architecture file.
* Wire subsegment number: a number assigned to a wire subsegment relative to the wire's start coordinate (see diagram below).
Used to define a collection of wire segments that have a start point in the same column (or row) of the FPGA.
* Wire switch point: a number assigned to a specific switch block along a wire segment
Ex: for a length-4 unidirectional wire segment going in the decreasing direction:
wire switch point 0<-------3<-------2<-------1<-------0
wire subsegment # 3 2 1 0
The new switch block format allows a user to specify mathematical permutation functions that describe how different types of wires will connect at different switchpoinnts. One or more wire types *and* one or more switch points define a set of wires, and each switch block connection is specified in terms of a source and destination set. Specifically, the permutation functions prescribe how a set of from_type/from_switchpoint wires (the source set) in one channel segment should connect to a set of to_type/to_switchpoint wires (the destination set) in an adjacent channel segment. This provides for an abstract but very flexible way of specifying different switch block patterns.
An example from an 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).
<!-- Specify that custom switch blocks will be used. This is backwards compatible with VPR's previous wilton/subset/univeral specification,
but "custom" is specified instead. -->
<switch_block type="custom"/> <-- backwards-compatible with VPR's previous wilton/subset/universal/full specification
...
...
<!-- Segment specification is as before, except that a "name" is also specified for each segment. Each segment defines a type of wire. -->
<segmentlist>
<segment freq="0.85" name="l4" length="4" type="unidir" Rmetal="232" Cmetal="0.0">
<mux name="l4_mux"/>
<sb type="pattern">1 1 1 1 1</sb>
<cb type="pattern">1 1 1 1</cb>
</segment>
<segment freq="0.15" name="l8_global" length="8" type="unidir" Rmetal="33.8" Cmetal="0.0">
<mux name="l8_mux"/>
<sb type="pattern">1 1 1 1 1 1 1 1 1</sb>
<cb type="pattern">1 1 1 1 1 1 1 1</cb>
</segment>
</segmentlist>
...
...
<!-- Custom switch blocks are declared here -->
<switchblocklist>
<!-- Each "switchblock" entry is given a name. Type specifies either a unidirectional or bidirectional switch block -->
<switchblock name="my_switchblock" type="unidir">
<!-- Location to implement this switch block (EVERYWHERE/CORE/PERIMETER/CORNER/FRINGE) -->
<switchblock_location type="EVERYWHERE"/>
<!-- A list of permutation functions. Any number can be specified (for instance two "lt" entries can be specified to increase
Fs of connections from the left to the top switch block sides -->
<switchfuncs>
<!-- "lr" means left-to-right, "lt" means left to top, etc. Formulas support different operators which are discussed later -->
<func type="lr" formula="t"/>
<func type="lt" formula="W-t"/>
<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"/>
<func type="rl" formula="t"/>
<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"/>
</switchfuncs>
<!-- Wireconn entries define the sets of wires that should be connected with the above permutation functions -->
<wireconn from_type="l4" to_type="l4" from_switchpoint="0,1,2,3" to_switchpoint="0"/>
<wireconn from_type="l8_global" to_type="l8_global" from_switchpoint="0,4" to_switchpoint="0"/>
<wireconn from_type="l8_global" to_type="l4" from_switchpoint="0,4" to_switchpoint="0"/>
</switchblock>
<switchblock name="another_switch_block" type="unidir">
... switch block description ...
</switchblock>
</switchblocklist>
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
*/
#include <cstring>
#include <algorithm>
#include <iterator>
#include <iostream>
#include "vtr_assert.h"
#include "vtr_memory.h"
#include "vpr_error.h"
#include "build_switchblocks.h"
#include "physical_types.h"
#include "parse_switchblocks.h"
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 wire segment type */
class Wire_Info{
public:
int length; /* the length of this type of wire segment in tiles */
int num_wires; /* total number of wires in a channel segment (basically W) */
int start; /* the wire index at which this type starts in the channel segment (0..W-1) */
void set(int len, int wires, int st){
length = len; num_wires = wires; start = st;
}
Wire_Info(){
this->set(0, 0, 0);
}
Wire_Info(int len, int wires, int st){
this->set(len, wires, st);
}
};
/************ Typedefs ************/
/* Used to get info about a given wire type based on the name */
typedef map< string, Wire_Info > t_wire_type_sizes;
/************ Function Declarations ************/
/* Counts the number of wires in each wire type in the specified channel */
static void count_wire_type_sizes(t_seg_details *channel, int nodes_per_chan,
t_wire_type_sizes *wire_type_sizes);
#ifdef FAST_SB_COMPUTATION
/* over all connected wire types (i,j), compute the maximum least common multiple of their wire lengths,
ie max(LCM(L_i, L_J)) */
static int get_max_lcm(vector<t_switchblock_inf> *switchblocks, t_wire_type_sizes *wire_type_sizes);
/* compute all the switchblocks around the perimeter of the FPGA for the given switchblock and wireconn */
static void compute_perimeter_switchblocks(t_chan_details *chan_details_x, t_chan_details *chan_details_y,
vector<t_switchblock_inf> *switchblocks, const DeviceGrid& grid, int nodes_per_chan,
t_wire_type_sizes *wire_type_sizes, e_directionality directionality,
t_sb_connection_map *sb_conns);
/* computes a horizontal line of switchblocks of size sb_row_size (or of grid.width()-4, whichever is smaller), starting
at coordinate (1,1) */
static void compute_switchblock_row(int sb_row_size, t_chan_details *chan_details_x, t_chan_details *chan_details_y,
vector<t_switchblock_inf> *switchblocks, const DeviceGrid& grid, int nodes_per_chan,
t_wire_type_sizes *wire_type_sizes, e_directionality directionality,
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( int sb_row_size,
int nodes_per_chan,
const DeviceGrid& grid, t_wire_type_sizes *wire_type_sizes,
e_directionality directionality, t_sb_connection_map *sb_row,
t_sb_connection_map *sb_conns );
#endif //FAST_SB_COMPUTATION
/* Compute the wire(s) that the wire at (x, y, from_side, to_side, from_wire) should connect to.
sb_conns is updated with the result */
static void compute_wire_connections(int x_coord, int y_coord,
enum e_side from_side, enum e_side to_side,
const t_chan_details& chan_details_x, const t_chan_details& chan_details_y,
t_switchblock_inf *sb,
const DeviceGrid& grid,
t_wire_type_sizes *wire_type_sizes, e_directionality directionality,
t_sb_connection_map *sb_conns);
/* ... sb_conn represents the 'coordinates' of the desired switch block connections */
static void compute_wireconn_connections(const DeviceGrid& grid, e_directionality directionality,
const t_chan_details& from_chan_details, const t_chan_details& to_chan_details, Switchblock_Lookup sb_conn,
int from_x, int from_y, int to_x, int to_y, t_rr_type from_chan_type, t_rr_type to_chan_type,
t_wire_type_sizes *wire_type_sizes, t_switchblock_inf *sb,
t_wireconn_inf *wireconn_ptr, t_sb_connection_map *sb_conns);
/* returns the wire indices belonging to the types in 'wire_type_vec' and switchpoints in 'points' at the given channel segment */
static void get_switchpoint_wires(const DeviceGrid& grid, const t_seg_details* chan_details,
t_rr_type chan_type, int x, int y, e_side side, const vector<t_wire_switchpoints>& wire_switchpoints_vec,
t_wire_type_sizes *wire_type_sizes, bool is_dest, vector<int> *wires);
static const t_chan_details& index_into_correct_chan(int tile_x, int tile_y, enum e_side side,
const t_chan_details& chan_details_x, const t_chan_details& chan_details_y,
int *chan_x, int *chan_y,
t_rr_type* chan_type);
/* checks whether the specified coordinates are out of bounds */
static bool coords_out_of_bounds(const DeviceGrid& grid, int x_coord, int y_coord,
e_rr_type chan_type);
/* returns the subsegment number of the specified wire at seg_coord*/
static int get_wire_subsegment_num(const DeviceGrid& grid, e_rr_type chan_type,
const t_seg_details &wire_details, int seg_coord);
int get_wire_segment_length(const DeviceGrid& grid, e_rr_type chan_type, const t_seg_details &wire_details);
/* Returns the switchpoint of the wire specified by wire_details at a segment coordinate
of seg_coord, and connection to the sb_side of the switchblock */
static int get_switchpoint_of_wire(const DeviceGrid& grid, e_rr_type chan_type,
const t_seg_details &wire_details, int seg_coord, 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(const DeviceGrid& grid, int x, int y, e_sb_location location);
/* checks if the specified coordinates represent a corner of the FPGA */
static bool is_corner(const DeviceGrid& grid, int x, int y);
/* checks if the specified coordinates correspond to one of the perimeter switchblocks */
static bool is_perimeter(const DeviceGrid& grid, int x, int y);
/* checks if the specified coordinates correspond to the core of the FPGA (i.e. not perimeter) */
static bool is_core(const DeviceGrid& grid, int x, int y);
/* adjusts a negative destination wire index calculated from a permutation formula */
static int adjust_formula_result(int dest_wire, int src_W, int dest_W, int connection_ind);
/************ Function Definitions ************/
/* allocate and build the switchblock permutation map */
t_sb_connection_map * alloc_and_load_switchblock_permutations(
const t_chan_details& chan_details_x, const t_chan_details& chan_details_y,
const DeviceGrid& grid,
vector<t_switchblock_inf> switchblocks,
t_chan_width *nodes_per_chan, 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;
/* We assume that x & y channels have the same ratios of wire 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_wire_type_sizes wire_type_sizes;
count_wire_type_sizes(chan_details_x[0][0], channel_width, &wire_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, &wire_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,
grid, channel_width, &wire_type_sizes, directionality, sb_conns);
/* compute the switchblock row */
compute_switchblock_row( max_lcm, chan_details_x, chan_details_y, &switchblocks,
grid, channel_width, &wire_type_sizes, directionality, &sb_row );
/* stamp-out the switchblock row throughout the rest of the FPGA */
stampout_switchblocks_from_row( max_lcm, channel_width,
grid, &wire_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. */
for (size_t x_coord = 0; x_coord < grid.width(); x_coord++){
for (size_t y_coord = 0; y_coord <= grid.height(); y_coord++){
if (sb_not_here(grid, x_coord, y_coord, sb.location)){
continue;
}
/* now we iterate over all the potential side1->side2 connections */
for (e_side from_side : {TOP, RIGHT, BOTTOM, LEFT}) {
for (e_side to_side : {TOP, RIGHT, BOTTOM, LEFT}) {
/* Fill appropriate entry of the sb_conns map with vector specifying the wires
the current wire will connect to */
compute_wire_connections(x_coord, y_coord, from_side, to_side,
chan_details_x, chan_details_y, &sb, grid,
&wire_type_sizes, directionality, sb_conns);
}
}
}
}
}
#endif
return sb_conns;
}
/* deallocates switch block connections sparse array */
void free_switchblock_permutations(t_sb_connection_map *sb_conns){
sb_conns->clear();
delete sb_conns;
sb_conns = nullptr;
/* 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. */
vtr::malloc_trim(0);
return;
}
#ifdef FAST_SB_COMPUTATION
/* over all connected wire types (i,j), compute the maximum least common multiple of their wire lengths,
ie max(LCM(L_i, L_J)) */
static int get_max_lcm( vector<t_switchblock_inf> *switchblocks, t_wire_type_sizes *wire_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 ((*wire_type_sizes).find(wc->from_type[ifrom]) != (*wire_type_sizes).end() &&
(*wire_type_sizes).find(wc->to_type[ito]) != (*wire_type_sizes).end()) {
// the condition can fail if freq of a seg is 0 (so it is in wc, but not in wire_type_size)
int length1 = wire_type_sizes->at(wc->from_type[ifrom]).length;
int length2 = wire_type_sizes->at(wc->to_type[ito]).length;
int current_lcm = vtr::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 grid.width()-4, whichever is smaller), starting
at coordinate (1,1) */
static void compute_switchblock_row(int sb_row_size, t_chan_details *chan_details_x, t_chan_details *chan_details_y,
vector<t_switchblock_inf> *switchblocks, const DeviceGrid& grid, int nodes_per_chan,
t_wire_type_sizes *wire_type_sizes, e_directionality directionality,
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(grid, x, y, sb->location)){
continue;
}
/* now we iterate over all the potential side1->side2 connections */
for (e_side from_side : {TOP, RIGHT, BOTTOM, LEFT}) {
for (e_side to_side : {TOP, RIGHT, BOTTOM, LEFT}) {
/* Fill appropriate entry of the sb_conns map with vector specifying the wires
the current wire will connect to */
compute_wire_connections(x, y, from_side, to_side,
chan_details_x, chan_details_y, sb, grid,
wire_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( int sb_row_size,
int nodes_per_chan,
const DeviceGrid& grid, t_wire_type_sizes *wire_type_sizes,
e_directionality directionality, t_sb_connection_map *sb_row,
t_sb_connection_map *sb_conns ){
/* over all x coordinates that may need stamping out */
for (int x = 1; x < grid.width() - 2; x++){ //-2 for no perim channels
/* over all y coordinates that may need stamping out */
for (int y = 1; y < grid.height() - 2; y++){ //-2 for no perim channels
/* perimeter has been precomputed */
if ( is_perimeter(grid, x, y) ){
continue;
}
/* over each source side */
for (e_side from_side : {TOP, RIGHT, BOTTOM, LEFT}) {
/* over each destination side */
for (e_side to_side : {TOP, RIGHT, BOTTOM, LEFT}) {
/* can't connect a side to itself */
if (from_side == to_side){
continue;
}
/* over each source wire */
for (int iwire = 0; iwire < nodes_per_chan; iwire++){
/* 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, from_side, to_side, iwire);
Switchblock_Lookup copy_key(copy_x, copy_y, from_side, to_side, iwire);
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(t_chan_details *chan_details_x, t_chan_details *chan_details_y,
vector<t_switchblock_inf> *switchblocks, const DeviceGrid& grid, int nodes_per_chan,
t_wire_type_sizes *wire_type_sizes, e_directionality directionality,
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+=grid.width()-2 to make more explicit what the ranges of the loop are
for (y = 0; y < grid.height(); y++){
if (sb_not_here(grid, x, y, sb->location)){
continue;
}
/* now we iterate over all the potential side1->side2 connections */
for (e_side from_side : {TOP, RIGHT, BOTTOM, LEFT}) {
for (e_side to_side : {TOP, RIGHT, BOTTOM, LEFT}) {
/* Fill appropriate entry of the sb_conns map with vector specifying the wires
the current wire will connect to */
compute_wire_connections(x, y, from_side, to_side,
chan_details_x, chan_details_y, sb, grid,
wire_type_sizes, directionality, sb_conns);
}
}
}
x = grid.width() - 2; //-2 for no perim channels
}
}
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 < grid.width(); x++){
if (sb_not_here(grid, x, y, sb->location)){
continue;
}
/* now we iterate over all the potential side1->side2 connections */
for (e_side from_side : {TOP, RIGHT, BOTTOM, LEFT}) {
for (e_side to_side : {TOP, RIGHT, BOTTOM, LEFT}) {
/* Fill appropriate entry of the sb_conns map with vector specifying the wires
the current wire will connect to */
compute_wire_connections(x, y, from_side, to_side,
chan_details_x, chan_details_y, sb, grid,
wire_type_sizes, directionality, sb_conns);
}
}
}
y = grid.height() - 2; //-2 for no perim channels
}
}
}
#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(const DeviceGrid& grid, int x, int y, 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(grid, x, y)){
sb_not_here = false;
}
break;
case E_CORNER:
if (is_corner(grid, x, y)){
sb_not_here = false;
}
break;
case E_CORE:
if (is_core(grid, x, y)){
sb_not_here = false;
}
break;
case E_FRINGE:
if (is_perimeter(grid, x, y) && !is_corner(grid, 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(const DeviceGrid& grid, int x, int y){
bool is_corner = false;
if ((x == 0 && y == 0) ||
(x == 0 && y == int(grid.height()) - 2) || //-2 for no perim channels
(x == int(grid.width()) - 2 && y == 0) || //-2 for no perim channels
(x == int(grid.width()) - 2 && y == int(grid.height()) - 2)){ //-2 for no perim channels
is_corner = true;
}
return is_corner;
}
/* checks if the specified coordinates correspond to one of the perimeter switchblocks */
static bool is_perimeter(const DeviceGrid& grid, int x, int y){
bool is_perimeter = false;
if (x == 0 || x == int(grid.width()) - 2 ||
y == 0 || y == int(grid.height()) - 2){
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(const DeviceGrid& grid, int x, int y){
bool is_core = !is_perimeter(grid, x, y);
return is_core;
}
/* Counts the number of wires in each wire type in the specified channel */
static void count_wire_type_sizes(t_seg_details *channel, int nodes_per_chan,
t_wire_type_sizes *wire_type_sizes){
string wire_type;
string new_type;
int new_length, length;
int new_start, start;
int num_wires = 0;
Wire_Info wire_info;
wire_type = channel[0].type_name;
length = channel[0].length;
start = 0;
for (int iwire = 0; iwire < nodes_per_chan; iwire++){
new_type = channel[iwire].type_name;
new_length = channel[iwire].length;
new_start = iwire;
if (new_type != wire_type){
wire_info.set(length, num_wires, start);
(*wire_type_sizes)[wire_type] = wire_info;
wire_type = new_type;
length = new_length;
start = new_start;
num_wires = 0;
}
num_wires++;
}
wire_info.set(length, num_wires, start);
(*wire_type_sizes)[wire_type] = wire_info;
return;
}
/* returns the wire indices belonging to the types in 'wire_type_vec' and switchpoints in 'points' at the given channel segment */
static void get_switchpoint_wires(const DeviceGrid& grid, const t_seg_details* chan_details,
t_rr_type chan_type, int x, int y, e_side side, const vector<t_wire_switchpoints>& wire_switchpoints_vec,
t_wire_type_sizes *wire_type_sizes, bool is_dest, vector<int> *wires){
wires->clear();
int seg_coord = x;
if (chan_type == CHANY){
seg_coord = y;
}
for (const t_wire_switchpoints& wire_switchpoints : wire_switchpoints_vec){
const auto& wire_type = wire_switchpoints.segment_name;
const auto& points = wire_switchpoints.switchpoints;
if ((*wire_type_sizes).find(wire_type) == (*wire_type_sizes).end()) {
// wire_type_sizes may not contain wire_type if its seg freq is 0
continue;
}
/* get the number of wires of given type */
int num_type_wires = wire_type_sizes->at(wire_type).num_wires;
/* get the last wire belonging to this type */
int first_type_wire = wire_type_sizes->at(wire_type).start;
int last_type_wire = first_type_wire + num_type_wires - 1;
/* walk through each wire segment of specified type and check whether it matches one
of the specified switchpoints */
for (int iwire = first_type_wire; iwire <= last_type_wire; iwire++){
e_direction seg_direction = chan_details[iwire].direction;
/* unidirectional wires going in the decreasing direction can have an outgoing edge
only from the top or right switch block sides, and an incoming edge only if they are
at the left or bottom sides (analogous for wires going in INC direction) */
if (side == TOP || side == RIGHT){
if (seg_direction == DEC_DIRECTION && is_dest){
continue;
}
if (seg_direction == INC_DIRECTION && !is_dest){
continue;
}
} else {
VTR_ASSERT(side == LEFT || side == BOTTOM);
if (seg_direction == DEC_DIRECTION && !is_dest){
continue;
}
if (seg_direction == INC_DIRECTION && is_dest){
continue;
}
}
int wire_switchpoint = get_switchpoint_of_wire(grid, chan_type, chan_details[iwire], seg_coord, side);
/* check if this wire belongs to one of the specified switchpoints; add it to our 'wires' vector if so */
if ( find( points.begin(), points.end(), wire_switchpoint ) != points.end() ){
wires->push_back( iwire );
}
}
}
sort(wires->begin(), wires->end());
}
/* Compute the wire(s) that the wire at (x, y, from_side, to_side) should connect to.
sb_conns is updated with the result */
static void compute_wire_connections(int x_coord, int y_coord,
enum e_side from_side, enum e_side to_side,
const t_chan_details& chan_details_x, const t_chan_details& chan_details_y,
t_switchblock_inf* sb,
const DeviceGrid& grid,
t_wire_type_sizes *wire_type_sizes,
e_directionality directionality,
t_sb_connection_map* sb_conns){
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;
SB_Side_Connection 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); /* 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 */
/* details for source channel */
const t_chan_details& from_chan_details = index_into_correct_chan(x_coord, y_coord, from_side, chan_details_x, chan_details_y,
&from_x, &from_y, &from_chan_type);
/* details for destination channel */
const t_chan_details& to_chan_details = index_into_correct_chan(x_coord, y_coord, to_side, chan_details_x, chan_details_y,
&to_x, &to_y, &to_chan_type);
/* make sure from_x/y and to_x/y aren't out of bounds */
if (coords_out_of_bounds(grid, to_x, to_y, to_chan_type) ||
coords_out_of_bounds(grid, 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 wire types/subsegment_nums */
t_wireconn_inf *wireconn_ptr = &sb->wireconns[iconn];
/* compute the destination wire segments to which the source wire segment should connect based on the
current wireconn */
compute_wireconn_connections(grid, directionality, from_chan_details, to_chan_details,
sb_conn, from_x, from_y, to_x, to_y, from_chan_type, to_chan_type, wire_type_sizes,
sb, wireconn_ptr, sb_conns);
}
return;
}
/* computes the destination wire segments that a source wire 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 wire segments (based on wire segment type & switchpoint
as defined at the top of this file), and the indices of wires to connect to are relative to these sets */
static void compute_wireconn_connections(const DeviceGrid& grid, e_directionality directionality,
const t_chan_details& from_chan_details, const t_chan_details& to_chan_details, Switchblock_Lookup sb_conn,
int from_x, int from_y, int to_x, int to_y, t_rr_type from_chan_type, t_rr_type to_chan_type,
t_wire_type_sizes *wire_type_sizes, t_switchblock_inf *sb,
t_wireconn_inf *wireconn_ptr, t_sb_connection_map *sb_conns){
/* vectors that will contain indices of the wires belonging to the source/dest wire types/points */
vector<int> potential_src_wires;
vector<int> potential_dest_wires;
get_switchpoint_wires(grid, from_chan_details[from_x][from_y], from_chan_type, from_x, from_y, sb_conn.from_side,
wireconn_ptr->from_switchpoint_set, wire_type_sizes, false, &potential_src_wires);
get_switchpoint_wires(grid, to_chan_details[to_x][to_y], to_chan_type, to_x, to_y, sb_conn.to_side,
wireconn_ptr->to_switchpoint_set, wire_type_sizes, true, &potential_dest_wires);
if (potential_src_wires.size() == 0 || potential_dest_wires.size() == 0) {
//Can't make any connections between empty sets
return;
}
/* At this point the vectors 'potential_src_wires' and 'potential_dest_wires' contain the indices of the from_type/from_point
and to_type/to_point wire segments. Now we compute the connections between them, according to permutation functions */
size_t src_W = potential_src_wires.size();
size_t dest_W = potential_dest_wires.size();
//TODO: We could add another user-configurable parameter to control ordering of types in the sets.
// Currently we just iterate through them in order, but we could:
// * randomly shuffle, or
// * interleave (to ensure good diversity)
//Determine how many connections to make
size_t num_conns = 0;
if (wireconn_ptr->num_conns_type == WireConnType::FROM) {
num_conns = potential_src_wires.size();
} else if (wireconn_ptr->num_conns_type == WireConnType::TO) {
num_conns = potential_dest_wires.size();
} else if (wireconn_ptr->num_conns_type == WireConnType::MIN) {
num_conns = std::min(potential_src_wires.size(), potential_dest_wires.size());
} else if (wireconn_ptr->num_conns_type == WireConnType::MAX) {
num_conns = std::max(potential_src_wires.size(), potential_dest_wires.size());
} else {
vpr_throw(VPR_ERROR_ARCH, __FILE__, __LINE__, "Unrecognized wireconn type");
}
for (size_t iconn = 0; iconn < num_conns; ++iconn) {
//Select the from wire
// We modulo by the src set size to wrap around if there are more connections that src wires
int src_wire_ind = iconn % potential_src_wires.size(); //Index in src set
int from_wire = potential_src_wires[src_wire_ind]; //Index in channel
e_direction from_wire_direction = from_chan_details[from_x][from_y][from_wire].direction;
if (from_wire_direction == INC_DIRECTION) {
/* if this is a unidirectional wire 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){
continue;
}
VTR_ASSERT(sb_conn.from_side == BOTTOM || sb_conn.from_side == LEFT);
} else if (from_wire_direction == DEC_DIRECTION){
/* a wire 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){
continue;
}
VTR_ASSERT(sb_conn.from_side == TOP || sb_conn.from_side == RIGHT);
} else {
VTR_ASSERT(from_wire_direction == BI_DIRECTION);
}
//Evaluate permutation functions for the from_wire
SB_Side_Connection side_conn(sb_conn.from_side, sb_conn.to_side);
vector<string> &permutations_ref = sb->permutation_map[side_conn];
for (int iperm = 0; iperm < (int)permutations_ref.size(); iperm++){
/* Convert the symbolic permutation formula to a number */
t_formula_data formula_data;
formula_data.set_var_value("W", dest_W);
formula_data.set_var_value("t", src_wire_ind);
int raw_dest_wire_ind = get_sb_formula_raw_result(permutations_ref[iperm].c_str(), formula_data);
int dest_wire_ind = adjust_formula_result(raw_dest_wire_ind, src_W, dest_W, iconn);
if(dest_wire_ind < 0){
vpr_throw(VPR_ERROR_ARCH, __FILE__, __LINE__, "Got a negative wire from switch block formula %s", permutations_ref[iperm].c_str());
}
int to_wire = potential_dest_wires[dest_wire_ind]; //Index in channel
/* create the struct containing information about the target wire segment which will be added to the
sb connections map */
t_switchblock_edge sb_edge;
sb_edge.from_wire = from_wire;
sb_edge.to_wire = to_wire;
sb_edge.switch_ind = to_chan_details[to_x][to_y][to_wire].arch_wire_switch;
/* and now, finally, add this switchblock connection to the switchblock connections map */
(*sb_conns)[sb_conn].push_back(sb_edge);
/* If bidir architecture, implement the reverse connection as well */
if (BI_DIRECTIONAL == directionality) {
t_switchblock_edge sb_reverse_edge = sb_edge;
std::swap(sb_reverse_edge.from_wire, sb_reverse_edge.to_wire);
//Since we are implementing the reverse connection we have swapped from and to.
//
//Coverity flags this (false positive), so annotatate so coverity ignores it:
// coverity[swapped_arguments : Intentional]
Switchblock_Lookup sb_conn_reverse(sb_conn.x_coord, sb_conn.y_coord, sb_conn.to_side, sb_conn.from_side);
(*sb_conns)[sb_conn_reverse].push_back(sb_reverse_edge);
}
}
}
}
/* 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 const t_chan_details& index_into_correct_chan(int tile_x, int tile_y, enum e_side side,
const t_chan_details& chan_details_x, const t_chan_details& chan_details_y,
int *set_x, int *set_y,
t_rr_type* chan_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_x = tile_x;
*set_y = tile_y+1;
*chan_type = CHANY;
return chan_details_y;
break;
case RIGHT:
/* this is x-channel belonging to tile to the right */
*set_x = tile_x+1;
*set_y = tile_y;
*chan_type = CHANX;
return chan_details_x;
break;
case BOTTOM:
/* this is y-channel on the right of the tile */
*set_x = tile_x;
*set_y = tile_y;
*chan_type = CHANY;
return chan_details_y;
break;
case LEFT:
/* this is x-channel on top of the tile */
*set_x = tile_x;
*set_y = tile_y;
*chan_type = CHANX;
return chan_details_x;
break;
default:
vpr_throw(VPR_ERROR_ARCH, __FILE__, __LINE__, "index_into_correct_chan: unknown side specified: %d\n", side);
break;
}
VTR_ASSERT(false);
return chan_details_x; //Unreachable
}
/* checks whether the specified coordinates are out of bounds */
static bool coords_out_of_bounds(const DeviceGrid& grid, int x_coord, int y_coord,
e_rr_type chan_type){
bool result = true;
if (CHANX == chan_type){
if (x_coord <=0 || x_coord >= int(grid.width())-1 || /* there is no x-channel at x=0 */
y_coord < 0 || y_coord >= int(grid.height())-1){
result = true;
} else {
result = false;
}
} else if (CHANY == chan_type){
if (x_coord < 0 || x_coord >= int(grid.width())-1 ||
y_coord <= 0 || y_coord >= int(grid.height())-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;
}
/* returns the subsegment number of the specified wire at seg_coord */
static int get_wire_subsegment_num(const DeviceGrid& grid, e_rr_type chan_type, const t_seg_details &wire_details, int seg_coord){
/* We get wire subsegment number by comparing the wire's seg_coord to the seg_start of the wire.
The offset between seg_start (or seg_end) and seg_coord is the subsegment 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 subsegment_num;
int seg_start = wire_details.seg_start;
int seg_end = wire_details.seg_end;
e_direction direction = wire_details.direction;
int wire_length = get_wire_segment_length(grid, chan_type, wire_details);
int min_seg;
/* determine the minimum and maximum values that the 'seg' coordinate
of a wire can take */
min_seg = 1;
if (seg_start != min_seg){
subsegment_num = seg_coord - seg_start;
} else {
subsegment_num = (wire_length-1) - (seg_end - seg_coord);
}
/* if this wire is going in the decreasing direction, reverse the subsegment num */
VTR_ASSERT(seg_end >= seg_start);
if (direction == DEC_DIRECTION){
subsegment_num = wire_length-1 - subsegment_num;
}
return subsegment_num;
}
/* returns wire segment length based on either:
1) the wire length specified in the segment details variable for this wire (if this wire segment doesn't span entire FPGA)
2) the seg_start and seg_end coordinates in the segment details for this wire (if this wire segment spans entire FPGA, as might happen for very long wires)
Computing the wire segment length in this way help to classify short vs long wire segments according to switchpoint. */
int get_wire_segment_length(const DeviceGrid& grid, e_rr_type chan_type, const t_seg_details &wire_details){
int wire_length;
int min_seg = 1;
int max_seg = grid.width() - 2; //-2 for no perim channels
if (chan_type == CHANY){
max_seg = grid.height() - 2; //-2 for no perim channels
}
int seg_start = wire_details.seg_start;
int seg_end = wire_details.seg_end;
if (seg_start == min_seg && seg_end == max_seg){
wire_length = seg_end - seg_start + 1;
} else {
wire_length = wire_details.length;
}
return wire_length;
}
/* Returns the switchpoint of the wire specified by wire_details at a segment coordinate
of seg_coord, and connection to the sb_side of the switchblock */
static int get_switchpoint_of_wire(const DeviceGrid& grid, e_rr_type chan_type,
const t_seg_details &wire_details, int seg_coord, e_side sb_side){
/* this function calculates the switchpoint of a given wire by first calculating
the subsegmennt number of the specified wire. For instance, for a wire with L=4:
switchpoint: 0-------1-------2-------3-------0
subsegment_num: 0 1 2 3
So knowing the wire's subsegment_num and which switchblock side it connects to is
enough to calculate the switchpoint
*/
int switchpoint;
/* get the minimum and maximum segment coordinate which a wire in this channel type can take */
int min_seg = 1;
int max_seg = grid.width() - 2; //-2 for no perim channels
if (chan_type == CHANY){
max_seg = grid.height() - 2; //-2 for no perim channels
}
/* check whether the current seg_coord/sb_side coordinate specifies a perimeter switch block side at which all wire segments terminate/start.
in this case only segments with switchpoints = 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){
switchpoint = 0;
} else {
int wire_length = get_wire_segment_length(grid, chan_type, wire_details);
int subsegment_num = get_wire_subsegment_num(grid, chan_type, wire_details, seg_coord);
e_direction direction = wire_details.direction;
if (LEFT == sb_side || BOTTOM == sb_side){
switchpoint = (subsegment_num + 1) % wire_length;
if (direction == DEC_DIRECTION){
switchpoint = subsegment_num;
}
} else {
VTR_ASSERT(RIGHT == sb_side || TOP == sb_side);
switchpoint = subsegment_num;
if (direction == DEC_DIRECTION){
switchpoint = (subsegment_num + 1) % wire_length;
}
}
}
return switchpoint;
}
/* adjusts the destination wire calculated from a permutation formula to account for negative indicies,
* source wire set offset, and modulo by destination wire set size
* */
static int adjust_formula_result(int dest_wire, int src_W, int dest_W, int connection_ind) {
int result = dest_wire;
if (dest_wire < 0) {
//Adjust for negative indicies
int mult = (-1*dest_wire) / dest_W + 1;
result = dest_wire + mult*dest_W;
}
//Offset the destination track by a multiple of src_W to ensure all destination tracks are covered
//
// The permutation formula produce a 1-to-1 mapping from src track to dest track (i.e. each source
// track is mapped to precisely one destination track). This is problematic if we are processing
// a wireconn which goes through the source set multiple times (e.g. dest set larger than src set while
// processing a WireConnType::TO), since the permutation formula will only generate src_W track indicies
// (leaving some of the destination tracks unconnected). To ensure we get different destination tracks on
// subsequent passes through the same source set, we offset the raw track by a multiple of src_W. Note the
// use of integer division; src_mult will equal 0 on the first pass, 1 on the second etc.
int src_mult = connection_ind / src_W;
result += src_W * src_mult;
//Final result must be modulo dest_W
result = (result + dest_W) % dest_W;
return result;
}