blob: 40302e2c7375caa6f9bcebde5e352f06ffa8bf0d [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2022 F4PGA Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
"""
This file implements a simple graph router used for complex block routing
graphs.
"""
import logging
from f4pga.utils.quicklogic.repacker.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)