| /* |
| * See vpr/SRC/route/build_switchblocks.c for a detailed description of how the new |
| * switch block format works and what files are involved. |
| * |
| * |
| * A large chunk of this file is dedicated to helping parse the initial switchblock |
| * specificaiton in the XML arch file, providing error checking, etc. |
| * |
| * Another large chunk of this file is dedicated to parsing the actual formulas |
| * specified by the switch block permutation functions into their numeric counterparts. |
| */ |
| |
| #include <string.h> |
| #include <string> |
| #include <sstream> |
| #include <vector> |
| #include <stack> |
| #include <utility> |
| #include <algorithm> |
| |
| #include "vtr_assert.h" |
| #include "vtr_util.h" |
| |
| #include "pugixml.hpp" |
| #include "pugixml_util.hpp" |
| |
| #include "arch_error.h" |
| |
| #include "read_xml_util.h" |
| #include "arch_util.h" |
| #include "arch_types.h" |
| #include "physical_types.h" |
| #include "parse_switchblocks.h" |
| |
| using pugiutil::ReqOpt; |
| |
| /**** Function Declarations ****/ |
| /*---- Functions for Parsing Switchblocks from Architecture ----*/ |
| |
| //Load an XML wireconn specification into a t_wireconn_inf |
| t_wireconn_inf parse_wireconn(pugi::xml_node node, const pugiutil::loc_data& loc_data); |
| |
| //Process the desired order of a wireconn |
| static void parse_switchpoint_order(const char* order, SwitchPointOrder& switchpoint_order); |
| |
| //Process a wireconn defined in the inline style (using attributes) |
| void parse_wireconn_inline(pugi::xml_node node, const pugiutil::loc_data& loc_data, t_wireconn_inf& wc); |
| |
| //Process a wireconn defined in the multinode style (more advanced specification) |
| void parse_wireconn_multinode(pugi::xml_node node, const pugiutil::loc_data& loc_data, t_wireconn_inf& wc); |
| |
| //Process a <from> or <to> sub-node of a multinode wireconn |
| t_wire_switchpoints parse_wireconn_from_to_node(pugi::xml_node node, const pugiutil::loc_data& loc_data); |
| |
| /* parses the wire types specified in the comma-separated 'ch' char array into the vector wire_points_vec. |
| * Spaces are trimmed off */ |
| static void parse_comma_separated_wire_types(const char* ch, std::vector<t_wire_switchpoints>& wire_switchpoints); |
| |
| /* parses the wirepoints specified in ch into the vector wire_points_vec */ |
| static void parse_comma_separated_wire_points(const char* ch, std::vector<t_wire_switchpoints>& wire_switchpoints); |
| |
| /* Parses the number of connections type */ |
| static void parse_num_conns(std::string num_conns, t_wireconn_inf& wireconn); |
| |
| /* checks for correctness of a unidir switchblock. */ |
| static void check_unidir_switchblock(const t_switchblock_inf* sb); |
| |
| /* checks for correctness of a bidir switchblock. */ |
| static void check_bidir_switchblock(const t_permutation_map* permutation_map); |
| |
| /* checks for correctness of a wireconn segment specification. */ |
| static void check_wireconn(const t_arch* arch, const t_wireconn_inf& wireconn); |
| |
| /**** Function Definitions ****/ |
| |
| /*---- Functions for Parsing Switchblocks from Architecture ----*/ |
| |
| /* Reads-in the wire connections specified for the switchblock in the xml arch file */ |
| void read_sb_wireconns(const t_arch_switch_inf* /*switches*/, int /*num_switches*/, pugi::xml_node Node, t_switchblock_inf* sb, const pugiutil::loc_data& loc_data) { |
| /* Make sure that Node is a switchblock */ |
| check_node(Node, "switchblock", loc_data); |
| |
| int num_wireconns; |
| pugi::xml_node SubElem; |
| |
| /* count the number of specified wire connections for this SB */ |
| num_wireconns = count_children(Node, "wireconn", loc_data, ReqOpt::OPTIONAL); |
| sb->wireconns.reserve(num_wireconns); |
| |
| if (num_wireconns > 0) { |
| SubElem = get_first_child(Node, "wireconn", loc_data); |
| } |
| for (int i = 0; i < num_wireconns; i++) { |
| t_wireconn_inf wc = parse_wireconn(SubElem, loc_data); |
| sb->wireconns.push_back(wc); |
| SubElem = SubElem.next_sibling(SubElem.name()); |
| } |
| |
| return; |
| } |
| |
| t_wireconn_inf parse_wireconn(pugi::xml_node node, const pugiutil::loc_data& loc_data) { |
| t_wireconn_inf wc; |
| |
| size_t num_children = count_children(node, "from", loc_data, ReqOpt::OPTIONAL); |
| num_children += count_children(node, "to", loc_data, ReqOpt::OPTIONAL); |
| |
| if (num_children == 0) { |
| parse_wireconn_inline(node, loc_data, wc); |
| } else { |
| VTR_ASSERT(num_children > 0); |
| parse_wireconn_multinode(node, loc_data, wc); |
| } |
| |
| return wc; |
| } |
| |
| void parse_wireconn_inline(pugi::xml_node node, const pugiutil::loc_data& loc_data, t_wireconn_inf& wc) { |
| //Parse an inline wireconn definition, using attributes |
| expect_only_attributes(node, {"num_conns", "from_type", "to_type", "from_switchpoint", "to_switchpoint", "from_order", "to_order"}, loc_data); |
| |
| /* get the connection style */ |
| const char* char_prop = get_attribute(node, "num_conns", loc_data).value(); |
| parse_num_conns(char_prop, wc); |
| |
| /* get from type */ |
| char_prop = get_attribute(node, "from_type", loc_data).value(); |
| parse_comma_separated_wire_types(char_prop, wc.from_switchpoint_set); |
| |
| /* get to type */ |
| char_prop = get_attribute(node, "to_type", loc_data).value(); |
| parse_comma_separated_wire_types(char_prop, wc.to_switchpoint_set); |
| |
| /* get the source wire point */ |
| char_prop = get_attribute(node, "from_switchpoint", loc_data).value(); |
| parse_comma_separated_wire_points(char_prop, wc.from_switchpoint_set); |
| |
| /* get the destination wire point */ |
| char_prop = get_attribute(node, "to_switchpoint", loc_data).value(); |
| parse_comma_separated_wire_points(char_prop, wc.to_switchpoint_set); |
| |
| char_prop = get_attribute(node, "from_order", loc_data, ReqOpt::OPTIONAL).value(); |
| parse_switchpoint_order(char_prop, wc.from_switchpoint_order); |
| |
| char_prop = get_attribute(node, "to_order", loc_data, ReqOpt::OPTIONAL).value(); |
| parse_switchpoint_order(char_prop, wc.to_switchpoint_order); |
| } |
| |
| void parse_wireconn_multinode(pugi::xml_node node, const pugiutil::loc_data& loc_data, t_wireconn_inf& wc) { |
| expect_only_children(node, {"from", "to"}, loc_data); |
| |
| /* get the connection style */ |
| const char* char_prop = get_attribute(node, "num_conns", loc_data).value(); |
| parse_num_conns(char_prop, wc); |
| |
| char_prop = get_attribute(node, "from_order", loc_data, ReqOpt::OPTIONAL).value(); |
| parse_switchpoint_order(char_prop, wc.from_switchpoint_order); |
| |
| char_prop = get_attribute(node, "to_order", loc_data, ReqOpt::OPTIONAL).value(); |
| parse_switchpoint_order(char_prop, wc.to_switchpoint_order); |
| |
| size_t num_from_children = count_children(node, "from", loc_data); |
| size_t num_to_children = count_children(node, "to", loc_data); |
| |
| VTR_ASSERT(num_from_children > 0); |
| VTR_ASSERT(num_to_children > 0); |
| |
| for (pugi::xml_node child : node.children()) { |
| if (child.name() == std::string("from")) { |
| t_wire_switchpoints from_switchpoints = parse_wireconn_from_to_node(child, loc_data); |
| wc.from_switchpoint_set.push_back(from_switchpoints); |
| } else if (child.name() == std::string("to")) { |
| t_wire_switchpoints to_switchpoints = parse_wireconn_from_to_node(child, loc_data); |
| wc.to_switchpoint_set.push_back(to_switchpoints); |
| } else { |
| archfpga_throw(loc_data.filename_c_str(), loc_data.line(node), "Unrecognized child node '%s' of parent node '%s'", |
| node.name(), child.name()); |
| } |
| } |
| } |
| |
| t_wire_switchpoints parse_wireconn_from_to_node(pugi::xml_node node, const pugiutil::loc_data& loc_data) { |
| expect_only_attributes(node, {"type", "switchpoint"}, loc_data); |
| |
| size_t attribute_count = count_attributes(node, loc_data); |
| |
| if (attribute_count != 2) { |
| archfpga_throw(loc_data.filename_c_str(), loc_data.line(node), "Expected only 2 attributes on node '%s'", |
| node.name()); |
| } |
| |
| t_wire_switchpoints wire_switchpoints; |
| wire_switchpoints.segment_name = get_attribute(node, "type", loc_data).value(); |
| |
| auto points_str = get_attribute(node, "switchpoint", loc_data).value(); |
| for (const auto& point_str : vtr::split(points_str, ",")) { |
| int switchpoint = vtr::atoi(point_str); |
| wire_switchpoints.switchpoints.push_back(switchpoint); |
| } |
| |
| if (wire_switchpoints.switchpoints.empty()) { |
| archfpga_throw(loc_data.filename_c_str(), loc_data.line(node), "Empty switchpoint specification", |
| node.name()); |
| } |
| |
| return wire_switchpoints; |
| } |
| |
| static void parse_switchpoint_order(const char* order, SwitchPointOrder& switchpoint_order) { |
| if (order == std::string("")) { |
| switchpoint_order = SwitchPointOrder::SHUFFLED; //Default |
| } else if (order == std::string("fixed")) { |
| switchpoint_order = SwitchPointOrder::FIXED; |
| } else if (order == std::string("shuffled")) { |
| switchpoint_order = SwitchPointOrder::SHUFFLED; |
| } else { |
| archfpga_throw(__FILE__, __LINE__, "Unrecognized switchpoint order '%s'", order); |
| } |
| } |
| |
| /* parses the wire types specified in the comma-separated 'ch' char array into the vector wire_points_vec. |
| * Spaces are trimmed off */ |
| static void parse_comma_separated_wire_types(const char* ch, std::vector<t_wire_switchpoints>& wire_switchpoints) { |
| auto types = vtr::split(ch, ","); |
| |
| if (types.empty()) { |
| archfpga_throw(__FILE__, __LINE__, "parse_comma_separated_wire_types: found empty wireconn wire type entry\n"); |
| } |
| |
| for (const auto& type : types) { |
| t_wire_switchpoints wsp; |
| wsp.segment_name = type; |
| |
| wire_switchpoints.push_back(wsp); |
| } |
| } |
| |
| /* parses the wirepoints specified in the comma-separated 'ch' char array into the vector wire_points_vec */ |
| static void parse_comma_separated_wire_points(const char* ch, std::vector<t_wire_switchpoints>& wire_switchpoints) { |
| auto points = vtr::split(ch, ","); |
| if (points.empty()) { |
| archfpga_throw(__FILE__, __LINE__, "parse_comma_separated_wire_points: found empty wireconn wire point entry\n"); |
| } |
| |
| for (const auto& point_str : points) { |
| int point = vtr::atoi(point_str); |
| |
| for (auto& wire_switchpoint : wire_switchpoints) { |
| wire_switchpoint.switchpoints.push_back(point); |
| } |
| } |
| } |
| |
| static void parse_num_conns(std::string num_conns, t_wireconn_inf& wireconn) { |
| //num_conns is now interpretted as a formula and processed in build_switchblocks |
| wireconn.num_conns_formula = num_conns; |
| } |
| |
| /* Loads permutation funcs specified under Node into t_switchblock_inf. Node should be |
| * <switchfuncs> */ |
| void read_sb_switchfuncs(pugi::xml_node Node, t_switchblock_inf* sb, const pugiutil::loc_data& loc_data) { |
| /* Make sure the passed-in is correct */ |
| check_node(Node, "switchfuncs", loc_data); |
| |
| pugi::xml_node SubElem; |
| |
| /* get the number of specified permutation functions */ |
| int num_funcs = count_children(Node, "func", loc_data, ReqOpt::OPTIONAL); |
| |
| const char* func_type; |
| const char* func_formula; |
| std::vector<std::string>* func_ptr; |
| |
| /* used to index into permutation map of switchblock */ |
| SB_Side_Connection conn; |
| |
| /* now we iterate through all the specified permutation functions, and |
| * load them into the switchblock structure as appropriate */ |
| if (num_funcs > 0) { |
| SubElem = get_first_child(Node, "func", loc_data); |
| } |
| for (int ifunc = 0; ifunc < num_funcs; ifunc++) { |
| /* get function type */ |
| func_type = get_attribute(SubElem, "type", loc_data).as_string(nullptr); |
| |
| /* get function formula */ |
| func_formula = get_attribute(SubElem, "formula", loc_data).as_string(nullptr); |
| |
| /* go through all the possible cases of func_type */ |
| if (0 == strcmp(func_type, "lt")) { |
| conn.set_sides(LEFT, TOP); |
| } else if (0 == strcmp(func_type, "lr")) { |
| conn.set_sides(LEFT, RIGHT); |
| } else if (0 == strcmp(func_type, "lb")) { |
| conn.set_sides(LEFT, BOTTOM); |
| } else if (0 == strcmp(func_type, "tl")) { |
| conn.set_sides(TOP, LEFT); |
| } else if (0 == strcmp(func_type, "tb")) { |
| conn.set_sides(TOP, BOTTOM); |
| } else if (0 == strcmp(func_type, "tr")) { |
| conn.set_sides(TOP, RIGHT); |
| } else if (0 == strcmp(func_type, "rt")) { |
| conn.set_sides(RIGHT, TOP); |
| } else if (0 == strcmp(func_type, "rl")) { |
| conn.set_sides(RIGHT, LEFT); |
| } else if (0 == strcmp(func_type, "rb")) { |
| conn.set_sides(RIGHT, BOTTOM); |
| } else if (0 == strcmp(func_type, "bl")) { |
| conn.set_sides(BOTTOM, LEFT); |
| } else if (0 == strcmp(func_type, "bt")) { |
| conn.set_sides(BOTTOM, TOP); |
| } else if (0 == strcmp(func_type, "br")) { |
| conn.set_sides(BOTTOM, RIGHT); |
| } else { |
| /* unknown permutation function */ |
| archfpga_throw(__FILE__, __LINE__, "Unknown permutation function specified: %s\n", func_type); |
| } |
| func_ptr = &(sb->permutation_map[conn]); |
| |
| /* Here we load the specified switch function(s) */ |
| func_ptr->push_back(std::string(func_formula)); |
| |
| func_ptr = nullptr; |
| /* get the next switchblock function */ |
| SubElem = SubElem.next_sibling(SubElem.name()); |
| } |
| |
| return; |
| } |
| |
| /* checks for correctness of switch block read-in from the XML architecture file */ |
| void check_switchblock(const t_switchblock_inf* sb, const t_arch* arch) { |
| /* get directionality */ |
| enum e_directionality directionality = sb->directionality; |
| |
| /* Check for errors in the switchblock descriptions */ |
| if (UNI_DIRECTIONAL == directionality) { |
| check_unidir_switchblock(sb); |
| } else { |
| VTR_ASSERT(BI_DIRECTIONAL == directionality); |
| check_bidir_switchblock(&(sb->permutation_map)); |
| } |
| |
| /* check that specified wires exist */ |
| for (const auto& wireconn : sb->wireconns) { |
| check_wireconn(arch, wireconn); |
| } |
| |
| //TODO: |
| /* check that the wire segment directionality matches the specified switch block directionality */ |
| /* check for duplicate names */ |
| /* check that specified switches exist */ |
| /* check that type of switchblock matches type of switch specified */ |
| } |
| |
| /* checks for correctness of a unidirectional switchblock. hard exit if error found (to be changed to throw later) */ |
| static void check_unidir_switchblock(const t_switchblock_inf* sb) { |
| /* Check that the destination wire points are always the starting points (i.e. of wire point 0) */ |
| for (const t_wireconn_inf& wireconn : sb->wireconns) { |
| for (const t_wire_switchpoints& wire_to_points : wireconn.to_switchpoint_set) { |
| if (wire_to_points.switchpoints.size() > 1 || wire_to_points.switchpoints[0] != 0) { |
| archfpga_throw(__FILE__, __LINE__, "Unidirectional switch blocks are currently only allowed to drive the start points of wire segments\n"); |
| } |
| } |
| } |
| } |
| |
| /* checks for correctness of a bidirectional switchblock */ |
| static void check_bidir_switchblock(const t_permutation_map* permutation_map) { |
| /**** check that if side1->side2 is specified, then side2->side1 is not, as it is implicit ****/ |
| |
| /* variable used to index into the permutation map */ |
| SB_Side_Connection conn; |
| |
| /* iterate over all combinations of from_side -> to side */ |
| for (e_side from_side : {TOP, RIGHT, BOTTOM, LEFT}) { |
| for (e_side to_side : {TOP, RIGHT, BOTTOM, LEFT}) { |
| /* can't connect a switchblock side to itself */ |
| if (from_side == to_side) { |
| continue; |
| } |
| |
| /* index into permutation map with this variable */ |
| conn.set_sides(from_side, to_side); |
| |
| /* check if a connection between these sides exists */ |
| t_permutation_map::const_iterator it = (*permutation_map).find(conn); |
| if (it != (*permutation_map).end()) { |
| /* the two sides are connected */ |
| /* check if the opposite connection has been specified */ |
| conn.set_sides(to_side, from_side); |
| it = (*permutation_map).find(conn); |
| if (it != (*permutation_map).end()) { |
| archfpga_throw(__FILE__, __LINE__, "If a bidirectional switch block specifies a connection from side1->side2, no connection should be specified from side2->side1 as it is implicit.\n"); |
| } |
| } |
| } |
| } |
| |
| return; |
| } |
| |
| static void check_wireconn(const t_arch* arch, const t_wireconn_inf& wireconn) { |
| for (const t_wire_switchpoints& wire_switchpoints : wireconn.from_switchpoint_set) { |
| auto seg_name = wire_switchpoints.segment_name; |
| |
| //Make sure the segment exists |
| const t_segment_inf* seg_info = find_segment(arch, seg_name); |
| if (!seg_info) { |
| archfpga_throw(__FILE__, __LINE__, "Failed to find segment '%s' for <wireconn> from type specification\n", seg_name.c_str()); |
| } |
| |
| //Check that the specified switch points are valid |
| for (int switchpoint : wire_switchpoints.switchpoints) { |
| if (switchpoint < 0) { |
| archfpga_throw(__FILE__, __LINE__, "Invalid <wireconn> from_switchpoint '%d' (must be >= 0)\n", switchpoint, seg_name.c_str()); |
| } |
| if (switchpoint >= seg_info->length) { |
| archfpga_throw(__FILE__, __LINE__, "Invalid <wireconn> from_switchpoints '%d' (must be < %d)\n", switchpoint, seg_info->length); |
| } |
| //TODO: check that points correspond to valid sb locations |
| } |
| } |
| |
| for (const t_wire_switchpoints& wire_switchpoints : wireconn.to_switchpoint_set) { |
| auto seg_name = wire_switchpoints.segment_name; |
| |
| //Make sure the segment exists |
| const t_segment_inf* seg_info = find_segment(arch, seg_name); |
| if (!seg_info) { |
| archfpga_throw(__FILE__, __LINE__, "Failed to find segment '%s' for <wireconn> to type specification\n", seg_name.c_str()); |
| } |
| |
| //Check that the specified switch points are valid |
| for (int switchpoint : wire_switchpoints.switchpoints) { |
| if (switchpoint < 0) { |
| archfpga_throw(__FILE__, __LINE__, "Invalid <wireconn> to_switchpoint '%d' (must be >= 0)\n", switchpoint, seg_name.c_str()); |
| } |
| if (switchpoint >= seg_info->length) { |
| archfpga_throw(__FILE__, __LINE__, "Invalid <wireconn> to_switchpoints '%d' (must be < %d)\n", switchpoint, seg_info->length); |
| } |
| //TODO: check that points correspond to valid sb locations |
| } |
| } |
| } |
| |
| /*---- Functions for Parsing the Symbolic Switchblock Formulas ----*/ |
| |
| /* returns integer result according to the specified switchblock formula and data. formula may be piece-wise */ |
| int get_sb_formula_raw_result(const char* formula, const t_formula_data& mydata) { |
| /* the result of the formula will be an integer */ |
| int result = -1; |
| |
| /* check formula */ |
| if (nullptr == formula) { |
| archfpga_throw(__FILE__, __LINE__, "in get_sb_formula_result: SB formula pointer NULL\n"); |
| } else if ('\0' == formula[0]) { |
| archfpga_throw(__FILE__, __LINE__, "in get_sb_formula_result: SB formula empty\n"); |
| } |
| |
| /* parse based on whether formula is piece-wise or not */ |
| if (is_piecewise_formula(formula)) { |
| //EXPERIMENTAL |
| result = parse_piecewise_formula(formula, mydata); |
| } else { |
| result = parse_formula(formula, mydata); |
| } |
| |
| return result; |
| } |