Add expanded blockages for paths an enclosures to handle wide metal spacing rules.

This commit is contained in:
Matt Guthaus 2018-11-02 11:11:32 -07:00
parent b24c8a42a1
commit 4d30f214da
5 changed files with 219 additions and 147 deletions

View File

@ -8,6 +8,10 @@ class direction(Enum):
WEST = 4 WEST = 4
UP = 5 UP = 5
DOWN = 6 DOWN = 6
NORTHEAST = 7
NORTHWEST = 8
SOUTHEAST = 9
SOUTHWEST = 10
def get_offset(direct): def get_offset(direct):
@ -26,8 +30,16 @@ class direction(Enum):
offset = vector3d(0,0,1) offset = vector3d(0,0,1)
elif direct==direction.DOWN: elif direct==direction.DOWN:
offset = vector3d(0,0,-1) 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: else:
debug.error("Invalid direction {}".format(dirct)) debug.error("Invalid direction {}".format(direct))
return offset return offset
@ -37,3 +49,16 @@ class direction(Enum):
def cardinal_offsets(): def cardinal_offsets():
return [direction.get_offset(d) for d in direction.cardinal_directions()] return [direction.get_offset(d) for d in direction.cardinal_directions()]
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()]

View File

@ -8,17 +8,18 @@ from vector3d import vector3d
def increment_set(curset, direct): def increment_set(curset, direct):
""" """
Return the cells incremented in given direction Return the cells incremented in given direction
""" """
offset = direction.get_offset(direct) offset = direction.get_offset(direct)
newset = set()
for c in curset:
newc = c+offset
newset.add(newc)
return newset
newset = set()
for c in curset:
newc = c+offset
newset.add(newc)
return newset
def remove_border(curset, direct): def remove_border(curset, direct):
""" """
@ -26,7 +27,7 @@ def remove_border(curset, direct):
""" """
border = get_border(curset, direct) border = get_border(curset, direct)
curset.difference_update(border) curset.difference_update(border)
def get_upper_right(curset): def get_upper_right(curset):
ur = None ur = None
@ -43,48 +44,48 @@ def get_lower_left(curset):
return ll return ll
def get_border( curset, direct): def get_border( curset, direct):
""" """
Return the furthest cell(s) in a given direction. Return the furthest cell(s) in a given direction.
""" """
# find direction-most cell(s) # find direction-most cell(s)
maxc = [] maxc = []
if direct==direction.NORTH: if direct==direction.NORTH:
for c in curset: for c in curset:
if len(maxc)==0 or c.y>maxc[0].y: if len(maxc)==0 or c.y>maxc[0].y:
maxc = [c] maxc = [c]
elif c.y==maxc[0].y: elif c.y==maxc[0].y:
maxc.append(c) maxc.append(c)
elif direct==direct.SOUTH: elif direct==direct.SOUTH:
for c in curset: for c in curset:
if len(maxc)==0 or c.y<maxc[0].y: if len(maxc)==0 or c.y<maxc[0].y:
maxc = [c] maxc = [c]
elif c.y==maxc[0].y: elif c.y==maxc[0].y:
maxc.append(c) maxc.append(c)
elif direct==direct.EAST: elif direct==direct.EAST:
for c in curset: for c in curset:
if len(maxc)==0 or c.x>maxc[0].x: if len(maxc)==0 or c.x>maxc[0].x:
maxc = [c] maxc = [c]
elif c.x==maxc[0].x: elif c.x==maxc[0].x:
maxc.append(c) maxc.append(c)
elif direct==direct.WEST: elif direct==direct.WEST:
for c in curset: for c in curset:
if len(maxc)==0 or c.x<maxc[0].x: if len(maxc)==0 or c.x<maxc[0].x:
maxc = [c] maxc = [c]
elif c.x==maxc[0].x: elif c.x==maxc[0].x:
maxc.append(c) maxc.append(c)
newset = set(maxc) newset = set(maxc)
return newset return newset
def expand_border(curset, direct): def expand_border(curset, direct):
""" """
Expand the current set of sells in a given direction. Expand the current set of sells in a given direction.
Only return the contiguous cells. Only return the contiguous cells.
""" """
border_set = get_border(curset, direct) border_set = get_border(curset, direct)
next_border_set = increment_set(border_set, direct) next_border_set = increment_set(border_set, direct)
return next_border_set return next_border_set
def expand_borders(curset): def expand_borders(curset):
""" """
@ -94,6 +95,47 @@ def expand_borders(curset):
south_set=expand_border(curset,direction.SOUTH) south_set=expand_border(curset,direction.SOUTH)
east_set=expand_border(curset,direction.EAST) east_set=expand_border(curset,direction.EAST)
west_set=expand_border(curset,direction.WEST) west_set=expand_border(curset,direction.WEST)
return(north_set, east_set, south_set, west_set) 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

View File

@ -2,6 +2,7 @@ from direction import direction
from pin_layout import pin_layout from pin_layout import pin_layout
from vector3d import vector3d from vector3d import vector3d
from vector import vector from vector import vector
import grid_utils
from tech import drc from tech import drc
import debug import debug
@ -34,6 +35,7 @@ class pin_group:
# The corresponding set of partially blocked grids for each pin group. # The corresponding set of partially blocked grids for each pin group.
# These are blockages for other nets but unblocked for routing this group. # These are blockages for other nets but unblocked for routing this group.
# These are also blockages if we used a simple enclosure to route to a rail.
self.blockages = set() self.blockages = set()
# This is a set of pin_layout shapes to cover the grids # This is a set of pin_layout shapes to cover the grids
@ -65,7 +67,7 @@ class pin_group:
def size(self): def size(self):
return len(self.grids) return len(self.grids)
def set_routed(self, value=True): def set_routed(self, value=True):
self.routed = value self.routed = value
@ -550,7 +552,7 @@ class pin_group:
return g1_grids,g2_grids return g1_grids,g2_grids
def convert_pin(self, router): def convert_pin(self):
""" """
Convert the list of pin shapes into sets of routing grids. Convert the list of pin shapes into sets of routing grids.
The secondary set of grids are "optional" pin shapes that could be The secondary set of grids are "optional" pin shapes that could be
@ -563,25 +565,25 @@ class pin_group:
for pin in pin_list: for pin in pin_list:
debug.info(2," Converting {0}".format(pin)) debug.info(2," Converting {0}".format(pin))
# Determine which tracks the pin overlaps # Determine which tracks the pin overlaps
pin_in_tracks=router.convert_pin_to_tracks(self.name, pin) pin_in_tracks=self.router.convert_pin_to_tracks(self.name, pin)
pin_set.update(pin_in_tracks) pin_set.update(pin_in_tracks)
# Blockages will be a super-set of pins since it uses the inflated pin shape. # Blockages will be a super-set of pins since it uses the inflated pin shape.
blockage_in_tracks = router.convert_blockage(pin) blockage_in_tracks = self.router.convert_blockage(pin)
blockage_set.update(blockage_in_tracks) blockage_set.update(blockage_in_tracks)
# If we have a blockage, we must remove the grids # If we have a blockage, we must remove the grids
# Remember, this excludes the pin blockages already # Remember, this excludes the pin blockages already
shared_set = pin_set & router.blocked_grids shared_set = pin_set & self.router.blocked_grids
if len(shared_set)>0: if len(shared_set)>0:
debug.info(2,"Removing pins {}".format(shared_set)) debug.info(2,"Removing pins {}".format(shared_set))
pin_set.difference_update(router.blocked_grids) pin_set.difference_update(self.router.blocked_grids)
shared_set = blockage_set & router.blocked_grids shared_set = blockage_set & self.router.blocked_grids
if len(shared_set)>0: if len(shared_set)>0:
debug.info(2,"Removing blocks {}".format(shared_set)) debug.info(2,"Removing blocks {}".format(shared_set))
blockage_set.difference_update(router.blocked_grids) blockage_set.difference_update(self.router.blocked_grids)
# At least one of the groups must have some valid tracks # At least one of the groups must have some valid tracks
if (len(pin_set)==0 and len(blockage_set)==0): if (len(pin_set)==0 and len(blockage_set)==0):
@ -596,3 +598,60 @@ class pin_group:
debug.info(2," pins {}".format(self.grids)) debug.info(2," pins {}".format(self.grids))
debug.info(2," secondary {}".format(self.secondary_grids)) debug.info(2," secondary {}".format(self.secondary_grids))
def recurse_simple_overlap_enclosure(self, start_set, direct):
"""
Recursive function to return set of tracks that connects to
the actual supply rail wire in a given direction (or terminating
when any track is no longer in the supply rail.
"""
next_set = grid_utils.expand_border(start_set, direct)
supply_tracks = self.router.supply_rail_tracks[self.name]
supply_wire_tracks = self.router.supply_rail_wire_tracks[self.name]
supply_overlap = next_set & supply_tracks
wire_overlap = next_set & supply_wire_tracks
# If the rail overlap is the same, we are done, since we connected to the actual wire
if len(wire_overlap)==len(start_set):
new_set = start_set | wire_overlap
# If the supply overlap is the same, keep expanding unti we hit the wire or move out of the rail region
elif len(supply_overlap)==len(start_set):
recurse_set = self.recurse_simple_overlap_enclosure(supply_overlap, direct)
new_set = start_set | supply_overlap | recurse_set
else:
# If we got no next set, we are done, can't expand!
new_set = set()
return new_set
def create_simple_overlap_enclosure(self, start_set):
"""
This takes a set of tracks that overlap a supply rail and creates an enclosure
that is ensured to overlap the supply rail wire.
It then adds rectangle(s) for the enclosure.
"""
additional_set = set()
# Check the layer of any element in the pin to determine which direction to route it
e = next(iter(start_set))
new_set = start_set.copy()
if e.z==0:
new_set = self.recurse_simple_overlap_enclosure(start_set, direction.NORTH)
if not new_set:
new_set = self.recurse_simple_overlap_enclosure(start_set, direction.SOUTH)
else:
new_set = self.recurse_simple_overlap_enclosure(start_set, direction.EAST)
if not new_set:
new_set = self.recurse_simple_overlap_enclosure(start_set, direction.WEST)
# Expand the pin grid set to include some extra grids that connect the supply rail
self.grids.update(new_set)
# Add the inflated set so we don't get wide metal spacing issues (if it exists)
self.blockages.update(grid_utils.inflate_set(new_set,self.router.supply_rail_space_width))
# Add the polygon enclosures and set this pin group as routed
self.set_routed()
self.enclosures = self.compute_enclosures()

View File

@ -59,6 +59,8 @@ class router(router_tech):
### The routed data structures ### The routed data structures
# A list of paths that have been "routed" # A list of paths that have been "routed"
self.paths = [] self.paths = []
# A list of path blockages (they might be expanded for wide metal DRC)
self.path_blockages = []
# The boundary will determine the limits to the size of the routing grid # The boundary will determine the limits to the size of the routing grid
self.boundary = self.layout.measureBoundary(self.top_name) self.boundary = self.layout.measureBoundary(self.top_name)
@ -326,10 +328,16 @@ class router(router_tech):
self.set_supply_rail_blocked(True) self.set_supply_rail_blocked(True)
# Block all of the pin components (some will be unblocked if they're a source/target) # Block all of the pin components (some will be unblocked if they're a source/target)
# Also block the previous routes
for name in self.pin_groups.keys(): for name in self.pin_groups.keys():
blockage_grids = {y for x in self.pin_groups[name] for y in x.grids} blockage_grids = {y for x in self.pin_groups[name] for y in x.grids}
self.set_blockages(blockage_grids,True) self.set_blockages(blockage_grids,True)
blockage_grids = {y for x in self.pin_groups[name] for y in x.blockages}
self.set_blockages(blockage_grids,True)
# FIXME: These duplicate a bit of work
# These are the paths that have already been routed.
self.set_path_blockages()
# Don't mark the other components as targets since we want to route # Don't mark the other components as targets since we want to route
# directly to a rail, but unblock all the source components so we can # directly to a rail, but unblock all the source components so we can
@ -337,8 +345,6 @@ class router(router_tech):
blockage_grids = {y for x in self.pin_groups[pin_name] for y in x.grids} blockage_grids = {y for x in self.pin_groups[pin_name] for y in x.grids}
self.set_blockages(blockage_grids,False) self.set_blockages(blockage_grids,False)
# These are the paths that have already been routed.
self.set_path_blockages()
# def translate_coordinates(self, coord, mirr, angle, xyShift): # def translate_coordinates(self, coord, mirr, angle, xyShift):
# """ # """
@ -387,16 +393,6 @@ class router(router_tech):
# z direction # z direction
return 2 return 2
def add_path_blockages(self):
"""
Go through all of the past paths and add them as blockages.
This is so we don't have to write/reload the GDS.
"""
for path in self.paths:
for grid in path:
self.rg.set_blocked(grid)
def clear_blockages(self): def clear_blockages(self):
""" """
Clear all blockages on the grid. Clear all blockages on the grid.
@ -411,9 +407,9 @@ class router(router_tech):
def set_path_blockages(self,value=True): def set_path_blockages(self,value=True):
""" Flag the paths as blockages """ """ Flag the paths as blockages """
# These are the paths that have already been routed. # These are the paths that have already been routed.
# This adds the initial blockges of the design for path_set in self.path_blockages:
for p in self.paths: for c in path_set:
p.set_blocked(value) self.rg.set_blocked(c,value)
def get_blockage_tracks(self, ll, ur, z): def get_blockage_tracks(self, ll, ur, z):
debug.info(3,"Converting blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) debug.info(3,"Converting blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z))
@ -696,7 +692,7 @@ class router(router_tech):
Convert the pin groups into pin tracks and blockage tracks. Convert the pin groups into pin tracks and blockage tracks.
""" """
for pg in self.pin_groups[pin_name]: for pg in self.pin_groups[pin_name]:
pg.convert_pin(self) pg.convert_pin()
@ -918,13 +914,6 @@ class router(router_tech):
return newpath return newpath
def add_path_blockages(self):
"""
Go through all of the past paths and add them as blockages.
This is so we don't have to write/reload the GDS.
"""
for path in self.paths:
self.rg.block_path(path)
def run_router(self, detour_scale): def run_router(self, detour_scale):
""" """
@ -936,6 +925,9 @@ class router(router_tech):
debug.info(2,"Found path: cost={0} ".format(cost)) debug.info(2,"Found path: cost={0} ".format(cost))
debug.info(3,str(path)) debug.info(3,str(path))
self.paths.append(path) self.paths.append(path)
path_set = grid_utils.flatten_set(path)
inflated_path = grid_utils.inflate_set(path_set,self.supply_rail_space_width)
self.path_blockages.append(inflated_path)
self.add_route(path) self.add_route(path)
else: else:
self.write_debug_gds("failed_route.gds") self.write_debug_gds("failed_route.gds")

View File

@ -99,7 +99,7 @@ class supply_router(router):
self.route_pins_to_rails(gnd_name) self.route_pins_to_rails(gnd_name)
#self.write_debug_gds("debug_pin_routes.gds",stop_program=True) #self.write_debug_gds("debug_pin_routes.gds",stop_program=True)
#self.write_debug_gds("final.gds") #self.write_debug_gds("final.gds",False)
return True return True
@ -110,77 +110,31 @@ class supply_router(router):
This checks for simple cases where a pin component already overlaps a supply rail. 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. 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_wire_tracks[pin_name]
# These are the wire and space tracks
supply_tracks = self.supply_rail_tracks[pin_name] supply_tracks = self.supply_rail_tracks[pin_name]
for pg in self.pin_groups[pin_name]: for pg in self.pin_groups[pin_name]:
if pg.is_routed(): if pg.is_routed():
continue continue
common_set = supply_tracks & pg.grids # First, check if we just overlap, if so, we are done.
overlap_grids = wire_tracks & pg.grids
if len(common_set)>0: if len(overlap_grids)>0:
self.create_simple_overlap_enclosure(pin_name, common_set)
pg.set_routed() pg.set_routed()
continue
# Else, if we overlap some of the space track, we can patch it with an enclosure
common_set = supply_tracks & pg.grids
if len(common_set)>0:
pg.create_simple_overlap_enclosure(common_set)
pg.add_enclosure(self.cell)
def recurse_simple_overlap_enclosure(self, pin_name, start_set, direct):
"""
Recursive function to return set of tracks that connects to
the actual supply rail wire in a given direction (or terminating
when any track is no longer in the supply rail.
"""
next_set = grid_utils.expand_border(start_set, direct)
supply_tracks = self.supply_rail_tracks[pin_name]
supply_wire_tracks = self.supply_rail_wire_tracks[pin_name]
supply_overlap = next_set & supply_tracks
wire_overlap = next_set & supply_wire_tracks
# If the rail overlap is the same, we are done, since we connected to the actual wire
if len(wire_overlap)==len(start_set):
new_set = start_set | wire_overlap
# If the supply overlap is the same, keep expanding unti we hit the wire or move out of the rail region
elif len(supply_overlap)==len(start_set):
recurse_set = self.recurse_simple_overlap_enclosure(pin_name, supply_overlap, direct)
new_set = start_set | supply_overlap | recurse_set
else:
# If we got no next set, we are done, can't expand!
new_set = set()
return new_set
def create_simple_overlap_enclosure(self, pin_name, start_set):
"""
This takes a set of tracks that overlap a supply rail and creates an enclosure
that is ensured to overlap the supply rail wire.
It then adds rectangle(s) for the enclosure.
"""
additional_set = set()
# Check the layer of any element in the pin to determine which direction to route it
e = next(iter(start_set))
new_set = start_set.copy()
if e.z==0:
new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.NORTH)
if not new_set:
new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.SOUTH)
else:
new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.EAST)
if not new_set:
new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.WEST)
pg = pin_group(name=pin_name, pin_set=[], router=self)
pg.grids=new_set
enclosure_list = pg.compute_enclosures()
for pin in enclosure_list:
debug.info(2,"Adding simple overlap enclosure {0} {1}".format(pin_name, pin))
self.cell.add_rect(layer=pin.layer,
offset=pin.ll(),
width=pin.width(),
height=pin.height())
def finalize_supply_rails(self, name): def finalize_supply_rails(self, name):
""" """
@ -478,6 +432,7 @@ class supply_router(router):
for index,pg in enumerate(self.pin_groups[pin_name]): for index,pg in enumerate(self.pin_groups[pin_name]):
if pg.is_routed(): if pg.is_routed():
continue continue
debug.info(2,"Routing component {0} {1}".format(pin_name, index)) debug.info(2,"Routing component {0} {1}".format(pin_name, index))
# Clear everything in the routing grid. # Clear everything in the routing grid.
@ -498,7 +453,6 @@ class supply_router(router):
# Actually run the A* router # Actually run the A* router
if not self.run_router(detour_scale=5): if not self.run_router(detour_scale=5):
self.write_debug_gds() self.write_debug_gds()
def add_supply_rail_target(self, pin_name): def add_supply_rail_target(self, pin_name):