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.
|
|
|
|
|
#
|
|
|
|
|
from openram import debug
|
|
|
|
|
from openram.base.vector import vector
|
2023-05-09 22:23:01 +02:00
|
|
|
from openram.base.vector3d import vector3d
|
2023-05-05 05:51:30 +02:00
|
|
|
from openram.gdsMill import gdsMill
|
|
|
|
|
from openram.tech import GDS
|
|
|
|
|
from openram.tech import layer as tech_layer
|
|
|
|
|
from openram import OPTS
|
|
|
|
|
from .router_tech import router_tech
|
2023-05-29 18:18:55 +02:00
|
|
|
from .hanan_graph import hanan_graph
|
2023-06-04 17:46:59 +02:00
|
|
|
from .hanan_shape import hanan_shape
|
2023-05-05 05:51:30 +02:00
|
|
|
|
|
|
|
|
|
2023-05-29 18:18:55 +02:00
|
|
|
class hanan_router(router_tech):
|
2023-05-05 05:51:30 +02:00
|
|
|
"""
|
2023-05-29 18:18:55 +02:00
|
|
|
This is the router class that implements Hanan graph routing algorithm.
|
2023-05-05 05:51:30 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, layers, design, bbox=None, pin_type=None):
|
|
|
|
|
|
|
|
|
|
router_tech.__init__(self, layers, route_track_width=1)
|
|
|
|
|
|
|
|
|
|
self.layers = layers
|
|
|
|
|
self.design = design
|
|
|
|
|
self.gds_filename = OPTS.openram_temp + "temp.gds"
|
|
|
|
|
self.pins = {}
|
|
|
|
|
self.all_pins = set()
|
|
|
|
|
self.blockages = []
|
|
|
|
|
|
2023-07-03 23:04:26 +02:00
|
|
|
# Set the offset here
|
|
|
|
|
self.offset = self.layer_widths[0] / 2
|
|
|
|
|
|
2023-05-05 05:51:30 +02:00
|
|
|
|
|
|
|
|
def route(self, vdd_name="vdd", gnd_name="gnd"):
|
|
|
|
|
""" Route the given pins in the given order. """
|
2023-05-23 03:16:49 +02:00
|
|
|
#debug.info(1, "Running router for {}...".format(pins))
|
|
|
|
|
self.write_debug_gds(gds_name="before.gds")
|
2023-05-05 05:51:30 +02:00
|
|
|
|
|
|
|
|
# Prepare gdsMill to find pins and blockages
|
2023-07-03 23:04:26 +02:00
|
|
|
self.prepare_gds_reader()
|
2023-05-05 05:51:30 +02:00
|
|
|
|
|
|
|
|
# Find pins to be routed
|
|
|
|
|
self.find_pins(vdd_name)
|
|
|
|
|
self.find_pins(gnd_name)
|
|
|
|
|
|
|
|
|
|
# Find blockages
|
|
|
|
|
self.find_blockages()
|
|
|
|
|
|
2023-07-03 23:04:26 +02:00
|
|
|
# Add vdd and gnd pins as blockages as well
|
|
|
|
|
# NOTE: This is done to make vdd and gnd pins DRC-safe
|
|
|
|
|
for pin in self.all_pins:
|
|
|
|
|
self.blockages.append(pin.inflated_pin(multiple=1, extra_spacing=self.offset, keep_link=True))
|
|
|
|
|
|
2023-07-02 01:14:56 +02:00
|
|
|
# Route vdd and gnd
|
|
|
|
|
for pin_name in [vdd_name, gnd_name]:
|
|
|
|
|
pins = self.pins[pin_name]
|
|
|
|
|
# Create minimum spanning tree connecting all pins
|
|
|
|
|
for source, target in self.get_mst_pairs(list(pins)):
|
2023-07-03 23:04:26 +02:00
|
|
|
# Create the Hanan graph
|
2023-07-02 01:14:56 +02:00
|
|
|
hg = hanan_graph(self)
|
|
|
|
|
hg.create_graph(source, target)
|
|
|
|
|
# Find the shortest path from source to target
|
|
|
|
|
path = hg.find_shortest_path()
|
2023-07-03 23:04:26 +02:00
|
|
|
debug.check(path is not None, "Couldn't route from {} to {}".format(source, target))
|
2023-07-02 01:14:56 +02:00
|
|
|
# Create the path shapes on layout
|
|
|
|
|
self.add_path(path)
|
2023-07-03 23:04:26 +02:00
|
|
|
# Find the recently added shapes
|
|
|
|
|
self.prepare_gds_reader()
|
|
|
|
|
self.find_blockages(pin_name)
|
2023-07-02 01:14:56 +02:00
|
|
|
|
|
|
|
|
self.write_debug_gds(gds_name="after.gds")
|
2023-05-05 05:51:30 +02:00
|
|
|
|
|
|
|
|
|
2023-07-03 23:04:26 +02:00
|
|
|
def prepare_gds_reader(self):
|
|
|
|
|
""" """
|
|
|
|
|
|
|
|
|
|
self.design.gds_write(self.gds_filename)
|
|
|
|
|
self.layout = gdsMill.VlsiLayout(units=GDS["unit"])
|
|
|
|
|
self.reader = gdsMill.Gds2reader(self.layout)
|
|
|
|
|
self.reader.loadFromFile(self.gds_filename)
|
|
|
|
|
|
|
|
|
|
|
2023-05-05 05:51:30 +02:00
|
|
|
def find_pins(self, pin_name):
|
|
|
|
|
""" """
|
|
|
|
|
debug.info(1, "Finding all pins for {}".format(pin_name))
|
|
|
|
|
|
|
|
|
|
shape_list = self.layout.getAllPinShapes(str(pin_name))
|
|
|
|
|
pin_set = set()
|
|
|
|
|
for shape in shape_list:
|
|
|
|
|
layer, boundary = shape
|
|
|
|
|
# gdsMill boundaries are in (left, bottom, right, top) order
|
|
|
|
|
# so repack and snap to the grid
|
2023-05-09 22:23:01 +02:00
|
|
|
ll = vector(boundary[0], boundary[1])
|
|
|
|
|
ur = vector(boundary[2], boundary[3])
|
2023-05-05 05:51:30 +02:00
|
|
|
rect = [ll, ur]
|
2023-07-03 23:04:26 +02:00
|
|
|
new_pin = hanan_shape(pin_name, rect, layer)
|
|
|
|
|
# Skip this pin if it's contained by another pin of the same type
|
|
|
|
|
if new_pin.contained_by_any(pin_set):
|
|
|
|
|
continue
|
|
|
|
|
# Remove any previous pin of the same type contained by this new pin
|
|
|
|
|
for pin in list(pin_set):
|
|
|
|
|
if new_pin.contains(pin):
|
|
|
|
|
pin_set.remove(pin)
|
|
|
|
|
elif new_pin.aligns(pin):
|
|
|
|
|
new_pin.bbox([pin])
|
|
|
|
|
pin_set.remove(pin)
|
|
|
|
|
pin_set.add(new_pin)
|
2023-05-05 05:51:30 +02:00
|
|
|
# Add these pins to the 'pins' dict
|
|
|
|
|
self.pins[pin_name] = pin_set
|
|
|
|
|
self.all_pins.update(pin_set)
|
|
|
|
|
|
|
|
|
|
|
2023-07-03 23:04:26 +02:00
|
|
|
def find_blockages(self, shape_name=None):
|
2023-05-05 05:51:30 +02:00
|
|
|
""" """
|
2023-07-03 23:04:26 +02:00
|
|
|
debug.info(1, "Finding blockages...")
|
|
|
|
|
|
|
|
|
|
prev_blockages = self.blockages[:]
|
2023-05-05 05:51:30 +02:00
|
|
|
|
2023-06-06 04:33:45 +02:00
|
|
|
blockages = []
|
2023-05-05 05:51:30 +02:00
|
|
|
for lpp in [self.vert_lpp, self.horiz_lpp]:
|
|
|
|
|
shapes = self.layout.getAllShapes(lpp)
|
|
|
|
|
for boundary in shapes:
|
|
|
|
|
# gdsMill boundaries are in (left, bottom, right, top) order
|
|
|
|
|
# so repack and snap to the grid
|
|
|
|
|
ll = vector(boundary[0], boundary[1])
|
|
|
|
|
ur = vector(boundary[2], boundary[3])
|
|
|
|
|
rect = [ll, ur]
|
2023-07-03 23:04:26 +02:00
|
|
|
if shape_name is None:
|
|
|
|
|
name = "blockage{}".format(len(blockages))
|
|
|
|
|
else:
|
|
|
|
|
name = shape_name
|
|
|
|
|
new_shape = hanan_shape(name, rect, lpp)
|
2023-05-05 05:51:30 +02:00
|
|
|
# If there is a rectangle that is the same in the pins,
|
|
|
|
|
# it isn't a blockage
|
2023-07-03 23:04:26 +02:00
|
|
|
if new_shape.contained_by_any(self.all_pins) or \
|
|
|
|
|
new_shape.contained_by_any(prev_blockages) or \
|
|
|
|
|
new_shape.contained_by_any(blockages):
|
2023-06-06 04:33:45 +02:00
|
|
|
continue
|
|
|
|
|
# Remove blockages contained by this new blockage
|
|
|
|
|
for i in range(len(blockages) - 1, -1, -1):
|
|
|
|
|
blockage = blockages[i]
|
|
|
|
|
# Remove the previous blockage contained by this new
|
|
|
|
|
# blockage
|
|
|
|
|
if new_shape.contains(blockage):
|
|
|
|
|
blockages.remove(blockage)
|
|
|
|
|
# Merge the previous blockage into this new blockage if
|
|
|
|
|
# they are aligning
|
|
|
|
|
elif new_shape.aligns(blockage):
|
|
|
|
|
new_shape.bbox([blockage])
|
|
|
|
|
blockages.remove(blockage)
|
|
|
|
|
blockages.append(new_shape)
|
|
|
|
|
|
|
|
|
|
# Inflate the shapes to prevent DRC errors
|
|
|
|
|
for blockage in blockages:
|
2023-07-03 23:04:26 +02:00
|
|
|
self.blockages.append(blockage.inflated_pin(multiple=1,
|
|
|
|
|
extra_spacing=self.offset,
|
|
|
|
|
keep_link=shape_name is not None))
|
|
|
|
|
# Remove blockages contained by this new blockage
|
|
|
|
|
for i in range(len(prev_blockages) - 1, -1, -1):
|
|
|
|
|
prev_blockage = prev_blockages[i]
|
|
|
|
|
# Remove the previous blockage contained by this new
|
|
|
|
|
# blockage
|
|
|
|
|
if blockage.contains(prev_blockage):
|
|
|
|
|
prev_blockages.remove(prev_blockage)
|
|
|
|
|
self.blockages.remove(prev_blockage)
|
|
|
|
|
# Merge the previous blockage into this new blockage if
|
|
|
|
|
# they are aligning
|
|
|
|
|
elif blockage.aligns(prev_blockage):
|
|
|
|
|
blockage.bbox([prev_blockage])
|
|
|
|
|
prev_blockages.remove(prev_blockage)
|
|
|
|
|
self.blockages.remove(prev_blockage)
|
2023-06-29 05:55:49 +02:00
|
|
|
|
2023-05-05 05:51:30 +02:00
|
|
|
|
2023-07-02 01:14:56 +02:00
|
|
|
def get_mst_pairs(self, pins):
|
|
|
|
|
"""
|
|
|
|
|
Return the pin pairs from the minimum spanning tree in a graph that
|
|
|
|
|
connects all pins together.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
pin_count = len(pins)
|
|
|
|
|
|
|
|
|
|
# Create an adjacency matrix that connects all pins
|
|
|
|
|
edges = [[0] * pin_count for i in range(pin_count)]
|
|
|
|
|
for i in range(pin_count):
|
|
|
|
|
for j in range(pin_count):
|
|
|
|
|
if i == j:
|
|
|
|
|
continue
|
|
|
|
|
edges[i][j] = pins[i].distance(pins[j])
|
|
|
|
|
|
|
|
|
|
pin_connected = [False] * pin_count
|
|
|
|
|
pin_connected[0] = True
|
|
|
|
|
|
|
|
|
|
# Add the minimum cost edge in each iteration (Prim's)
|
|
|
|
|
mst_pairs = []
|
|
|
|
|
for i in range(pin_count - 1):
|
|
|
|
|
min_cost = float("inf")
|
|
|
|
|
s = 0
|
|
|
|
|
t = 0
|
|
|
|
|
# Iterate over already connected pins
|
|
|
|
|
for m in range(pin_count):
|
|
|
|
|
# Skip if not connected
|
|
|
|
|
if not pin_connected[m]:
|
|
|
|
|
continue
|
|
|
|
|
# Iterate over this pin's neighbors
|
|
|
|
|
for n in range(pin_count):
|
|
|
|
|
# Skip if already connected or isn't a neighbor
|
|
|
|
|
if pin_connected[n] or edges[m][n] == 0:
|
|
|
|
|
continue
|
|
|
|
|
# Choose this edge if it's better the the current one
|
|
|
|
|
if edges[m][n] < min_cost:
|
|
|
|
|
min_cost = edges[m][n]
|
|
|
|
|
s = m
|
|
|
|
|
t = n
|
|
|
|
|
pin_connected[t] = True
|
|
|
|
|
mst_pairs.append((pins[s], pins[t]))
|
|
|
|
|
|
|
|
|
|
return mst_pairs
|
|
|
|
|
|
|
|
|
|
|
2023-05-09 22:23:01 +02:00
|
|
|
def add_path(self, path):
|
2023-05-29 21:43:43 +02:00
|
|
|
""" Add the route path to the layout. """
|
2023-05-09 22:23:01 +02:00
|
|
|
|
2023-06-04 19:56:50 +02:00
|
|
|
coordinates = self.prepare_path(path)
|
2023-05-29 06:25:11 +02:00
|
|
|
self.design.add_route(layers=self.layers,
|
|
|
|
|
coordinates=coordinates,
|
|
|
|
|
layer_widths=self.layer_widths)
|
2023-05-09 22:23:01 +02:00
|
|
|
|
|
|
|
|
|
2023-06-04 19:56:50 +02:00
|
|
|
def prepare_path(self, path):
|
|
|
|
|
"""
|
|
|
|
|
Remove unnecessary nodes on the path to reduce the number of shapes in
|
|
|
|
|
the layout.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
last_added = path[0]
|
|
|
|
|
coordinates = [path[0].center]
|
2023-06-15 20:08:13 +02:00
|
|
|
direction = path[0].get_direction(path[1])
|
2023-06-04 19:56:50 +02:00
|
|
|
candidate = path[1]
|
|
|
|
|
for i in range(2, len(path)):
|
|
|
|
|
node = path[i]
|
2023-06-15 20:08:13 +02:00
|
|
|
current_direction = node.get_direction(candidate)
|
2023-06-04 19:56:50 +02:00
|
|
|
# Skip the previous candidate since the current node follows the
|
|
|
|
|
# same direction
|
|
|
|
|
if direction == current_direction:
|
|
|
|
|
candidate = node
|
|
|
|
|
else:
|
|
|
|
|
last_added = candidate
|
|
|
|
|
coordinates.append(candidate.center)
|
|
|
|
|
direction = current_direction
|
|
|
|
|
candidate = node
|
|
|
|
|
if candidate.center not in coordinates:
|
|
|
|
|
coordinates.append(candidate.center)
|
|
|
|
|
return coordinates
|
|
|
|
|
|
|
|
|
|
|
2023-07-03 23:04:26 +02:00
|
|
|
def write_debug_gds(self, gds_name="debug_route.gds", hg=None, source=None, target=None):
|
2023-05-05 05:51:30 +02:00
|
|
|
""" """
|
|
|
|
|
|
2023-07-03 23:04:26 +02:00
|
|
|
self.add_router_info(hg, source, target)
|
2023-05-05 05:51:30 +02:00
|
|
|
self.design.gds_write(gds_name)
|
|
|
|
|
self.del_router_info()
|
|
|
|
|
|
|
|
|
|
|
2023-07-03 23:04:26 +02:00
|
|
|
def add_router_info(self, hg=None, source=None, target=None):
|
2023-05-05 05:51:30 +02:00
|
|
|
""" """
|
|
|
|
|
|
|
|
|
|
# Display the inflated blockage
|
2023-07-03 23:04:26 +02:00
|
|
|
if hg:
|
|
|
|
|
for blockage in self.blockages:
|
|
|
|
|
if blockage in hg.graph_blockages:
|
|
|
|
|
self.add_object_info(blockage, "blockage{}++[{}]".format(self.get_zindex(blockage.lpp), blockage.name))
|
|
|
|
|
else:
|
|
|
|
|
self.add_object_info(blockage, "blockage{}[{}]".format(self.get_zindex(blockage.lpp), blockage.name))
|
|
|
|
|
for node in hg.nodes:
|
2023-05-23 03:16:49 +02:00
|
|
|
offset = (node.center.x, node.center.y)
|
2023-05-31 05:09:10 +02:00
|
|
|
self.design.add_label(text="n{}".format(node.center.z),
|
2023-05-23 03:16:49 +02:00
|
|
|
layer="text",
|
|
|
|
|
offset=offset)
|
2023-07-03 23:04:26 +02:00
|
|
|
else:
|
|
|
|
|
for blockage in self.blockages:
|
|
|
|
|
self.add_object_info(blockage, "blockage{}".format(self.get_zindex(blockage.lpp)))
|
2023-05-05 05:51:30 +02:00
|
|
|
if source:
|
2023-05-09 22:23:01 +02:00
|
|
|
self.add_object_info(source, "source")
|
2023-05-05 05:51:30 +02:00
|
|
|
if target:
|
2023-05-09 22:23:01 +02:00
|
|
|
self.add_object_info(target, "target")
|
2023-05-05 05:51:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def del_router_info(self):
|
|
|
|
|
""" """
|
|
|
|
|
|
|
|
|
|
lpp = tech_layer["text"]
|
|
|
|
|
self.design.objs = [x for x in self.design.objs if x.lpp != lpp]
|
2023-05-09 22:23:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|