2023-05-05 05:51:30 +02:00
|
|
|
# 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
|
2023-05-29 06:25:11 +02:00
|
|
|
from copy import deepcopy
|
2023-05-05 05:51:30 +02:00
|
|
|
from openram import debug
|
2023-05-29 06:25:11 +02:00
|
|
|
from openram.base.pin_layout import pin_layout
|
|
|
|
|
from openram.base.vector import vector
|
2023-05-22 22:08:21 +02:00
|
|
|
from openram.base.vector3d import vector3d
|
|
|
|
|
from .direction import direction
|
2023-05-29 18:18:55 +02:00
|
|
|
from .hanan_node import hanan_node
|
|
|
|
|
from .hanan_utils import *
|
2023-05-05 05:51:30 +02:00
|
|
|
|
|
|
|
|
|
2023-05-29 18:18:55 +02:00
|
|
|
class hanan_graph:
|
|
|
|
|
""" This is the Hanan graph created from the blockages. """
|
2023-05-05 05:51:30 +02:00
|
|
|
|
2023-05-22 22:08:21 +02:00
|
|
|
def __init__(self, router):
|
2023-05-09 22:23:01 +02:00
|
|
|
|
2023-05-29 21:43:43 +02:00
|
|
|
# This is the Hanan router that uses this graph
|
2023-05-22 22:08:21 +02:00
|
|
|
self.router = router
|
2023-05-05 05:51:30 +02:00
|
|
|
|
|
|
|
|
|
2023-05-29 06:25:11 +02:00
|
|
|
def is_on_same_layer(self, point, shape):
|
|
|
|
|
""" Return if the point is on the same layer as the shape. """
|
|
|
|
|
|
|
|
|
|
return point.z == self.router.get_zindex(shape.lpp)
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
This function assumes that blockages are rectangular.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Check if any blockage blocks this probe
|
|
|
|
|
for blockage in self.graph_blockages:
|
|
|
|
|
if not self.is_on_same_layer(p1, blockage):
|
|
|
|
|
continue
|
|
|
|
|
ll, ur = blockage.rect
|
|
|
|
|
right_x = ur[0]
|
|
|
|
|
upper_y = ur[1]
|
|
|
|
|
left_x = ll[0]
|
|
|
|
|
lower_y = ll[1]
|
|
|
|
|
# Check if blocked vertically
|
|
|
|
|
if is_between(left_x, right_x, p1.x) and (is_between(p1.y, p2.y, upper_y) or is_between(p1.y, p2.y, lower_y)):
|
|
|
|
|
return True
|
|
|
|
|
# Check if blocked horizontally
|
|
|
|
|
if is_between(upper_y, lower_y, p1.y) and (is_between(p1.x, p2.x, left_x) or is_between(p1.x, p2.x, right_x)):
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
2023-05-30 22:36:38 +02:00
|
|
|
def create_graph(self, source, target):
|
2023-05-29 06:25:11 +02:00
|
|
|
""" Create the Hanan graph to run routing on later. """
|
2023-05-30 22:36:38 +02:00
|
|
|
debug.info(0, "Creating the Hanan graph for source '{0}' and target'{1}'.".format(source, target))
|
2023-05-05 05:51:30 +02:00
|
|
|
|
|
|
|
|
# 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([source, target])
|
2023-05-29 06:25:11 +02:00
|
|
|
debug.info(0, "Routing region is {}".format(region.rect))
|
2023-05-05 05:51:30 +02:00
|
|
|
|
2023-05-22 22:08:21 +02:00
|
|
|
# Find the blockages that are in the routing area
|
|
|
|
|
self.graph_blockages = []
|
|
|
|
|
for blockage in self.router.blockages:
|
2023-05-29 06:25:11 +02:00
|
|
|
if region.overlaps(blockage):
|
2023-05-22 22:08:21 +02:00
|
|
|
self.graph_blockages.append(blockage)
|
|
|
|
|
debug.info(0, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages)))
|
|
|
|
|
|
2023-05-30 22:36:38 +02:00
|
|
|
# Create the Hanan graph
|
|
|
|
|
x_values, y_values = self.generate_cartesian_values(source, target)
|
|
|
|
|
self.generate_hanan_nodes(x_values, y_values)
|
|
|
|
|
self.remove_blocked_nodes()
|
|
|
|
|
debug.info(0, "Number of nodes in the routing graph: {}".format(len(self.nodes)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_cartesian_values(self, source, target):
|
|
|
|
|
"""
|
|
|
|
|
Generate x and y values from all the corners of the shapes in this
|
|
|
|
|
region.
|
|
|
|
|
"""
|
|
|
|
|
|
2023-05-29 06:25:11 +02:00
|
|
|
# Obtain the x and y values for Hanan grid
|
2023-05-30 06:49:00 +02:00
|
|
|
x_values = set()
|
|
|
|
|
y_values = set()
|
2023-05-22 22:08:21 +02:00
|
|
|
offset = max(self.router.horiz_track_width, self.router.vert_track_width) / 2
|
2023-05-30 22:36:38 +02:00
|
|
|
|
2023-05-29 06:25:11 +02:00
|
|
|
# Add the source and target pins first
|
2023-05-30 22:36:38 +02:00
|
|
|
for shape in [source, target]:
|
2023-05-29 06:25:11 +02:00
|
|
|
aspect_ratio = shape.width() / shape.height()
|
|
|
|
|
# If the pin is tall or fat, add two points on the ends
|
|
|
|
|
if aspect_ratio <= 0.5: # Tall pin
|
|
|
|
|
uc = shape.uc()
|
|
|
|
|
bc = shape.bc()
|
|
|
|
|
points = [vector(uc.x, uc.y - offset),
|
|
|
|
|
vector(bc.x, bc.y + offset)]
|
|
|
|
|
elif aspect_ratio >= 2: # Fat pin
|
|
|
|
|
lc = shape.lc()
|
|
|
|
|
rc = shape.rc()
|
|
|
|
|
points = [vector(lc.x + offset, lc.y),
|
|
|
|
|
vector(rc.x - offset, rc.y)]
|
|
|
|
|
else: # Square-like pin
|
|
|
|
|
center = shape.center()
|
2023-05-30 06:49:00 +02:00
|
|
|
x_values.add(center.x)
|
|
|
|
|
y_values.add(center.y)
|
2023-05-29 21:43:43 +02:00
|
|
|
continue
|
|
|
|
|
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
|
|
|
|
2023-05-29 06:25:11 +02:00
|
|
|
# Add corners for blockages
|
2023-05-22 22:08:21 +02:00
|
|
|
for blockage in self.graph_blockages:
|
|
|
|
|
ll, ur = blockage.rect
|
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-22 22:08:21 +02:00
|
|
|
|
2023-05-30 22:36:38 +02:00
|
|
|
return x_values, y_values
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_hanan_nodes(self, x_values, y_values):
|
|
|
|
|
"""
|
|
|
|
|
Generate all Hanan nodes using the cartesian values and connect the
|
|
|
|
|
orthogonal neighbors.
|
|
|
|
|
"""
|
|
|
|
|
|
2023-05-22 22:08:21 +02:00
|
|
|
# Generate Hanan points here (cartesian product of all x and y values)
|
2023-05-30 06:49:00 +02:00
|
|
|
y_len = len(y_values)
|
|
|
|
|
self.nodes = []
|
2023-05-22 22:08:21 +02:00
|
|
|
for x in x_values:
|
|
|
|
|
for y in y_values:
|
2023-05-30 06:49:00 +02:00
|
|
|
below_node = hanan_node([x, y, 0])
|
|
|
|
|
above_node = hanan_node([x, y, 1])
|
2023-05-31 05:09:10 +02:00
|
|
|
|
2023-05-30 06:49:00 +02:00
|
|
|
# Connect these two neighbors
|
|
|
|
|
below_node.add_neighbor(above_node)
|
2023-05-31 05:09:10 +02:00
|
|
|
|
|
|
|
|
# Find potential neighbor nodes
|
|
|
|
|
belows = []
|
|
|
|
|
aboves = []
|
2023-05-30 06:49:00 +02:00
|
|
|
count = len(self.nodes) // 2
|
|
|
|
|
if count % y_len: # Down
|
2023-05-31 05:09:10 +02:00
|
|
|
belows.append(-2)
|
|
|
|
|
aboves.append(-1)
|
2023-05-30 06:49:00 +02:00
|
|
|
if count >= y_len: # Left
|
2023-05-31 05:09:10 +02:00
|
|
|
belows.append(-(y_len * 2))
|
|
|
|
|
aboves.append(-(y_len * 2) + 1)
|
|
|
|
|
|
|
|
|
|
# Add these connections if not blocked by a blockage
|
|
|
|
|
for i in belows:
|
|
|
|
|
node = self.nodes[i]
|
|
|
|
|
if not self.is_probe_blocked(below_node.center, node.center):
|
|
|
|
|
below_node.add_neighbor(node)
|
|
|
|
|
for i in aboves:
|
|
|
|
|
node = self.nodes[i]
|
|
|
|
|
if not self.is_probe_blocked(above_node.center, node.center):
|
|
|
|
|
above_node.add_neighbor(node)
|
|
|
|
|
|
2023-05-30 06:49:00 +02:00
|
|
|
self.nodes.append(below_node)
|
|
|
|
|
self.nodes.append(above_node)
|
2023-05-22 22:08:21 +02:00
|
|
|
|
2023-05-30 22:36:38 +02:00
|
|
|
|
|
|
|
|
def remove_blocked_nodes(self):
|
|
|
|
|
""" Remove the Hanan nodes that are blocked by a blockage. """
|
|
|
|
|
|
2023-05-22 22:08:21 +02:00
|
|
|
# Remove blocked points
|
2023-05-30 20:10:34 +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
|
2023-05-22 22:08:21 +02:00
|
|
|
for blockage in self.graph_blockages:
|
2023-05-31 05:09:10 +02:00
|
|
|
# Remove if the node is inside a blockage
|
2023-05-29 06:25:11 +02:00
|
|
|
if self.is_on_same_layer(point, blockage) and is_in_region(point, blockage):
|
2023-05-30 06:49:00 +02:00
|
|
|
node.remove_all_neighbors()
|
|
|
|
|
self.nodes.remove(node)
|
2023-05-22 22:08:21 +02:00
|
|
|
break
|
2023-05-09 22:23:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_shortest_path(self, source, target):
|
|
|
|
|
"""
|
|
|
|
|
Find the shortest path from the source node to target node using the
|
|
|
|
|
A* algorithm.
|
|
|
|
|
"""
|
|
|
|
|
|
2023-05-22 22:08:21 +02:00
|
|
|
# Find source and target nodes
|
2023-05-29 06:25:11 +02:00
|
|
|
sources = []
|
2023-05-30 06:49:00 +02:00
|
|
|
targets = []
|
2023-05-22 22:08:21 +02:00
|
|
|
for node in self.nodes:
|
2023-05-29 06:25:11 +02:00
|
|
|
if self.is_on_same_layer(node.center, source) and is_in_region(node.center, source):
|
|
|
|
|
sources.append(node)
|
2023-05-30 06:49:00 +02:00
|
|
|
elif self.is_on_same_layer(node.center, target) and is_in_region(node.center, target):
|
2023-05-29 06:25:11 +02:00
|
|
|
targets.append(node)
|
2023-05-09 22:23:01 +02:00
|
|
|
|
|
|
|
|
# Heuristic function to calculate the scores
|
2023-05-29 06:25:11 +02:00
|
|
|
def h(node):
|
|
|
|
|
""" Return the estimated distance to closest target. """
|
|
|
|
|
min_dist = float("inf")
|
|
|
|
|
for t in targets:
|
|
|
|
|
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 = {}
|
|
|
|
|
|
2023-05-29 06:25:11 +02:00
|
|
|
# Initialize score values for the source nodes
|
|
|
|
|
for node in sources:
|
|
|
|
|
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]
|
|
|
|
|
|
|
|
|
|
# Return if already discovered
|
|
|
|
|
if current in close_set:
|
|
|
|
|
continue
|
|
|
|
|
close_set.add(current)
|
|
|
|
|
|
|
|
|
|
# Check if we've reached the target
|
2023-05-29 06:25:11 +02:00
|
|
|
if current in targets:
|
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
|
|
|
|
|
|
|
|
|
|
# Update neighbor scores
|
|
|
|
|
for node in current.neighbors:
|
2023-05-23 03:16:49 +02:00
|
|
|
tentative_score = current.get_edge_cost(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
|