blob: 3ff2eca62b4880fae404410f486d1092ce1da117 [file] [log] [blame]
#include <cstdio>
#include "vtr_log.h"
#include "vpr_types.h"
#include "globals.h"
#include "route_export.h"
#include "route_common.h"
#include "route_breadth_first.h"
//Print out extensive debug information about router operations
//#define ROUTER_DEBUG
/********************* Subroutines local to this module *********************/
static bool breadth_first_route_net(ClusterNetId net_id, float bend_cost);
static void breadth_first_expand_trace_segment(t_trace* start_ptr,
int remaining_connections_to_sink,
std::vector<int>& modified_rr_node_inf);
static void breadth_first_expand_neighbours(int inode, float pcost, ClusterNetId net_id, float bend_cost);
static void breadth_first_add_to_heap(const float path_cost, const float bend_cost, const int from_node, const int to_node, const int iconn);
static float evaluate_node_cost(const float prev_path_cost, const float bend_cost, const int from_node, const int to_node);
static void breadth_first_add_source_to_heap(ClusterNetId net_id);
/************************ Subroutine definitions ****************************/
bool try_breadth_first_route(const t_router_opts& router_opts) {
/* Iterated maze router ala Pathfinder Negotiated Congestion algorithm, *
* (FPGA 95 p. 111). Returns true if it can route this FPGA, false if *
* it can't. */
VTR_LOG(
"**********************************************************************\n"
"* !!! WARNING !!! *\n"
"* *\n"
"* Routing with the DEPRECATED 'Breadth-First' router, which *\n"
"* is inferrior and may be removed in a future release. *\n"
"* *\n"
"* Use the 'Timing-Driven' router instead, which requires much *\n"
"* less run-time and produces higher quality results *\n"
"* (even with it no timing information is available). *\n"
"* *\n"
"* !!! WARNING !!! *\n"
"**********************************************************************\n"
"\n");
float pres_fac;
bool success, is_routable, rip_up_local_opins;
int itry;
auto& cluster_ctx = g_vpr_ctx.clustering();
auto& route_ctx = g_vpr_ctx.mutable_routing();
/* Usually the first iteration uses a very small (or 0) pres_fac to find *
* the shortest path and get a congestion map. For fast compiles, I set *
* pres_fac high even for the first iteration. */
pres_fac = router_opts.first_iter_pres_fac;
for (itry = 1; itry <= router_opts.max_router_iterations; itry++) {
VTR_LOG("Routing Iteration %d\n", itry);
/* Reset "is_routed" and "is_fixed" flags to indicate nets not pre-routed (yet) */
for (auto net_id : cluster_ctx.clb_nlist.nets()) {
route_ctx.net_status[net_id].is_routed = false;
route_ctx.net_status[net_id].is_fixed = false;
}
for (auto net_id : cluster_ctx.clb_nlist.nets()) {
is_routable = try_breadth_first_route_net(net_id, pres_fac, router_opts);
if (!is_routable) {
return (false);
}
}
/* Make sure any CLB OPINs used up by subblocks being hooked directly *
* to them are reserved for that purpose. */
if (itry == 1)
rip_up_local_opins = false;
else
rip_up_local_opins = true;
reserve_locally_used_opins(pres_fac, router_opts.acc_fac, rip_up_local_opins);
success = feasible_routing();
if (success) {
VTR_LOG("Successfully routed after %d routing iterations.\n", itry);
return (true);
}
if (itry == 1)
pres_fac = router_opts.initial_pres_fac;
else
pres_fac *= router_opts.pres_fac_mult;
pres_fac = std::min(pres_fac, static_cast<float>(HUGE_POSITIVE_FLOAT / 1e5));
pathfinder_update_cost(pres_fac, router_opts.acc_fac);
}
VTR_LOG("Routing failed.\n");
#ifdef ROUTER_DEBUG
print_invalid_routing_info();
#endif
return (false);
}
bool try_breadth_first_route_net(ClusterNetId net_id, float pres_fac, const t_router_opts& router_opts) {
bool is_routed = false;
auto& cluster_ctx = g_vpr_ctx.clustering();
auto& route_ctx = g_vpr_ctx.mutable_routing();
if (route_ctx.net_status[net_id].is_fixed) { /* Skip pre-routed nets. */
is_routed = true;
} else if (cluster_ctx.clb_nlist.net_is_ignored(net_id)) { /* Skip ignored nets. */
is_routed = true;
} else {
pathfinder_update_path_cost(route_ctx.trace[net_id].head, -1, pres_fac);
is_routed = breadth_first_route_net(net_id, router_opts.bend_cost);
/* Impossible to route? (disconnected rr_graph) */
if (is_routed) {
route_ctx.net_status[net_id].is_routed = false;
} else {
VTR_LOG("Routing failed.\n");
}
pathfinder_update_path_cost(route_ctx.trace[net_id].head, 1, pres_fac);
}
return (is_routed);
}
static bool breadth_first_route_net(ClusterNetId net_id, float bend_cost) {
/* Uses a maze routing (Dijkstra's) algorithm to route a net. The net *
* begins at the net output, and expands outward until it hits a target *
* pin. The algorithm is then restarted with the entire first wire segment *
* included as part of the source this time. For an n-pin net, the maze *
* router is invoked n-1 times to complete all the connections. net_id is *
* the index of the net to be routed. Bends are penalized by bend_cost *
* (which is typically zero for detailed routing and nonzero only for global *
* routing), since global routes with lots of bends are tougher to detailed *
* route (using a detailed router like SEGA). *
* If this routine finds that a net *cannot* be connected (due to a complete *
* lack of potential paths, rather than congestion), it returns false, as *
* routing is impossible on this architecture. Otherwise it returns true. */
int inode, remaining_connections_to_sink;
float pcost, new_pcost;
t_heap* current;
t_trace* tptr;
auto& cluster_ctx = g_vpr_ctx.clustering();
auto& route_ctx = g_vpr_ctx.mutable_routing();
#ifdef ROUTER_DEBUG
VTR_LOG("Routing Net %zu (%zu sinks)\n", size_t(net_id), cluster_ctx.clb_nlist.net_sinks(net_id).size());
#endif
free_traceback(net_id);
breadth_first_add_source_to_heap(net_id);
mark_ends(net_id);
tptr = nullptr;
remaining_connections_to_sink = 0;
auto src_pin_id = cluster_ctx.clb_nlist.net_driver(net_id);
std::vector<int> modified_rr_node_inf; //RR node indicies with modified rr_node_route_inf
for (auto pin_id : cluster_ctx.clb_nlist.net_sinks(net_id)) { /* Need n-1 wires to connect n pins */
breadth_first_expand_trace_segment(tptr, remaining_connections_to_sink, modified_rr_node_inf);
current = get_heap_head();
if (current == nullptr) { /* Infeasible routing. No possible path for net. */
VTR_LOG("Cannot route net #%zu (%s) from (%s) to sink pin (%s) -- no possible path.\n",
size_t(net_id), cluster_ctx.clb_nlist.net_name(net_id).c_str(),
cluster_ctx.clb_nlist.pin_name(src_pin_id).c_str(),
cluster_ctx.clb_nlist.pin_name(pin_id).c_str());
reset_path_costs(modified_rr_node_inf); /* Clean up before leaving. */
return (false);
}
inode = current->index;
#ifdef ROUTER_DEBUG
VTR_LOG(" Popped node %d\n", inode);
#endif
while (route_ctx.rr_node_route_inf[inode].target_flag == 0) {
pcost = route_ctx.rr_node_route_inf[inode].path_cost;
new_pcost = current->cost;
if (pcost > new_pcost) { /* New path is lowest cost. */
#ifdef ROUTER_DEBUG
VTR_LOG(" New best cost %g\n", new_pcost);
#endif
#ifdef ROUTER_DEBUG
VTR_LOG(" Setting routing paths for associated node %d\n", current->index);
#endif
add_to_mod_list(current->index, modified_rr_node_inf);
route_ctx.rr_node_route_inf[current->index].path_cost = new_pcost;
route_ctx.rr_node_route_inf[current->index].prev_node = current->u.prev.node;
route_ctx.rr_node_route_inf[current->index].prev_edge = current->u.prev.edge;
#ifdef ROUTER_DEBUG
VTR_LOG(" Expanding node %d neighbours\n", inode);
#endif
breadth_first_expand_neighbours(inode, new_pcost, net_id, bend_cost);
}
free_heap_data(current);
current = get_heap_head();
if (current == nullptr) { /* Impossible routing. No path for net. */
VTR_LOG("Cannot route net #%zu (%s) from (%s) to sink pin (%s) -- no possible path.\n",
size_t(net_id), cluster_ctx.clb_nlist.net_name(net_id).c_str(),
cluster_ctx.clb_nlist.pin_name(src_pin_id).c_str(), cluster_ctx.clb_nlist.pin_name(pin_id).c_str());
reset_path_costs(modified_rr_node_inf);
return (false);
}
inode = current->index;
#ifdef ROUTER_DEBUG
VTR_LOG(" Popped node %d\n", inode);
#endif
}
#ifdef ROUTER_DEBUG
VTR_LOG(" Found target node %d\n", inode);
#endif
if (current != nullptr) {
//Update link to target
add_to_mod_list(current->index, modified_rr_node_inf);
route_ctx.rr_node_route_inf[current->index].path_cost = current->cost;
route_ctx.rr_node_route_inf[current->index].prev_node = current->u.prev.node;
route_ctx.rr_node_route_inf[current->index].prev_edge = current->u.prev.edge;
}
route_ctx.rr_node_route_inf[inode].target_flag--; /* Connected to this SINK. */
remaining_connections_to_sink = route_ctx.rr_node_route_inf[inode].target_flag;
tptr = update_traceback(current, net_id);
free_heap_data(current);
}
#ifdef ROUTER_DEBUG
VTR_LOG("Routed Net %zu\n", size_t(net_id));
#endif
empty_heap();
reset_path_costs(modified_rr_node_inf);
return (true);
}
static void breadth_first_expand_trace_segment(t_trace* start_ptr,
int remaining_connections_to_sink,
std::vector<int>& modified_rr_node_inf) {
/* Adds all the rr_nodes in the traceback segment starting at tptr (and *
* continuing to the end of the traceback) to the heap with a cost of zero. *
* This allows expansion to begin from the existing wiring. The *
* remaining_connections_to_sink value is 0 if the route segment ending *
* at this location is the last one to connect to the SINK ending the route *
* segment. This is the usual case. If it is not the last connection this *
* net must make to this SINK, I have a hack to ensure the next connection *
* to this SINK goes through a different IPIN. Without this hack, the *
* router would always put all the connections from this net to this SINK *
* through the same IPIN. With LUTs or cluster-based logic blocks, you *
* should never have a net connecting to two logically-equivalent pins on *
* the same logic block, so the hack will never execute. If your logic *
* block is an and-gate, however, nets might connect to two and-inputs on *
* the same logic block, and since the and-inputs are logically-equivalent, *
* this means two connections to the same SINK. */
t_trace *tptr, *next_ptr;
int inode, sink_node, last_ipin_node;
auto& device_ctx = g_vpr_ctx.device();
auto& route_ctx = g_vpr_ctx.mutable_routing();
tptr = start_ptr;
if (tptr != nullptr && device_ctx.rr_nodes[tptr->index].type() == SINK) {
/* During logical equivalence case, only use one opin */
tptr = tptr->next;
}
if (remaining_connections_to_sink == 0) { /* Usual case. */
while (tptr != nullptr) {
#ifdef ROUTER_DEBUG
VTR_LOG(" Adding previous routing node %d to heap\n", tptr->index);
#endif
node_to_heap(tptr->index, 0., NO_PREVIOUS, NO_PREVIOUS, OPEN, OPEN);
tptr = tptr->next;
}
} else { /* This case never executes for most logic blocks. */
/* Weird case. Lots of hacks. The cleanest way to do this would be to empty *
* the heap, update the congestion due to the partially-completed route, put *
* the whole route so far (excluding IPINs and SINKs) on the heap with cost *
* 0., and expand till you hit the next SINK. That would be slow, so I *
* do some hacks to enable incremental wavefront expansion instead. */
if (tptr == nullptr)
return; /* No route yet */
next_ptr = tptr->next;
last_ipin_node = OPEN; /* Stops compiler from complaining. */
/* Can't put last SINK on heap with NO_PREVIOUS, etc, since that won't let *
* us reach it again. Instead, leave the last traceback element (SINK) off *
* the heap. */
while (next_ptr != nullptr) {
inode = tptr->index;
#ifdef ROUTER_DEBUG
VTR_LOG(" Adding previous routing node %d to heap*\n", tptr->index);
#endif
node_to_heap(inode, 0., NO_PREVIOUS, NO_PREVIOUS, OPEN, OPEN);
if (device_ctx.rr_nodes[inode].type() == IPIN)
last_ipin_node = inode;
tptr = next_ptr;
next_ptr = tptr->next;
}
VTR_ASSERT(last_ipin_node >= 0);
/* This will stop the IPIN node used to get to this SINK from being *
* reexpanded for the remainder of this net's routing. This will make us *
* hook up more IPINs to this SINK (which is what we want). If IPIN *
* doglegs are allowed in the graph, we won't be able to use this IPIN to *
* do a dogleg, since it won't be re-expanded. Shouldn't be a big problem. */
add_to_mod_list(last_ipin_node, modified_rr_node_inf);
route_ctx.rr_node_route_inf[last_ipin_node].path_cost = -HUGE_POSITIVE_FLOAT;
/* Also need to mark the SINK as having high cost, so another connection can *
* be made to it. */
sink_node = tptr->index;
add_to_mod_list(sink_node, modified_rr_node_inf);
route_ctx.rr_node_route_inf[sink_node].path_cost = HUGE_POSITIVE_FLOAT;
/* Finally, I need to remove any pending connections to this SINK via the *
* IPIN I just used (since they would result in congestion). Scan through *
* the heap to do this. */
invalidate_heap_entries(sink_node, last_ipin_node);
}
}
static void breadth_first_expand_neighbours(int inode, float pcost, ClusterNetId net_id, float bend_cost) {
/* Puts all the rr_nodes adjacent to inode on the heap. rr_nodes outside *
* the expanded bounding box specified in route_bb are not added to the *
* heap. pcost is the path_cost to get to inode. */
int iconn, to_node, num_edges;
auto& device_ctx = g_vpr_ctx.device();
auto& route_ctx = g_vpr_ctx.routing();
num_edges = device_ctx.rr_nodes[inode].num_edges();
for (iconn = 0; iconn < num_edges; iconn++) {
to_node = device_ctx.rr_nodes[inode].edge_sink_node(iconn);
if (device_ctx.rr_nodes[to_node].xhigh() < route_ctx.route_bb[net_id].xmin
|| device_ctx.rr_nodes[to_node].xlow() > route_ctx.route_bb[net_id].xmax
|| device_ctx.rr_nodes[to_node].yhigh() < route_ctx.route_bb[net_id].ymin
|| device_ctx.rr_nodes[to_node].ylow() > route_ctx.route_bb[net_id].ymax)
continue; /* Node is outside (expanded) bounding box. */
breadth_first_add_to_heap(pcost, bend_cost, inode, to_node, iconn);
}
}
//Add to_node to the heap, and also add any nodes which are connected by non-configurable edges
static void breadth_first_add_to_heap(const float path_cost, const float bend_cost, const int from_node, const int to_node, const int iconn) {
#ifdef ROUTER_DEBUG
VTR_LOG(" Expanding node %d\n", to_node);
#endif
//Create a heap element to represent this node (and any non-configurably connected nodes)
t_heap* next = alloc_heap_data();
next->index = to_node;
next->backward_path_cost = OPEN;
next->R_upstream = OPEN;
next->cost = std::numeric_limits<float>::infinity();
//Path cost to 'to_node'
float new_path_cost = evaluate_node_cost(path_cost, bend_cost, from_node, to_node);
next->cost = new_path_cost;
//Record how we reached this node
next->index = to_node;
next->u.prev.edge = iconn;
next->u.prev.node = from_node;
add_to_heap(next);
}
static float evaluate_node_cost(const float prev_path_cost, const float bend_cost, const int from_node, const int to_node) {
auto& device_ctx = g_vpr_ctx.device();
float tot_cost = prev_path_cost + get_rr_cong_cost(to_node);
if (bend_cost != 0.) {
t_rr_type from_type = device_ctx.rr_nodes[from_node].type();
t_rr_type to_type = device_ctx.rr_nodes[to_node].type();
if ((from_type == CHANX && to_type == CHANY)
|| (from_type == CHANY && to_type == CHANX))
tot_cost += bend_cost;
}
return tot_cost;
}
static void breadth_first_add_source_to_heap(ClusterNetId net_id) {
/* Adds the SOURCE of this net to the heap. Used to start a net's routing. */
int inode;
float cost;
auto& route_ctx = g_vpr_ctx.routing();
inode = route_ctx.net_rr_terminals[net_id][0]; /* SOURCE */
cost = get_rr_cong_cost(inode);
#ifdef ROUTER_DEBUG
VTR_LOG(" Adding Source node %d to heap\n", inode);
#endif
node_to_heap(inode, cost, NO_PREVIOUS, NO_PREVIOUS, OPEN, OPEN);
}