| #!/usr/bin/env python3 | |
| """ | |
| This file implements a simple graph router used for complex block routing | |
| graphs. | |
| """ | |
| import logging | |
| from pb_rr_graph import NodeType | |
| # ============================================================================= | |
| class Net: | |
| """ | |
| This class represents a net for the router. It holds the driver (source) | |
| node as well as all sink node ids. | |
| """ | |
| def __init__(self, name): | |
| self.name = name | |
| self.sources = set() | |
| self.sinks = set() | |
| self.is_routed = False | |
| def __str__(self): | |
| return "{}, {} sources, max_fanout={}, {}".format( | |
| self.name, len(self.sources), len(self.sinks), | |
| "routed" if self.is_routed else "unrouted" | |
| ) | |
| # ============================================================================= | |
| class Router: | |
| """ | |
| Simple graph router. | |
| Currently the routed does a greedy depth-first routing. Each time a path | |
| from a sink to a source is found it gets immediately annotated with nets. | |
| """ | |
| def __init__(self, graph): | |
| self.graph = graph | |
| self.nets = {} | |
| # Discover nets from the graph | |
| self.discover_nets() | |
| def discover_nets(self): | |
| """ | |
| Scans the graph looking for nets | |
| """ | |
| # TODO: For now look for nets only assuming that all of them are | |
| # unrouted. | |
| sources = {} | |
| sinks = {} | |
| for node in self.graph.nodes.values(): | |
| if node.type not in [NodeType.SOURCE, NodeType.SINK]: | |
| continue | |
| # No net | |
| if node.net is None: | |
| continue | |
| # Got a source | |
| if node.type == NodeType.SOURCE: | |
| if node.net not in sources: | |
| sources[node.net] = set() | |
| sources[node.net].add(node.id) | |
| # Got a sink | |
| elif node.type == NodeType.SINK: | |
| if node.net not in sinks: | |
| sinks[node.net] = set() | |
| sinks[node.net].add(node.id) | |
| # Make nets | |
| nets = set(sinks.keys()) | set(sources.keys()) | |
| for net_name in nets: | |
| net = Net(net_name) | |
| # A net may or may not have a source node(s). If there are no | |
| # sources then one will be created during routing when a route | |
| # reaches a node of the top-level CLB. | |
| if net_name in sources: | |
| net.sources = sources[net_name] | |
| # A net may or may not have at leas one sink node. If there are | |
| # no sinks then no routing will be done. | |
| if net_name in sinks: | |
| net.sinks = sinks[net_name] | |
| self.nets[net_name] = net | |
| # DEBUG | |
| logging.debug(" Nets:") | |
| keys = sorted(list(self.nets.keys())) | |
| for key in keys: | |
| logging.debug(" " + str(self.nets[key])) | |
| def route_net(self, net, debug=False): | |
| """ | |
| Routes a single net. | |
| """ | |
| top_level_sources = set() | |
| def walk_depth_first(node, curr_route=None): | |
| # FIXME: Two possible places for optimization: | |
| # - create an edge lookup list indexed by dst node ids | |
| # - do not copy the current route list for each recursion level | |
| # Track the route | |
| if not curr_route: | |
| curr_route = [] | |
| curr_route.append(node.id) | |
| # We've hit a node of the same net. Finish. | |
| if node.type in [NodeType.SOURCE, NodeType.PORT]: | |
| if node.net == net.name: | |
| return curr_route | |
| # The node is aleady occupied by a different net | |
| if node.net is not None: | |
| if node.net != net.name: | |
| return None | |
| # This node is a free source node. If it is a top-level source then | |
| # store it. | |
| if node.type == NodeType.SOURCE and node.net is None: | |
| if node.path.count(".") == 1: | |
| top_level_sources.add(node.id) | |
| # Check all incoming edges | |
| for edge in self.graph.edges: | |
| if edge.dst_id == node.id: | |
| # Recurse | |
| next_node = self.graph.nodes[edge.src_id] | |
| route = walk_depth_first(next_node, list(curr_route)) | |
| # We have a route, terminate | |
| if route: | |
| return route | |
| return None | |
| # Search for a route | |
| logging.debug(" " + net.name) | |
| # This net has no sinks. Remove annotation from the source nodes | |
| if not net.sinks: | |
| for node_id in net.sources: | |
| node = self.graph.nodes[node_id] | |
| node.net = None | |
| # Route all sinks to any of the net sources | |
| for sink in net.sinks: | |
| # Find the route | |
| node = self.graph.nodes[sink] | |
| top_level_sources = set() | |
| route = walk_depth_first(node) | |
| # No route found. Check if we have some free top-level ports that | |
| # we can use. | |
| if not route and top_level_sources: | |
| # Use the frist one | |
| top_node_id = next(iter(top_level_sources)) | |
| top_node = self.graph.nodes[top_node_id] | |
| top_node.net = net.name | |
| # Retry | |
| route = walk_depth_first(node) | |
| # No route found | |
| if not route: | |
| logging.critical(" No route found!") | |
| # DEBUG | |
| if debug: | |
| with open("unrouted.dot", "w") as fp: | |
| fp.write( | |
| self.graph.dump_dot( | |
| color_by="net", | |
| highlight_nodes=net.sources | set([sink]) | |
| ) | |
| ) | |
| # Raise an exception | |
| raise RuntimeError( | |
| "Unroutable net '{}' from {} to {}".format( | |
| net.name, | |
| [self.graph.nodes[node_id] for node_id in net.sources], | |
| self.graph.nodes[sink] | |
| ) | |
| ) | |
| # Annotate all nodes of the route | |
| for node_id in route: | |
| self.graph.nodes[node_id].net = net.name | |
| # Success | |
| net.is_routed = True | |
| def route_nets(self, nets=None, debug=False): | |
| """ | |
| Routes net with specified names or all nets if no names are given. | |
| """ | |
| # Use all if explicit list not provided | |
| if nets is None: | |
| nets = sorted(list(self.nets.keys())) | |
| # Route nets | |
| for net_name in nets: | |
| self.route_net(self.nets[net_name], debug=debug) |