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

View File

@ -3,8 +3,8 @@
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz # Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved. # All rights reserved.
# #
import heapq
from openram import debug from openram import debug
from openram.base.vector import vector
from .navigation_node import navigation_node from .navigation_node import navigation_node
from .navigation_blockage import navigation_blockage from .navigation_blockage import navigation_blockage
@ -12,8 +12,9 @@ from .navigation_blockage import navigation_blockage
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): def __init__(self, track_width):
pass
self.track_width = track_width
def is_probe_blocked(self, p1, p2): 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)) 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 # Find the region to be routed and only include objects inside that region
s_ll, sou_ur = layout_source.rect s_ll, s_ur = layout_source.rect
t_ll, tar_ur = layout_target.rect t_ll, t_ur = layout_target.rect
ll = vector(min(s_ll.x, t_ll.x), max(s_ll.y, t_ll.y)) region = (s_ll.min(t_ll), s_ur.min(t_ur))
ur = vector(max(sou_ur.x, tar_ur.x), min(sou_ur.y, tar_ur.y)) debug.info(0, "Routing region is ll: '{0}' ur: '{1}'".format(region[0], region[1]))
region = (ll, ur)
# Instantiate "navigation blockage" objects from layout blockages # Instantiate "navigation blockage" objects from layout blockages
self.nav_blockages = [] self.nav_blockages = []
@ -65,37 +65,155 @@ class navigation_graph:
# Create the corner nodes # Create the corner nodes
for blockage in self.nav_blockages: 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 # Create intersection nodes
# NOTE: Intersection nodes are used to connect boundaries of blockages # NOTE: Intersection nodes are used to connect boundaries of blockages
# perpendicularly. # perpendicularly.
new_nodes = [] debug.info(0, "Number of objects: {}".format(len(connect_objs)))
debug.info(0, "Number of blockages: {}".format(len(self.nav_blockages))) for i in range(len(connect_objs)):
debug.info(0, "Number of nodes: {}".format(len(self.nodes))) obj1 = connect_objs[i]
for i in range(len(self.nodes)): for j in range(i + 1, len(connect_objs)):
debug.info(3, "Creating intersections for node #{}".format(i)) obj2 = connect_objs[j]
node1 = self.nodes[i] node1, node2 = get_closest_nodes(obj1, obj2)
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
# Try two different corners # Try two different corners
for k in [0, 1]: for k in [0, 1]:
# Create a node at the perpendicular corner of these two nodes # 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 # 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 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(node1)
corner.add_neighbor(node2) corner.add_neighbor(node2)
new_nodes.append(corner) self.nodes.append(corner)
self.nodes.extend(new_nodes)
debug.info(0, "Number of nodes after intersections: {}".format(len(self.nodes))) # 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): def is_between(a, b, mid):
""" Return if 'mid' is between 'a' and 'b'. """ """ Return if 'mid' is between 'a' and 'b'. """
return (a < mid and mid < b) or (b < mid and mid < a) 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 # Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved. # All rights reserved.
# #
from openram.base.vector import vector
from openram.base.vector3d import vector3d
class navigation_node: class navigation_node:
""" This class represents a node on the navigation graph. """ """ 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 = [] self.neighbors = []
@ -26,3 +41,12 @@ class navigation_node:
if node in self.neighbors: if node in self.neighbors:
self.neighbors.remove(node) self.neighbors.remove(node)
node.neighbors.remove(self) 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 import debug
from openram.base.pin_layout import pin_layout from openram.base.pin_layout import pin_layout
from openram.base.vector import vector from openram.base.vector import vector
from openram.base.vector3d import vector3d
from openram.gdsMill import gdsMill from openram.gdsMill import gdsMill
from openram.tech import GDS from openram.tech import GDS
from openram.tech import layer as tech_layer from openram.tech import layer as tech_layer
@ -26,6 +27,7 @@ class navigation_router(router_tech):
self.layers = layers self.layers = layers
self.design = design self.design = design
self.gds_filename = OPTS.openram_temp + "temp.gds" self.gds_filename = OPTS.openram_temp + "temp.gds"
self.track_width = 1
self.pins = {} self.pins = {}
self.all_pins = set() self.all_pins = set()
self.blockages = [] self.blockages = []
@ -49,12 +51,18 @@ class navigation_router(router_tech):
self.find_blockages() self.find_blockages()
# Create the navigation graph # Create the navigation graph
self.nav = navigation_graph()
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.create_graph(vdd_0, vdd_1, self.blockages) 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) self.write_debug_gds(source=vdd_0, target=vdd_1)
@ -68,8 +76,8 @@ class navigation_router(router_tech):
layer, boundary = shape layer, boundary = shape
# gdsMill boundaries are in (left, bottom, right, top) order # gdsMill boundaries are in (left, bottom, right, top) order
# so repack and snap to the grid # so repack and snap to the grid
ll = vector(boundary[0], boundary[1]).snap_to_grid() ll = vector(boundary[0], boundary[1])
ur = vector(boundary[2], boundary[3]).snap_to_grid() ur = vector(boundary[2], boundary[3])
rect = [ll, ur] rect = [ll, ur]
pin = pin_layout(pin_name, rect, layer) pin = pin_layout(pin_name, rect, layer)
pin_set.add(pin) pin_set.add(pin)
@ -80,7 +88,7 @@ class navigation_router(router_tech):
def find_blockages(self): 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]: for lpp in [self.vert_lpp, self.horiz_lpp]:
shapes = self.layout.getAllShapes(lpp) shapes = self.layout.getAllShapes(lpp)
@ -92,8 +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) lpp).inflated_pin()
# 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):
@ -107,6 +114,30 @@ class navigation_router(router_tech):
return False 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): 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 # Display the inflated blockage
for blockage in self.nav.nav_blockages: for blockage in self.nav.nav_blockages:
ll, ur = blockage.ll, blockage.ur self.add_object_info(blockage, "blockage")
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)
for node in self.nav.nodes: for node in self.nav.nodes:
self.design.add_rect_center(layer="text", offset = (node.center.x, node.center.y)
offset=node.position, self.design.add_label(text="O",
width=1,
height=1)
self.design.add_label(text="-0-",
layer="text", layer="text",
offset=node.position) offset=offset)
if source: if source:
self.design.add_label(text="source", self.add_object_info(source, "source")
layer="text",
offset=source.rect[0])
if target: if target:
self.design.add_label(text="target", self.add_object_info(target, "target")
layer="text",
offset=target.rect[0])
def del_router_info(self): def del_router_info(self):
@ -151,3 +168,16 @@ class navigation_router(router_tech):
lpp = tech_layer["text"] lpp = tech_layer["text"]
self.design.objs = [x for x in self.design.objs if x.lpp != lpp] 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)