blob: 5a25248e31cad6dd84b55bf900df5b4c08055fc1 [file] [log] [blame]
/*
* 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 "vtr_log.h"
#include "vpr_error.h"
#include "build_switchblocks.h"
#include "physical_types.h"
#include "parse_switchblocks.h"
#include "expr_eval.h"
/************ 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);
}
};
struct t_wire_switchpoint {
int wire; //Wire index within the channel
int switchpoint; //Switchpoint of the wire
};
/************ Typedefs ************/
/* Used to get info about a given wire type based on the name */
typedef std::map<std::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(const t_chan_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, vtr::RandState& rand_state);
/* ... 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, vtr::RandState& rand_state);
static int evaluate_num_conns_formula(std::string num_conns_formula, int from_wire_count, int to_wire_count);
/* returns the wire indices belonging to the types in 'wire_type_vec' and switchpoints in 'points' at the given channel segment */
static std::vector<t_wire_switchpoint> get_switchpoint_wires(const DeviceGrid& grid, const t_chan_seg_details* chan_details, t_rr_type chan_type, int x, int y, e_side side, const std::vector<t_wire_switchpoints>& wire_switchpoints_vec, t_wire_type_sizes* wire_type_sizes, bool is_dest, SwitchPointOrder order, vtr::RandState& rand_state);
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_chan_seg_details& wire_details, int seg_coord);
int get_wire_segment_length(const DeviceGrid& grid, e_rr_type chan_type, const t_chan_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_chan_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,
std::vector<t_switchblock_inf> switchblocks,
t_chan_width* nodes_per_chan,
e_directionality directionality,
vtr::RandState& rand_state) {
/* 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_FATAL_ERROR(VPR_ERROR_ARCH, "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].data(), 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_FATAL_ERROR(VPR_ERROR_ARCH, "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, rand_state);
}
}
}
}
}
#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_FATAL_ERROR(VPR_ERROR_ARCH, "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(const t_chan_seg_details* channel, int nodes_per_chan, t_wire_type_sizes* wire_type_sizes) {
std::string wire_type;
std::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 std::vector<t_wire_switchpoint> get_switchpoint_wires(const DeviceGrid& grid, const t_chan_seg_details* chan_details, t_rr_type chan_type, int x, int y, e_side side, const std::vector<t_wire_switchpoints>& wire_switchpoints_vec, t_wire_type_sizes* wire_type_sizes, bool is_dest, SwitchPointOrder switchpoint_order, vtr::RandState& rand_state) {
std::vector<t_wire_switchpoint> all_collected_wire_switchpoints;
int seg_coord = x;
if (chan_type == CHANY) {
seg_coord = y;
}
for (const t_wire_switchpoints& wire_switchpoints : wire_switchpoints_vec) {
std::vector<t_wire_switchpoint> collected_wire_switchpoints;
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.
*
* Note that we walk through the points in order, this ensures that returned switchpoints
* match the order specified in the architecture, which we assume is a priority order specified
* by the archtitect.
*/
for (int valid_switchpoint : points) {
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 (wire_switchpoint != valid_switchpoint) continue;
collected_wire_switchpoints.push_back({iwire, wire_switchpoint});
}
}
all_collected_wire_switchpoints.insert(all_collected_wire_switchpoints.end(),
collected_wire_switchpoints.begin(), collected_wire_switchpoints.end());
}
if (switchpoint_order == SwitchPointOrder::SHUFFLED) {
//We new re-order the switchpoints to try to make adjacent switchpoints have different values
vtr::shuffle(all_collected_wire_switchpoints.begin(), all_collected_wire_switchpoints.end(), rand_state);
} else {
VTR_ASSERT(switchpoint_order == SwitchPointOrder::FIXED);
//Already ordered so same switchpoints are adjacent by above collection loop
}
return all_collected_wire_switchpoints;
}
/* 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, vtr::RandState& rand_state) {
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, rand_state);
}
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, vtr::RandState& rand_state) {
constexpr bool verbose = false;
/* vectors that will contain indices of the wires belonging to the source/dest wire types/points */
std::vector<t_wire_switchpoint> potential_src_wires = get_switchpoint_wires(grid, from_chan_details[from_x][from_y].data(), from_chan_type, from_x, from_y, sb_conn.from_side,
wireconn_ptr->from_switchpoint_set, wire_type_sizes, false, wireconn_ptr->from_switchpoint_order, rand_state);
std::vector<t_wire_switchpoint> potential_dest_wires = get_switchpoint_wires(grid, to_chan_details[to_x][to_y].data(), to_chan_type, to_x, to_y, sb_conn.to_side,
wireconn_ptr->to_switchpoint_set, wire_type_sizes, true, wireconn_ptr->to_switchpoint_order, rand_state);
VTR_LOGV(verbose, "SB_LOC: %d,%d %s->%s\n", sb_conn.x_coord, sb_conn.y_coord, SIDE_STRING[sb_conn.from_side], SIDE_STRING[sb_conn.to_side]);
//Define to print out specific wire-switchpoints used in to/from sets, if verbose is set true
#if 0
for (auto from_set : wireconn_ptr->from_switchpoint_set) {
VTR_LOGV(verbose, " FROM_SET: %s @", from_set.segment_name.c_str());
for (int switchpoint : from_set.switchpoints) {
VTR_LOGV(verbose, "%d ", switchpoint);
}
}
VTR_LOGV(verbose, "\n");
for (auto to_set : wireconn_ptr->to_switchpoint_set) {
VTR_LOGV(verbose, " TO_SET: %s @", to_set.segment_name.c_str());
for (int switchpoint : to_set.switchpoints) {
VTR_LOGV(verbose, "%d ", switchpoint);
}
}
VTR_LOGV(verbose, "\n");
vector<std::string> src_wire_str;
for (t_wire_switchpoint wire_switchpoint : potential_src_wires) {
src_wire_str.push_back(std::to_string(wire_switchpoint.wire) + "@" + std::to_string(wire_switchpoint.switchpoint));
}
vector<std::string> dst_wire_str;
for (t_wire_switchpoint wire_switchpoint : potential_dest_wires) {
dst_wire_str.push_back(std::to_string(wire_switchpoint.wire) + "@" + std::to_string(wire_switchpoint.switchpoint));
}
auto src_str = vtr::join(src_wire_str, ", ");
auto dst_str = vtr::join(dst_wire_str, ", ");
VTR_LOGV(verbose, " SRC_WIRES: %s\n", src_str.c_str());
VTR_LOGV(verbose, " DST_WIRES: %s\n", dst_str.c_str());
#endif
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
int num_conns = evaluate_num_conns_formula(wireconn_ptr->num_conns_formula, potential_src_wires.size(), potential_dest_wires.size());
VTR_ASSERT_MSG(num_conns >= 0, "Number of switchblock connections to create must be non-negative");
VTR_LOGV(verbose, " num_conns: %zu\n", num_conns);
for (size_t iconn = 0; iconn < size_t(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].wire; //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);
std::vector<std::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_FATAL_ERROR(VPR_ERROR_ARCH, "Got a negative wire from switch block formula %s", permutations_ref[iperm].c_str());
}
int to_wire = potential_dest_wires[dest_wire_ind].wire; //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();
VTR_LOGV(verbose, " make_conn: %d -> %d switch=%d\n", sb_edge.from_wire, sb_edge.to_wire, sb_edge.switch_ind);
/* 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);
}
}
}
}
static int evaluate_num_conns_formula(std::string num_conns_formula, int from_wire_count, int to_wire_count) {
t_formula_data vars;
vars.set_var_value("from", from_wire_count);
vars.set_var_value("to", to_wire_count);
return parse_formula(num_conns_formula, vars);
}
/* 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_FATAL_ERROR(VPR_ERROR_ARCH, "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_FATAL_ERROR(VPR_ERROR_ARCH, "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_chan_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_chan_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_chan_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;
}