OpenRAM/compiler/router/graph.py

282 lines
10 KiB
Python
Raw Normal View History

# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved.
#
2023-05-09 22:23:01 +02:00
import heapq
from copy import deepcopy
from openram import debug
from openram.base.vector import vector
from openram.base.vector3d import vector3d
2023-06-06 04:33:45 +02:00
from openram.tech import drc
from .direction import direction
2023-07-13 21:07:55 +02:00
from .graph_node import graph_node
from .graph_probe import graph_probe
2023-07-13 21:07:55 +02:00
class graph:
""" This is the graph created from the blockages. """
def __init__(self, router):
2023-05-09 22:23:01 +02:00
2023-07-13 21:07:55 +02:00
# This is the graph router that uses this graph
self.router = router
self.source_nodes = []
self.target_nodes = []
2023-07-17 05:41:58 +02:00
def is_routable(self, shape):
""" Return if a shape is routable in this graph. """
return shape.name == self.source.name
def inside_shape(self, point, shape):
""" Return if the point is inside the shape. """
# Check if they're on the same layer
if point.z != self.router.get_zindex(shape.lpp):
return False
# Check if the point is inside the shape
ll, ur = shape.rect
return shape.on_segment(ll, point, ur)
2023-05-31 05:09:10 +02:00
def is_probe_blocked(self, p1, p2):
"""
Return if a probe sent from p1 to p2 encounters a blockage.
The probe must be sent vertically or horizontally.
This function assumes that p1 and p2 are on the same layer.
"""
2023-07-13 21:07:55 +02:00
probe_shape = graph_probe(p1, p2, self.router.vert_lpp if p1.z else self.router.horiz_lpp)
2023-05-31 05:09:10 +02:00
# Check if any blockage blocks this probe
for blockage in self.graph_blockages:
# Check if two shapes overlap
# Inflated blockages of pins don't block probes
2023-07-17 05:41:58 +02:00
if blockage.overlaps(probe_shape) and (not self.is_routable(blockage) or not blockage.inflated_from.overlaps(probe_shape)):
2023-05-31 05:09:10 +02:00
return True
return False
2023-05-30 22:36:38 +02:00
def create_graph(self, source, target):
2023-07-13 21:07:55 +02:00
""" Create the graph to run routing on later. """
debug.info(2, "Creating the graph for source '{}' and target'{}'.".format(source, target))
2023-07-17 05:41:58 +02:00
# Save source and target information
self.source = source
self.target = target
# Find the region to be routed and only include objects inside that region
2023-05-30 22:36:38 +02:00
region = deepcopy(source)
region.bbox([target])
2023-07-17 05:25:15 +02:00
region = region.inflated_pin(spacing=self.router.track_space,
multiple=1)
2023-07-13 20:29:51 +02:00
debug.info(3, "Routing region is {}".format(region.rect))
# Find the blockages that are in the routing area
self.graph_blockages = []
for blockage in self.router.blockages:
# FIXME: Include pins as blockages as well to prevent DRC errors
2023-07-17 05:41:58 +02:00
if self.is_routable(blockage):
continue
# Set the region's lpp to current blockage's lpp so that the
# overlaps method works
region.lpp = blockage.lpp
if region.overlaps(blockage):
self.graph_blockages.append(blockage)
2023-07-13 20:29:51 +02:00
debug.info(3, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages)))
2023-07-13 21:07:55 +02:00
# Create the graph
x_values, y_values = self.generate_cartesian_values()
2023-07-13 21:07:55 +02:00
self.generate_graph_nodes(x_values, y_values)
2023-07-17 05:41:58 +02:00
self.save_end_nodes()
2023-07-13 20:29:51 +02:00
debug.info(3, "Number of nodes in the routing graph: {}".format(len(self.nodes)))
2023-05-30 22:36:38 +02:00
def generate_cartesian_values(self):
2023-05-30 22:36:38 +02:00
"""
Generate x and y values from all the corners of the shapes in the
routing region.
2023-05-30 22:36:38 +02:00
"""
2023-05-30 06:49:00 +02:00
x_values = set()
y_values = set()
2023-05-30 22:36:38 +02:00
# Add inner values for blockages of the routed type
x_offset = vector(self.router.offset, 0)
y_offset = vector(0, self.router.offset)
for shape in [self.source, self.target]:
aspect_ratio = shape.width() / shape.height()
# FIXME: Aspect ratio may not be the best way to determine this
# If the pin is tall or fat, add two points on the ends
if aspect_ratio <= 0.5: # Tall pin
points = [shape.bc() + y_offset, shape.uc() - y_offset]
elif aspect_ratio >= 2: # Fat pin
points = [shape.lc() + x_offset, shape.rc() - x_offset]
else: # Square-like pin
2023-06-06 04:33:45 +02:00
points = [shape.center()]
2023-05-29 21:43:43 +02:00
for p in points:
2023-05-30 06:49:00 +02:00
x_values.add(p.x)
y_values.add(p.y)
2023-05-30 22:36:38 +02:00
# Add corners for blockages
2023-06-06 04:33:45 +02:00
offset = drc["grid"]
for blockage in self.graph_blockages:
ll, ur = blockage.rect
# Add minimum offset to the blockage corner nodes to prevent overlap
2023-05-30 06:49:00 +02:00
x_values.update([ll.x - offset, ur.x + offset])
y_values.update([ll.y - offset, ur.y + offset])
# Sort x and y values
x_values = list(x_values)
y_values = list(y_values)
x_values.sort()
y_values.sort()
2023-05-30 22:36:38 +02:00
return x_values, y_values
2023-07-13 21:07:55 +02:00
def generate_graph_nodes(self, x_values, y_values):
2023-05-30 22:36:38 +02:00
"""
2023-07-13 21:07:55 +02:00
Generate all graph nodes using the cartesian values and connect the
2023-05-30 22:36:38 +02:00
orthogonal neighbors.
"""
# Generate all nodes
2023-05-30 06:49:00 +02:00
self.nodes = []
for x in x_values:
for y in y_values:
for z in [0, 1]:
self.nodes.append(graph_node([x, y, z]))
# Mark nodes that will be removed
self.mark_blocked_nodes()
# Connect closest nodes that won't be removed
def search(index, condition, shift):
""" Search and connect neighbor nodes. """
base_nodes = self.nodes[index:index+2]
found = [hasattr(base_nodes[0], "remove"),
hasattr(base_nodes[1], "remove")]
while condition(index) and not all(found):
nodes = self.nodes[index - shift:index - shift + 2]
for k in range(2):
if not found[k] and not hasattr(nodes[k], "remove"):
found[k] = True
if not self.is_probe_blocked(base_nodes[k].center, nodes[k].center):
base_nodes[k].add_neighbor(nodes[k])
index -= shift
y_len = len(y_values)
for i in range(0, len(self.nodes), 2):
search(i, lambda count: (count / 2) % y_len, 2) # Down
search(i, lambda count: (count / 2) >= y_len, y_len * 2) # Left
# FIXME: Avoid vias for inter-layer edges
if not hasattr(self.nodes[i], "remove") and \
not hasattr(self.nodes[i + 1], "remove"):
self.nodes[i].add_neighbor(self.nodes[i + 1])
# Remove marked nodes
self.remove_blocked_nodes()
2023-05-30 22:36:38 +02:00
def mark_blocked_nodes(self):
""" Mark graph nodes to be removed that are blocked by a blockage. """
2023-05-30 22:36:38 +02:00
for i in range(len(self.nodes) - 1, -1, -1):
node = self.nodes[i]
2023-05-30 06:49:00 +02:00
point = node.center
for blockage in self.graph_blockages:
2023-05-31 05:09:10 +02:00
# Remove if the node is inside a blockage
# If the blockage is an inflated routable, remove if outside
# the routable shape
2023-07-17 05:41:58 +02:00
if self.inside_shape(point, blockage) and (not self.is_routable(blockage) or not self.inside_shape(point, blockage.inflated_from)):
node.remove = True
break
2023-05-09 22:23:01 +02:00
def remove_blocked_nodes(self):
""" Remove graph nodes that are marked to be removed. """
for i in range(len(self.nodes) - 1, -1, -1):
node = self.nodes[i]
if hasattr(node, "remove"):
node.remove_all_neighbors()
self.nodes.remove(node)
2023-07-17 05:41:58 +02:00
def save_end_nodes(self):
""" Save graph nodes that are inside source and target pins. """
for node in self.nodes:
if self.inside_shape(node.center, self.source):
self.source_nodes.append(node)
elif self.inside_shape(node.center, self.target):
self.target_nodes.append(node)
def find_shortest_path(self):
2023-05-09 22:23:01 +02:00
"""
Find the shortest path from the source node to target node using the
A* algorithm.
"""
# Heuristic function to calculate the scores
def h(node):
""" Return the estimated distance to the closest target. """
min_dist = float("inf")
for t in self.target_nodes:
dist = t.center.distance(node.center) + abs(t.center.z - node.center.z)
if dist < min_dist:
min_dist = dist
return min_dist
# Initialize data structures to be used for A* search
2023-05-09 22:23:01 +02:00
queue = []
close_set = set()
came_from = {}
g_scores = {}
f_scores = {}
# Initialize score values for the source nodes
for node in self.source_nodes:
g_scores[node.id] = 0
f_scores[node.id] = h(node)
heapq.heappush(queue, (f_scores[node.id], node.id, node))
2023-05-09 22:23:01 +02:00
# Run the A* algorithm
while len(queue) > 0:
# Get the closest node from the queue
current = heapq.heappop(queue)[2]
# Skip this node if already discovered
2023-05-09 22:23:01 +02:00
if current in close_set:
continue
close_set.add(current)
# Check if we've reached the target
if current in self.target_nodes:
2023-05-09 22:23:01 +02:00
path = []
while current.id in came_from:
path.append(current)
current = came_from[current.id]
path.append(current)
return path
# Get the previous node to better calculate the next costs
prev_node = None
if current.id in came_from:
prev_node = came_from[current.id]
2023-05-09 22:23:01 +02:00
# Update neighbor scores
for node in current.neighbors:
tentative_score = current.get_edge_cost(node, prev_node) + g_scores[current.id]
2023-05-09 22:23:01 +02:00
if node.id not in g_scores or tentative_score < g_scores[node.id]:
came_from[node.id] = current
g_scores[node.id] = tentative_score
f_scores[node.id] = tentative_score + h(node)
heapq.heappush(queue, (f_scores[node.id], node.id, node))
# Return None if not connected
return None