mirror of https://github.com/VLSIDA/OpenRAM.git
Merge changes from subprocess_fix
This commit is contained in:
commit
230454d567
|
|
@ -14,7 +14,6 @@ from openram.base import rom_verilog
|
|||
from openram import OPTS, print_time
|
||||
from openram.sram_factory import factory
|
||||
from openram.tech import drc, layer, parameter
|
||||
from openram.router import router_tech
|
||||
|
||||
|
||||
class rom_bank(design,rom_verilog):
|
||||
|
|
@ -111,21 +110,16 @@ class rom_bank(design,rom_verilog):
|
|||
self.place_top_level_pins()
|
||||
self.route_output_buffers()
|
||||
|
||||
rt = router_tech(self.supply_stack, 1)
|
||||
init_bbox = self.get_bbox(side="ring",
|
||||
margin=rt.track_width)
|
||||
self.route_supplies(init_bbox)
|
||||
# We need the initial bbox for the supply rings later
|
||||
# because the perimeter pins will change the bbox
|
||||
# FIXME: Somehow ROM layout behaves weird and doesn't add all the pin
|
||||
# shapes before routing supplies
|
||||
init_bbox = self.get_bbox()
|
||||
if OPTS.route_supplies:
|
||||
self.route_supplies(init_bbox)
|
||||
# Route the pins to the perimeter
|
||||
if OPTS.perimeter_pins:
|
||||
# We now route the escape routes far enough out so that they will
|
||||
# reach past the power ring or stripes on the sides
|
||||
bbox = self.get_bbox(side="ring",
|
||||
margin=11*rt.track_width)
|
||||
self.route_escape_pins(bbox)
|
||||
|
||||
|
||||
self.route_escape_pins(init_bbox)
|
||||
|
||||
|
||||
def setup_layout_constants(self):
|
||||
|
|
@ -450,24 +444,17 @@ class rom_bank(design,rom_verilog):
|
|||
pin_num = msb - self.col_bits
|
||||
self.add_io_pin(self.decode_inst, "A{}".format(pin_num), name)
|
||||
|
||||
def route_supplies(self, bbox=None):
|
||||
def route_supplies(self, bbox):
|
||||
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
for inst in self.insts:
|
||||
self.copy_power_pins(inst, pin_name)
|
||||
|
||||
if not OPTS.route_supplies:
|
||||
# Do not route the power supply (leave as must-connect pins)
|
||||
return
|
||||
elif OPTS.route_supplies == "grid":
|
||||
from openram.router import supply_grid_router as router
|
||||
else:
|
||||
from openram.router import supply_tree_router as router
|
||||
rtr=router(layers=self.supply_stack,
|
||||
design=self,
|
||||
bbox=bbox,
|
||||
pin_type=OPTS.supply_pin_type)
|
||||
|
||||
from openram.router import supply_router as router
|
||||
rtr = router(layers=self.supply_stack,
|
||||
design=self,
|
||||
bbox=bbox,
|
||||
pin_type=OPTS.supply_pin_type)
|
||||
rtr.route()
|
||||
|
||||
if OPTS.supply_pin_type in ["left", "right", "top", "bottom", "ring"]:
|
||||
|
|
@ -507,7 +494,7 @@ class rom_bank(design,rom_verilog):
|
|||
pins_to_route.append("clk")
|
||||
pins_to_route.append("cs")
|
||||
from openram.router import signal_escape_router as router
|
||||
rtr=router(layers=self.m3_stack,
|
||||
design=self,
|
||||
bbox=bbox)
|
||||
rtr.escape_route(pins_to_route)
|
||||
rtr = router(layers=self.m3_stack,
|
||||
bbox=bbox,
|
||||
design=self)
|
||||
rtr.route(pins_to_route)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ from openram.base import channel_route
|
|||
from openram.base import design
|
||||
from openram.base import verilog
|
||||
from openram.base import lef
|
||||
from openram.router import router_tech
|
||||
from openram.sram_factory import factory
|
||||
from openram.tech import spice
|
||||
from openram import OPTS, print_time
|
||||
|
|
@ -252,18 +251,11 @@ class sram_1bank(design, verilog, lef):
|
|||
for inst in self.insts:
|
||||
self.copy_power_pins(inst, pin_name, self.ext_supply[pin_name])
|
||||
|
||||
if not OPTS.route_supplies:
|
||||
# Do not route the power supply (leave as must-connect pins)
|
||||
return
|
||||
elif OPTS.route_supplies == "grid":
|
||||
from openram.router import supply_grid_router as router
|
||||
else:
|
||||
from openram.router import supply_tree_router as router
|
||||
rtr=router(layers=self.supply_stack,
|
||||
design=self,
|
||||
bbox=bbox,
|
||||
pin_type=OPTS.supply_pin_type)
|
||||
|
||||
from openram.router import supply_router as router
|
||||
rtr = router(layers=self.supply_stack,
|
||||
design=self,
|
||||
bbox=bbox,
|
||||
pin_type=OPTS.supply_pin_type)
|
||||
rtr.route()
|
||||
|
||||
if OPTS.supply_pin_type in ["left", "right", "top", "bottom", "ring"]:
|
||||
|
|
@ -288,7 +280,7 @@ class sram_1bank(design, verilog, lef):
|
|||
pin.width(),
|
||||
pin.height())
|
||||
|
||||
elif OPTS.route_supplies and OPTS.supply_pin_type == "single":
|
||||
elif OPTS.supply_pin_type == "single":
|
||||
# Update these as we may have routed outside the region (perimeter pins)
|
||||
lowest_coord = self.find_lowest_coords()
|
||||
|
||||
|
|
@ -326,7 +318,7 @@ class sram_1bank(design, verilog, lef):
|
|||
# Grid is left with many top level pins
|
||||
pass
|
||||
|
||||
def route_escape_pins(self, bbox):
|
||||
def route_escape_pins(self, bbox=None):
|
||||
"""
|
||||
Add the top-level pins for a single bank SRAM with control.
|
||||
"""
|
||||
|
|
@ -370,10 +362,10 @@ class sram_1bank(design, verilog, lef):
|
|||
pins_to_route.append("spare_wen{0}[{1}]".format(port, bit))
|
||||
|
||||
from openram.router import signal_escape_router as router
|
||||
rtr=router(layers=self.m3_stack,
|
||||
design=self,
|
||||
bbox=bbox)
|
||||
rtr.escape_route(pins_to_route)
|
||||
rtr = router(layers=self.m3_stack,
|
||||
bbox=bbox,
|
||||
design=self)
|
||||
rtr.route(pins_to_route)
|
||||
|
||||
def compute_bus_sizes(self):
|
||||
""" Compute the independent bus widths shared between two and four bank SRAMs """
|
||||
|
|
@ -1077,24 +1069,15 @@ class sram_1bank(design, verilog, lef):
|
|||
# Some technologies have an isolation
|
||||
self.add_dnwell(inflate=2.5)
|
||||
|
||||
init_bbox = self.get_bbox()
|
||||
# Route the supplies together and/or to the ring/stripes.
|
||||
# This is done with the original bbox since the escape routes need to
|
||||
# be outside of the ring for OpenLane
|
||||
rt = router_tech(self.supply_stack, 1)
|
||||
init_bbox = self.get_bbox(side="ring",
|
||||
margin=rt.track_width)
|
||||
|
||||
# We need the initial bbox for the supply rings later
|
||||
# because the perimeter pins will change the bbox
|
||||
# Route the pins to the perimeter
|
||||
if OPTS.perimeter_pins:
|
||||
# We now route the escape routes far enough out so that they will
|
||||
# reach past the power ring or stripes on the sides
|
||||
bbox = self.get_bbox(side="ring",
|
||||
margin=11*rt.track_width)
|
||||
self.route_escape_pins(bbox)
|
||||
|
||||
self.route_supplies(init_bbox)
|
||||
self.route_escape_pins(init_bbox)
|
||||
if OPTS.route_supplies:
|
||||
self.route_supplies(init_bbox)
|
||||
|
||||
|
||||
def route_dffs(self, add_routes=True):
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ class options(optparse.Values):
|
|||
# When enabled, layout is not generated (and no DRC or LVS are performed)
|
||||
netlist_only = False
|
||||
# Whether we should do the final power routing
|
||||
route_supplies = "tree"
|
||||
route_supplies = True
|
||||
supply_pin_type = "ring"
|
||||
# This determines whether LVS and DRC is checked at all.
|
||||
check_lvsdrc = False
|
||||
|
|
|
|||
|
|
@ -3,8 +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_router import *
|
||||
|
|
|
|||
|
|
@ -1,75 +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 enum import Enum
|
||||
from openram import debug
|
||||
from openram.base.vector3d import vector3d
|
||||
|
||||
|
||||
class direction(Enum):
|
||||
NORTH = 1
|
||||
SOUTH = 2
|
||||
EAST = 3
|
||||
WEST = 4
|
||||
UP = 5
|
||||
DOWN = 6
|
||||
NORTHEAST = 7
|
||||
NORTHWEST = 8
|
||||
SOUTHEAST = 9
|
||||
SOUTHWEST = 10
|
||||
|
||||
def get_offset(direct):
|
||||
"""
|
||||
Returns the vector offset for a given direction.
|
||||
"""
|
||||
if direct==direction.NORTH:
|
||||
offset = vector3d(0, 1, 0)
|
||||
elif direct==direction.SOUTH:
|
||||
offset = vector3d(0, -1 ,0)
|
||||
elif direct==direction.EAST:
|
||||
offset = vector3d(1, 0, 0)
|
||||
elif direct==direction.WEST:
|
||||
offset = vector3d(-1, 0, 0)
|
||||
elif direct==direction.UP:
|
||||
offset = vector3d(0, 0, 1)
|
||||
elif direct==direction.DOWN:
|
||||
offset = vector3d(0, 0, -1)
|
||||
elif direct==direction.NORTHEAST:
|
||||
offset = vector3d(1, 1, 0)
|
||||
elif direct==direction.NORTHWEST:
|
||||
offset = vector3d(-1, 1, 0)
|
||||
elif direct==direction.SOUTHEAST:
|
||||
offset = vector3d(1, -1, 0)
|
||||
elif direct==direction.SOUTHWEST:
|
||||
offset = vector3d(-1, -1, 0)
|
||||
else:
|
||||
debug.error("Invalid direction {}".format(direct))
|
||||
|
||||
return offset
|
||||
|
||||
def cardinal_directions(up_down_too=False):
|
||||
temp_dirs = [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST]
|
||||
if up_down_too:
|
||||
temp_dirs.extend([direction.UP, direction.DOWN])
|
||||
return temp_dirs
|
||||
|
||||
def cardinal_offsets(up_down_too=False):
|
||||
return [direction.get_offset(d) for d in direction.cardinal_directions(up_down_too)]
|
||||
|
||||
def all_directions():
|
||||
return [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST,
|
||||
direction.NORTHEAST, direction.NORTHWEST, direction.SOUTHEAST, direction.SOUTHWEST]
|
||||
|
||||
def all_offsets():
|
||||
return [direction.get_offset(d) for d in direction.all_directions()]
|
||||
|
||||
def all_neighbors(cell):
|
||||
return [cell + x for x in direction.all_offsets()]
|
||||
|
||||
def cardinal_neighbors(cell):
|
||||
return [cell + x for x in direction.cardinal_offsets()]
|
||||
|
||||
|
|
@ -0,0 +1,442 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
import heapq
|
||||
from copy import deepcopy
|
||||
from openram import debug
|
||||
from openram.base.vector import vector
|
||||
from openram.base.vector3d import vector3d
|
||||
from openram.tech import drc
|
||||
from .graph_node import graph_node
|
||||
from .graph_probe import graph_probe
|
||||
from .graph_utils import snap
|
||||
|
||||
|
||||
class graph:
|
||||
""" This is the graph created from the blockages. """
|
||||
|
||||
def __init__(self, router):
|
||||
|
||||
# This is the graph router that uses this graph
|
||||
self.router = router
|
||||
self.source_nodes = []
|
||||
self.target_nodes = []
|
||||
|
||||
|
||||
def is_routable(self, shape):
|
||||
""" Return if a shape is routable in this graph. """
|
||||
|
||||
return shape.name == self.source.name
|
||||
|
||||
|
||||
def inside_shape(self, point, shape):
|
||||
""" Return if the point is inside the shape. """
|
||||
|
||||
# Check if they're on the same layer
|
||||
if point.z != self.router.get_zindex(shape.lpp):
|
||||
return False
|
||||
# Check if the point is inside the shape
|
||||
ll, ur = shape.rect
|
||||
return shape.on_segment(ll, point, ur)
|
||||
|
||||
|
||||
def get_safe_pin_values(self, pin):
|
||||
""" Get the safe x and y values of the given pin. """
|
||||
|
||||
# Constant values
|
||||
pin = pin.get_core()
|
||||
offset = self.router.half_wire
|
||||
spacing = self.router.track_space
|
||||
size_limit = snap(offset * 4 + spacing)
|
||||
|
||||
x_values = []
|
||||
y_values = []
|
||||
# If one axis size of the pin is greater than the limit, we will take
|
||||
# two points at both ends. Otherwise, we will only take the center of
|
||||
# this pin.
|
||||
if pin.width() > size_limit:
|
||||
x_values.append(snap(pin.lx() + offset))
|
||||
x_values.append(snap(pin.rx() - offset))
|
||||
else:
|
||||
x_values.append(snap(pin.cx()))
|
||||
if pin.height() > size_limit:
|
||||
y_values.append(snap(pin.by() + offset))
|
||||
y_values.append(snap(pin.uy() - offset))
|
||||
else:
|
||||
y_values.append(snap(pin.cy()))
|
||||
|
||||
return x_values, y_values
|
||||
|
||||
|
||||
def is_probe_blocked(self, p1, p2):
|
||||
"""
|
||||
Return if a probe sent from p1 to p2 encounters a blockage.
|
||||
The probe must be sent vertically or horizontally.
|
||||
This function assumes that p1 and p2 are on the same layer.
|
||||
"""
|
||||
|
||||
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:
|
||||
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
|
||||
# Probe is blocked if the shape isn't routable
|
||||
if not self.is_routable(blockage):
|
||||
return True
|
||||
blockage = blockage.get_core()
|
||||
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:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_node_blocked(self, node, pin_safe=True):
|
||||
""" Return if a node is blocked by a blockage. """
|
||||
|
||||
p = node.center
|
||||
x = p.x
|
||||
y = p.y
|
||||
z = p.z
|
||||
|
||||
def closest(value, checklist):
|
||||
""" Return the distance of the closest value in the checklist. """
|
||||
diffs = [abs(value - other) for other in checklist]
|
||||
return snap(min(diffs))
|
||||
|
||||
wide = self.router.track_wire
|
||||
half_wide = self.router.half_wire
|
||||
spacing = snap(self.router.track_space + half_wide + drc["grid"])
|
||||
blocked = False
|
||||
for blockage in self.graph_blockages:
|
||||
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
|
||||
# Blocked if not routable
|
||||
if not self.is_routable(blockage):
|
||||
blocked = True
|
||||
continue
|
||||
blockage = blockage.get_core()
|
||||
ll, ur = blockage.rect
|
||||
# Not overlapping
|
||||
if ll.x > x or x > ur.x or ll.y > y or y > ur.y:
|
||||
blocked = True
|
||||
continue
|
||||
# Check if the node is too close to one edge of the shape
|
||||
lengths = [blockage.width(), blockage.height()]
|
||||
centers = blockage.center()
|
||||
ll, ur = blockage.rect
|
||||
safe = [True, True]
|
||||
for i in range(2):
|
||||
if lengths[i] >= wide:
|
||||
min_diff = closest(p[i], [ll[i], ur[i]])
|
||||
if min_diff < half_wide:
|
||||
safe[i] = False
|
||||
elif centers[i] != p[i]:
|
||||
safe[i] = False
|
||||
if not all(safe):
|
||||
blocked = True
|
||||
continue
|
||||
# Check if the node is in a safe region of the shape
|
||||
xs, ys = self.get_safe_pin_values(blockage)
|
||||
xdiff = closest(p.x, xs)
|
||||
ydiff = closest(p.y, ys)
|
||||
if xdiff == 0 and ydiff == 0:
|
||||
if pin_safe and blockage in [self.source, self.target]:
|
||||
return False
|
||||
elif xdiff < spacing and ydiff < spacing:
|
||||
blocked = True
|
||||
return blocked
|
||||
|
||||
|
||||
def is_via_blocked(self, nodes):
|
||||
""" Return if a via on the given point is blocked by another via. """
|
||||
|
||||
# If the nodes are blocked by a blockage other than a via
|
||||
for node in nodes:
|
||||
if self.is_node_blocked(node, pin_safe=False):
|
||||
return True
|
||||
# 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:
|
||||
ll, ur = via.rect
|
||||
# Not overlapping
|
||||
if ll.x > x or x > ur.x or ll.y > y or y > ur.y:
|
||||
continue
|
||||
center = via.center()
|
||||
# If not in the center
|
||||
if center.x != x or center.y != y:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
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))
|
||||
|
||||
# Save source and target information
|
||||
self.source = source
|
||||
self.target = target
|
||||
|
||||
# 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))
|
||||
|
||||
# Find the blockages that are in the routing area
|
||||
self.graph_blockages = []
|
||||
self.find_graph_blockages(region)
|
||||
|
||||
# Find the vias that are in the routing area
|
||||
self.graph_vias = []
|
||||
self.find_graph_vias(region)
|
||||
|
||||
# Generate the cartesian values from shapes in the area
|
||||
x_values, y_values = self.generate_cartesian_values()
|
||||
# Adjust the routing region to include "edge" shapes
|
||||
region.bbox(self.graph_blockages)
|
||||
# Find and include edge shapes to prevent DRC errors
|
||||
self.find_graph_blockages(region)
|
||||
# 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)))
|
||||
|
||||
# Return the region to scale later if no path is found
|
||||
return region.rect
|
||||
|
||||
|
||||
def find_graph_blockages(self, region):
|
||||
""" Find blockages that overlap the routing region. """
|
||||
|
||||
for blockage in self.router.blockages:
|
||||
# Skip if already included
|
||||
if blockage in self.graph_blockages:
|
||||
continue
|
||||
# Set the region's lpp to current blockage's lpp so that the
|
||||
# overlaps method works
|
||||
region.lpp = blockage.lpp
|
||||
if region.overlaps(blockage):
|
||||
self.graph_blockages.append(blockage)
|
||||
# Make sure that the source or target fake pins are included as blockage
|
||||
for shape in [self.source, self.target]:
|
||||
for blockage in self.graph_blockages:
|
||||
blockage = blockage.get_core()
|
||||
if shape == blockage:
|
||||
break
|
||||
else:
|
||||
self.graph_blockages.append(shape)
|
||||
|
||||
|
||||
def find_graph_vias(self, region):
|
||||
""" Find vias that overlap the routing region. """
|
||||
|
||||
for via in self.router.vias:
|
||||
# Skip if already included
|
||||
if via in self.graph_vias:
|
||||
continue
|
||||
# Set the regions's lpp to current via's lpp so that the
|
||||
# overlaps method works
|
||||
region.lpp = via.lpp
|
||||
if region.overlaps(via):
|
||||
self.graph_vias.append(via)
|
||||
|
||||
|
||||
def generate_cartesian_values(self):
|
||||
"""
|
||||
Generate x and y values from all the corners of the shapes in the
|
||||
routing region.
|
||||
"""
|
||||
|
||||
x_values = set()
|
||||
y_values = set()
|
||||
|
||||
# Add inner values for blockages of the routed type
|
||||
for shape in self.graph_blockages:
|
||||
if not self.is_routable(shape):
|
||||
continue
|
||||
# Get the safe pin values
|
||||
xs, ys = self.get_safe_pin_values(shape)
|
||||
x_values.update(xs)
|
||||
y_values.update(ys)
|
||||
|
||||
# Add corners for blockages
|
||||
offset = vector([drc["grid"]] * 2)
|
||||
for blockage in self.graph_blockages:
|
||||
ll, ur = blockage.rect
|
||||
# Add minimum offset to the blockage corner nodes to prevent overlap
|
||||
nll = snap(ll - offset)
|
||||
nur = snap(ur + offset)
|
||||
x_values.update([nll.x, nur.x])
|
||||
y_values.update([nll.y, nur.y])
|
||||
|
||||
# Add center values for existing vias
|
||||
for via in self.graph_vias:
|
||||
p = via.center()
|
||||
x_values.add(p.x)
|
||||
y_values.add(p.y)
|
||||
|
||||
# Sort x and y values
|
||||
x_values = list(x_values)
|
||||
y_values = list(y_values)
|
||||
x_values.sort()
|
||||
y_values.sort()
|
||||
|
||||
return x_values, y_values
|
||||
|
||||
|
||||
def generate_graph_nodes(self, x_values, y_values):
|
||||
"""
|
||||
Generate all graph nodes using the cartesian values and connect the
|
||||
orthogonal neighbors.
|
||||
"""
|
||||
|
||||
# Generate all nodes
|
||||
self.nodes = []
|
||||
for x in x_values:
|
||||
for y in y_values:
|
||||
for z in [0, 1]:
|
||||
self.nodes.append(graph_node([x, y, z]))
|
||||
|
||||
# Mark nodes that will be removed
|
||||
self.mark_blocked_nodes()
|
||||
|
||||
# Connect closest nodes that won't be removed
|
||||
def search(index, condition, shift):
|
||||
""" Search and connect neighbor nodes. """
|
||||
base_nodes = self.nodes[index:index+2]
|
||||
found = [base_nodes[0].remove,
|
||||
base_nodes[1].remove]
|
||||
while condition(index) and not all(found):
|
||||
nodes = self.nodes[index - shift:index - shift + 2]
|
||||
for k in range(2):
|
||||
if not found[k] and not nodes[k].remove:
|
||||
found[k] = True
|
||||
if not self.is_probe_blocked(base_nodes[k].center, nodes[k].center):
|
||||
base_nodes[k].add_neighbor(nodes[k])
|
||||
index -= shift
|
||||
y_len = len(y_values)
|
||||
for i in range(0, len(self.nodes), 2):
|
||||
search(i, lambda count: (count / 2) % y_len, 2) # Down
|
||||
search(i, lambda count: (count / 2) >= y_len, y_len * 2) # Left
|
||||
if not self.nodes[i].remove and \
|
||||
not self.nodes[i + 1].remove and \
|
||||
not self.is_via_blocked(self.nodes[i:i+2]):
|
||||
self.nodes[i].add_neighbor(self.nodes[i + 1])
|
||||
|
||||
# Remove marked nodes
|
||||
self.remove_blocked_nodes()
|
||||
|
||||
|
||||
def mark_blocked_nodes(self):
|
||||
""" Mark graph nodes to be removed that are blocked by a blockage. """
|
||||
|
||||
for i in range(len(self.nodes) - 1, -1, -1):
|
||||
node = self.nodes[i]
|
||||
if self.is_node_blocked(node):
|
||||
node.remove = True
|
||||
|
||||
|
||||
def remove_blocked_nodes(self):
|
||||
""" Remove graph nodes that are marked to be removed. """
|
||||
|
||||
for i in range(len(self.nodes) - 1, -1, -1):
|
||||
node = self.nodes[i]
|
||||
if node.remove:
|
||||
node.remove_all_neighbors()
|
||||
self.nodes.remove(node)
|
||||
|
||||
|
||||
def save_end_nodes(self):
|
||||
""" Save graph nodes that are inside source and target pins. """
|
||||
|
||||
for node in self.nodes:
|
||||
if self.inside_shape(node.center, self.source):
|
||||
self.source_nodes.append(node)
|
||||
elif self.inside_shape(node.center, self.target):
|
||||
self.target_nodes.append(node)
|
||||
|
||||
|
||||
def find_shortest_path(self):
|
||||
"""
|
||||
Find the shortest path from the source node to target node using the
|
||||
A* algorithm.
|
||||
"""
|
||||
|
||||
# Heuristic function to calculate the scores
|
||||
def h(node):
|
||||
""" Return the estimated distance to the closest target. """
|
||||
min_dist = float("inf")
|
||||
for t in self.target_nodes:
|
||||
dist = t.center.distance(node.center) + abs(t.center.z - node.center.z)
|
||||
if dist < min_dist:
|
||||
min_dist = dist
|
||||
return min_dist
|
||||
|
||||
# Initialize data structures to be used for A* search
|
||||
queue = []
|
||||
close_set = set()
|
||||
came_from = {}
|
||||
g_scores = {}
|
||||
f_scores = {}
|
||||
|
||||
# Initialize score values for the source nodes
|
||||
for node in self.source_nodes:
|
||||
g_scores[node.id] = 0
|
||||
f_scores[node.id] = h(node)
|
||||
heapq.heappush(queue, (f_scores[node.id], node.id, node))
|
||||
|
||||
# Run the A* algorithm
|
||||
while len(queue) > 0:
|
||||
# Get the closest node from the queue
|
||||
current = heapq.heappop(queue)[2]
|
||||
|
||||
# Skip this node if already discovered
|
||||
if current in close_set:
|
||||
continue
|
||||
close_set.add(current)
|
||||
|
||||
# Check if we've reached the target
|
||||
if current in self.target_nodes:
|
||||
path = []
|
||||
while current.id in came_from:
|
||||
path.append(current)
|
||||
current = came_from[current.id]
|
||||
path.append(current)
|
||||
return path
|
||||
|
||||
# Get the previous node to better calculate the next costs
|
||||
prev_node = None
|
||||
if current.id in came_from:
|
||||
prev_node = came_from[current.id]
|
||||
|
||||
# Update neighbor scores
|
||||
for node in current.neighbors:
|
||||
tentative_score = current.get_edge_cost(node, prev_node) + g_scores[current.id]
|
||||
if node.id not in g_scores or tentative_score < g_scores[node.id]:
|
||||
came_from[node.id] = current
|
||||
g_scores[node.id] = tentative_score
|
||||
f_scores[node.id] = tentative_score + h(node)
|
||||
heapq.heappush(queue, (f_scores[node.id], node.id, node))
|
||||
|
||||
# Return None if not connected
|
||||
return None
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
from openram.base.vector3d import vector3d
|
||||
from openram.tech import drc
|
||||
|
||||
|
||||
class graph_node:
|
||||
""" This class represents a node on the graph. """
|
||||
|
||||
# This is used to assign unique ids to nodes
|
||||
next_id = 0
|
||||
|
||||
def __init__(self, center):
|
||||
|
||||
self.id = graph_node.next_id
|
||||
graph_node.next_id += 1
|
||||
if isinstance(center, vector3d):
|
||||
self.center = center
|
||||
else:
|
||||
self.center = vector3d(center)
|
||||
self.neighbors = []
|
||||
self.remove = False
|
||||
|
||||
|
||||
def add_neighbor(self, other):
|
||||
""" Connect two nodes. """
|
||||
|
||||
if other not in self.neighbors:
|
||||
self.neighbors.append(other)
|
||||
other.neighbors.append(self)
|
||||
|
||||
|
||||
def remove_neighbor(self, other):
|
||||
""" Disconnect two nodes. """
|
||||
|
||||
if other in self.neighbors:
|
||||
self.neighbors.remove(other)
|
||||
other.neighbors.remove(self)
|
||||
|
||||
|
||||
def remove_all_neighbors(self):
|
||||
""" Disconnect all current neighbors. """
|
||||
|
||||
for neighbor in self.neighbors:
|
||||
self.neighbors.remove(neighbor)
|
||||
neighbor.neighbors.remove(self)
|
||||
|
||||
|
||||
def get_direction(self, b):
|
||||
""" Return the direction of path from a to b. """
|
||||
|
||||
horiz = self.center.x == b.center.x
|
||||
vert = self.center.y == b.center.y
|
||||
return (horiz, vert)
|
||||
|
||||
|
||||
def get_edge_cost(self, other, prev_node=None):
|
||||
""" Get the cost of going from this node to the other node. """
|
||||
|
||||
if other in self.neighbors:
|
||||
is_vertical = self.center.x == other.center.x
|
||||
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
|
||||
# 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)
|
||||
return layer_dist + via_dist
|
||||
return float("inf")
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
class graph_probe:
|
||||
"""
|
||||
This class represents a probe sent from one point to another on Hanan graph.
|
||||
This is used to mimic the pin_layout class to utilize its methods.
|
||||
"""
|
||||
|
||||
def __init__(self, p1, p2, lpp):
|
||||
|
||||
self.rect = (p1.min(p2), p1.max(p2))
|
||||
self.lpp = lpp
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
from openram.base.pin_layout import pin_layout
|
||||
from openram.base.vector import vector
|
||||
from openram.tech import drc
|
||||
from .graph_utils import snap
|
||||
|
||||
|
||||
class graph_shape(pin_layout):
|
||||
"""
|
||||
This class inherits the pin_layout class to change some of its behavior for
|
||||
the graph router.
|
||||
"""
|
||||
|
||||
def __init__(self, name, rect, layer_name_pp, core=None):
|
||||
|
||||
pin_layout.__init__(self, name, rect, layer_name_pp)
|
||||
|
||||
# Snap the shape to the grid here
|
||||
ll, ur = self.rect
|
||||
self.rect = [snap(ll), snap(ur)]
|
||||
# Core is the original shape from which this shape is inflated
|
||||
self.core = core
|
||||
|
||||
|
||||
def center(self):
|
||||
""" Override the default `center` behavior. """
|
||||
|
||||
return snap(super().center())
|
||||
|
||||
|
||||
def height(self):
|
||||
""" Override the default `height` behavior. """
|
||||
|
||||
return snap(super().height())
|
||||
|
||||
|
||||
def width(self):
|
||||
""" Override the default `width` behavior. """
|
||||
|
||||
return snap(super().width())
|
||||
|
||||
|
||||
def rename(self, new_name):
|
||||
""" Change the name of `self` and `self.core`. """
|
||||
|
||||
self.name = new_name
|
||||
self.get_core().name = new_name
|
||||
|
||||
|
||||
def get_core(self):
|
||||
"""
|
||||
Return `self` if `self.core` is None. Otherwise, return `self.core`.
|
||||
"""
|
||||
|
||||
if self.core is None:
|
||||
return self
|
||||
return self.core
|
||||
|
||||
|
||||
def inflated_pin(self, spacing=None, multiple=0.5, extra_spacing=0):
|
||||
""" Override the default inflated_pin behavior. """
|
||||
|
||||
ll, ur = self.inflate(spacing, multiple)
|
||||
extra = vector([extra_spacing] * 2)
|
||||
newll = ll - extra
|
||||
newur = ur + extra
|
||||
inflated_area = (newll, newur)
|
||||
return graph_shape(self.name, inflated_area, self.layer, self)
|
||||
|
||||
|
||||
def multiply(self, scale):
|
||||
""" Multiply the width and height with the scale value. """
|
||||
|
||||
width = (self.width() * (scale - 1)) / 2
|
||||
height = (self.height() * (scale - 1)) / 2
|
||||
ll, ur = self.rect
|
||||
newll = vector(ll.x - width, ll.y - height)
|
||||
newur = vector(ur.x + width, ur.y + height)
|
||||
self.rect = [snap(newll), snap(newur)]
|
||||
|
||||
|
||||
def core_contained_by_any(self, shape_list):
|
||||
"""
|
||||
Return if the core of this shape is contained by any shape's core in the
|
||||
given list.
|
||||
"""
|
||||
|
||||
self_core = self.get_core()
|
||||
for shape in shape_list:
|
||||
shape_core = shape.get_core()
|
||||
if shape_core.contains(self_core):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def aligns(self, other):
|
||||
""" Return if the other shape aligns with this shape. """
|
||||
|
||||
# Shapes must overlap to be able to align
|
||||
if not self.overlaps(other):
|
||||
return False
|
||||
ll, ur = self.rect
|
||||
oll, our = other.rect
|
||||
if ll.x == oll.x and ur.x == our.x:
|
||||
return True
|
||||
if ll.y == oll.y and ur.y == our.y:
|
||||
return True
|
||||
return False
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
"""
|
||||
Utility functions for graph router.
|
||||
"""
|
||||
from openram.base import vector
|
||||
from openram.tech import drc
|
||||
|
||||
|
||||
def snap(a):
|
||||
""" Use custom `snap` since `vector.snap_to_grid` isn't working. """
|
||||
|
||||
if isinstance(a, vector):
|
||||
return vector(snap(a.x), snap(a.y))
|
||||
return round(a, len(str(drc["grid"]).split('.')[1]))
|
||||
|
|
@ -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
|
|
@ -67,6 +67,11 @@ class router_tech:
|
|||
self.vert_layer_minwidth = max(self.vert_layer_minwidth, max_via_size)
|
||||
self.horiz_layer_minwidth = max(self.horiz_layer_minwidth, max_via_size)
|
||||
|
||||
# Update spacing for the new widths
|
||||
max_width = max(self.vert_layer_minwidth, self.horiz_layer_minwidth)
|
||||
self.vert_layer_spacing = self.get_layer_space(1, max_width)
|
||||
self.horiz_layer_spacing = self.get_layer_space(0, max_width)
|
||||
|
||||
self.horiz_track_width = self.horiz_layer_minwidth + self.horiz_layer_spacing
|
||||
self.vert_track_width = self.vert_layer_minwidth + self.vert_layer_spacing
|
||||
|
||||
|
|
@ -109,23 +114,34 @@ class router_tech:
|
|||
else:
|
||||
debug.error("Invalid zindex {}".format(zindex), -1)
|
||||
|
||||
def get_lpp(self, zindex):
|
||||
if zindex == 1:
|
||||
return self.vert_lpp
|
||||
elif zindex == 0:
|
||||
return self.horiz_lpp
|
||||
else:
|
||||
debug.error("Invalid zindex {}".format(zindex), -1)
|
||||
|
||||
def get_layer_width_space(self, zindex):
|
||||
"""
|
||||
These are the width and spacing of a supply layer given a supply rail
|
||||
of the given number of min wire widths.
|
||||
"""
|
||||
if zindex==1:
|
||||
layer_name = self.vert_layer_name
|
||||
elif zindex==0:
|
||||
layer_name = self.horiz_layer_name
|
||||
else:
|
||||
debug.error("Invalid zindex for track", -1)
|
||||
|
||||
min_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf)
|
||||
|
||||
min_width = self.route_track_width * drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, math.inf)
|
||||
min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), self.route_track_width * min_wire_width, math.inf)
|
||||
|
||||
min_width = self.get_layer_width(zindex)
|
||||
min_spacing = self.get_layer_space(zindex, min_width)
|
||||
return (min_width, min_spacing)
|
||||
|
||||
def get_layer_width(self, zindex):
|
||||
""" Return the minimum width of a layer. """
|
||||
layer_name = self.get_layer(zindex)
|
||||
min_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf)
|
||||
min_width = self.route_track_width * drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, math.inf)
|
||||
return min_width
|
||||
|
||||
def get_layer_space(self, zindex, width=None):
|
||||
""" Return the minimum spacing of a layer given wire width. """
|
||||
if width is None:
|
||||
width = self.get_layer_width(zindex)
|
||||
layer_name = self.get_layer(zindex)
|
||||
min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), self.route_track_width * width, math.inf)
|
||||
return min_spacing
|
||||
|
|
|
|||
|
|
@ -1,104 +1,169 @@
|
|||
# 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)
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
from datetime import datetime
|
||||
from openram import debug
|
||||
from openram import print_time
|
||||
from openram.base.vector import vector
|
||||
from openram import OPTS
|
||||
from .graph import graph
|
||||
from .graph_shape import graph_shape
|
||||
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.
|
||||
This is the signal escape router that uses the Hanan grid graph method.
|
||||
"""
|
||||
|
||||
def __init__(self, layers, design, bbox=None, margin=0):
|
||||
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
|
||||
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
|
||||
self.replace_layout_pins()
|
||||
|
||||
|
||||
def add_perimeter_fake_pins(self):
|
||||
"""
|
||||
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).
|
||||
Add the fake pins on the perimeter to where the signals will be routed.
|
||||
"""
|
||||
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)
|
||||
ll, ur = self.bbox
|
||||
wide = self.track_wire
|
||||
|
||||
return min(x_dist, y_dist)
|
||||
for side in ["top", "bottom", "left", "right"]:
|
||||
vertical = side in ["left", "right"]
|
||||
|
||||
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)
|
||||
# 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)
|
||||
|
||||
start_time = datetime.now()
|
||||
self.find_pins_and_blockages(pin_names)
|
||||
print_time("Finding pins and blockages",datetime.now(), start_time, 3)
|
||||
# Calculate width and height
|
||||
shape = ur - ll
|
||||
if vertical:
|
||||
shape_width = wide
|
||||
shape_height = shape.y
|
||||
else:
|
||||
shape_width = shape.x
|
||||
shape_height = wide
|
||||
|
||||
# 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))
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
def get_closest_perimeter_fake_pin(self, pin):
|
||||
""" Return the closest fake pin for the given pin. """
|
||||
|
||||
#self.write_debug_gds("final_escape_router.gds",False)
|
||||
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
|
||||
|
||||
return True
|
||||
|
||||
def route_signal(self, pin_name, side="all"):
|
||||
def get_route_pairs(self, pin_names):
|
||||
""" Return the pairs to be routed. """
|
||||
|
||||
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))
|
||||
to_route = []
|
||||
for name in pin_names:
|
||||
pin = next(iter(self.pins[name]))
|
||||
fake = self.get_closest_perimeter_fake_pin(pin)
|
||||
to_route.append((pin, fake, pin.distance(fake)))
|
||||
return sorted(to_route, key=lambda x: x[2])
|
||||
|
||||
# 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)
|
||||
def replace_layout_pins(self):
|
||||
""" Replace the old layout pins with new ones around the perimeter. """
|
||||
|
||||
# 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)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
# 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 import OPTS
|
||||
from .graph import graph
|
||||
from .graph_shape import graph_shape
|
||||
from .router import router
|
||||
|
||||
|
||||
class supply_router(router):
|
||||
"""
|
||||
This is the supply router that uses the Hanan grid graph method.
|
||||
"""
|
||||
|
||||
def __init__(self, layers, design, bbox=None, pin_type=None):
|
||||
|
||||
# `router` is the base router class
|
||||
router.__init__(self, layers, design, bbox)
|
||||
|
||||
# Side supply pin type
|
||||
# (can be "top", "bottom", "right", "left", and "ring")
|
||||
self.pin_type = pin_type
|
||||
# New pins are the side supply pins
|
||||
self.new_pins = {}
|
||||
|
||||
|
||||
def route(self, vdd_name="vdd", gnd_name="gnd"):
|
||||
""" Route the given pins in the given order. """
|
||||
debug.info(1, "Running router for {} and {}...".format(vdd_name, gnd_name))
|
||||
|
||||
# Save pin names
|
||||
self.vdd_name = vdd_name
|
||||
self.gnd_name = gnd_name
|
||||
|
||||
# Prepare gdsMill to find pins and blockages
|
||||
self.prepare_gds_reader()
|
||||
|
||||
# Find pins to be routed
|
||||
self.find_pins(vdd_name)
|
||||
self.find_pins(gnd_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 side pins
|
||||
if self.pin_type in ["top", "bottom", "right", "left"]:
|
||||
self.add_side_pin(vdd_name)
|
||||
self.add_side_pin(gnd_name)
|
||||
elif self.pin_type == "ring":
|
||||
self.add_ring_pin(vdd_name)
|
||||
self.add_ring_pin(gnd_name)
|
||||
else:
|
||||
debug.warning("Side supply pins aren't created.")
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
|
||||
def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4):
|
||||
""" Add supply pin to one side of the layout. """
|
||||
|
||||
ll, ur = self.bbox
|
||||
vertical = side in ["left", "right"]
|
||||
inner = pin_name == self.gnd_name
|
||||
|
||||
# Calculate wires' wideness
|
||||
wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1)
|
||||
|
||||
# Calculate the offset for the inner ring
|
||||
if inner:
|
||||
margin = wideness * 2
|
||||
else:
|
||||
margin = 0
|
||||
|
||||
# Calculate the lower left coordinate
|
||||
if side == "top":
|
||||
offset = vector(ll.x + margin, ur.y - wideness - margin)
|
||||
elif side == "bottom":
|
||||
offset = vector(ll.x + margin, ll.y + margin)
|
||||
elif side == "left":
|
||||
offset = vector(ll.x + margin, ll.y + margin)
|
||||
elif side == "right":
|
||||
offset = vector(ur.x - wideness - margin, ll.y + margin)
|
||||
|
||||
# Calculate width and height
|
||||
shape = ur - ll
|
||||
if vertical:
|
||||
shape_width = wideness
|
||||
shape_height = shape.y
|
||||
else:
|
||||
shape_width = shape.x
|
||||
shape_height = wideness
|
||||
if inner:
|
||||
if vertical:
|
||||
shape_height -= margin * 2
|
||||
else:
|
||||
shape_width -= margin * 2
|
||||
|
||||
# Add this new pin
|
||||
layer = self.get_layer(int(vertical))
|
||||
pin = self.design.add_layout_pin(text=pin_name,
|
||||
layer=layer,
|
||||
offset=offset,
|
||||
width=shape_width,
|
||||
height=shape_height)
|
||||
|
||||
# Add fake pins on this new pin evenly
|
||||
fake_pins = []
|
||||
if vertical:
|
||||
space = (shape_height - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1)
|
||||
start_offset = vector(offset.x, offset.y + wideness)
|
||||
else:
|
||||
space = (shape_width - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1)
|
||||
start_offset = vector(offset.x + wideness, offset.y)
|
||||
for i in range(1, num_fake_pins + 1):
|
||||
if vertical:
|
||||
offset = vector(start_offset.x, start_offset.y + i * (space + self.track_wire))
|
||||
ll = vector(offset.x, offset.y - self.track_wire)
|
||||
ur = vector(offset.x + wideness, offset.y)
|
||||
else:
|
||||
offset = vector(start_offset.x + i * (space + self.track_wire), start_offset.y)
|
||||
ll = vector(offset.x - self.track_wire, offset.y)
|
||||
ur = vector(offset.x, offset.y + wideness)
|
||||
rect = [ll, ur]
|
||||
fake_pin = graph_shape(name=pin_name,
|
||||
rect=rect,
|
||||
layer_name_pp=layer)
|
||||
fake_pins.append(fake_pin)
|
||||
return pin, fake_pins
|
||||
|
||||
|
||||
def add_ring_pin(self, pin_name, num_vias=3, num_fake_pins=4):
|
||||
""" Add the supply ring to the layout. """
|
||||
|
||||
# Add side pins
|
||||
new_pins = []
|
||||
for side in ["top", "bottom", "right", "left"]:
|
||||
new_shape, fake_pins = self.add_side_pin(pin_name, side, num_vias, num_fake_pins)
|
||||
ll, ur = new_shape.rect
|
||||
rect = [ll, ur]
|
||||
layer = self.get_layer(side in ["left", "right"])
|
||||
new_pin = graph_shape(name=pin_name,
|
||||
rect=rect,
|
||||
layer_name_pp=layer)
|
||||
new_pins.append(new_pin)
|
||||
self.pins[pin_name].update(fake_pins)
|
||||
self.fake_pins.extend(fake_pins)
|
||||
|
||||
# Add vias to the corners
|
||||
shift = self.track_wire + self.track_space
|
||||
half_wide = self.track_wire / 2
|
||||
for i in range(4):
|
||||
ll, ur = new_pins[i].rect
|
||||
if i % 2:
|
||||
top_left = vector(ur.x - (num_vias - 1) * shift - half_wide, ll.y + (num_vias - 1) * shift + half_wide)
|
||||
else:
|
||||
top_left = vector(ll.x + half_wide, ur.y - half_wide)
|
||||
for j in range(num_vias):
|
||||
for k in range(num_vias):
|
||||
offset = vector(top_left.x + j * shift, top_left.y - k * shift)
|
||||
self.design.add_via_center(layers=self.layers,
|
||||
offset=offset)
|
||||
|
||||
# Save side pins for routing
|
||||
self.new_pins[pin_name] = new_pins
|
||||
for pin in new_pins:
|
||||
self.blockages.append(self.inflate_shape(pin))
|
||||
|
||||
|
||||
def get_mst_pairs(self, pins):
|
||||
"""
|
||||
Return the pin pairs from the minimum spanning tree in a graph that
|
||||
connects all pins together.
|
||||
"""
|
||||
|
||||
pin_count = len(pins)
|
||||
|
||||
# Create an adjacency matrix that connects all pins
|
||||
edges = [[0] * pin_count for i in range(pin_count)]
|
||||
for i in range(pin_count):
|
||||
for j in range(pin_count):
|
||||
# Skip if they're the same pin
|
||||
if i == j:
|
||||
continue
|
||||
# Skip if both pins are fake
|
||||
if pins[i] in self.fake_pins and pins[j] in self.fake_pins:
|
||||
continue
|
||||
edges[i][j] = pins[i].distance(pins[j])
|
||||
|
||||
pin_connected = [False] * pin_count
|
||||
pin_connected[0] = True
|
||||
|
||||
# Add the minimum cost edge in each iteration (Prim's)
|
||||
mst_pairs = []
|
||||
for i in range(pin_count - 1):
|
||||
min_cost = float("inf")
|
||||
s = 0
|
||||
t = 0
|
||||
# Iterate over already connected pins
|
||||
for m in range(pin_count):
|
||||
# Skip if not connected
|
||||
if not pin_connected[m]:
|
||||
continue
|
||||
# Iterate over this pin's neighbors
|
||||
for n in range(pin_count):
|
||||
# Skip if already connected or isn't a neighbor
|
||||
if pin_connected[n] or edges[m][n] == 0:
|
||||
continue
|
||||
# Choose this edge if it's better the the current one
|
||||
if edges[m][n] < min_cost:
|
||||
min_cost = edges[m][n]
|
||||
s = m
|
||||
t = n
|
||||
pin_connected[t] = True
|
||||
mst_pairs.append((pins[s], pins[t]))
|
||||
|
||||
return mst_pairs
|
||||
|
||||
|
||||
def get_new_pins(self, name):
|
||||
""" Return the new supply pins added by this router. """
|
||||
|
||||
return self.new_pins[name]
|
||||
|
|
@ -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)
|
||||
|
|
@ -23,9 +23,6 @@ class sram_1bank_2mux_1rw_1r_spare_cols_test(openram_test):
|
|||
openram.init_openram(config_file, is_unit_test=True)
|
||||
from openram import sram_config
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_r_ports = 1
|
||||
OPTS.num_w_ports = 0
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ class sram_1bank_2mux_1w_1r_spare_cols_test(openram_test):
|
|||
openram.init_openram(config_file, is_unit_test=True)
|
||||
from openram import sram_config
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
OPTS.num_rw_ports = 0
|
||||
OPTS.num_w_ports = 1
|
||||
OPTS.num_r_ports = 1
|
||||
|
|
|
|||
|
|
@ -31,9 +31,6 @@ class sram_1bank_2mux_global_test(openram_test):
|
|||
num_spare_rows = 0
|
||||
num_spare_cols = 0
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
c = sram_config(word_size=8,
|
||||
num_words=32,
|
||||
num_banks=1,
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ class sram_1bank_2mux_wmask_spare_cols_test(openram_test):
|
|||
num_spare_rows = 0
|
||||
num_spare_cols = 0
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
c = sram_config(word_size=8,
|
||||
write_size=4,
|
||||
num_words=64,
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ class sram_1bank_2mux_wmask_test(openram_test):
|
|||
num_spare_rows = 0
|
||||
num_spare_cols = 0
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
c = sram_config(word_size=8,
|
||||
write_size=4,
|
||||
num_words=64,
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ class sram_1bank_4mux_1rw_1r_test(openram_test):
|
|||
openram.init_openram(config_file, is_unit_test=True)
|
||||
from openram import sram_config
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_r_ports = 1
|
||||
OPTS.num_w_ports = 0
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ class sram_1bank_4mux_test(openram_test):
|
|||
num_spare_rows = 0
|
||||
num_spare_cols = 0
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
c = sram_config(word_size=4,
|
||||
num_words=64,
|
||||
num_banks=1,
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ class sram_1bank_8mux_1rw_1r_test(openram_test):
|
|||
openram.init_openram(config_file, is_unit_test=True)
|
||||
from openram import sram_config
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_r_ports = 1
|
||||
OPTS.num_w_ports = 0
|
||||
|
|
|
|||
Loading…
Reference in New Issue