| /* |
| 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; |
| } |
| |