Use Hanan points to generate the routing graph

This commit is contained in:
Eren Dogan 2023-05-22 13:08:21 -07:00
parent cd339ebbd0
commit 648a631a28
5 changed files with 132 additions and 199 deletions

View File

@ -1,37 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved.
#
from .navigation_node import navigation_node
class navigation_blockage:
""" This class represents a blockage on the navigation graph. """
def __init__(self, ll, ur, layer=0):
self.ll = ll
self.ur = ur
self.layer = layer
@property
def rect(self):
""" """
return self.ll, self.ur
def create_corner_nodes(self, offset):
""" Create nodes on all 4 corners of this blockage. """
self.corners = []
self.corners.append(navigation_node([self.ll[0], self.ll[1], self.layer], offset, -1, -1))
self.corners.append(navigation_node([self.ll[0], self.ur[1], self.layer], offset, -1, 1))
self.corners.append(navigation_node([self.ur[0], self.ll[1], self.layer], offset, 1, -1))
self.corners.append(navigation_node([self.ur[0], self.ur[1], self.layer], offset, 1, 1))
self.corners[0].add_neighbor(self.corners[1])
self.corners[0].add_neighbor(self.corners[2])
self.corners[3].add_neighbor(self.corners[1])
self.corners[3].add_neighbor(self.corners[2])

View File

@ -5,41 +5,21 @@
# #
import heapq import heapq
from openram import debug from openram import debug
from openram.base.vector3d import vector3d
from .direction import direction
from .navigation_node import navigation_node from .navigation_node import navigation_node
from .navigation_blockage import navigation_blockage from .navigation_utils import *
class navigation_graph: class navigation_graph:
""" This is the navigation graph created from the blockages. """ """ This is the navigation graph created from the blockages. """
def __init__(self, track_width): def __init__(self, router):
self.track_width = track_width self.router = router
def is_probe_blocked(self, p1, p2): def create_graph(self, layout_source, layout_target):
"""
Return if a probe sent from p1 to p2 encounters a blockage.
The probe must be sent vertically or horizontally.
This method assumes that blockages are rectangular.
"""
# Check if any blockage blocks this probe
for blockage in self.nav_blockages:
right_x = blockage.ur[0]
upper_y = blockage.ur[1]
left_x = blockage.ll[0]
lower_y = blockage.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
def create_graph(self, layout_source, layout_target, layout_blockages):
""" """ """ """
debug.info(0, "Creating the navigation graph for source '{0}' and target'{1}'.".format(layout_source, layout_target)) debug.info(0, "Creating the navigation graph for source '{0}' and target'{1}'.".format(layout_source, layout_target))
@ -49,71 +29,78 @@ class navigation_graph:
region = (s_ll.min(t_ll), s_ur.min(t_ur)) region = (s_ll.min(t_ll), s_ur.min(t_ur))
debug.info(0, "Routing region is ll: '{0}' ur: '{1}'".format(region[0], region[1])) debug.info(0, "Routing region is ll: '{0}' ur: '{1}'".format(region[0], region[1]))
# Instantiate "navigation blockage" objects from layout blockages # Find the blockages that are in the routing area
self.nav_blockages = [] self.graph_blockages = []
for layout_blockage in layout_blockages: for blockage in self.router.blockages:
ll, ur = layout_blockage.rect ll, ur = blockage.rect
if (is_between(region[0].x, region[1].x, ll.x) and is_between(region[0].y, region[1].y, ll.y)) or \ if is_in_region(ll, region) or is_in_region(ur, region):
(is_between(region[0].x, region[1].x, ur.x) and is_between(region[0].y, region[1].y, ur.y)): self.graph_blockages.append(blockage)
self.nav_blockages.append(navigation_blockage(ll, ur)) debug.info(0, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages)))
# Obtain the x and y points for Hanan grid
x_values = []
y_values = []
offset = max(self.router.horiz_track_width, self.router.vert_track_width) / 2
for shape in [layout_source, layout_target]:
center = shape.center()
x_values.append(center.x)
y_values.append(center.y)
for blockage in self.graph_blockages:
ll, ur = blockage.rect
x_values.extend([ll.x - offset, ur.x + offset])
y_values.extend([ll.y - offset, ur.y + offset])
# Generate Hanan points here (cartesian product of all x and y values)
hanan_points = []
for x in x_values:
for y in y_values:
hanan_points.append(vector3d(x, y, 0))
hanan_points.append(vector3d(x, y, 1))
# Remove blocked points
for point in hanan_points.copy():
for blockage in self.graph_blockages:
ll, ur = blockage.rect
if self.router.get_zindex(blockage.lpp) == point.z and is_in_region(point, blockage.rect):
hanan_points.remove(point)
break
# Create graph nodes from Hanan points
self.nodes = [] self.nodes = []
for point in hanan_points:
self.nodes.append(navigation_node(point))
# Add source and target for this graph # Connect closest points avoiding blockages
self.nodes.append(navigation_node(layout_source.center())) for i in range(len(self.nodes)):
self.nodes.append(navigation_node(layout_target.center())) node = self.nodes[i]
for d in direction.cardinal_offsets():
# Create the corner nodes min_dist = float("inf")
for blockage in self.nav_blockages: min_neighbor = None
blockage.create_corner_nodes(self.track_width / 2) for j in range(i + 1, len(self.nodes)):
neighbor = self.nodes[j]
# These nodes will be connected to create the final graph if node.center.z != neighbor.center.z:
connect_objs = []
connect_objs.extend(self.nodes)
connect_objs.extend(self.nav_blockages)
# Create intersection nodes
# NOTE: Intersection nodes are used to connect boundaries of blockages
# perpendicularly.
debug.info(0, "Number of objects: {}".format(len(connect_objs)))
for i in range(len(connect_objs)):
obj1 = connect_objs[i]
for j in range(i + 1, len(connect_objs)):
obj2 = connect_objs[j]
node1, node2 = get_closest_nodes(obj1, obj2)
# Try two different corners
for k in [0, 1]:
# Create a node at the perpendicular corner of these two nodes
x_node = node1 if k else node2
y_node = node2 if k else node1
corner = navigation_node([x_node.center[0], y_node.center[1], 0])
# Skip this corner if the perpendicular connection is blocked
if self.is_probe_blocked(corner.center, node1.center) or self.is_probe_blocked(corner.center, node2.center):
continue continue
# Check if this new node stands on an existing connection distance_vector = neighbor.center - node.center
self.remove_intersected_neighbors(node1, corner, k) distance = node.center.distance(neighbor.center)
self.remove_intersected_neighbors(node2, corner, not(k)) if (distance_vector.x or (distance_vector.y * d.y <= 0)) and \
# Add this new node to the graph (distance_vector.y or (distance_vector.x * d.x <= 0)):
corner.add_neighbor(node1) continue
corner.add_neighbor(node2) if distance < min_dist:
self.nodes.append(corner) min_dist = distance
min_neighbor = neighbor
if min_neighbor:
node.add_neighbor(min_neighbor)
# Add corner nodes from blockages after intersections # Connect nodes that are on top of each other
for blockage in self.nav_blockages: for i in range(len(self.nodes)):
self.nodes.extend(blockage.corners) node = self.nodes[i]
debug.info(0, "Number of nodes after corners: {}".format(len(self.nodes))) for j in range(i + 1, len(self.nodes)):
neighbor = self.nodes[j]
if node.center.x == neighbor.center.x and \
def remove_intersected_neighbors(self, node, corner, axis): node.center.y == neighbor.center.y and \
""" """ node.center.z != neighbor.center.z:
node.add_neighbor(neighbor)
a = node.center debug.info(0, "Number of nodes in the routing graph: {}".format(len(self.nodes)))
mid = corner.center
for neighbor in node.neighbors:
b = neighbor.center
if a[not(axis)] == b[not(axis)] and is_between(a[axis], b[axis], mid[axis]):
neighbor.remove_neighbor(node)
neighbor.add_neighbor(corner)
def find_shortest_path(self, source, target): def find_shortest_path(self, source, target):
@ -122,11 +109,19 @@ class navigation_graph:
A* algorithm. A* algorithm.
""" """
source = self.nodes[0] # Find source and target nodes
target = self.nodes[1] source_center = source.center()
source_center = vector3d(source_center.x, source_center.y, self.router.get_zindex(source.lpp))
target_center = target.center()
target_center = vector3d(target_center.x, target_center.y, self.router.get_zindex(target.lpp))
for node in self.nodes:
if node.center == source_center:
source = node
if node.center == target_center:
target = node
# Heuristic function to calculate the scores # Heuristic function to calculate the scores
h = lambda node: target.center.distance(node.center) h = lambda node: target.center.distance(node.center) + abs(target.center.z - node.center.z)
queue = [] queue = []
close_set = set() close_set = set()
@ -161,7 +156,7 @@ class navigation_graph:
# Update neighbor scores # Update neighbor scores
for node in current.neighbors: for node in current.neighbors:
tentative_score = current.get_edge_cost(node) + g_scores[current.id] tentative_score = self.get_edge_cost(current, node) + g_scores[current.id]
if node.id not in g_scores or tentative_score < g_scores[node.id]: if node.id not in g_scores or tentative_score < g_scores[node.id]:
came_from[node.id] = current came_from[node.id] = current
g_scores[node.id] = tentative_score g_scores[node.id] = tentative_score
@ -172,48 +167,14 @@ class navigation_graph:
return None return None
def is_between(a, b, mid): def get_edge_cost(self, source, target):
""" Return if 'mid' is between 'a' and 'b'. """ """ """
return (a < mid and mid < b) or (b < mid and mid < a) if target in source.neighbors:
is_vertical = source.center.x == target.center.x
layer_dist = source.center.distance(target.center)
def get_closest_nodes(a, b): if is_vertical != bool(source.center.z):
""" """ layer_dist *= 2
via_dist = abs(source.center.z - target.center.z) * 2
if isinstance(a, navigation_node) and isinstance(b, navigation_node): return layer_dist + via_dist
return a, b return float("inf")
if isinstance(a, navigation_blockage) and isinstance(b, navigation_blockage):
min_dist = float("inf")
min_a = None
min_b = None
for node_a in a.corners:
for node_b in b.corners:
dist = node_a.center.distance(node_b.center)
if dist < min_dist:
min_dist = dist
min_a = node_a
min_b = node_b
return min_a, min_b
if isinstance(a, navigation_node):
min_dist = float("inf")
min_a = None
min_b = None
for node_b in b.corners:
dist = a.center.distance(node_b.center)
if dist < min_dist:
min_dist = dist
min_a = a
min_b = node_b
return min_a, min_b
if isinstance(b, navigation_node):
min_dist = float("inf")
min_a = None
min_b = None
for node_a in a.corners:
dist = b.center.distance(node_a.center)
if dist < min_dist:
min_dist = dist
min_a = node_a
min_b = b
return min_a, min_b

View File

@ -13,26 +13,23 @@ class navigation_node:
# This is used to assign unique ids to nodes # This is used to assign unique ids to nodes
next_id = 0 next_id = 0
def __init__(self, center, offset=None, horizontal=1, vertical=1): def __init__(self, center):
self.id = navigation_node.next_id self.id = navigation_node.next_id
navigation_node.next_id += 1 navigation_node.next_id += 1
if isinstance(center, vector): if isinstance(center, vector3d):
self.center = vector3d(center[0], center[1], 0)
elif isinstance(center, vector3d):
self.center = center self.center = center
else: else:
self.center = vector3d(center) self.center = vector3d(center)
if offset:
self.center += vector3d(offset * horizontal, offset * vertical, 0)
self.neighbors = [] self.neighbors = []
def add_neighbor(self, node): def add_neighbor(self, node):
""" Connect two nodes. """ """ Connect two nodes. """
self.neighbors.append(node) if node not in self.neighbors:
node.neighbors.append(self) self.neighbors.append(node)
node.neighbors.append(self)
def remove_neighbor(self, node): def remove_neighbor(self, node):
@ -47,6 +44,5 @@ class navigation_node:
""" Return the cost of going to node. """ """ Return the cost of going to node. """
if node in self.neighbors: if node in self.neighbors:
return self.center.distance(node.center) return self.center.distance(node.center) + abs(self.center.z - node.center.z)
else: return float("inf")
return float("inf")

View File

@ -35,7 +35,7 @@ class navigation_router(router_tech):
def route(self, vdd_name="vdd", gnd_name="gnd"): def route(self, vdd_name="vdd", gnd_name="gnd"):
""" Route the given pins in the given order. """ """ Route the given pins in the given order. """
#debug.info(0, "Running router for {}...".format(pins)) debug.info(1, "Running router for {}...".format(pins))
# Prepare gdsMill to find pins and blockages # Prepare gdsMill to find pins and blockages
self.design.gds_write(self.gds_filename) self.design.gds_write(self.gds_filename)
@ -54,8 +54,8 @@ class navigation_router(router_tech):
pin_iter = iter(self.pins["vdd"]) pin_iter = iter(self.pins["vdd"])
vdd_0 = next(pin_iter) vdd_0 = next(pin_iter)
vdd_1 = next(pin_iter) vdd_1 = next(pin_iter)
self.nav = navigation_graph(self.track_width) self.nav = navigation_graph(self)
self.nav.create_graph(vdd_0, vdd_1, self.blockages) self.nav.create_graph(vdd_0, vdd_1)
# Find the shortest path from source to target # Find the shortest path from source to target
path = self.nav.find_shortest_path(vdd_0, vdd_1) path = self.nav.find_shortest_path(vdd_0, vdd_1)
@ -100,7 +100,7 @@ class navigation_router(router_tech):
rect = [ll, ur] rect = [ll, ur]
new_shape = pin_layout("blockage{}".format(len(self.blockages)), new_shape = pin_layout("blockage{}".format(len(self.blockages)),
rect, rect,
lpp).inflated_pin() lpp).inflated_pin(multiple=1)
# If there is a rectangle that is the same in the pins, # If there is a rectangle that is the same in the pins,
# it isn't a blockage # it isn't a blockage
if new_shape not in self.all_pins and not self.pin_contains(new_shape): if new_shape not in self.all_pins and not self.pin_contains(new_shape):
@ -124,18 +124,10 @@ class navigation_router(router_tech):
def connect_nodes(self, a, b): def connect_nodes(self, a, b):
""" Connect nodes 'a' and 'b' with a wire. """ """ Connect nodes 'a' and 'b' with a wire. """
# Calculate the shape of the wire if a.center.x == b.center.x and a.center.y == b.center.y:
track_offset = vector3d(self.track_width / 2, self.track_width / 2, 0) self.design.add_via_center(self.layers, vector(a.center.x, b.center.y))
ll = a.center.min(b.center) - track_offset else:
ur = a.center.max(b.center) + track_offset self.design.add_path(self.get_layer(a.center.z), [a.center, b.center])
debug.info(0, "Adding wire: ({}, {})".format(ll, ur))
# Add the shape to the layout
self.design.add_rect(layer="text",
offset=(ll[0], ll[1]),
width=ur.x - ll.x,
height=ur.y - ll.y)
def write_debug_gds(self, gds_name="debug_route.gds", source=None, target=None): def write_debug_gds(self, gds_name="debug_route.gds", source=None, target=None):
@ -150,7 +142,7 @@ class navigation_router(router_tech):
""" """ """ """
# Display the inflated blockage # Display the inflated blockage
for blockage in self.nav.nav_blockages: for blockage in self.nav.graph_blockages:
self.add_object_info(blockage, "blockage") self.add_object_info(blockage, "blockage")
for node in self.nav.nodes: for node in self.nav.nodes:
offset = (node.center.x, node.center.y) offset = (node.center.x, node.center.y)

View File

@ -0,0 +1,21 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved.
#
"""
Utility functions for navigation router.
"""
def is_in_region(point, region):
""""""
if is_between(region[0].x, region[1].x, point.x) and is_between(region[0].y, region[1].y, point.y):
return True
return False
def is_between(a, b, mid):
""" Return if 'mid' is between 'a' and 'b'. """
return (a < mid and mid < b) or (b < mid and mid < a)