Remove grid-based routers and replace them with the gridless router

This commit is contained in:
Eren Dogan 2023-08-01 10:59:55 -07:00
parent da24c52c52
commit 6c70396a05
15 changed files with 250 additions and 4142 deletions

View File

@ -3,9 +3,5 @@
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved.
#
from .router import *
from .signal_escape_router import *
from .signal_router import *
from .supply_grid_router import *
from .supply_tree_router import *
from .supply_graph_router import *
from .supply_router import *

View File

@ -1,331 +0,0 @@
# 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
from openram.gdsMill import gdsMill
from openram.tech import GDS
from openram.tech import drc
from openram.tech import layer as tech_layer
from openram import OPTS
from .router_tech import router_tech
from .graph_shape import graph_shape
from .graph_utils import snap
class graph_router(router_tech):
"""
This is the base class for routers that use the Hanan grid graph method.
"""
def __init__(self, layers, design, bbox=None):
# `router_tech` contains tech constants for the router
router_tech.__init__(self, layers, route_track_width=1)
# Layers that can be used for routing
self.layers = layers
# This is the `hierarchy_layout` object
self.design = design
# Temporary GDSII file name to find pins and blockages
self.gds_filename = OPTS.openram_temp + "temp.gds"
# Dictionary for vdd and gnd pins
self.pins = {}
# Set of all the pins
self.all_pins = set()
# This is all the blockages including the pins. The graph class handles
# pins as blockages while considering their routability
self.blockages = []
# This is all the vias between routing layers
self.vias = []
# Fake pins are imaginary pins on the side supply pins to route other
# pins to them
self.fake_pins = []
# Set the offset here
self.half_wire = snap(self.track_wire / 2)
def prepare_gds_reader(self):
""" Write the current layout to a temporary file to read the layout. """
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)
def merge_shapes(self, merger, shape_list):
"""
Merge shapes in the list into the merger if they are contained or
aligned by the merger.
"""
merger_core = merger.get_core()
for shape in list(shape_list):
shape_core = shape.get_core()
# If merger contains the shape, remove it from the list
if merger_core.contains(shape_core):
shape_list.remove(shape)
# If the merger aligns with the shape, expand the merger and remove
# the shape from the list
elif merger_core.aligns(shape_core):
merger.bbox([shape])
merger_core.bbox([shape_core])
shape_list.remove(shape)
def find_pins(self, pin_name):
""" Find the pins with the given name. """
debug.info(2, "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
ll = vector(boundary[0], boundary[1])
ur = vector(boundary[2], boundary[3])
rect = [ll, ur]
new_pin = graph_shape(pin_name, rect, layer)
# Skip this pin if it's contained by another pin of the same type
if new_pin.core_contained_by_any(pin_set):
continue
# Merge previous pins into this one if possible
self.merge_shapes(new_pin, pin_set)
pin_set.add(new_pin)
# Add these pins to the 'pins' dict
self.pins[pin_name] = pin_set
self.all_pins.update(pin_set)
def find_blockages(self, name="blockage"):
""" Find all blockages in the routing layers. """
debug.info(2, "Finding blockages...")
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
ll = vector(boundary[0], boundary[1])
ur = vector(boundary[2], boundary[3])
rect = [ll, ur]
new_shape = graph_shape(name, rect, lpp)
new_shape = self.inflate_shape(new_shape)
# Skip this blockage if it's contained by a pin or an existing
# blockage
if new_shape.core_contained_by_any(self.all_pins) or \
new_shape.core_contained_by_any(self.blockages):
continue
# Merge previous blockages into this one if possible
self.merge_shapes(new_shape, self.blockages)
self.blockages.append(new_shape)
def find_vias(self):
""" Find all vias in the routing layers. """
debug.info(2, "Finding vias...")
# Prepare lpp values here
from openram.tech import layer
via_lpp = layer[self.via_layer_name]
valid_lpp = self.horiz_lpp
shapes = self.layout.getAllShapes(via_lpp)
for boundary in shapes:
# gdsMill boundaries are in (left, bottom, right, top) order
ll = vector(boundary[0], boundary[1])
ur = vector(boundary[2], boundary[3])
rect = [ll, ur]
new_shape = graph_shape("via", rect, valid_lpp)
# Skip this via if it's contained by an existing via blockage
if new_shape.contained_by_any(self.vias):
continue
self.vias.append(self.inflate_shape(new_shape, is_via=True))
def convert_vias(self):
""" Convert vias that overlap a pin. """
for via in self.vias:
via_core = via.get_core()
for pin in self.all_pins:
pin_core = pin.get_core()
via_core.lpp = pin_core.lpp
# If the via overlaps a pin, change its name
if via_core.overlaps(pin_core):
via.rename(pin.name)
break
def convert_blockages(self):
""" Convert blockages that overlap a pin. """
# NOTE: You need to run `convert_vias()` before since a blockage may
# be connected to a pin through a via.
for blockage in self.blockages:
blockage_core = blockage.get_core()
for pin in self.all_pins:
pin_core = pin.get_core()
# If the blockage overlaps a pin, change its name
if blockage_core.overlaps(pin_core):
blockage.rename(pin.name)
break
else:
for via in self.vias:
# Skip if this via isn't connected to a pin
if via.name == "via":
continue
via_core = via.get_core()
via_core.lpp = blockage_core.lpp
# If the blockage overlaps a pin via, change its name
if blockage_core.overlaps(via_core):
blockage.rename(via.name)
break
def inflate_shape(self, shape, is_pin=False, is_via=False):
""" Inflate a given shape with spacing rules. """
# Pins must keep their center lines away from any blockage to prevent
# the nodes from being unconnected
if is_pin:
xdiff = self.layer_widths[0] - shape.width()
ydiff = self.layer_widths[0] - shape.height()
diff = max(xdiff, ydiff) / 2
spacing = self.track_space + drc["grid"]
if diff > 0:
spacing += diff
# Vias are inflated by the maximum spacing rule
elif is_via:
spacing = self.track_space
# Blockages are inflated by their layer's corresponding spacing rule
else:
if self.get_zindex(shape.lpp) == 1:
spacing = self.vert_layer_spacing
else:
spacing = self.horiz_layer_spacing
# If the shape is wider than the supply wire width, its spacing can be
# different
wide = min(shape.width(), shape.height())
if wide > self.layer_widths[0]:
spacing = self.get_layer_space(self.get_zindex(shape.lpp), wide)
return shape.inflated_pin(spacing=spacing,
extra_spacing=self.half_wire)
def add_path(self, path):
""" Add the route path to the layout. """
nodes = self.prepare_path(path)
self.add_route(nodes)
def prepare_path(self, path):
"""
Remove unnecessary nodes on the path to reduce the number of shapes in
the layout.
"""
last_added = path[0]
nodes = [path[0]]
direction = path[0].get_direction(path[1])
candidate = path[1]
for i in range(2, len(path)):
node = path[i]
current_direction = node.get_direction(candidate)
# Skip the previous candidate since the current node follows the
# same direction
if direction == current_direction:
candidate = node
else:
last_added = candidate
nodes.append(candidate)
direction = current_direction
candidate = node
if candidate not in nodes:
nodes.append(candidate)
return nodes
def add_route(self, nodes):
"""
Custom `add_route` function since `hierarchy_layout.add_route` isn't
working for this router.
"""
for i in range(0, len(nodes) - 1):
start = nodes[i].center
end = nodes[i + 1].center
direction = nodes[i].get_direction(nodes[i + 1])
diff = start - end
offset = start.min(end)
offset = vector(offset.x - self.half_wire,
offset.y - self.half_wire)
if direction == (1, 1): # Via
offset = vector(start.x, start.y)
self.design.add_via_center(layers=self.layers,
offset=offset)
else: # Wire
self.design.add_rect(layer=self.get_layer(start.z),
offset=offset,
width=abs(diff.x) + self.track_wire,
height=abs(diff.y) + self.track_wire)
def write_debug_gds(self, gds_name, g=None, source=None, target=None):
""" Write the debug GDSII file for the router. """
self.add_router_info(g, source, target)
self.design.gds_write(gds_name)
self.del_router_info()
def add_router_info(self, g=None, source=None, target=None):
"""
Add debug information to the text layer about the graph and router.
"""
# Display the inflated blockage
if g:
for blockage in self.blockages:
if blockage in g.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 g.nodes:
offset = (node.center.x, node.center.y)
self.design.add_label(text="n{}".format(node.center.z),
layer="text",
offset=offset)
else:
for blockage in self.blockages:
self.add_object_info(blockage, "blockage{}".format(self.get_zindex(blockage.lpp)))
for pin in self.fake_pins:
self.add_object_info(pin, "fake")
if source:
self.add_object_info(source, "source")
if target:
self.add_object_info(target, "target")
def del_router_info(self):
""" Delete router information from the text layer. """
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):
""" Add debug information to the text layer about an object. """
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)

View File

@ -1,215 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from openram import debug
from openram.base.vector3d import vector3d
from .grid_cell import grid_cell
class grid:
"""
A two layer routing map. Each cell can be blocked in the vertical
or horizontal layer.
"""
# costs are relative to a unit grid
# non-preferred cost allows an off-direction jog of 1 grid
# rather than 2 vias + preferred direction (cost 5)
VIA_COST = 2
NONPREFERRED_COST = 4
PREFERRED_COST = 1
def __init__(self, ll, ur, track_width):
""" Initialize the map and define the costs. """
# list of the source/target grid coordinates
self.source = set()
self.target = set()
self.track_width = track_width
self.track_widths = [self.track_width, self.track_width, 1.0]
self.track_factor = [1 / self.track_width, 1 / self.track_width, 1.0]
# The bounds are in grids for this
# This is really lower left bottom layer and upper right top layer in 3D.
self.ll = vector3d(ll.x, ll.y, 0).scale(self.track_factor).round()
self.ur = vector3d(ur.x, ur.y, 0).scale(self.track_factor).round()
debug.info(1, "BBOX coords: ll=" + str(ll) + " ur=" + str(ur))
debug.info(1, "BBOX grids: ll=" + str(self.ll) + " ur=" + str(self.ur))
# let's leave the map sparse, cells are created on demand to reduce memory
self.map={}
def add_all_grids(self):
for x in range(self.ll.x, self.ur.x, 1):
for y in range(self.ll.y, self.ur.y, 1):
self.add_map(vector3d(x, y, 0))
self.add_map(vector3d(x, y, 1))
def set_blocked(self, n, value=True):
if not isinstance(n, vector3d):
for item in n:
self.set_blocked(item, value)
else:
self.add_map(n)
self.map[n].blocked=value
def is_blocked(self, n):
if not isinstance(n, vector3d):
for item in n:
if self.is_blocked(item):
return True
else:
return False
else:
self.add_map(n)
return self.map[n].blocked
def is_inside(self, n):
if not isinstance(n, vector3d):
for item in n:
if self.is_inside(item):
return True
else:
return False
else:
return n.x >= self.ll.x and n.x <= self.ur.x and n.y >= self.ll.y and n.y <= self.ur.y
def set_path(self, n, value=True):
if isinstance(n, (list, tuple, set, frozenset)):
for item in n:
self.set_path(item, value)
else:
self.add_map(n)
self.map[n].path=value
def clear_blockages(self):
for k in self.map:
self.map[k].blocked=False
def clear_source(self):
for k in self.map:
self.map[k].source=False
self.source = set()
def set_source(self, n):
if not isinstance(n, vector3d):
for item in n:
self.set_source(item)
else:
self.add_map(n)
self.map[n].source=True
self.map[n].blocked=False
self.source.add(n)
def clear_target(self):
for k in self.map:
self.map[k].target=False
self.target = set()
def set_target(self, n):
if not isinstance(n, vector3d):
for item in n:
self.set_target(item)
else:
self.add_map(n)
self.map[n].target=True
self.map[n].blocked=False
self.target.add(n)
def add_source(self, track_list):
debug.info(3, "Adding source list={0}".format(str(track_list)))
for n in track_list:
debug.info(4, "Adding source ={0}".format(str(n)))
self.set_source(n)
# self.set_blocked(n, False)
def add_target(self, track_list):
debug.info(3, "Adding target list={0}".format(str(track_list)))
for n in track_list:
debug.info(4, "Adding target ={0}".format(str(n)))
self.set_target(n)
# self.set_blocked(n, False)
def get_perimeter_list(self, side="left", layers=[0, 1], width=1, margin=0, offset=0):
"""
Side specifies which side.
Layer specifies horizontal (0) or vertical (1)
Width specifies how wide the perimeter "stripe" should be.
Works from the inside out from the bbox (ll, ur)
"""
if "ring" in side:
ring_width = width
else:
ring_width = 0
if "ring" in side:
ring_offset = offset
else:
ring_offset = 0
perimeter_list = []
# Add the left/right columns
if side=="all" or "left" in side:
for x in range(self.ll.x - offset, self.ll.x - width - offset, -1):
for y in range(self.ll.y - ring_offset - margin - ring_width + 1, self.ur.y + ring_offset + margin + ring_width, 1):
for layer in layers:
perimeter_list.append(vector3d(x, y, layer))
if side=="all" or "right" in side:
for x in range(self.ur.x + offset, self.ur.x + width + offset, 1):
for y in range(self.ll.y - ring_offset - margin - ring_width + 1, self.ur.y + ring_offset + margin + ring_width, 1):
for layer in layers:
perimeter_list.append(vector3d(x, y, layer))
if side=="all" or "bottom" in side:
for y in range(self.ll.y - offset, self.ll.y - width - offset, -1):
for x in range(self.ll.x - ring_offset - margin - ring_width + 1, self.ur.x + ring_offset + margin + ring_width, 1):
for layer in layers:
perimeter_list.append(vector3d(x, y, layer))
if side=="all" or "top" in side:
for y in range(self.ur.y + offset, self.ur.y + width + offset, 1):
for x in range(self.ll.x - ring_offset - margin - ring_width + 1, self.ur.x + ring_offset + margin + ring_width, 1):
for layer in layers:
perimeter_list.append(vector3d(x, y, layer))
# Add them all to the map
self.add_map(perimeter_list)
return perimeter_list
def add_perimeter_target(self, side="all", layers=[0, 1]):
debug.info(3, "Adding perimeter target")
perimeter_list = self.get_perimeter_list(side, layers)
self.set_target(perimeter_list)
def is_target(self, point):
"""
Point is in the target set, so we are done.
"""
return point in self.target
def add_map(self, n):
"""
Add a point to the map if it doesn't exist.
"""
if not isinstance(n, vector3d):
for item in n:
self.add_map(item)
else:
if n not in self.map:
self.map[n]=grid_cell()
def block_path(self, path):
"""
Mark the path in the routing grid as blocked.
Also unsets the path flag.
"""
path.set_path(False)
path.set_blocked(True)

View File

@ -1,52 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
class grid_cell:
"""
A single cell that can be occupied in a given layer, blocked,
visited, etc.
"""
def __init__(self):
self.path = False
self.blocked = False
self.source = False
self.target = False
# -1 means it isn't visited yet
self.min_cost = -1
def reset(self):
"""
Reset the dynamic info about routing.
"""
self.min_cost=-1
self.min_path=None
self.blocked=False
self.source=False
self.target=False
def get_cost(self):
# We can display the cost of the frontier
if self.min_cost > 0:
return self.min_cost
def get_type(self):
type_string = ""
if self.blocked:
type_string += "X"
if self.source:
type_string += "S"
if self.target:
type_string += "T"
if self.path:
type_string += "P"
return type_string

View File

@ -1,215 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from itertools import tee
from openram.base.vector3d import vector3d
from .grid import grid
from .direction import direction
class grid_path:
"""
A grid path is a list of lists of grid cells.
It can have a width that is more than one cell.
All of the sublists will be the same dimension.
Cells should be continguous.
It can have a name to define pin shapes as well.
"""
def __init__(self, items=[], name=""):
self.name = name
if items:
self.pathlist = [items]
else:
self.pathlist = []
def __str__(self):
p = str(self.pathlist)
if self.name != "":
return (str(self.name) + " : " + p)
return p
def __setitem__(self, index, value):
"""
override setitem function
can set value by pathinstance[index]=value
"""
self.pathlist[index]=value
def __getitem__(self, index):
"""
override getitem function
can get value by value=pathinstance[index]
"""
return self.pathlist[index]
def __contains__(self, key):
"""
Determine if cell exists in this path
"""
# FIXME: Could maintain a hash to make in O(1)
for sublist in self.pathlist:
for item in sublist:
if item == key:
return True
else:
return False
def __add__(self, items):
"""
Override add to do append
"""
return self.pathlist.extend(items)
def __len__(self):
return len(self.pathlist)
def trim_last(self):
"""
Drop the last item
"""
if len(self.pathlist)>0:
self.pathlist.pop()
def trim_first(self):
"""
Drop the first item
"""
if len(self.pathlist)>0:
self.pathlist.pop(0)
def append(self,item):
"""
Append the list of items to the cells
"""
self.pathlist.append(item)
def extend(self,item):
"""
Extend the list of items to the cells
"""
self.pathlist.extend(item)
def set_path(self,value=True):
for sublist in self.pathlist:
for p in sublist:
p.path=value
def set_blocked(self,value=True):
for sublist in self.pathlist:
for p in sublist:
p.blocked=value
def get_grids(self):
"""
Return a set of all the grids in this path.
"""
newset = set()
for sublist in self.pathlist:
newset.update(sublist)
return newset
def get_wire_grids(self, start_index, end_index):
"""
Return a set of all the wire grids in this path.
These are the indices in the wave path in a certain range.
"""
newset = set()
for sublist in self.pathlist:
newset.update(sublist[start_index:end_index])
return newset
def cost(self):
"""
The cost of the path is the length plus a penalty for the number
of vias. We assume that non-preferred direction is penalized.
This cost only works with 1 wide tracks.
"""
# Ignore the source pin layer change, FIXME?
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
plist = list(pairwise(self.pathlist))
cost = 0
for p0list,p1list in plist:
# This is because they are "waves" so pick the first item
p0=p0list[0]
p1=p1list[0]
if p0.z != p1.z: # via
cost += grid.VIA_COST
elif p0.x != p1.x and p0.z==1: # horizontal on vertical layer
cost += grid.NONPREFERRED_COST
elif p0.y != p1.y and p0.z==0: # vertical on horizontal layer
cost += grid.NONPREFERRED_COST
else:
cost += grid.PREFERRED_COST
return cost
def expand_dirs(self):
"""
Expand from the end in each of the four cardinal directions plus up
or down but not expanding to blocked cells. Expands in all
directions regardless of preferred directions.
If the width is more than one, it can only expand in one direction
(for now). This is assumed for the supply router for now.
"""
neighbors = []
for d in direction.cardinal_directions(True):
n = self.neighbor(d)
if n:
neighbors.append(n)
return neighbors
def neighbor(self, d):
offset = direction.get_offset(d)
newwave = [point + offset for point in self.pathlist[-1]]
if newwave in self.pathlist:
return None
elif newwave[0].z>1 or newwave[0].z<0:
return None
return newwave
def set_layer(self, zindex):
new_pathlist = [vector3d(item.x, item.y, zindex) for wave in self.pathlist for item in wave]
self.pathlist = new_pathlist
def overlap(self, other):
"""
Return the overlap waves ignoring different layers
"""
my_zindex = self.pathlist[0][0].z
other_flat_cells = [vector3d(item.x,item.y,my_zindex) for wave in other.pathlist for item in wave]
# This keeps the wave structure of the self layer
shared_waves = []
for wave in self.pathlist:
for item in wave:
# If any item in the wave is not contained, skip it
if not item in other_flat_cells:
break
else:
shared_waves.append(wave)
if len(shared_waves)>0:
ll = shared_waves[0][0]
ur = shared_waves[-1][-1]
return [ll,ur]
return None

View File

@ -1,167 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
"""
Some utility functions for sets of grid cells.
"""
import math
from openram.base.vector3d import vector3d
from .direction import direction
def increment_set(curset, direct):
"""
Return the cells incremented in given direction
"""
offset = direction.get_offset(direct)
newset = set()
for c in curset:
newc = c+offset
newset.add(newc)
return newset
def remove_border(curset, direct):
"""
Remove the cells on a given border.
"""
border = get_border(curset, direct)
curset.difference_update(border)
def get_upper_right(curset):
ur = None
for p in curset:
if ur == None or (p.x>=ur.x and p.y>=ur.y):
ur = p
return ur
def get_lower_left(curset):
ll = None
for p in curset:
if ll == None or (p.x<=ll.x and p.y<=ll.y):
ll = p
return ll
def get_border(curset, direct):
"""
Return the furthest cell(s) in a given direction.
"""
# find direction-most cell(s)
maxc = []
if direct==direction.NORTH:
for c in curset:
if len(maxc)==0 or c.y>maxc[0].y:
maxc = [c]
elif c.y==maxc[0].y:
maxc.append(c)
elif direct==direct.SOUTH:
for c in curset:
if len(maxc)==0 or c.y<maxc[0].y:
maxc = [c]
elif c.y==maxc[0].y:
maxc.append(c)
elif direct==direct.EAST:
for c in curset:
if len(maxc)==0 or c.x>maxc[0].x:
maxc = [c]
elif c.x==maxc[0].x:
maxc.append(c)
elif direct==direct.WEST:
for c in curset:
if len(maxc)==0 or c.x<maxc[0].x:
maxc = [c]
elif c.x==maxc[0].x:
maxc.append(c)
newset = set(maxc)
return newset
def expand_border(curset, direct):
"""
Expand the current set of sells in a given direction.
Only return the contiguous cells.
"""
border_set = get_border(curset, direct)
next_border_set = increment_set(border_set, direct)
return next_border_set
def expand_borders(curset):
"""
Return the expansions in planar directions.
"""
north_set=expand_border(curset,direction.NORTH)
south_set=expand_border(curset,direction.SOUTH)
east_set=expand_border(curset,direction.EAST)
west_set=expand_border(curset,direction.WEST)
return(north_set, east_set, south_set, west_set)
def inflate_cell(cell, distance):
"""
Expand the current cell in all directions and return the set.
"""
newset = set(cell)
if distance==0:
return(newset)
# recursively call this based on the distance
for offset in direction.all_offsets():
# FIXME: If distance is large this will be inefficient, but it is like 1 or 2
newset.update(inflate_cell(cell+offset,distance-1))
return newset
def inflate_set(curset, distance):
"""
Expand the set in all directions by the given number of grids.
"""
if distance<=0:
return curset
newset = curset.copy()
# Add all my neighbors
for c in curset:
newset.update(direction.all_neighbors(c))
# Recurse with less depth
return inflate_set(newset,distance-1)
def flatten_set(curset):
"""
Flatten until we have a set of vector3d objects.
"""
newset = set()
for c in curset:
if isinstance(c,vector3d):
newset.add(c)
else:
newset.update(flatten_set(c))
return newset
def distance_set(coord, curset):
"""
Return the distance from a coordinate to any item in the set
"""
min_dist = math.inf
for c in curset:
min_dist = min(coord.euclidean_distance(c), min_dist)
return min_dist

View File

@ -1,689 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from openram import debug
from openram.base.vector import vector
from openram.base.vector3d import vector3d
from openram.base.pin_layout import pin_layout
from .direction import direction
class pin_group:
"""
A class to represent a group of rectangular design pin.
It requires a router to define the track widths and blockages which
determine how pin shapes get mapped to tracks.
It is initially constructed with a single set of (touching) pins.
"""
def __init__(self, name, pin_set, router):
self.name = name
# Flag for when it is routed
self.routed = False
# Flag for when it is enclosed
self.enclosed = False
# This is a list because we can have a pin
# group of disconnected sets of pins
# and these are represented by separate lists
self.pins = set(pin_set)
# Remove any redundant pins (i.e. contained in other pins)
self.remove_redundant_pins()
self.router = router
# These are the corresponding pin grids for each pin group.
self.grids = set()
# These are the secondary grids that could
# or could not be part of the pin
self.secondary_grids = set()
# The set of blocked grids due to this pin
self.blockages = set()
# This is a set of pin_layout shapes to cover the grids
self.enclosures = set()
def __str__(self):
""" override print function output """
total_string = "(pg {} ".format(self.name)
pin_string = "\n pins={}".format(self.pins)
total_string += pin_string
grids_string = "\n grids={}".format(self.grids)
total_string += grids_string
grids_string = "\n secondary={}".format(self.secondary_grids)
total_string += grids_string
if self.enclosed:
enclosure_string = "\n enclose={}".format(self.enclosures)
total_string += enclosure_string
total_string += ")"
return total_string
def add_pin(self, pin):
self.pins.add(pin)
self.remove_redundant_pins()
def __repr__(self):
""" override repr function output """
return str(self)
def size(self):
return len(self.grids)
def set_routed(self, value=True):
self.routed = value
def is_routed(self):
return self.routed
def remove_redundant_pins(self):
"""
Remove redundant pin shapes
"""
new_pin_list = self.remove_redundant_shapes(list(self.pins))
self.pins = set(new_pin_list)
def remove_redundant_shapes(self, pin_list):
"""
Remove any pin layout that is contained within another.
Returns a new list without modifying pin_list.
"""
local_debug = False
if local_debug:
debug.info(0, "INITIAL: {}".format(pin_list))
add_indices = set(range(len(pin_list)))
# This is n^2, but the number is small
for index1, pin1 in enumerate(pin_list):
# If we remove this pin, it can't contain other pins
if index1 not in add_indices:
continue
for index2, pin2 in enumerate(pin_list):
# Can't contain yourself,
# but compare the indices and not the pins
# so you can remove duplicate copies.
if index1 == index2:
continue
# If we already removed it, can't remove it again...
if index2 not in add_indices:
continue
if pin1.contains(pin2):
if local_debug:
debug.info(0, "{0} contains {1}".format(pin1, pin2))
add_indices.remove(index2)
new_pin_list = [pin_list[x] for x in add_indices]
if local_debug:
debug.info(0, "FINAL : {}".format(new_pin_list))
return new_pin_list
def compute_enclosures(self):
"""
Find the minimum rectangle enclosures of the given tracks.
"""
# Enumerate every possible enclosure
pin_list = []
for seed in self.grids:
(ll, ur) = self.enclose_pin_grids(seed,
direction.NORTH,
direction.EAST)
enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z)
pin_list.append(enclosure)
(ll, ur) = self.enclose_pin_grids(seed,
direction.EAST,
direction.NORTH)
enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z)
pin_list.append(enclosure)
if len(pin_list) == 0:
debug.error("Did not find any enclosures for {}".format(self.name))
self.router.write_debug_gds("pin_enclosure_error.gds")
# Now simplify the enclosure list
new_pin_list = self.remove_redundant_shapes(pin_list)
# Now add the right name
for pin in new_pin_list:
pin.name = self.name
debug.check(len(new_pin_list) > 0,
"Did not find any enclosures.")
return new_pin_list
def compute_connector(self, pin, enclosure):
"""
Compute a shape to connect the pin to the enclosure shape.
This assumes the shape will be the dimension of the pin.
"""
if pin.xoverlaps(enclosure):
# Is it vertical overlap, extend pin shape to enclosure
plc = pin.lc()
prc = pin.rc()
elc = enclosure.lc()
# erc = enclosure.rc()
ymin = min(plc.y, elc.y)
ymax = max(plc.y, elc.y)
ll = vector(plc.x, ymin)
ur = vector(prc.x, ymax)
elif pin.yoverlaps(enclosure):
# Is it horizontal overlap, extend pin shape to enclosure
pbc = pin.bc()
puc = pin.uc()
ebc = enclosure.bc()
# euc = enclosure.uc()
xmin = min(pbc.x, ebc.x)
xmax = max(pbc.x, ebc.x)
ll = vector(xmin, pbc.y)
ur = vector(xmax, puc.y)
else:
# Neither, so we must do a corner-to corner
pc = pin.center()
ec = enclosure.center()
xmin = min(pc.x, ec.x)
xmax = max(pc.x, ec.x)
ymin = min(pc.y, ec.y)
ymax = max(pc.y, ec.y)
ll = vector(xmin, ymin)
ur = vector(xmax, ymax)
if ll.x == ur.x or ll.y == ur.y:
return None
p = pin_layout(pin.name, [ll, ur], pin.layer)
return p
def find_above_connector(self, pin, enclosures):
"""
Find the enclosure that is to above the pin
and make a connector to it's upper edge.
"""
# Create the list of shapes that contain the pin edge
edge_list = []
for shape in enclosures:
if shape.xcontains(pin):
edge_list.append(shape)
# Sort them by their bottom edge
edge_list.sort(key=lambda x: x.by(), reverse=True)
# Find the bottom edge that is next to the pin's top edge
above_item = None
for item in edge_list:
if item.by() >= pin.uy():
above_item = item
else:
break
# There was nothing
if not above_item:
return None
# If it already overlaps, no connector needed
if above_item.overlaps(pin):
return None
# Otherwise, make a connector to the item
p = self.compute_connector(pin, above_item)
return p
def find_below_connector(self, pin, enclosures):
"""
Find the enclosure that is below the pin
and make a connector to it's upper edge.
"""
# Create the list of shapes that contain the pin edge
edge_list = []
for shape in enclosures:
if shape.xcontains(pin):
edge_list.append(shape)
# Sort them by their upper edge
edge_list.sort(key=lambda x: x.uy())
# Find the upper edge that is next to the pin's bottom edge
bottom_item = None
for item in edge_list:
if item.uy() <= pin.by():
bottom_item = item
else:
break
# There was nothing to the left
if not bottom_item:
return None
# If it already overlaps, no connector needed
if bottom_item.overlaps(pin):
return None
# Otherwise, make a connector to the item
p = self.compute_connector(pin, bottom_item)
return p
def find_left_connector(self, pin, enclosures):
"""
Find the enclosure that is to the left of the pin
and make a connector to it's right edge.
"""
# Create the list of shapes that contain the pin edge
edge_list = []
for shape in enclosures:
if shape.ycontains(pin):
edge_list.append(shape)
# Sort them by their right edge
edge_list.sort(key=lambda x: x.rx())
# Find the right edge that is to the pin's left edge
left_item = None
for item in edge_list:
if item.rx() <= pin.lx():
left_item = item
else:
break
# There was nothing to the left
if not left_item:
return None
# If it already overlaps, no connector needed
if left_item.overlaps(pin):
return None
# Otherwise, make a connector to the item
p = self.compute_connector(pin, left_item)
return p
def find_right_connector(self, pin, enclosures):
"""
Find the enclosure that is to the right of the pin
and make a connector to it's left edge.
"""
# Create the list of shapes that contain the pin edge
edge_list = []
for shape in enclosures:
if shape.ycontains(pin):
edge_list.append(shape)
# Sort them by their right edge
edge_list.sort(key=lambda x: x.lx(), reverse=True)
# Find the left edge that is next to the pin's right edge
right_item = None
for item in edge_list:
if item.lx() >= pin.rx():
right_item = item
else:
break
# There was nothing to the right
if not right_item:
return None
# If it already overlaps, no connector needed
if right_item.overlaps(pin):
return None
# Otherwise, make a connector to the item
p = self.compute_connector(pin, right_item)
return p
def find_smallest_connector(self, pin_list, shape_list):
"""
Compute all of the connectors between the overlapping
pins and enclosure shape list.
Return the smallest.
"""
smallest = None
for pin in pin_list:
for enclosure in shape_list:
new_enclosure = self.compute_connector(pin, enclosure)
if not smallest or new_enclosure.area() < smallest.area():
smallest = new_enclosure
return smallest
def find_smallest_overlapping(self, pin_list, shape_list):
"""
Find the smallest area shape in shape_list that overlaps with any
pin in pin_list by a min width.
"""
smallest_shape = None
for pin in pin_list:
overlap_shape = self.find_smallest_overlapping_pin(pin, shape_list)
if overlap_shape:
# overlap_length = pin.overlap_length(overlap_shape)
if not smallest_shape or overlap_shape.area() < smallest_shape.area():
smallest_shape = overlap_shape
return smallest_shape
def find_smallest_overlapping_pin(self, pin, shape_list):
"""
Find the smallest area shape in shape_list that overlaps with any
pin in pin_list by a min width.
"""
smallest_shape = None
zindex = self.router.get_zindex(pin.lpp[0])
(min_width, min_space) = self.router.get_layer_width_space(zindex)
# Now compare it with every other shape to check how much they overlap
for other in shape_list:
overlap_length = pin.overlap_length(other)
if overlap_length > min_width:
if not smallest_shape or other.area() < smallest_shape.area():
smallest_shape = other
return smallest_shape
def overlap_any_shape(self, pin_list, shape_list):
"""
Does the given pin overlap any of the shapes in the pin list.
"""
for pin in pin_list:
for other in shape_list:
if pin.overlaps(other):
return True
return False
def max_pin_layout(self, pin_list):
"""
Return the max area pin_layout
"""
biggest = pin_list[0]
for pin in pin_list:
if pin.area() > biggest.area():
biggest = pin
return pin
def enclose_pin_grids(self, ll, dir1=direction.NORTH, dir2=direction.EAST):
"""
This encloses a single pin component with a rectangle
starting with the seed and expanding dir1 until blocked
and then dir2 until blocked.
dir1 and dir2 should be two orthogonal directions.
"""
offset1 = direction.get_offset(dir1)
offset2 = direction.get_offset(dir2)
# We may have started with an empty set
debug.check(len(self.grids) > 0, "Cannot seed an grid empty set.")
common_blockages = self.router.get_blocked_grids() & self.grids
# Start with the ll and make the widest row
row = [ll]
# Move in dir1 while we can
while True:
next_cell = row[-1] + offset1
# Can't move if not in the pin shape
if next_cell in self.grids and next_cell not in common_blockages:
row.append(next_cell)
else:
break
# Move in dir2 while we can
while True:
next_row = [x + offset2 for x in row]
for cell in next_row:
# Can't move if any cell is not in the pin shape
if cell not in self.grids or cell in common_blockages:
break
else:
row = next_row
# Skips the second break
continue
# Breaks from the nested break
break
# Add a shape from ll to ur
ur = row[-1]
return (ll, ur)
def enclose_pin(self):
"""
If there is one set of connected pin shapes,
this will find the smallest rectangle enclosure that
overlaps with any pin.
If there is not, it simply returns all the enclosures.
"""
self.enclosed = True
# Compute the enclosure pin_layout list of the set of tracks
self.enclosures = self.compute_enclosures()
# Find a connector to every pin and add it to the enclosures
for pin in self.pins:
# If it is contained, it won't need a connector
if pin.contained_by_any(self.enclosures):
continue
# Find a connector in the cardinal directions
# If there is overlap, but it isn't contained,
# these could all be None
# These could also be none if the pin is
# diagonal from the enclosure
left_connector = self.find_left_connector(pin, self.enclosures)
right_connector = self.find_right_connector(pin, self.enclosures)
above_connector = self.find_above_connector(pin, self.enclosures)
below_connector = self.find_below_connector(pin, self.enclosures)
connector_list = [left_connector,
right_connector,
above_connector,
below_connector]
filtered_list = list(filter(lambda x: x != None, connector_list))
if (len(filtered_list) > 0):
import copy
bbox_connector = copy.copy(pin)
bbox_connector.bbox(filtered_list)
self.enclosures.append(bbox_connector)
# Now, make sure each pin touches an enclosure.
# If not, add another (diagonal) connector.
# This could only happen when there was no enclosure
# in any cardinal direction from a pin
if not self.overlap_any_shape(self.pins, self.enclosures):
connector = self.find_smallest_connector(self.pins,
self.enclosures)
if not connector:
debug.error("Could not find a connector for {} with {}".format(self.pins,
self.enclosures))
self.router.write_debug_gds("no_connector.gds")
self.enclosures.append(connector)
# At this point, the pins are overlapping,
# but there might be more than one!
overlap_set = set()
for pin in self.pins:
overlap_set.update(self.transitive_overlap(pin, self.enclosures))
# Use the new enclosures and recompute the grids
# that correspond to them
if len(overlap_set) < len(self.enclosures):
self.enclosures = overlap_set
self.grids = set()
# Also update the grid locations with the new
# (possibly pruned) enclosures
for enclosure in self.enclosures:
(sufficient, insufficient) = self.router.convert_pin_to_tracks(self.name,
enclosure)
self.grids.update(sufficient)
debug.info(3, "Computed enclosure(s) {0}\n {1}\n {2}\n {3}".format(self.name,
self.pins,
self.grids,
self.enclosures))
def transitive_overlap(self, shape, shape_list):
"""
Given shape, find the elements in shape_list that overlap transitively.
I.e. if shape overlaps A and A overlaps B, return both A and B.
"""
augmented_shape_list = set(shape_list)
old_connected_set = set()
connected_set = set([shape])
# Repeat as long as we expand the set
while len(connected_set) > len(old_connected_set):
old_connected_set = connected_set
connected_set = set([shape])
for old_shape in old_connected_set:
for cur_shape in augmented_shape_list:
if old_shape.overlaps(cur_shape):
connected_set.add(cur_shape)
# Remove the original shape
connected_set.remove(shape)
# if len(connected_set)<len(shape_list):
# import pprint
# print("S: ",shape)
# pprint.pprint(shape_list)
# pprint.pprint(connected_set)
return connected_set
def add_enclosure(self, cell):
"""
Add the enclosure shape to the given cell.
"""
for enclosure in self.enclosures:
debug.info(4, "Adding enclosure {0} {1}".format(self.name,
enclosure))
cell.add_rect(layer=enclosure.layer,
offset=enclosure.ll(),
width=enclosure.width(),
height=enclosure.height())
def perimeter_grids(self):
"""
Return a list of the grids on the perimeter.
This assumes that we have a single contiguous shape.
"""
perimeter_set = set()
cardinal_offsets = direction.cardinal_offsets()
for g1 in self.grids:
neighbor_grids = [g1 + offset for offset in cardinal_offsets]
neighbor_count = sum([x in self.grids for x in neighbor_grids])
# If we aren't completely enclosed, we are on the perimeter
if neighbor_count < 4:
perimeter_set.add(g1)
return perimeter_set
def adjacent(self, other):
"""
Chck if the two pin groups have at least one adjacent pin grid.
"""
# We could optimize this to just check the boundaries
for g1 in self.perimeter_grids():
for g2 in other.perimeter_grids():
if g1.adjacent(g2):
return True
return False
def adjacent_grids(self, other, separation):
"""
Determine the sets of grids that are within a separation distance
of any grid in the other set.
"""
# We could optimize this to just check the boundaries
adj_grids = set()
for g1 in self.grids:
for g2 in other.grids:
if g1.distance(g2) <= separation:
adj_grids.add(g1)
return adj_grids
def convert_pin(self):
"""
Convert the list of pin shapes into sets of routing grids.
The secondary set of grids are "optional" pin shapes that
should be either blocked or part of the pin.
"""
# Set of tracks that overlap a pin
pin_set = set()
# Set of track adjacent to or paritally overlap a pin (not full DRC connection)
partial_set = set()
# for pin in self.pins:
# lx = pin.lx()
# ly = pin.by()
# if lx > 87.9 and lx < 87.99 and ly > 18.56 and ly < 18.6:
# breakpoint()
for pin in self.pins:
debug.info(4, " Converting {0}".format(pin))
# Determine which tracks the pin overlaps
(sufficient, insufficient) = self.router.convert_pin_to_tracks(self.name,
pin)
pin_set.update(sufficient)
partial_set.update(insufficient)
# Blockages will be a super-set of pins since
# it uses the inflated pin shape.
blockage_in_tracks = self.router.convert_blockage(pin)
# Must include the pins here too because these are computed in a different
# way than blockages.
blockages = sufficient | insufficient | blockage_in_tracks
self.blockages.update(blockages)
# If we have a blockage, we must remove the grids
# Remember, this excludes the pin blockages already
blocked_grids = self.router.get_blocked_grids()
pin_set.difference_update(blocked_grids)
partial_set.difference_update(blocked_grids)
# At least one of the groups must have some valid tracks
if (len(pin_set) == 0 and len(partial_set) == 0):
# debug.warning("Pin is very close to metal blockage.\nAttempting to expand blocked pin {}".format(self.pins))
for pin in self.pins:
debug.warning(" Expanding conversion {0}".format(pin))
# Determine which tracks the pin overlaps
(sufficient, insufficient) = self.router.convert_pin_to_tracks(self.name,
pin,
expansion=1)
# This time, don't remove blockages in the hopes that it might be ok.
# Could cause DRC problems!
pin_set.update(sufficient)
partial_set.update(insufficient)
# If it's still empty, we must bail.
if len(pin_set) == 0 and len(partial_set) == 0:
debug.error("Unable to find unblocked pin {} {}".format(self.name,
self.pins))
self.router.write_debug_gds("blocked_pin.gds")
# Consider the fully connected set first and if not the partial set
# if len(pin_set) > 0:
# self.grids = pin_set
# else:
# self.grids = partial_set
# Just using the full set simplifies the enclosures, otherwise
# we get some pin enclose DRC errors due to off grid pins
self.grids = pin_set | partial_set
if len(self.grids) < 0:
debug.error("Did not find any unblocked grids: {}".format(str(self.pins)))
self.router.write_debug_gds("blocked_pin.gds")
# Remember the secondary grids for removing adjacent pins
self.secondary_grids = partial_set
debug.info(4, " pins {}".format(self.grids))
debug.info(4, " secondary {}".format(self.secondary_grids))

File diff suppressed because it is too large Load Diff

View File

@ -1,104 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from datetime import datetime
from openram import debug
from openram import print_time
from .router import router
from .signal_grid import signal_grid
class signal_escape_router(router):
"""
A router that routes signals to perimeter and makes pins.
"""
def __init__(self, layers, design, bbox=None, margin=0):
"""
This will route on layers in design. It will get the blockages from
either the gds file name or the design itself (by saving to a gds file).
"""
router.__init__(self,
layers=layers,
design=design,
bbox=bbox,
margin=margin)
def perimeter_dist(self, pin_name):
"""
Return the shortest Manhattan distance to the bounding box perimeter.
"""
loc = self.cell.get_pin(pin_name).center()
x_dist = min(loc.x - self.ll.x, self.ur.x - loc.x)
y_dist = min(loc.y - self.ll.y, self.ur.y - loc.y)
return min(x_dist, y_dist)
def escape_route(self, pin_names):
"""
Takes a list of tuples (name, side) and routes them. After routing,
it removes the old pin and places a new one on the perimeter.
"""
self.create_routing_grid(signal_grid)
start_time = datetime.now()
self.find_pins_and_blockages(pin_names)
print_time("Finding pins and blockages",datetime.now(), start_time, 3)
# Order the routes by closest to the perimeter first
# This prevents some pins near the perimeter from being blocked by other pins
ordered_pin_names = sorted(pin_names, key=lambda x: self.perimeter_dist(x))
# Route the supply pins to the supply rails
# Route vdd first since we want it to be shorter
start_time = datetime.now()
for pin_name in ordered_pin_names:
self.route_signal(pin_name)
# if pin_name == "dout0[1]":
# self.write_debug_gds("postroute.gds", True)
print_time("Maze routing pins",datetime.now(), start_time, 3)
#self.write_debug_gds("final_escape_router.gds",False)
return True
def route_signal(self, pin_name, side="all"):
for detour_scale in [5 * pow(2, x) for x in range(5)]:
debug.info(1, "Escape routing {0} with scale {1}".format(pin_name, detour_scale))
# Clear everything in the routing grid.
self.rg.reinit()
# This is inefficient since it is non-incremental, but it was
# easier to debug.
self.prepare_blockages()
self.clear_blockages(pin_name)
# Add the single component of the pin as the source
# which unmarks it as a blockage too
self.add_source(pin_name)
# Marks the grid cells all along the perimeter as a target
self.add_perimeter_target(side)
# if pin_name == "dout0[3]":
# self.write_debug_gds("pre_route.gds", False)
# breakpoint()
# Actually run the A* router
if self.run_router(detour_scale=detour_scale):
new_pin = self.get_perimeter_pin()
self.cell.replace_layout_pin(pin_name, new_pin)
return
# if pin_name == "dout0[3]":
# self.write_debug_gds("pre_route.gds", False)
# breakpoint()
self.write_debug_gds("debug_route.gds", True)

View File

@ -1,165 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from copy import deepcopy
from heapq import heappush,heappop
from openram import debug
from openram.base.vector3d import vector3d
from .grid import grid
from .grid_path import grid_path
class signal_grid(grid):
"""
Expand the two layer grid to include A* search functions for a source and target.
"""
def __init__(self, ll, ur, track_factor):
""" Create a routing map of width x height cells and 2 in the z-axis. """
grid.__init__(self, ll, ur, track_factor)
def reinit(self):
""" Reinitialize everything for a new route. """
# Reset all the cells in the map
for p in self.map.values():
p.reset()
self.clear_source()
self.clear_target()
def init_queue(self):
"""
Populate the queue with all the source pins with cost
to the target. Each item is a path of the grid cells.
We will use an A* search, so this cost must be pessimistic.
Cost so far will be the length of the path.
"""
# Counter is used to not require data comparison in Python 3.x
# Items will be returned in order they are added during cost ties
self.q = []
self.counter = 0
for s in self.source:
cost = self.cost_to_target(s)
debug.info(3, "Init: cost=" + str(cost) + " " + str([s]))
heappush(self.q, (cost, self.counter, grid_path([vector3d(s)])))
self.counter += 1
def route(self, detour_scale):
"""
This does the A* maze routing with preferred direction routing.
This only works for 1 track wide routes!
"""
# We set a cost bound of the HPWL for run-time. This can be
# over-ridden if the route fails due to pruning a feasible solution.
any_source_element = next(iter(self.source))
cost_bound = detour_scale * self.cost_to_target(any_source_element) * grid.PREFERRED_COST
# Check if something in the queue is already a source and a target!
for s in self.source:
if self.is_target(s):
return((grid_path([vector3d(s)]), 0))
# Put the source items into the queue
self.init_queue()
# Keep expanding and adding to the priority queue until we are done
while len(self.q)>0:
# should we keep the path in the queue as well or just the final node?
(cost, count, curpath) = heappop(self.q)
debug.info(3, "Queue size: size=" + str(len(self.q)) + " " + str(cost))
debug.info(4, "Expanding: cost=" + str(cost) + " " + str(curpath))
# expand the last element
neighbors = self.expand_dirs(curpath)
debug.info(4, "Neighbors: " + str(neighbors))
for n in neighbors:
# make a new copy of the path to not update the old ones
newpath = deepcopy(curpath)
# node is added to the map by the expand routine
newpath.append(n)
# check if we hit the target and are done
if self.is_target(n[0]): # This uses the [0] item because we are assuming 1-track wide
return (newpath, newpath.cost())
else:
# current path cost + predicted cost
current_cost = newpath.cost()
target_cost = self.cost_to_target(n[0])
predicted_cost = current_cost + target_cost
# only add the cost if it is less than our bound
if (predicted_cost < cost_bound):
if (self.map[n[0]].min_cost==-1 or predicted_cost<self.map[n[0]].min_cost):
self.map[n[0]].min_path = newpath
self.map[n[0]].min_cost = predicted_cost
debug.info(4, "Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath))
# add the cost to get to this point if we haven't reached it yet
heappush(self.q, (predicted_cost, self.counter, newpath))
self.counter += 1
#else:
# print("Better previous cost.")
#else:
# print("Cost bounded")
return (None, None)
def expand_dirs(self, curpath):
"""
Expand each of the four cardinal directions plus up or down
but not expanding to blocked cells. Expands in all directions
regardless of preferred directions.
"""
# Expand all directions.
neighbors = curpath.expand_dirs()
# Filter the out of region ones
# Filter the blocked ones
valid_neighbors = [x for x in neighbors if self.is_inside(x) and not self.is_blocked(x)]
return valid_neighbors
def hpwl(self, src, dest):
"""
Return half perimeter wire length from point to another.
Either point can have positive or negative coordinates.
Include the via penalty if there is one.
"""
hpwl = abs(src.x - dest.x)
hpwl += abs(src.y - dest.y)
if src.x!=dest.x and src.y!=dest.y:
hpwl += grid.VIA_COST
return hpwl
def cost_to_target(self, source):
"""
Find the cheapest HPWL distance to any target point ignoring
blockages for A* search.
"""
any_target_element = next(iter(self.target))
cost = self.hpwl(source, any_target_element)
for t in self.target:
cost = min(self.hpwl(source, t), cost)
return cost
def get_inertia(self, p0, p1):
"""
Sets the direction based on the previous direction we came from.
"""
# direction (index) of movement
if p0.x==p1.x:
return 1
elif p0.y==p1.y:
return 0
else:
# z direction
return 2

View File

@ -1,69 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from openram import debug
from openram.router import router
class signal_router(router):
"""
A router class to read an obstruction map from a gds and plan a
route on a given layer. This is limited to two layer routes.
"""
def __init__(self, layers, design, bbox=None):
"""
This will route on layers in design. It will get the blockages from
either the gds file name or the design itself (by saving to a gds file).
"""
router.__init__(self, layers, design, bbox)
def route(self, src, dest, detour_scale=5):
"""
Route a single source-destination net and return
the simplified rectilinear path. Cost factor is how sub-optimal to explore for a feasible route.
This is used to speed up the routing when there is not much detouring needed.
"""
debug.info(1, "Running signal router from {0} to {1}...".format(src, dest))
self.pins[src] = []
self.pins[dest] = []
# Clear the pins if we have previously routed
if (hasattr(self, 'rg')):
self.clear_pins()
else:
# Creat a routing grid over the entire area
# FIXME: This could be created only over the routing region,
# but this is simplest for now.
self.create_routing_grid(signal_grid)
# Get the pin shapes
self.find_pins_and_blockages([src, dest])
# Block everything
self.prepare_blockages()
# Clear the pins we are routing
self.set_blockages(self.pin_components[src], False)
self.set_blockages(self.pin_components[dest], False)
# Now add the src/tgt if they are not blocked by other shapes
self.add_source(src)
self.add_target(dest)
if not self.run_router(detour_scale=detour_scale):
self.write_debug_gds(stop_program=False)
return False
#self.write_debug_gds(stop_program=False)
return True

View File

@ -1,79 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from .signal_grid import signal_grid
from .grid_path import grid_path
class supply_grid(signal_grid):
"""
This routes a supply grid. It is derived from a signal grid because it still
routes the pins to the supply rails using the same routines.
It has a few extra routines to support "waves" which are multiple track wide
directional routes (no bends).
"""
def __init__(self, ll, ur, track_width):
""" Create a routing map of width x height cells and 2 in the z-axis. """
signal_grid.__init__(self, ll, ur, track_width)
def reinit(self):
""" Reinitialize everything for a new route. """
self.source = set()
self.target = set()
# Reset all the cells in the map
for p in self.map.values():
p.reset()
def find_start_wave(self, wave, direct):
"""
Finds the first loc starting at loc and up that is open.
Returns None if it reaches max size first.
"""
# Don't expand outside the bounding box
if wave[0].x > self.ur.x:
return None
if wave[-1].y > self.ur.y:
return None
while wave and self.is_wave_blocked(wave):
wf = grid_path(wave)
wave = wf.neighbor(direct)
# Bail out if we couldn't increment futher
if wave[0].x > self.ur.x or wave[-1].y > self.ur.y:
return None
# Return a start if it isn't blocked
if not self.is_wave_blocked(wave):
return wave
return wave
def is_wave_blocked(self, wave):
"""
Checks if any of the locations are blocked
"""
for v in wave:
if self.is_blocked(v):
return True
else:
return False
def probe(self, wave, direct):
"""
Expand the wave until there is a blockage and return
the wave path.
"""
wave_path = grid_path()
while wave and not self.is_wave_blocked(wave):
if wave[0].x > self.ur.x or wave[-1].y > self.ur.y:
break
wave_path.append(wave)
wave = wave_path.neighbor(direct)
return wave_path

View File

@ -1,394 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from datetime import datetime
from openram import debug
from openram.base.vector3d import vector3d
from openram import print_time
from .router import router
from .direction import direction
from .supply_grid import supply_grid
from . import grid_utils
class supply_grid_router(router):
"""
A router class to read an obstruction map from a gds and
routes a grid to connect the supply on the two layers.
"""
def __init__(self, layers, design, bbox=None, pin_type=None):
"""
This will route on layers in design. It will get the blockages from
either the gds file name or the design itself (by saving to a gds file).
"""
start_time = datetime.now()
# Power rail width in minimum wire widths
self.route_track_width = 1
router.__init__(self, layers, design, bbox=bbox, margin=margin, route_track_width=self.route_track_width)
# The list of supply rails (grid sets) that may be routed
self.supply_rails = {}
# This is the same as above but as a sigle set for the all the rails
self.supply_rail_tracks = {}
print_time("Init supply router", datetime.now(), start_time, 3)
def route(self, vdd_name="vdd", gnd_name="gnd"):
"""
Add power supply rails and connect all pins to these rails.
"""
debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
self.vdd_name = vdd_name
self.gnd_name = gnd_name
# Clear the pins if we have previously routed
if (hasattr(self, 'rg')):
self.clear_pins()
else:
# Creat a routing grid over the entire area
# FIXME: This could be created only over the routing region,
# but this is simplest for now.
self.create_routing_grid(supply_grid)
# Get the pin shapes
start_time = datetime.now()
self.find_pins_and_blockages([self.vdd_name, self.gnd_name])
print_time("Finding pins and blockages", datetime.now(), start_time, 3)
# Add the supply rails in a mesh network and connect H/V with vias
start_time = datetime.now()
# Block everything
self.prepare_blockages()
self.clear_blockages(self.gnd_name)
# Determine the rail locations
self.route_supply_rails(self.gnd_name, 0)
# Block everything
self.prepare_blockages()
self.clear_blockages(self.vdd_name)
# Determine the rail locations
self.route_supply_rails(self.vdd_name, 1)
print_time("Routing supply rails", datetime.now(), start_time, 3)
start_time = datetime.now()
self.route_simple_overlaps(vdd_name)
self.route_simple_overlaps(gnd_name)
print_time("Simple overlap routing", datetime.now(), start_time, 3)
# Route the supply pins to the supply rails
# Route vdd first since we want it to be shorter
start_time = datetime.now()
self.route_pins_to_rails(vdd_name)
self.route_pins_to_rails(gnd_name)
print_time("Maze routing supplies", datetime.now(), start_time, 3)
# self.write_debug_gds("final.gds", False)
# Did we route everything??
if not self.check_all_routed(vdd_name):
return False
if not self.check_all_routed(gnd_name):
return False
return True
def route_simple_overlaps(self, pin_name):
"""
This checks for simple cases where a pin component already overlaps a supply rail.
It will add an enclosure to ensure the overlap in wide DRC rule cases.
"""
debug.info(1, "Routing simple overlap pins for {0}".format(pin_name))
# These are the wire tracks
wire_tracks = self.supply_rail_tracks[pin_name]
routed_count=0
for pg in self.pin_groups[pin_name]:
if pg.is_routed():
continue
# First, check if we just overlap, if so, we are done.
overlap_grids = wire_tracks & pg.grids
if len(overlap_grids)>0:
routed_count += 1
pg.set_routed()
continue
# Else, if we overlap some of the space track, we can patch it with an enclosure
# pg.create_simple_overlap_enclosure(pg.grids)
# pg.add_enclosure(self.cell)
debug.info(1, "Routed {} simple overlap pins".format(routed_count))
def finalize_supply_rails(self, name):
"""
Determine which supply rails overlap and can accomodate a via.
Remove any supply rails that do not have a via since they are disconnected.
NOTE: It is still possible though unlikely that there are disconnected groups of rails.
"""
all_rails = self.supply_rails[name]
connections = set()
via_areas = []
for i1, r1 in enumerate(all_rails):
# Only consider r1 horizontal rails
e = next(iter(r1))
if e.z==1:
continue
# We need to move this rail to the other layer for the z indices to match
# during the intersection. This also makes a copy.
new_r1 = {vector3d(i.x, i.y, 1) for i in r1}
for i2, r2 in enumerate(all_rails):
# Never compare to yourself
if i1==i2:
continue
# Only consider r2 vertical rails
e = next(iter(r2))
if e.z==0:
continue
# Determine if we have sufficient overlap and, if so,
# remember:
# the indices to determine a rail is connected to another
# the overlap area for placement of a via
overlap = new_r1 & r2
if len(overlap) >= 1:
debug.info(3, "Via overlap {0} {1}".format(len(overlap),overlap))
connections.update([i1, i2])
via_areas.append(overlap)
# Go through and add the vias at the center of the intersection
for area in via_areas:
ll = grid_utils.get_lower_left(area)
ur = grid_utils.get_upper_right(area)
center = (ll + ur).scale(0.5, 0.5, 0)
self.add_via(center, 1)
# Determien which indices were not connected to anything above
missing_indices = set([x for x in range(len(self.supply_rails[name]))])
missing_indices.difference_update(connections)
# Go through and remove those disconnected indices
# (No via was added, so that doesn't need to be removed)
for rail_index in sorted(missing_indices, reverse=True):
ll = grid_utils.get_lower_left(all_rails[rail_index])
ur = grid_utils.get_upper_right(all_rails[rail_index])
debug.info(1, "Removing disconnected supply rail {0} .. {1}".format(ll, ur))
self.supply_rails[name].pop(rail_index)
# Make the supply rails into a big giant set of grids for easy blockages.
# Must be done after we determine which ones are connected.
self.create_supply_track_set(name)
def add_supply_rails(self, name):
"""
Add the shapes that represent the routed supply rails.
This is after the paths have been pruned and only include rails that are
connected with vias.
"""
for rail in self.supply_rails[name]:
ll = grid_utils.get_lower_left(rail)
ur = grid_utils.get_upper_right(rail)
z = ll.z
pin = self.compute_pin_enclosure(ll, ur, z, name)
debug.info(3, "Adding supply rail {0} {1}->{2} {3}".format(name, ll, ur, pin))
self.cell.add_layout_pin(text=name,
layer=pin.layer,
offset=pin.ll(),
width=pin.width(),
height=pin.height())
def compute_supply_rails(self, name, supply_number):
"""
Compute the unblocked locations for the horizontal and vertical supply rails.
Go in a raster order from bottom to the top (for horizontal) and left to right
(for vertical). Start with an initial start_offset in x and y direction.
"""
self.supply_rails[name]=[]
max_yoffset = self.rg.ur.y
max_xoffset = self.rg.ur.x
min_yoffset = self.rg.ll.y
min_xoffset = self.rg.ll.x
# Horizontal supply rails
start_offset = min_yoffset + supply_number
for offset in range(start_offset, max_yoffset, 2):
# Seed the function at the location with the given width
wave = [vector3d(min_xoffset, offset, 0)]
# While we can keep expanding east in this horizontal track
while wave and wave[0].x < max_xoffset:
added_rail = self.find_supply_rail(name, wave, direction.EAST)
if not added_rail:
# Just seed with the next one
wave = [x+vector3d(1, 0, 0) for x in wave]
else:
# Seed with the neighbor of the end of the last rail
wave = added_rail.neighbor(direction.EAST)
# Vertical supply rails
start_offset = min_xoffset + supply_number
for offset in range(start_offset, max_xoffset, 2):
# Seed the function at the location with the given width
wave = [vector3d(offset, min_yoffset, 1)]
# While we can keep expanding north in this vertical track
while wave and wave[0].y < max_yoffset:
added_rail = self.find_supply_rail(name, wave, direction.NORTH)
if not added_rail:
# Just seed with the next one
wave = [x + vector3d(0, 1, 0) for x in wave]
else:
# Seed with the neighbor of the end of the last rail
wave = added_rail.neighbor(direction.NORTH)
def find_supply_rail(self, name, seed_wave, direct):
"""
Find a start location, probe in the direction, and see if the rail is big enough
to contain a via, and, if so, add it.
"""
# Sweep to find an initial unblocked valid wave
start_wave = self.rg.find_start_wave(seed_wave, direct)
# This means there were no more unblocked grids in the row/col
if not start_wave:
return None
wave_path = self.probe_supply_rail(name, start_wave, direct)
self.approve_supply_rail(name, wave_path)
# Return the rail whether we approved it or not,
# as it will be used to find the next start location
return wave_path
def probe_supply_rail(self, name, start_wave, direct):
"""
This finds the first valid starting location and routes a supply rail
in the given direction.
It returns the space after the end of the rail to seed another call for multiple
supply rails in the same "track" when there is a blockage.
"""
# Expand the wave to the right
wave_path = self.rg.probe(start_wave, direct)
if not wave_path:
return None
# drop the first and last steps to leave escape routing room
# around the blockage that stopped the probe
# except, don't drop the first if it is the first in a row/column
if (direct==direction.NORTH and start_wave[0].y>0):
wave_path.trim_first()
elif (direct == direction.EAST and start_wave[0].x>0):
wave_path.trim_first()
wave_path.trim_last()
return wave_path
def approve_supply_rail(self, name, wave_path):
"""
Check if the supply rail is sufficient (big enough) and add it to the
data structure. Return whether it was added or not.
"""
# We must have at least 2 tracks to drop plus 2 tracks for a via
if len(wave_path) >= 4 * self.route_track_width:
grid_set = wave_path.get_grids()
self.supply_rails[name].append(grid_set)
return True
return False
def route_supply_rails(self, name, supply_number):
"""
Route the horizontal and vertical supply rails across the entire design.
Must be done with lower left at 0,0
"""
debug.info(1, "Routing supply rail {0}.".format(name))
# Compute the grid locations of the supply rails
self.compute_supply_rails(name, supply_number)
# Add the supply rail vias (and prune disconnected rails)
self.finalize_supply_rails(name)
# Add the rails themselves
self.add_supply_rails(name)
def create_supply_track_set(self, pin_name):
"""
Make a single set of all the tracks for the rail and wire itself.
"""
rail_set = set()
for rail in self.supply_rails[pin_name]:
rail_set.update(rail)
self.supply_rail_tracks[pin_name] = rail_set
def route_pins_to_rails(self, pin_name):
"""
This will route each of the remaining pin components to the supply rails.
After it is done, the cells are added to the pin blockage list.
"""
remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name])
debug.info(1, "Maze routing {0} with {1} pin components to connect.".format(pin_name,
remaining_components))
for index, pg in enumerate(self.pin_groups[pin_name]):
if pg.is_routed():
continue
debug.info(3, "Routing component {0} {1}".format(pin_name, index))
# Clear everything in the routing grid.
self.rg.reinit()
# This is inefficient since it is non-incremental, but it was
# easier to debug.
self.prepare_blockages()
self.clear_blockages(self.vdd_name)
# Add the single component of the pin as the source
# which unmarks it as a blockage too
self.add_pin_component_source(pin_name, index)
# Add all of the rails as targets
# Don't add the other pins, but we could?
self.add_supply_rail_target(pin_name)
# Actually run the A* router
if not self.run_router(detour_scale=5):
self.write_debug_gds("debug_route.gds")
# if index==3 and pin_name=="vdd":
# self.write_debug_gds("route.gds",False)
def add_supply_rail_target(self, pin_name):
"""
Add the supply rails of given name as a routing target.
"""
debug.info(4, "Add supply rail target {}".format(pin_name))
# Add the wire itself as the target
self.rg.set_target(self.supply_rail_tracks[pin_name])
# But unblock all the rail tracks including the space
self.rg.set_blocked(self.supply_rail_tracks[pin_name], False)
def set_supply_rail_blocked(self, value=True):
"""
Add the supply rails of given name as a routing target.
"""
debug.info(4, "Blocking supply rail")
for rail_name in self.supply_rail_tracks:
self.rg.set_blocked(self.supply_rail_tracks[rail_name])

View File

@ -7,11 +7,11 @@ from openram import debug
from openram.base.vector import vector
from openram import OPTS
from .graph import graph
from .graph_router import graph_router
from .router import router
from .graph_shape import graph_shape
class supply_graph_router(graph_router):
class supply_router(router):
"""
This is the supply router that uses the Hanan grid graph method.
"""

View File

@ -1,208 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from datetime import datetime
from scipy.sparse import csr_matrix
from scipy.sparse.csgraph import minimum_spanning_tree
from openram import debug
from openram import print_time
from .router import router
from . import grid_utils
from .signal_grid import signal_grid
class supply_tree_router(router):
"""
A router class to read an obstruction map from a gds and
routes a grid to connect the supply on the two layers.
"""
def __init__(self, layers, design, bbox=None, pin_type=None):
"""
This will route on layers in design. It will get the blockages from
either the gds file name or the design itself (by saving to a gds file).
"""
# Power rail width in minimum wire widths
# This is set to match the signal router so that the grids are aligned
# for prettier routes.
self.route_track_width = 1
# The pin escape router already made the bounding box big enough,
# so we can use the regular bbox here.
if pin_type:
debug.check(pin_type in ["left", "right", "top", "bottom", "single", "ring"],
"Invalid pin type {}".format(pin_type))
self.pin_type = pin_type
router.__init__(self,
layers,
design,
bbox=bbox,
route_track_width=self.route_track_width)
def route(self, vdd_name="vdd", gnd_name="gnd"):
"""
Route the two nets in a single layer.
Setting pin stripe will make a power rail on the left side.
"""
debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
self.vdd_name = vdd_name
self.gnd_name = gnd_name
# Clear the pins if we have previously routed
if (hasattr(self, 'rg')):
self.clear_pins()
else:
# Creat a routing grid over the entire area
# FIXME: This could be created only over the routing region,
# but this is simplest for now.
self.create_routing_grid(signal_grid)
start_time = datetime.now()
# Get the pin shapes
self.find_pins_and_blockages([self.vdd_name, self.gnd_name])
print_time("Finding pins and blockages", datetime.now(), start_time, 3)
# Add side pins if enabled
if self.pin_type in ["left", "right", "top", "bottom"]:
self.add_side_supply_pin(self.vdd_name, side=self.pin_type)
self.add_side_supply_pin(self.gnd_name, side=self.pin_type)
elif self.pin_type == "ring":
self.add_ring_supply_pin(self.vdd_name)
self.add_ring_supply_pin(self.gnd_name)
#self.write_debug_gds("initial_tree_router.gds",False)
#breakpoint()
# Route the supply pins to the supply rails
# Route vdd first since we want it to be shorter
start_time = datetime.now()
self.route_pins(vdd_name)
self.route_pins(gnd_name)
print_time("Maze routing supplies", datetime.now(), start_time, 3)
# Did we route everything??
if not self.check_all_routed(vdd_name):
return False
if not self.check_all_routed(gnd_name):
return False
return True
def route_pins(self, pin_name):
"""
This will route each of the remaining pin components to the other pins.
After it is done, the cells are added to the pin blockage list.
"""
remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name])
debug.info(1, "Routing {0} with {1} pins.".format(pin_name,
remaining_components))
# Save pin center locations
if False:
debug.info(2, "Creating location file {0}_{1}.csv".format(self.cell.name, pin_name))
f = open("{0}_{1}.csv".format(self.cell.name, pin_name), "w")
pin_size = len(self.pin_groups[pin_name])
for index1, pg1 in enumerate(self.pin_groups[pin_name]):
location = list(pg1.grids)[0]
f.write("{0},{1},{2}\n".format(location.x, location.y, location.z))
f.close()
# Create full graph
debug.info(2, "Creating adjacency matrix")
pin_size = len(self.pin_groups[pin_name])
adj_matrix = [[0] * pin_size for i in range(pin_size)]
for index1, pg1 in enumerate(self.pin_groups[pin_name]):
for index2, pg2 in enumerate(self.pin_groups[pin_name]):
if index1>=index2:
continue
dist = int(grid_utils.distance_set(list(pg1.grids)[0], pg2.grids))
adj_matrix[index1][index2] = dist
# Find MST
debug.info(2, "Finding Minimum Spanning Tree")
X = csr_matrix(adj_matrix)
from scipy.sparse import save_npz
#print("Saving {}.npz".format(self.cell.name))
#save_npz("{}.npz".format(self.cell.name), X)
#exit(1)
Tcsr = minimum_spanning_tree(X)
mst = Tcsr.toarray().astype(int)
connections = []
for x in range(pin_size):
for y in range(pin_size):
if x >= y:
continue
if mst[x][y]>0:
connections.append((x, y))
# Route MST components
level=99
for index, (src, dest) in enumerate(connections):
if not (index % 25):
debug.info(1, "{0} supply segments routed, {1} remaining.".format(index, len(connections) - index))
self.route_signal(pin_name, src, dest)
if False and pin_name == "gnd":
debug.info(level, "\nSRC {}: ".format(src) + str(self.pin_groups[pin_name][src].grids) + str(self.pin_groups[pin_name][src].blockages))
debug.info(level, ("DST {}: ".format(dest) + str(self.pin_groups[pin_name][dest].grids) + str(self.pin_groups[pin_name][dest].blockages)))
self.write_debug_gds("post_{0}_{1}.gds".format(src, dest), False)
#self.write_debug_gds("final_tree_router_{}.gds".format(pin_name), False)
#return
def route_signal(self, pin_name, src_idx, dest_idx):
# First pass, try to route normally
# Second pass, clear prior pin blockages so that you can route over other metal
# of the same supply. Otherwise, this can create a lot of circular routes due to accidental overlaps.
for unblock_routes in [False, True]:
for detour_scale in [2 * pow(2, x) for x in range(5)]:
debug.info(2, "Routing {0} to {1} with scale {2}".format(src_idx, dest_idx, detour_scale))
# Clear everything in the routing grid.
self.rg.reinit()
# This is inefficient since it is non-incremental, but it was
# easier to debug.
self.prepare_blockages(src=(pin_name, src_idx), dest=(pin_name, dest_idx))
if unblock_routes:
msg = "Unblocking supply self blockages to improve access (may cause DRC errors):\n{0}\n{1})"
debug.warning(msg.format(pin_name,
self.pin_groups[pin_name][src_idx].pins))
self.set_blockages(self.path_blockages, False)
# Add the single component of the pin as the source
# which unmarks it as a blockage too
self.set_pin_component_source(pin_name, src_idx)
# Marks all pin components except index as target
# which unmarks it as a blockage too
self.set_pin_component_target(pin_name, dest_idx)
# Actually run the A* router
if self.run_router(detour_scale=detour_scale):
return
#if detour_scale > 2:
# self.write_debug_gds("route_{0}_{1}_d{2}.gds".format(src_idx, dest_idx, detour_scale), False)
self.write_debug_gds("debug_route.gds", True)
def add_io_pin(self, instance, pin_name, new_name=""):
"""
Add a signle input or output pin up to metal 3.
"""
pin = instance.get_pins(pin_name)
if new_name == "":
new_name = pin_name
# Just use the power pin function for now to save code
self.add_power_pin(name=new_name, loc=pin.center(), start_layer=pin.layer)