471 lines
19 KiB
C++
471 lines
19 KiB
C++
/*
|
|
* 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;
|
|
}
|