OpenRAM/compiler/router/signal_escape_router.py

234 lines
8.1 KiB
Python

# 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.base.vector3d import vector3d
from openram import OPTS
from .graph import graph
from .graph_shape import graph_shape
from .router import router
class signal_escape_router(router):
"""
This is the signal escape router that uses the Hanan grid graph method.
"""
def __init__(self, layers, design, bbox=None):
# `router` is the base router class
router.__init__(self, layers, design, bbox)
# New pins are the side supply pins
self.new_pins = {}
def route(self, pin_names):
""" Route the given pins to the perimeter. """
debug.info(1, "Running signal escape router...")
# Prepare gdsMill to find pins and blockages
self.prepare_gds_reader()
# Find pins to be routed
for name in pin_names:
self.find_pins(name)
# Find blockages and vias
self.find_blockages()
self.find_vias()
# Convert blockages and vias if they overlap a pin
self.convert_vias()
self.convert_blockages()
# Add fake pins on the perimeter to do the escape routing on
self.add_perimeter_fake_pins()
# Add vdd and gnd pins as blockages as well
# NOTE: This is done to make vdd and gnd pins DRC-safe
for pin in self.all_pins:
self.blockages.append(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
# 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
wide = self.track_wire
for side in ["top", "bottom", "left", "right"]:
vertical = side in ["left", "right"]
# Calculate the lower left coordinate
if side == "top":
offset = vector(ll.x, ur.y - wide)
elif side == "bottom":
offset = vector(ll.x, ll.y)
elif side == "left":
offset = vector(ll.x, ll.y)
elif side == "right":
offset = vector(ur.x - wide, ll.y)
# Calculate width and height
shape = ur - ll
if vertical:
shape_width = wide
shape_height = shape.y
else:
shape_width = shape.x
shape_height = wide
# Add this new pin
# They must lie on the non-preferred direction since the side supply
# pins will lie on the preferred direction
layer = self.get_layer(int(not vertical))
nll = vector(offset.x, offset.y)
nur = vector(offset.x + shape_width, offset.y + shape_height)
rect = [nll, nur]
pin = graph_shape(name="fake",
rect=rect,
layer_name_pp=layer)
self.fake_pins.append(pin)
def create_fake_pin(self, pin):
""" Create a fake pin on the perimeter orthogonal to the given pin. """
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):
""" Return the pairs to be routed. """
to_route = []
for name in pin_names:
pin = next(iter(self.pins[name]))
fake = self.create_fake_pin(pin)
to_route.append((pin, fake, pin.distance(fake)))
return sorted(to_route, key=lambda x: x[2])
def replace_layout_pins(self):
""" Replace the old layout pins with new ones around the perimeter. """
for name, pin in self.new_pins.items():
pin = graph_shape(pin.name, pin.boundary, pin.lpp)
# Find the intersection of this pin on the perimeter
for fake in self.fake_pins:
edge = pin.intersection(fake)
if edge:
break
self.design.replace_layout_pin(name, edge)