mirror of https://github.com/VLSIDA/OpenRAM.git
Remove grid-based routers and replace them with the gridless router
This commit is contained in:
parent
da24c52c52
commit
6c70396a05
|
|
@ -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 *
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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])
|
||||
|
|
@ -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.
|
||||
"""
|
||||
|
|
@ -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)
|
||||
Loading…
Reference in New Issue