merge dev

This commit is contained in:
Jesse Cirimelli-Low 2023-09-02 12:27:38 -07:00
commit 5605154cc2
8 changed files with 343 additions and 92 deletions

View File

@ -1 +1 @@
1.2.29
1.2.33

63
compiler/router/bbox.py Normal file
View File

@ -0,0 +1,63 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved.
#
from openram.base.vector import vector
from .graph_utils import snap
class bbox:
"""
This class represents a bounding box object that is used in `bbox_node`
class. We are using bbox objects to group shapes in the router graphs.
"""
def __init__(self, shape=None):
self.shape = shape
self.rect = None
if self.shape:
self.rect = self.shape.rect
def area(self):
""" Return the area of this bbox. """
ll, ur = self.rect
width = ur.x - ll.x
height = ur.y - ll.y
return snap(width * height)
def merge(self, other):
""" Return the bbox created by merging two bbox objects. """
ll, ur = self.rect
oll, our = other.rect
min_x = min(ll.x, oll.x)
max_x = max(ur.x, our.x)
min_y = min(ll.y, oll.y)
max_y = max(ur.y, our.y)
rect = [vector(min_x, min_y), vector(max_x, max_y)]
merged = bbox()
merged.rect = rect
return merged
def overlap(self, other):
""" Return the bbox created by overlapping two bbox objects. """
ll, ur = self.rect
oll, our = other.rect
min_x = max(ll.x, oll.x)
max_x = min(ur.x, our.x)
min_y = max(ll.y, oll.y)
max_y = min(ur.y, our.y)
if max_x >= min_x and max_y >= min_y:
rect = [vector(min_x, min_y), vector(max_x, max_y)]
else:
return None
overlapped = bbox()
overlapped.rect = rect
return overlapped

View File

@ -0,0 +1,117 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved.
#
from .bbox import bbox
class bbox_node:
"""
This class represents a node in the bbox tree structure. Bbox trees are
binary trees we use to partition the shapes in the routing region so that
we can detect overlaps faster in a binary search-like manner.
"""
def __init__(self, bbox, left=None, right=None):
self.bbox = bbox
self.is_leaf = not left and not right
self.left = left
self.right = right
def iterate_point(self, point, check_done=False):
""" Iterate over shapes in the tree that overlap the given point. """
px, py = point.x, point.y
# Return this shape if it's a leaf
if self.is_leaf:
ll, ur = self.bbox.rect
if check_done or (ll.x <= px and px <= ur.x and ll.y <= py and py <= ur.y):
yield self.bbox.shape
else:
# Check the left child
if self.left:
ll, ur = self.left.bbox.rect
if ll.x <= px and px <= ur.x and ll.y <= py and py <= ur.y:
yield from self.left.iterate_point(point, True)
# Check the right child
if self.right:
ll, ur = self.right.bbox.rect
if ll.x <= px and px <= ur.x and ll.y <= py and py <= ur.y:
yield from self.right.iterate_point(point, True)
def iterate_shape(self, shape, check_done=False):
""" Iterate over shapes in the tree that overlap the given shape. """
sll, sur = shape.rect
# Return this shape if it's a leaf
if self.is_leaf:
ll, ur = self.bbox.rect
if check_done or (ll.x <= sur.x and sll.x <= ur.x and ll.y <= sur.y and sll.y <= ur.y):
yield self.bbox.shape
else:
# Check the left child
if self.left:
ll, ur = self.left.bbox.rect
if ll.x <= sur.x and sll.x <= ur.x and ll.y <= sur.y and sll.y <= ur.y:
yield from self.left.iterate_shape(shape, True)
# Check the right child
if self.right:
ll, ur = self.right.bbox.rect
if ll.x <= sur.x and sll.x <= ur.x and ll.y <= sur.y and sll.y <= ur.y:
yield from self.right.iterate_shape(shape, True)
def get_costs(self, bbox):
""" Return the costs of bbox nodes after merging the given bbox. """
# Find the new areas for all possible cases
self_merge = bbox.merge(self.bbox)
left_merge = bbox.merge(self.left.bbox)
right_merge = bbox.merge(self.right.bbox)
# Add the change in areas as cost
self_cost = self_merge.area()
left_cost = self_merge.area() - self.bbox.area()
left_cost += left_merge.area() - self.left.bbox.area()
right_cost = self_merge.area() - self.bbox.area()
right_cost += right_merge.area() - self.right.bbox.area()
# Add the overlaps in areas as cost
self_overlap = self.bbox.overlap(bbox)
left_overlap = left_merge.overlap(self.right.bbox)
right_overlap = right_merge.overlap(self.left.bbox)
if self_overlap:
self_cost += self_overlap.area()
if left_overlap:
left_cost += left_overlap.area()
if right_overlap:
right_cost += right_overlap.area()
return self_cost, left_cost, right_cost
def insert(self, bbox):
""" Insert a bbox to the bbox tree. """
if self.is_leaf:
# Put the current bbox to the left child
self.left = bbox_node(self.bbox)
# Put the new bbox to the right child
self.right = bbox_node(bbox)
else:
# Calculate the costs of adding the new bbox
self_cost, left_cost, right_cost = self.get_costs(bbox)
if self_cost < left_cost and self_cost < right_cost: # Add here
self.left = bbox_node(self.bbox, left=self.left, right=self.right)
self.right = bbox_node(bbox)
elif left_cost < right_cost: # Add to the left
self.left.insert(bbox)
else: # Add to the right
self.right.insert(bbox)
# Update the current bbox
self.bbox = self.left.bbox.merge(self.right.bbox)
self.is_leaf = False

View File

@ -9,6 +9,8 @@ from openram import debug
from openram.base.vector import vector
from openram.base.vector3d import vector3d
from openram.tech import drc
from .bbox import bbox
from .bbox_node import bbox_node
from .graph_node import graph_node
from .graph_probe import graph_probe
from .graph_utils import snap
@ -80,11 +82,8 @@ class graph:
probe_shape = graph_probe(p1, p2, self.router.get_lpp(p1.z))
pll, pur = probe_shape.rect
# Check if any blockage blocks this probe
for blockage in self.graph_blockages:
for blockage in self.blockage_bbox_tree.iterate_shape(probe_shape):
bll, bur = blockage.rect
# Not overlapping
if bll.x > pur.x or pll.x > bur.x or bll.y > pur.y or pll.y > bur.y:
continue
# Not on the same layer
if not blockage.same_lpp(blockage.lpp, probe_shape.lpp):
continue
@ -116,11 +115,8 @@ class graph:
half_wide = self.router.half_wire
spacing = snap(self.router.track_space + half_wide + drc["grid"])
blocked = False
for blockage in self.graph_blockages:
for blockage in self.blockage_bbox_tree.iterate_point(p):
ll, ur = blockage.rect
# Not overlapping
if ll.x > x or x > ur.x or ll.y > y or y > ur.y:
continue
# Not on the same layer
if self.router.get_zindex(blockage.lpp) != z:
continue
@ -168,11 +164,16 @@ class graph:
for node in nodes:
if self.is_node_blocked(node, pin_safe=False):
return True
# Skip if no via is present
if len(self.graph_vias) == 0:
return False
# If the nodes are blocked by a via
x = node.center.x
y = node.center.y
z = node.center.z
for via in self.graph_vias:
for via in self.via_bbox_tree.iterate_point(node.center):
ll, ur = via.rect
# Not overlapping
if ll.x > x or x > ur.x or ll.y > y or y > ur.y:
@ -186,7 +187,7 @@ class graph:
def create_graph(self, source, target, scale=1):
""" Create the graph to run routing on later. """
debug.info(2, "Creating the graph for source '{}' and target'{}'.".format(source, target))
debug.info(3, "Creating the graph for source '{}' and target'{}'.".format(source, target))
# Save source and target information
self.source = source
@ -195,9 +196,8 @@ class graph:
# Find the region to be routed and only include objects inside that region
region = deepcopy(source)
region.bbox([target])
region.multiply(scale)
region = region.inflated_pin(spacing=self.router.track_space)
debug.info(3, "Routing region is {}".format(region.rect))
region = region.inflated_pin(spacing=self.router.track_width + self.router.track_space)
debug.info(4, "Routing region is {}".format(region.rect))
# Find the blockages that are in the routing area
self.graph_blockages = []
@ -213,13 +213,15 @@ class graph:
region.bbox(self.graph_blockages)
# Find and include edge shapes to prevent DRC errors
self.find_graph_blockages(region)
# Build the bbox tree
self.build_bbox_trees()
# Generate the graph nodes from cartesian values
self.generate_graph_nodes(x_values, y_values)
# Save the graph nodes that lie in source and target shapes
self.save_end_nodes()
debug.info(3, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages)))
debug.info(3, "Number of vias detected in the routing region: {}".format(len(self.graph_vias)))
debug.info(3, "Number of nodes in the routing graph: {}".format(len(self.nodes)))
debug.info(4, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages)))
debug.info(4, "Number of vias detected in the routing region: {}".format(len(self.graph_vias)))
debug.info(4, "Number of nodes in the routing graph: {}".format(len(self.nodes)))
# Return the region to scale later if no path is found
return region.rect
@ -261,6 +263,21 @@ class graph:
self.graph_vias.append(via)
def build_bbox_trees(self):
""" Build bbox trees for blockages and vias in the routing region. """
# Bbox tree for blockages
self.blockage_bbox_tree = bbox_node(bbox(self.graph_blockages[0]))
for i in range(1, len(self.graph_blockages)):
self.blockage_bbox_tree.insert(bbox(self.graph_blockages[i]))
# Bbox tree for vias
if len(self.graph_vias) == 0:
return
self.via_bbox_tree = bbox_node(bbox(self.graph_vias[0]))
for i in range(1, len(self.graph_vias)):
self.via_bbox_tree.insert(bbox(self.graph_vias[i]))
def generate_cartesian_values(self):
"""
Generate x and y values from all the corners of the shapes in the

View File

@ -65,10 +65,10 @@ class graph_node:
layer_dist = self.center.distance(other.center)
# Double the cost if the edge is in non-preferred direction
if is_vertical != bool(self.center.z):
layer_dist *= 2
layer_dist *= 4
# Add a constant wire cost to prevent dog-legs
if prev_node and self.get_direction(prev_node) != self.get_direction(other):
layer_dist += drc["grid"]
via_dist = abs(self.center.z - other.center.z)
via_dist = abs(self.center.z - other.center.z) * 2
return layer_dist + via_dist
return float("inf")

View File

@ -87,7 +87,7 @@ class router(router_tech):
def find_pins(self, pin_name):
""" Find the pins with the given name. """
debug.info(2, "Finding all pins for {}".format(pin_name))
debug.info(4, "Finding all pins for {}".format(pin_name))
shape_list = self.layout.getAllPinShapes(str(pin_name))
pin_set = set()
@ -111,7 +111,7 @@ class router(router_tech):
def find_blockages(self, name="blockage"):
""" Find all blockages in the routing layers. """
debug.info(2, "Finding blockages...")
debug.info(4, "Finding blockages...")
for lpp in [self.vert_lpp, self.horiz_lpp]:
shapes = self.layout.getAllShapes(lpp)
@ -134,7 +134,7 @@ class router(router_tech):
def find_vias(self):
""" Find all vias in the routing layers. """
debug.info(2, "Finding vias...")
debug.info(4, "Finding vias...")
# Prepare lpp values here
from openram.tech import layer

View File

@ -5,6 +5,7 @@
#
from openram import debug
from openram.base.vector import vector
from openram.base.vector3d import vector3d
from openram import OPTS
from .graph import graph
from .graph_shape import graph_shape
@ -53,45 +54,89 @@ class signal_escape_router(router):
self.blockages.append(self.inflate_shape(pin))
# Route vdd and gnd
routed_count = 0
routed_max = len(pin_names)
for source, target, _ in self.get_route_pairs(pin_names):
# Change fake pin's name so the graph will treat it as routable
target.name = source.name
# This is the routing region scale
scale = 1
while True:
# Create the graph
g = graph(self)
region = g.create_graph(source, target, scale)
# Find the shortest path from source to target
path = g.find_shortest_path()
# If there is no path found, exponentially try again with a
# larger routing region
if path is None:
rll, rur = region
bll, bur = self.bbox
# Stop scaling the region and throw an error
if rll.x < bll.x and rll.y < bll.y and \
rur.x > bur.x and rur.y > bur.y:
self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target)
debug.error("Couldn't route from {} to {}.".format(source, target), -1)
# Exponentially scale the region
scale *= 2
debug.info(0, "Retry routing in larger routing region with scale {}".format(scale))
continue
# Create the path shapes on layout
new_shapes = self.add_path(path)
self.new_pins[source.name] = new_shapes[0]
# Find the recently added shapes
self.prepare_gds_reader()
self.find_blockages(name)
self.find_vias()
break
# Create the graph
g = graph(self)
g.create_graph(source, target)
# Find the shortest path from source to target
path = g.find_shortest_path()
# If no path is found, throw an error
if path is None:
self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target)
debug.error("Couldn't route from {} to {}.".format(source, target), -1)
# Create the path shapes on layout
new_wires, new_vias = self.add_path(path)
self.new_pins[source.name] = new_wires[-1]
# Find the recently added shapes
self.find_blockages(name, new_wires)
self.find_vias(new_vias)
routed_count += 1
debug.info(2, "Routed {} of {} signal pins".format(routed_count, routed_max))
self.replace_layout_pins()
def get_closest_edge(self, point):
""" Return a point's the closest edge and the edge's axis direction. """
ll, ur = self.bbox
# Snap the pin to the perimeter and break the iteration
ll_diff_x = abs(point.x - ll.x)
ll_diff_y = abs(point.y - ll.y)
ur_diff_x = abs(point.x - ur.x)
ur_diff_y = abs(point.y - ur.y)
min_diff = min(ll_diff_x, ll_diff_y, ur_diff_x, ur_diff_y)
if min_diff == ll_diff_x:
return "left", True
if min_diff == ll_diff_y:
return "bottom", False
if min_diff == ur_diff_x:
return "right", True
return "top", False
def prepare_path(self, path):
"""
Override the `prepare_path` method from the `router` class to prevent
overflows from the SRAM layout area.
"""
ll, ur = self.bbox
nodes = super().prepare_path(path)
new_nodes = []
for i in range(len(nodes)):
node = nodes[i]
c = node.center
# Haven't overflown yet
if ll.x < c.x and c.x < ur.x and ll.y < c.y and c.y < ur.y:
new_nodes.append(node)
continue
# Snap the pin to the perimeter and break the iteration
edge, _ = self.get_closest_edge(c)
if edge == "left":
fake_center = vector3d(ll.x + self.half_wire, c.y, c.z)
if edge == "bottom":
fake_center = vector3d(c.x, ll.y + self.half_wire, c.z)
if edge == "right":
fake_center = vector3d(ur.x - self.half_wire, c.y, c.z)
if edge == "top":
fake_center = vector3d(c.x, ur.y - self.half_wire, c.z)
node.center = fake_center
new_nodes.append(node)
break
return new_nodes
def add_perimeter_fake_pins(self):
"""
Add the fake pins on the perimeter to where the signals will be routed.
These perimeter fake pins are only used to replace layout pins at the
end of routing.
"""
ll, ur = self.bbox
@ -132,17 +177,36 @@ class signal_escape_router(router):
self.fake_pins.append(pin)
def get_closest_perimeter_fake_pin(self, pin):
""" Return the closest fake pin for the given pin. """
def create_fake_pin(self, pin):
""" Create a fake pin on the perimeter orthogonal to the given pin. """
min_dist = float("inf")
close_fake = None
for fake in self.fake_pins:
dist = pin.distance(fake)
if dist < min_dist:
min_dist = dist
close_fake = fake
return close_fake
ll, ur = self.bbox
c = pin.center()
# Find the closest edge
edge, vertical = self.get_closest_edge(c)
# Keep the fake pin out of the SRAM layout are so that they won't be
# blocked by previous signals if they're on the same orthogonal line
if edge == "left":
fake_center = vector(ll.x - self.track_wire * 2, c.y)
if edge == "bottom":
fake_center = vector(c.x, ll.y - self.track_wire * 2)
if edge == "right":
fake_center = vector(ur.x + self.track_wire * 2, c.y)
if edge == "top":
fake_center = vector(c.x, ur.y + self.track_wire * 2)
# Create the fake pin shape
layer = self.get_layer(int(not vertical))
half_wire_vector = vector([self.half_wire] * 2)
nll = fake_center - half_wire_vector
nur = fake_center + half_wire_vector
rect = [nll, nur]
pin = graph_shape(name="fake",
rect=rect,
layer_name_pp=layer)
return pin
def get_route_pairs(self, pin_names):
@ -151,7 +215,7 @@ class signal_escape_router(router):
to_route = []
for name in pin_names:
pin = next(iter(self.pins[name]))
fake = self.get_closest_perimeter_fake_pin(pin)
fake = self.create_fake_pin(pin)
to_route.append((pin, fake, pin.distance(fake)))
return sorted(to_route, key=lambda x: x[2])

View File

@ -67,39 +67,29 @@ class supply_router(router):
self.blockages.append(self.inflate_shape(pin))
# Route vdd and gnd
routed_count = 0
routed_max = len(self.pins[vdd_name]) + len(self.pins[gnd_name])
for pin_name in [vdd_name, gnd_name]:
pins = self.pins[pin_name]
# Route closest pins according to the minimum spanning tree
for source, target in self.get_mst_pairs(list(pins)):
# This is the routing region scale
scale = 1
while True:
# Create the graph
g = graph(self)
region = g.create_graph(source, target, scale)
# Find the shortest path from source to target
path = g.find_shortest_path()
# If there is no path found, exponentially try again with a
# larger routing region
if path is None:
rll, rur = region
bll, bur = self.bbox
# Stop scaling the region and throw an error
if rll.x < bll.x and rll.y < bll.y and \
rur.x > bur.x and rur.y > bur.y:
self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target)
debug.error("Couldn't route from {} to {}.".format(source, target), -1)
# Exponentially scale the region
scale *= 2
debug.info(0, "Retry routing in larger routing region with scale {}".format(scale))
continue
# Create the path shapes on layout
self.add_path(path)
# Find the recently added shapes
self.prepare_gds_reader()
self.find_blockages(pin_name)
self.find_vias()
break
# Create the graph
g = graph(self)
g.create_graph(source, target)
# Find the shortest path from source to target
path = g.find_shortest_path()
# If no path is found, throw an error
if path is None:
self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target)
debug.error("Couldn't route from {} to {}.".format(source, target), -1)
# Create the path shapes on layout
new_wires, new_vias = self.add_path(path)
# Find the recently added shapes
self.find_blockages(pin_name, new_wires)
self.find_vias(new_vias)
# Report routed count
routed_count += 1
debug.info(2, "Routed {} of {} supply pins".format(routed_count, routed_max))
def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4):