Add A* algorithm for navigation router

This commit is contained in:
Eren Dogan 2023-05-09 13:23:01 -07:00
parent 909ac6ce68
commit cd339ebbd0
4 changed files with 244 additions and 66 deletions

View File

@ -3,29 +3,35 @@
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved.
#
from openram.base.vector import vector
from .navigation_node import navigation_node
class navigation_blockage:
""" This class represents a blockage on the navigation graph. """
def __init__(self, ll, ur):
def __init__(self, ll, ur, layer=0):
self.ll = ll
self.ur = ur
self.layer = layer
def create_corner_nodes(self):
@property
def rect(self):
""" """
return self.ll, self.ur
def create_corner_nodes(self, offset):
""" Create nodes on all 4 corners of this blockage. """
corners = []
corners.append(navigation_node(vector(self.ll[0], self.ll[1])))
corners.append(navigation_node(vector(self.ll[0], self.ur[1])))
corners.append(navigation_node(vector(self.ur[0], self.ll[1])))
corners.append(navigation_node(vector(self.ur[0], self.ur[1])))
corners[0].add_neighbor(corners[1])
corners[0].add_neighbor(corners[2])
corners[3].add_neighbor(corners[1])
corners[3].add_neighbor(corners[2])
return corners
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

@ -3,8 +3,8 @@
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved.
#
import heapq
from openram import debug
from openram.base.vector import vector
from .navigation_node import navigation_node
from .navigation_blockage import navigation_blockage
@ -12,8 +12,9 @@ from .navigation_blockage import navigation_blockage
class navigation_graph:
""" This is the navigation graph created from the blockages. """
def __init__(self):
pass
def __init__(self, track_width):
self.track_width = track_width
def is_probe_blocked(self, p1, p2):
@ -43,11 +44,10 @@ class navigation_graph:
debug.info(0, "Creating the navigation graph for source '{0}' and target'{1}'.".format(layout_source, layout_target))
# Find the region to be routed and only include objects inside that region
s_ll, sou_ur = layout_source.rect
t_ll, tar_ur = layout_target.rect
ll = vector(min(s_ll.x, t_ll.x), max(s_ll.y, t_ll.y))
ur = vector(max(sou_ur.x, tar_ur.x), min(sou_ur.y, tar_ur.y))
region = (ll, ur)
s_ll, s_ur = layout_source.rect
t_ll, t_ur = layout_target.rect
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]))
# Instantiate "navigation blockage" objects from layout blockages
self.nav_blockages = []
@ -65,37 +65,155 @@ class navigation_graph:
# Create the corner nodes
for blockage in self.nav_blockages:
self.nodes.extend(blockage.create_corner_nodes())
blockage.create_corner_nodes(self.track_width / 2)
# These nodes will be connected to create the final graph
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.
new_nodes = []
debug.info(0, "Number of blockages: {}".format(len(self.nav_blockages)))
debug.info(0, "Number of nodes: {}".format(len(self.nodes)))
for i in range(len(self.nodes)):
debug.info(3, "Creating intersections for node #{}".format(i))
node1 = self.nodes[i]
for j in range(i + 1, len(self.nodes)):
node2 = self.nodes[j]
# Skip if the nodes are already connected
if node1 in node2.neighbors:
continue
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
corner = navigation_node(vector(node1.position[k], node2.position[int(not k)]))
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.position, node1.position) or self.is_probe_blocked(corner.position, node2.position):
if self.is_probe_blocked(corner.center, node1.center) or self.is_probe_blocked(corner.center, node2.center):
continue
# Check if this new node stands on an existing connection
self.remove_intersected_neighbors(node1, corner, k)
self.remove_intersected_neighbors(node2, corner, not(k))
# Add this new node to the graph
corner.add_neighbor(node1)
corner.add_neighbor(node2)
new_nodes.append(corner)
self.nodes.extend(new_nodes)
debug.info(0, "Number of nodes after intersections: {}".format(len(self.nodes)))
self.nodes.append(corner)
# Add corner nodes from blockages after intersections
for blockage in self.nav_blockages:
self.nodes.extend(blockage.corners)
debug.info(0, "Number of nodes after corners: {}".format(len(self.nodes)))
def remove_intersected_neighbors(self, node, corner, axis):
""" """
a = node.center
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):
"""
Find the shortest path from the source node to target node using the
A* algorithm.
"""
source = self.nodes[0]
target = self.nodes[1]
# Heuristic function to calculate the scores
h = lambda node: target.center.distance(node.center)
queue = []
close_set = set()
came_from = {}
g_scores = {}
f_scores = {}
# Initialize score values for the source node
g_scores[source.id] = 0
f_scores[source.id] = h(source)
heapq.heappush(queue, (f_scores[source.id], source.id, source))
# 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
if current == target:
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:
tentative_score = current.get_edge_cost(node) + g_scores[current.id]
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
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)
def get_closest_nodes(a, b):
""" """
if isinstance(a, navigation_node) and isinstance(b, navigation_node):
return a, b
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

@ -3,13 +3,28 @@
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved.
#
from openram.base.vector import vector
from openram.base.vector3d import vector3d
class navigation_node:
""" This class represents a node on the navigation graph. """
def __init__(self, position):
# This is used to assign unique ids to nodes
next_id = 0
self.position = position
def __init__(self, center, offset=None, horizontal=1, vertical=1):
self.id = navigation_node.next_id
navigation_node.next_id += 1
if isinstance(center, vector):
self.center = vector3d(center[0], center[1], 0)
elif isinstance(center, vector3d):
self.center = center
else:
self.center = vector3d(center)
if offset:
self.center += vector3d(offset * horizontal, offset * vertical, 0)
self.neighbors = []
@ -26,3 +41,12 @@ class navigation_node:
if node in self.neighbors:
self.neighbors.remove(node)
node.neighbors.remove(self)
def get_edge_cost(self, node):
""" Return the cost of going to node. """
if node in self.neighbors:
return self.center.distance(node.center)
else:
return float("inf")

View File

@ -6,6 +6,7 @@
from openram import debug
from openram.base.pin_layout import pin_layout
from openram.base.vector import vector
from openram.base.vector3d import vector3d
from openram.gdsMill import gdsMill
from openram.tech import GDS
from openram.tech import layer as tech_layer
@ -26,6 +27,7 @@ class navigation_router(router_tech):
self.layers = layers
self.design = design
self.gds_filename = OPTS.openram_temp + "temp.gds"
self.track_width = 1
self.pins = {}
self.all_pins = set()
self.blockages = []
@ -49,12 +51,18 @@ class navigation_router(router_tech):
self.find_blockages()
# Create the navigation graph
self.nav = navigation_graph()
pin_iter = iter(self.pins["vdd"])
vdd_0 = next(pin_iter)
vdd_1 = next(pin_iter)
self.nav = navigation_graph(self.track_width)
self.nav.create_graph(vdd_0, vdd_1, self.blockages)
# Find the shortest path from source to target
path = self.nav.find_shortest_path(vdd_0, vdd_1)
# Create the path shapes on layout
self.add_path(path)
self.write_debug_gds(source=vdd_0, target=vdd_1)
@ -68,8 +76,8 @@ class navigation_router(router_tech):
layer, boundary = shape
# gdsMill boundaries are in (left, bottom, right, top) order
# so repack and snap to the grid
ll = vector(boundary[0], boundary[1]).snap_to_grid()
ur = vector(boundary[2], boundary[3]).snap_to_grid()
ll = vector(boundary[0], boundary[1])
ur = vector(boundary[2], boundary[3])
rect = [ll, ur]
pin = pin_layout(pin_name, rect, layer)
pin_set.add(pin)
@ -80,7 +88,7 @@ class navigation_router(router_tech):
def find_blockages(self):
""" """
debug.info(1, "Finding all blockages")
debug.info(1, "Finding all blockages...")
for lpp in [self.vert_lpp, self.horiz_lpp]:
shapes = self.layout.getAllShapes(lpp)
@ -92,8 +100,7 @@ class navigation_router(router_tech):
rect = [ll, ur]
new_shape = pin_layout("blockage{}".format(len(self.blockages)),
rect,
lpp)
lpp).inflated_pin()
# If there is a rectangle that is the same in the pins,
# it isn't a blockage
if new_shape not in self.all_pins and not self.pin_contains(new_shape):
@ -107,6 +114,30 @@ class navigation_router(router_tech):
return False
def add_path(self, path):
""" """
for i in range(len(path) - 1):
self.connect_nodes(path[i], path[i + 1])
def connect_nodes(self, a, b):
""" Connect nodes 'a' and 'b' with a wire. """
# Calculate the shape of the wire
track_offset = vector3d(self.track_width / 2, self.track_width / 2, 0)
ll = a.center.min(b.center) - track_offset
ur = a.center.max(b.center) + track_offset
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):
""" """
@ -120,30 +151,16 @@ class navigation_router(router_tech):
# Display the inflated blockage
for blockage in self.nav.nav_blockages:
ll, ur = blockage.ll, blockage.ur
self.design.add_rect(layer="text",
offset=ll,
width=ur.x - ll.x,
height=ur.y - ll.y)
self.design.add_label(text="blockage",
layer="text",
offset=ll)
self.add_object_info(blockage, "blockage")
for node in self.nav.nodes:
self.design.add_rect_center(layer="text",
offset=node.position,
width=1,
height=1)
self.design.add_label(text="-0-",
offset = (node.center.x, node.center.y)
self.design.add_label(text="O",
layer="text",
offset=node.position)
offset=offset)
if source:
self.design.add_label(text="source",
layer="text",
offset=source.rect[0])
self.add_object_info(source, "source")
if target:
self.design.add_label(text="target",
layer="text",
offset=target.rect[0])
self.add_object_info(target, "target")
def del_router_info(self):
@ -151,3 +168,16 @@ class navigation_router(router_tech):
lpp = tech_layer["text"]
self.design.objs = [x for x in self.design.objs if x.lpp != lpp]
def add_object_info(self, obj, label):
""" """
ll, ur = obj.rect
self.design.add_rect(layer="text",
offset=ll,
width=ur.x - ll.x,
height=ur.y - ll.y)
self.design.add_label(text=label,
layer="text",
offset=ll)