blob: 2c0e7c435a10211cd92c31e8e1774ad3ee0ebf23 [file] [log] [blame]
#include "vtr_log.h"
#include "vtr_memory.h"
#include "vpr_types.h"
#include "vpr_error.h"
#include "globals.h"
#include "rr_graph.h"
#include "check_rr_graph.h"
/*********************** Subroutines local to this module *******************/
static bool rr_node_is_global_clb_ipin(int inode);
static void check_unbuffered_edges(int from_node);
static bool has_adjacent_channel(const t_rr_node& node, const DeviceGrid& grid);
static void check_rr_edge(int from_node, int from_edge, int to_node);
/************************ Subroutine definitions ****************************/
void check_rr_graph(const t_graph_type graph_type,
const DeviceGrid& grid,
const std::vector<t_physical_tile_type>& types) {
e_route_type route_type = DETAILED;
if (graph_type == GRAPH_GLOBAL) {
route_type = GLOBAL;
}
auto& device_ctx = g_vpr_ctx.device();
auto total_edges_to_node = std::vector<int>(device_ctx.rr_nodes.size());
auto switch_types_from_current_to_node = std::vector<unsigned char>(device_ctx.rr_nodes.size());
const int num_rr_switches = device_ctx.rr_switch_inf.size();
for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) {
device_ctx.rr_nodes[inode].validate();
/* Ignore any uninitialized rr_graph nodes */
if ((device_ctx.rr_nodes[inode].type() == SOURCE)
&& (device_ctx.rr_nodes[inode].xlow() == 0) && (device_ctx.rr_nodes[inode].ylow() == 0)
&& (device_ctx.rr_nodes[inode].xhigh() == 0) && (device_ctx.rr_nodes[inode].yhigh() == 0)) {
continue;
}
t_rr_type rr_type = device_ctx.rr_nodes[inode].type();
int num_edges = device_ctx.rr_nodes[inode].num_edges();
check_rr_node(inode, route_type, device_ctx);
/* Check all the connectivity (edges, etc.) information. */
std::map<int, std::vector<int>> edges_from_current_to_node;
for (int iedge = 0; iedge < num_edges; iedge++) {
int to_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge);
if (to_node < 0 || to_node >= (int)device_ctx.rr_nodes.size()) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_graph: node %d has an edge %d.\n"
"\tEdge is out of range.\n",
inode, to_node);
}
check_rr_edge(inode, iedge, to_node);
edges_from_current_to_node[to_node].push_back(iedge);
total_edges_to_node[to_node]++;
auto switch_type = device_ctx.rr_nodes[inode].edge_switch(iedge);
if (switch_type < 0 || switch_type >= num_rr_switches) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_graph: node %d has a switch type %d.\n"
"\tSwitch type is out of range.\n",
inode, switch_type);
}
} /* End for all edges of node. */
//Check that multiple edges between the same from/to nodes make sense
for (int iedge = 0; iedge < num_edges; iedge++) {
int to_node = device_ctx.rr_nodes[inode].edge_sink_node(iedge);
if (edges_from_current_to_node[to_node].size() == 1) continue; //Single edges are always OK
VTR_ASSERT_MSG(edges_from_current_to_node[to_node].size() > 1, "Expect multiple edges");
t_rr_type to_rr_type = device_ctx.rr_nodes[to_node].type();
//Only expect chan <-> chan connections to have multiple edges
if ((to_rr_type != CHANX && to_rr_type != CHANY)
|| (rr_type != CHANX && rr_type != CHANY)) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_graph: node %d (%s) connects to node %d (%s) %zu times - multi-connections only expected for CHAN->CHAN.\n",
inode, rr_node_typename[rr_type], to_node, rr_node_typename[to_rr_type], edges_from_current_to_node[to_node].size());
}
//Between two wire segments
VTR_ASSERT_MSG(to_rr_type == CHANX || to_rr_type == CHANY, "Expect channel type");
VTR_ASSERT_MSG(rr_type == CHANX || rr_type == CHANY, "Expect channel type");
//While multiple connections between the same wires can be electrically legal,
//they are redundant if they are of the same switch type.
//
//Identify any such edges with identical switches
std::map<short, int> switch_counts;
for (auto edge : edges_from_current_to_node[to_node]) {
auto edge_switch = device_ctx.rr_nodes[inode].edge_switch(edge);
switch_counts[edge_switch]++;
}
//Tell the user about any redundant edges
for (auto kv : switch_counts) {
if (kv.second <= 1) continue;
auto switch_type = device_ctx.rr_switch_inf[kv.first].type();
VPR_ERROR(VPR_ERROR_ROUTE, "in check_rr_graph: node %d has %d redundant connections to node %d of switch type %d (%s)",
inode, kv.second, to_node, kv.first, SWITCH_TYPE_STRINGS[size_t(switch_type)]);
}
}
/* Slow test could leave commented out most of the time. */
check_unbuffered_edges(inode);
//Check that all config/non-config edges are appropriately organized
for (auto edge : device_ctx.rr_nodes[inode].configurable_edges()) {
if (!device_ctx.rr_nodes[inode].edge_is_configurable(edge)) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "in check_rr_graph: node %d edge %d is non-configurable, but in configurable edges",
inode, edge);
}
}
for (auto edge : device_ctx.rr_nodes[inode].non_configurable_edges()) {
if (device_ctx.rr_nodes[inode].edge_is_configurable(edge)) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "in check_rr_graph: node %d edge %d is configurable, but in non-configurable edges",
inode, edge);
}
}
} /* End for all rr_nodes */
/* I built a list of how many edges went to everything in the code above -- *
* now I check that everything is reachable. */
bool is_fringe_warning_sent = false;
for (size_t inode = 0; inode < device_ctx.rr_nodes.size(); inode++) {
t_rr_type rr_type = device_ctx.rr_nodes[inode].type();
if (rr_type != SOURCE) {
if (total_edges_to_node[inode] < 1 && !rr_node_is_global_clb_ipin(inode)) {
/* A global CLB input pin will not have any edges, and neither will *
* a SOURCE or the start of a carry-chain. Anything else is an error.
* For simplicity, carry-chain input pin are entirely ignored in this test
*/
bool is_chain = false;
if (rr_type == IPIN) {
t_physical_tile_type_ptr type = device_ctx.grid[device_ctx.rr_nodes[inode].xlow()][device_ctx.rr_nodes[inode].ylow()].type;
for (const t_fc_specification& fc_spec : types[type->index].fc_specs) {
if (fc_spec.fc_value == 0 && fc_spec.seg_index == 0) {
is_chain = true;
}
}
}
const auto& node = device_ctx.rr_nodes[inode];
bool is_fringe = ((device_ctx.rr_nodes[inode].xlow() == 1)
|| (device_ctx.rr_nodes[inode].ylow() == 1)
|| (device_ctx.rr_nodes[inode].xhigh() == int(grid.width()) - 2)
|| (device_ctx.rr_nodes[inode].yhigh() == int(grid.height()) - 2));
bool is_wire = (device_ctx.rr_nodes[inode].type() == CHANX
|| device_ctx.rr_nodes[inode].type() == CHANY);
if (!is_chain && !is_fringe && !is_wire) {
if (node.type() == IPIN || node.type() == OPIN) {
if (has_adjacent_channel(node, device_ctx.grid)) {
auto block_type = device_ctx.grid[node.xlow()][node.ylow()].type;
VTR_LOG_ERROR("in check_rr_graph: node %d (%s) at (%d,%d) block=%s side=%s has no fanin.\n",
inode, node.type_string(), node.xlow(), node.ylow(), block_type->name, node.side_string());
}
} else {
VTR_LOG_ERROR("in check_rr_graph: node %d (%s) has no fanin.\n",
inode, device_ctx.rr_nodes[inode].type_string());
}
} else if (!is_chain && !is_fringe_warning_sent) {
VTR_LOG_WARN(
"in check_rr_graph: fringe node %d %s at (%d,%d) has no fanin.\n"
"\t This is possible on a fringe node based on low Fc_out, N, and certain lengths.\n",
inode, device_ctx.rr_nodes[inode].type_string(), device_ctx.rr_nodes[inode].xlow(), device_ctx.rr_nodes[inode].ylow());
is_fringe_warning_sent = true;
}
}
} else { /* SOURCE. No fanin for now; change if feedthroughs allowed. */
if (total_edges_to_node[inode] != 0) {
VTR_LOG_ERROR("in check_rr_graph: SOURCE node %d has a fanin of %d, expected 0.\n",
inode, total_edges_to_node[inode]);
}
}
}
}
static bool rr_node_is_global_clb_ipin(int inode) {
/* Returns true if inode refers to a global CLB input pin node. */
int ipin;
t_physical_tile_type_ptr type;
auto& device_ctx = g_vpr_ctx.device();
type = device_ctx.grid[device_ctx.rr_nodes[inode].xlow()][device_ctx.rr_nodes[inode].ylow()].type;
if (device_ctx.rr_nodes[inode].type() != IPIN)
return (false);
ipin = device_ctx.rr_nodes[inode].ptc_num();
return type->is_ignored_pin[ipin];
}
void check_rr_node(int inode, enum e_route_type route_type, const DeviceContext& device_ctx) {
/* This routine checks that the rr_node is inside the grid and has a valid
* pin number, etc.
*/
int xlow, ylow, xhigh, yhigh, ptc_num, capacity;
t_rr_type rr_type;
t_physical_tile_type_ptr type;
int nodes_per_chan, tracks_per_node, num_edges, cost_index;
float C, R;
rr_type = device_ctx.rr_nodes[inode].type();
xlow = device_ctx.rr_nodes[inode].xlow();
xhigh = device_ctx.rr_nodes[inode].xhigh();
ylow = device_ctx.rr_nodes[inode].ylow();
yhigh = device_ctx.rr_nodes[inode].yhigh();
ptc_num = device_ctx.rr_nodes[inode].ptc_num();
capacity = device_ctx.rr_nodes[inode].capacity();
cost_index = device_ctx.rr_nodes[inode].cost_index();
type = nullptr;
const auto& grid = device_ctx.grid;
if (xlow > xhigh || ylow > yhigh) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: rr endpoints are (%d,%d) and (%d,%d).\n", xlow, ylow, xhigh, yhigh);
}
if (xlow < 0 || xhigh > int(grid.width()) - 1 || ylow < 0 || yhigh > int(grid.height()) - 1) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: rr endpoints (%d,%d) and (%d,%d) are out of range.\n", xlow, ylow, xhigh, yhigh);
}
if (ptc_num < 0) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: inode %d (type %d) had a ptc_num of %d.\n", inode, rr_type, ptc_num);
}
if (cost_index < 0 || cost_index >= (int)device_ctx.rr_indexed_data.size()) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: node %d cost index (%d) is out of range.\n", inode, cost_index);
}
/* Check that the segment is within the array and such. */
type = device_ctx.grid[xlow][ylow].type;
switch (rr_type) {
case SOURCE:
case SINK:
if (type == nullptr) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: node %d (type %d) is at an illegal clb location (%d, %d).\n", inode, rr_type, xlow, ylow);
}
if (xlow != (xhigh - type->width + 1) || ylow != (yhigh - type->height + 1)) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: node %d (type %d) has endpoints (%d,%d) and (%d,%d)\n", inode, rr_type, xlow, ylow, xhigh, yhigh);
}
break;
case IPIN:
case OPIN:
if (type == nullptr) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: node %d (type %d) is at an illegal clb location (%d, %d).\n", inode, rr_type, xlow, ylow);
}
if (xlow != xhigh || ylow != yhigh) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: node %d (type %d) has endpoints (%d,%d) and (%d,%d)\n", inode, rr_type, xlow, ylow, xhigh, yhigh);
}
break;
case CHANX:
if (xlow < 1 || xhigh > int(grid.width()) - 2 || yhigh > int(grid.height()) - 2 || yhigh != ylow) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: CHANX out of range for endpoints (%d,%d) and (%d,%d)\n", xlow, ylow, xhigh, yhigh);
}
if (route_type == GLOBAL && xlow != xhigh) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: node %d spans multiple channel segments (not allowed for global routing).\n", inode);
}
break;
case CHANY:
if (xhigh > int(grid.width()) - 2 || ylow < 1 || yhigh > int(grid.height()) - 2 || xlow != xhigh) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"Error in check_rr_node: CHANY out of range for endpoints (%d,%d) and (%d,%d)\n", xlow, ylow, xhigh, yhigh);
}
if (route_type == GLOBAL && ylow != yhigh) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: node %d spans multiple channel segments (not allowed for global routing).\n", inode);
}
break;
default:
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: Unexpected segment type: %d\n", rr_type);
}
/* Check that it's capacities and such make sense. */
switch (rr_type) {
case SOURCE:
if (ptc_num >= type->num_class
|| type->class_inf[ptc_num].type != DRIVER) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: inode %d (type %d) had a ptc_num of %d.\n", inode, rr_type, ptc_num);
}
if (type->class_inf[ptc_num].num_pins != capacity) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: inode %d (type %d) had a capacity of %d.\n", inode, rr_type, capacity);
}
break;
case SINK:
if (ptc_num >= type->num_class
|| type->class_inf[ptc_num].type != RECEIVER) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: inode %d (type %d) had a ptc_num of %d.\n", inode, rr_type, ptc_num);
}
if (type->class_inf[ptc_num].num_pins != capacity) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: inode %d (type %d) has a capacity of %d.\n", inode, rr_type, capacity);
}
break;
case OPIN:
if (ptc_num >= type->num_pins
|| type->class_inf[type->pin_class[ptc_num]].type != DRIVER) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: inode %d (type %d) had a ptc_num of %d.\n", inode, rr_type, ptc_num);
}
if (capacity != 1) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: inode %d (type %d) has a capacity of %d.\n", inode, rr_type, capacity);
}
break;
case IPIN:
if (ptc_num >= type->num_pins
|| type->class_inf[type->pin_class[ptc_num]].type != RECEIVER) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: inode %d (type %d) had a ptc_num of %d.\n", inode, rr_type, ptc_num);
}
if (capacity != 1) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: inode %d (type %d) has a capacity of %d.\n", inode, rr_type, capacity);
}
break;
case CHANX:
if (route_type == DETAILED) {
nodes_per_chan = device_ctx.chan_width.max;
tracks_per_node = 1;
} else {
nodes_per_chan = 1;
tracks_per_node = device_ctx.chan_width.x_list[ylow];
}
if (ptc_num >= nodes_per_chan) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: inode %d (type %d) has a ptc_num of %d.\n", inode, rr_type, ptc_num);
}
if (capacity != tracks_per_node) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: inode %d (type %d) has a capacity of %d.\n", inode, rr_type, capacity);
}
break;
case CHANY:
if (route_type == DETAILED) {
nodes_per_chan = device_ctx.chan_width.max;
tracks_per_node = 1;
} else {
nodes_per_chan = 1;
tracks_per_node = device_ctx.chan_width.y_list[xlow];
}
if (ptc_num >= nodes_per_chan) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: inode %d (type %d) has a ptc_num of %d.\n", inode, rr_type, ptc_num);
}
if (capacity != tracks_per_node) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: inode %d (type %d) has a capacity of %d.\n", inode, rr_type, capacity);
}
break;
default:
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: Unexpected segment type: %d\n", rr_type);
}
/* Check that the number of (out) edges is reasonable. */
num_edges = device_ctx.rr_nodes[inode].num_edges();
if (rr_type != SINK && rr_type != IPIN) {
if (num_edges <= 0) {
/* Just a warning, since a very poorly routable rr-graph could have nodes with no edges. *
* If such a node was ever used in a final routing (not just in an rr_graph), other *
* error checks in check_routing will catch it. */
//Don't worry about disconnect PINs which have no adjacent channels (i.e. on the device perimeter)
bool check_for_out_edges = true;
if (rr_type == IPIN || rr_type == OPIN) {
if (!has_adjacent_channel(device_ctx.rr_nodes[inode], device_ctx.grid)) {
check_for_out_edges = false;
}
}
if (check_for_out_edges) {
std::string info = describe_rr_node(inode);
VTR_LOG_WARN("in check_rr_node: %s has no out-going edges.\n", info.c_str());
}
}
} else if (rr_type == SINK) { /* SINK -- remove this check if feedthroughs allowed */
if (num_edges != 0) {
VPR_FATAL_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: node %d is a sink, but has %d edges.\n", inode, num_edges);
}
}
/* Check that the capacitance and resistance are reasonable. */
C = device_ctx.rr_nodes[inode].C();
R = device_ctx.rr_nodes[inode].R();
if (rr_type == CHANX || rr_type == CHANY) {
if (C < 0. || R < 0.) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: node %d of type %d has R = %g and C = %g.\n", inode, rr_type, R, C);
}
} else {
if (C != 0. || R != 0.) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_rr_node: node %d of type %d has R = %g and C = %g.\n", inode, rr_type, R, C);
}
}
}
static void check_unbuffered_edges(int from_node) {
/* This routine checks that all pass transistors in the routing truly are *
* bidirectional. It may be a slow check, so don't use it all the time. */
int from_edge, to_node, to_edge, from_num_edges, to_num_edges;
t_rr_type from_rr_type, to_rr_type;
short from_switch_type;
bool trans_matched;
auto& device_ctx = g_vpr_ctx.device();
from_rr_type = device_ctx.rr_nodes[from_node].type();
if (from_rr_type != CHANX && from_rr_type != CHANY)
return;
from_num_edges = device_ctx.rr_nodes[from_node].num_edges();
for (from_edge = 0; from_edge < from_num_edges; from_edge++) {
to_node = device_ctx.rr_nodes[from_node].edge_sink_node(from_edge);
to_rr_type = device_ctx.rr_nodes[to_node].type();
if (to_rr_type != CHANX && to_rr_type != CHANY)
continue;
from_switch_type = device_ctx.rr_nodes[from_node].edge_switch(from_edge);
if (device_ctx.rr_switch_inf[from_switch_type].buffered())
continue;
/* We know that we have a pass transistor from from_node to to_node. Now *
* check that there is a corresponding edge from to_node back to *
* from_node. */
to_num_edges = device_ctx.rr_nodes[to_node].num_edges();
trans_matched = false;
for (to_edge = 0; to_edge < to_num_edges; to_edge++) {
if (device_ctx.rr_nodes[to_node].edge_sink_node(to_edge) == from_node
&& device_ctx.rr_nodes[to_node].edge_switch(to_edge) == from_switch_type) {
trans_matched = true;
break;
}
}
if (trans_matched == false) {
VPR_ERROR(VPR_ERROR_ROUTE,
"in check_unbuffered_edges:\n"
"connection from node %d to node %d uses an unbuffered switch (switch type %d '%s')\n"
"but there is no corresponding unbuffered switch edge in the other direction.\n",
from_node, to_node, from_switch_type, device_ctx.rr_switch_inf[from_switch_type].name);
}
} /* End for all from_node edges */
}
static bool has_adjacent_channel(const t_rr_node& node, const DeviceGrid& grid) {
VTR_ASSERT(node.type() == IPIN || node.type() == OPIN);
if ((node.xlow() == 0 && node.side() != RIGHT) //left device edge connects only along block's right side
|| (node.ylow() == int(grid.height() - 1) && node.side() != BOTTOM) //top device edge connects only along block's bottom side
|| (node.xlow() == int(grid.width() - 1) && node.side() != LEFT) //right deivce edge connects only along block's left side
|| (node.ylow() == 0 && node.side() != TOP) //bottom deivce edge connects only along block's top side
) {
return false;
}
return true; //All other blocks will be surrounded on all sides by channels
}
static void check_rr_edge(int from_node, int iedge, int to_node) {
auto& device_ctx = g_vpr_ctx.device();
//Check that to to_node's fan-in is correct, given the switch type
int iswitch = device_ctx.rr_nodes[from_node].edge_switch(iedge);
auto switch_type = device_ctx.rr_switch_inf[iswitch].type();
int to_fanin = device_ctx.rr_nodes[to_node].fan_in();
switch (switch_type) {
case SwitchType::BUFFER:
//Buffer switches are non-configurable, and uni-directional -- they must have only one driver
if (to_fanin != 1) {
std::string msg = "Non-configurable BUFFER type switch must have only one driver. ";
msg += vtr::string_fmt(" Actual fan-in was %d (expected 1).\n", to_fanin);
msg += " Possible cause is complex block output pins connecting to:\n";
msg += " " + describe_rr_node(to_node);
VPR_FATAL_ERROR(VPR_ERROR_ROUTE, msg.c_str());
}
case SwitchType::TRISTATE: //Fallthrough
case SwitchType::MUX: //Fallthrough
case SwitchType::PASS_GATE: //Fallthrough
case SwitchType::SHORT: //Fallthrough
break; //pass
default:
VPR_FATAL_ERROR(VPR_ERROR_ROUTE, "Invalid switch type %d", switch_type);
}
}