| #!/usr/bin/env python3 |
| """ |
| This utility script allows to walk through the routing graph from a given |
| starting node id to a given target node id. If the target node id is not |
| given then it lists all available routes which start at the starting node. |
| """ |
| |
| import sys |
| import argparse |
| import gc |
| from collections import namedtuple |
| |
| import lxml.etree as ET |
| |
| from progressbar import progressbar |
| |
| # ============================================================================= |
| |
| |
| class RoutingGraph(object): |
| """ |
| A class which represent the routing graph and allows traversing it. |
| """ |
| |
| Node = namedtuple("Node", "id type xlow ylow xhigh yhigh") |
| WalkContext = namedtuple("WalkContext", "node_id depth") |
| |
| def __init__(self, xml_file): |
| """ |
| Constructs the graph given a VPR routing graph file |
| |
| Args: |
| xml_file: Name of the XML file with the graph. |
| """ |
| |
| self.nodes = {} |
| |
| self.edges_from = {} |
| self.edges_to = {} |
| |
| # Load and parse the routing graph |
| self._load_and_parse_rr_graph(xml_file) |
| |
| def _load_and_parse_rr_graph(self, xml_file): |
| """ |
| Loads the routing graph, extracts nodes and edges from the XML. |
| |
| Args: |
| xml_file: Name of the XML file with the graph. |
| """ |
| |
| def append_edge(edge_dict, key_node, target_node): |
| if key_node not in edge_dict.keys(): |
| edge_dict[key_node] = [target_node] |
| else: |
| edge_dict[key_node].append(target_node) |
| |
| print("Loading and parsing XML file...") |
| |
| # Load and initialize parser |
| parser = ET.iterparse(xml_file, events=("start", "end")) |
| parser = iter(parser) |
| |
| # Parse |
| expect_nodes = False |
| expect_edges = False |
| |
| for event, element in progressbar(parser): |
| |
| # Start |
| if event == "start": |
| |
| # Begin "rr_nodes" |
| if element.tag == "rr_nodes": |
| expect_nodes = True |
| # Begin "rr_edges" |
| if element.tag == "rr_edges": |
| expect_edges = True |
| |
| # End |
| elif event == "end": |
| |
| # End "rr_nodes" |
| if element.tag == "rr_nodes": |
| expect_nodes = False |
| # End "rr_edges" |
| if element.tag == "rr_edges": |
| expect_edges = False |
| |
| # Got a complete node element |
| if expect_nodes and element.tag == "node": |
| |
| # Find the "loc" tag |
| xml_loc = element.find("loc") |
| |
| # Append the node |
| node = RoutingGraph.Node( |
| id=int(element.get("id")), |
| type=element.get("type"), |
| xlow=int(xml_loc.get("xlow")), |
| ylow=int(xml_loc.get("ylow")), |
| xhigh=int(xml_loc.get("xhigh")), |
| yhigh=int(xml_loc.get("yhigh")) |
| ) |
| self.nodes[node.id] = node |
| |
| # Got a complete edge element |
| if expect_edges and element.tag == "edge": |
| src_node = int(element.get("src_node")) |
| dst_node = int(element.get("sink_node")) |
| |
| # Append edge |
| append_edge(self.edges_from, src_node, dst_node) |
| append_edge(self.edges_to, dst_node, src_node) |
| |
| # Clear the element |
| if element.tag != "loc": |
| element.clear() |
| |
| # Clean up |
| del parser |
| gc.collect() |
| |
| print( |
| "{} nodes, {} edges".format(len(self.nodes), len(self.edges_from)) |
| ) |
| |
| def node_to_string(self, node_id): |
| """ |
| Converts node information to string |
| |
| Args: |
| node_id: Numerical identifier of a graph node |
| |
| Returns: |
| String with a pretty node description |
| """ |
| |
| node = self.nodes[node_id] |
| |
| return "%s:%d [%d,%d,%d,%d]" % ( |
| node.type, node.id, node.xlow, node.ylow, node.xhigh, node.yhigh |
| ) |
| |
| def verify_route(self, route, walk_direction): |
| """ |
| Checks wheter a given route is valid |
| |
| Args: |
| route: Route as a sequence of node ids |
| walk_direction: Direction of the route. When > 0 its along graph |
| edges direction, when < 0 its the opposite direction. |
| |
| Returns: |
| True or False |
| """ |
| |
| visited_nodes = set() |
| |
| # Walk through the route |
| for i in range(len(route) - 1): |
| |
| # Get nodes |
| src_id = route[i] |
| dst_id = route[i + 1] |
| |
| # Find edge from src to dst |
| edge_valid = False |
| |
| if walk_direction > 0: |
| if src_id in self.edges_from: |
| for dst in self.edges_from[src_id]: |
| if dst == dst_id: |
| edge_valid = True |
| break |
| |
| if walk_direction < 0: |
| if src_id in self.edges_to: |
| for dst in self.edges_to[src_id]: |
| if dst == dst_id: |
| edge_valid = True |
| break |
| |
| # Edge not valid |
| if not edge_valid: |
| return False |
| |
| # We have already been there |
| if src_id in visited_nodes: |
| return False |
| |
| visited_nodes.add(src_id) |
| |
| # Check the last node |
| if route[-1] in visited_nodes: |
| return False |
| |
| return True |
| |
| def walk(self, start_node_id, route_callback, walk_direction): |
| """ |
| Walk the routing graph from a given starting node id. |
| |
| Args: |
| start_node_id: Identifier of the starting node. It should be |
| a SOURCE/OPIN or SINK/IPIN. |
| walk_direction: Direction of the route. When > 0 its SOURCE to |
| SINK, when < 0 it is SINK to SOURCE. |
| route_callback: A callback function to be called on every |
| route found starting from the start node id and ending on |
| a graph leaf. If the function returns False then the walk |
| stops, if returns true then the walk continues. |
| """ |
| |
| # Copy all nodes. A node is removed from this set once visited. |
| walk_nodes = set([node.id for node in self.nodes.values()]) |
| |
| # Add the starting node id |
| stack = list() |
| stack.append(RoutingGraph.WalkContext(start_node_id, 0)) |
| |
| # Walk the graph |
| route = [] |
| while len(stack) > 0: |
| |
| # Get current context |
| context = stack.pop() |
| |
| # Roll back current route |
| if context.depth < len(route): |
| route = route[:context.depth] |
| |
| # Add current node to the route |
| route.append(context.node_id) |
| |
| # Add all nodes that can be reached from this one through edges |
| is_leaf = True |
| |
| # Walking forward |
| if walk_direction > 0: |
| if context.node_id in self.edges_from.keys(): |
| for dst in self.edges_from[context.node_id]: |
| # We haven't visited the target |
| if dst in walk_nodes: |
| stack.append( |
| RoutingGraph.WalkContext( |
| dst, context.depth + 1 |
| ) |
| ) |
| walk_nodes.remove(dst) |
| is_leaf = False |
| |
| # Walking backward |
| if walk_direction < 0: |
| if context.node_id in self.edges_to.keys(): |
| for src in self.edges_to[context.node_id]: |
| # We haven't visited the target |
| if src in walk_nodes: |
| stack.append( |
| RoutingGraph.WalkContext( |
| src, context.depth + 1 |
| ) |
| ) |
| walk_nodes.remove(src) |
| is_leaf = False |
| |
| # We are in a leaf node |
| if is_leaf: |
| |
| # Check the route |
| if not self.verify_route(route, walk_direction): |
| raise RuntimeError("The route was invalid!") |
| |
| # Invoke the callback |
| res = route_callback(self, route) |
| |
| # Stop walk |
| if not res: |
| return |
| |
| |
| # ============================================================================= |
| |
| |
| def print_route(graph, route): |
| """ |
| Print given route with detailed node informations |
| |
| Args: |
| graph: A RoutingGraph object |
| route: A route as a sequence of node ids |
| """ |
| |
| for id in route: |
| sys.stdout.write("%s, " % graph.node_to_string(id)) |
| sys.stdout.write("\n") |
| |
| |
| def save_route(route, fp): |
| """ |
| Save the route to a file (only node indices) |
| |
| Args: |
| route: A route as a sequence of node ids |
| fp: Open file object |
| """ |
| |
| for id in route: |
| fp.write("%d," % id) |
| |
| fp.write("\n") |
| fp.flush() |
| |
| |
| # ============================================================================= |
| |
| |
| def main(): |
| """ |
| The main. |
| |
| Returns: |
| None |
| """ |
| |
| # Parse arguments |
| parser = argparse.ArgumentParser( |
| description=__doc__, |
| formatter_class=argparse.RawDescriptionHelpFormatter |
| ) |
| |
| parser.add_argument( |
| "--rr_graph", type=str, required=True, help="Routing graph XML file" |
| ) |
| parser.add_argument( |
| "-s", |
| "--start_inode", |
| type=int, |
| required=True, |
| help="Start node id (required)" |
| ) |
| parser.add_argument( |
| "-e", |
| "--end_inode", |
| type=int, |
| default=-1, |
| help="End node id. If not specified then all reachable" |
| "leaf nodes will be reported." |
| ) |
| parser.add_argument( |
| "--route", |
| type=str, |
| default="route.txt", |
| help="Output route file. Will contain comma separated" |
| " indices of visited nodes. One line per route." |
| ) |
| parser.add_argument( |
| "--print", |
| action="store_true", |
| help="Print routes to stdout as they are discovered" |
| ) |
| parser.add_argument( |
| "--chan_too", |
| action="store_true", |
| help="When specified routes which end on CHANX/CHANY " |
| "are saved to the route file too." |
| ) |
| |
| if len(sys.argv) <= 1: |
| parser.print_help() |
| exit(-1) |
| |
| args = parser.parse_args() |
| |
| # Load the routing graph |
| rr_graph = RoutingGraph(args.rr_graph) |
| |
| # Open the route file |
| route_file = open(args.route, "w") |
| target_reached = False |
| |
| # The route callback |
| def route_callback(graph, route): |
| nonlocal target_reached |
| |
| # Get endpoint |
| endpoint = graph.nodes[route[-1]] |
| |
| # Check if we hit CHANX/CHANY if we do not want to output them then |
| # skip those routes. |
| if not args.chan_too: |
| if endpoint.type == "CHANX" or endpoint.type == "CHANY": |
| return True |
| |
| # If we are looking for a particular target node then do not save/print |
| # other routes. |
| if args.end_inode >= 0 and endpoint.id != args.end_inode: |
| return True |
| |
| # Save the route |
| save_route(route, route_file) |
| |
| # Print the route |
| if args.print: |
| print_route(graph, route) |
| |
| # Hit anything |
| if args.end_inode < 0: |
| target_reached = True |
| |
| # Hit target, stop |
| if args.end_inode == endpoint.id: |
| target_reached = True |
| return False |
| |
| return True |
| |
| # Determine walk direction |
| node = rr_graph.nodes[args.start_inode] |
| |
| if node.type == "SOURCE" or node.type == "OPIN": |
| walk_direction = +1 |
| print("Walking forward from %d" % args.start_inode, end="") |
| |
| if node.type == "SINK" or node.type == "IPIN": |
| walk_direction = -1 |
| print("Walking backward from %d" % args.start_inode, end="") |
| |
| if args.end_inode >= 0: |
| print(" to %d..." % args.end_inode) |
| else: |
| print("...") |
| |
| # Start the walk |
| rr_graph.walk(args.start_inode, route_callback, walk_direction) |
| |
| # Check if we have reached the target |
| if not target_reached: |
| print("No route to the target node!") |
| exit(-1) |
| |
| |
| # ============================================================================= |
| |
| if __name__ == "__main__": |
| main() |