Convert supply tracks to sets for simpler algorithms.

This commit is contained in:
Matt Guthaus 2018-10-20 10:33:10 -07:00
parent 0aad61892b
commit a1f2a5befe
4 changed files with 273 additions and 129 deletions

View File

@ -67,13 +67,15 @@ class grid_path:
""" """
Drop the last item Drop the last item
""" """
self.pathlist.pop() if len(self.pathlist)>0:
self.pathlist.pop()
def trim_first(self): def trim_first(self):
""" """
Drop the first item Drop the first item
""" """
self.pathlist.pop(0) if len(self.pathlist)>0:
self.pathlist.pop(0)
def append(self,item): def append(self,item):
""" """
@ -97,6 +99,25 @@ class grid_path:
for p in sublist: for p in sublist:
p.blocked=value 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): def cost(self):
""" """
The cost of the path is the length plus a penalty for the number The cost of the path is the length plus a penalty for the number

View File

@ -0,0 +1,103 @@
"""
Some utility functions for sets of grid cells.
"""
import debug
from direction import direction
from vector3d import vector3d
def increment_set(curset, direct):
"""
Return the cells incremented in 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)
else:
debug.error("Invalid direction {}".format(dirct))
newset = set()
for c in curset:
newc = c+offset
newset.add(newc)
return newset
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)

View File

@ -9,6 +9,7 @@ from vector import vector
from vector3d import vector3d from vector3d import vector3d
from globals import OPTS from globals import OPTS
from pprint import pformat from pprint import pformat
import grid_utils
class router: class router:
""" """
@ -837,8 +838,9 @@ class router:
""" """
Remove any pin layout that is contained within another. Remove any pin layout that is contained within another.
""" """
local_debug = False
print("INITIAL:",pin_list) if local_debug:
debug.info(0,"INITIAL:",pin_list)
# Make a copy of the list to start # Make a copy of the list to start
new_pin_list = pin_list.copy() new_pin_list = pin_list.copy()
@ -854,7 +856,8 @@ class router:
if pin1 in new_pin_list: if pin1 in new_pin_list:
new_pin_list.remove(pin1) new_pin_list.remove(pin1)
print("FINAL :",new_pin_list) if local_debug:
debug.info(0,"FINAL :",new_pin_list)
return new_pin_list return new_pin_list
def compute_enclosures(self, tracks): def compute_enclosures(self, tracks):
@ -932,7 +935,8 @@ class router:
# If so, we are done. # If so, we are done.
# FIXME: Check if by more than a DRC width # FIXME: Check if by more than a DRC width
if self.overlap_any_shape(pin_group, enclosure_list): if self.overlap_any_shape(pin_group, enclosure_list):
debug.info(2,"Pin overlaps enclosure {0}".format(pin_name)) #debug.info(2,"Pin overlaps enclosure {0}".format(pin_name))
pass
else: else:
new_enclosure = self.find_smallest_connector(pin_group, enclosure_list) new_enclosure = self.find_smallest_connector(pin_group, enclosure_list)
debug.info(2,"Adding connector enclosure {0} {1}".format(pin_name, new_enclosure)) debug.info(2,"Adding connector enclosure {0} {1}".format(pin_name, new_enclosure))
@ -1114,29 +1118,6 @@ class router:
offset=vector(loc.x,loc.y), offset=vector(loc.x,loc.y),
size=(size,size)) size=(size,size))
def add_wavepath(self, name, path):
"""
Add the current wave to the given design instance.
This is a single rectangle that is multiple tracks wide.
It must pay attention to wide metal spacing rules.
"""
path=self.prepare_path(path)
ll = path[0][0]
ur = path[-1][-1]
z = ll.z
pin = self.compute_wide_enclosure(ll, ur, z, name)
#print(ll, ur, ll.z, "->",pin)
self.cell.add_layout_pin(text=name,
layer=pin.layer,
offset=pin.ll(),
width=pin.width(),
height=pin.height())
return pin
def compute_pin_enclosure(self, ll, ur, zindex, name=""): def compute_pin_enclosure(self, ll, ur, zindex, name=""):
""" """
Enclose the tracks from ll to ur in a single rectangle that meets Enclose the tracks from ll to ur in a single rectangle that meets

View File

@ -9,6 +9,7 @@ from vector import vector
from vector3d import vector3d from vector3d import vector3d
from router import router from router import router
from direction import direction from direction import direction
import grid_utils
class supply_router(router): class supply_router(router):
""" """
@ -24,9 +25,10 @@ class supply_router(router):
router.__init__(self, layers, design, gds_filename) router.__init__(self, layers, design, gds_filename)
# The list of supply rails that may be routed # The list of supply rails (grid sets) that may be routed
self.supply_rails = [] self.supply_rails = {}
# This is the same as above but the sets of pins self.supply_rail_wires = {}
# This is the same as above but as a sigle set for the all the rails
self.supply_rail_tracks = {} self.supply_rail_tracks = {}
self.supply_rail_wire_tracks = {} self.supply_rail_wire_tracks = {}
@ -76,17 +78,17 @@ class supply_router(router):
self.prepare_blockages(self.vdd_name) self.prepare_blockages(self.vdd_name)
# Determine the rail locations # Determine the rail locations
self.route_supply_rails(self.vdd_name,1) self.route_supply_rails(self.vdd_name,1)
#self.write_debug_gds("debug_rails.gds",stop_program=True)
#self.write_debug_gds("pre_pin_debug.gds",stop_program=True)
remaining_vdd_pin_indices = self.route_simple_overlaps(vdd_name) remaining_vdd_pin_indices = self.route_simple_overlaps(vdd_name)
remaining_gnd_pin_indices = self.route_simple_overlaps(gnd_name) remaining_gnd_pin_indices = self.route_simple_overlaps(gnd_name)
#self.write_debug_gds("debug_simple_route.gds",stop_program=True)
# Route the supply pins to the supply rails # Route the supply pins to the supply rails
# Route vdd first since we want it to be shorter # Route vdd first since we want it to be shorter
self.route_pins_to_rails(vdd_name, remaining_vdd_pin_indices) self.route_pins_to_rails(vdd_name, remaining_vdd_pin_indices)
self.route_pins_to_rails(gnd_name, remaining_gnd_pin_indices) self.route_pins_to_rails(gnd_name, remaining_gnd_pin_indices)
#self.write_debug_gds("debug_pin_routes.gds",stop_program=True)
self.write_debug_gds("post_pin_debug.gds",stop_program=False)
return True return True
@ -122,7 +124,6 @@ class supply_router(router):
the actual supply rail wire in a given direction (or terminating the actual supply rail wire in a given direction (or terminating
when any track is no longer in the supply rail. when any track is no longer in the supply rail.
""" """
import grid_utils
next_set = grid_utils.expand_border(start_set, direct) next_set = grid_utils.expand_border(start_set, direct)
supply_tracks = self.supply_rail_tracks[pin_name] supply_tracks = self.supply_rail_tracks[pin_name]
@ -154,8 +155,6 @@ class supply_router(router):
that is ensured to overlap the supply rail wire. that is ensured to overlap the supply rail wire.
It then adds rectangle(s) for the enclosure. It then adds rectangle(s) for the enclosure.
""" """
import grid_utils
additional_set = set() additional_set = set()
# Check the layer of any element in the pin to determine which direction to route it # Check the layer of any element in the pin to determine which direction to route it
e = next(iter(start_set)) e = next(iter(start_set))
@ -180,46 +179,52 @@ class supply_router(router):
def connect_supply_rails(self, name): def finalize_supply_rails(self, name):
""" """
Determine which supply rails overlap and can accomodate a via. Determine which supply rails overlap and can accomodate a via.
Remove any supply rails that do not have a via since they are disconnected. 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. NOTE: It is still possible though unlikely that there are disconnected groups of rails.
""" """
# Split into horizontal and vertical paths all_rails = self.supply_rail_wires[name]
vertical_rails = [x for x in self.supply_rails if x[0][0].z==1 and x.name==name]
horizontal_rails = [x for x in self.supply_rails if x[0][0].z==0 and x.name==name]
# Flag to see if each supply rail has at least one via (i.e. it is "connected") connections = set()
vertical_flags = [False] * len(vertical_rails)
horizontal_flags = [False] * len(horizontal_rails)
# Compute a list of "shared areas" that are bigger than a via
via_areas = [] via_areas = []
for vindex,v in enumerate(vertical_rails): for i1,r1 in enumerate(all_rails):
for hindex,h in enumerate(horizontal_rails): # We need to move this rail to the other layer for the intersection to work
# Compute the overlap of the two paths, None if no overlap e = next(iter(r1))
overlap = v.overlap(h) newz = (e.z+1)%2
if overlap: new_r1 = {vector3d(i.x,i.y,newz) for i in r1}
(ll,ur) = overlap for i2,r2 in enumerate(all_rails):
# We can add a via only if it is a full track width in each dimension if i1==i2:
if ur.x-ll.x >= self.rail_track_width-1 and ur.y-ll.y >= self.rail_track_width-1: continue
vertical_flags[vindex]=True overlap = new_r1 & r2
horizontal_flags[hindex]=True if len(overlap) >= self.supply_rail_wire_width**2:
via_areas.append(overlap) connections.add(i1)
connections.add(i2)
via_areas.append(overlap)
# Go through and add the vias at the center of the intersection # Go through and add the vias at the center of the intersection
for (ll,ur) in via_areas: 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) center = (ll + ur).scale(0.5,0.5,0)
self.add_via(center,self.rail_track_width) self.add_via(center,self.rail_track_width)
# Retrieve the original indices into supply_rails for removal all_indices = set([x for x in range(len(self.supply_rails[name]))])
remove_hrails = [rail for flag,rail in zip(horizontal_flags,horizontal_rails) if not flag] missing_indices = all_indices ^ connections
remove_vrails = [rail for flag,rail in zip(vertical_flags,vertical_rails) if not flag]
for rail in remove_hrails + remove_vrails: for rail_index in missing_indices:
debug.info(1,"Removing disconnected supply rail {0} .. {1}".format(rail[0][0],rail[-1][-1])) ll = grid_utils.get_lower_left(all_rails[rail_index])
self.supply_rails.remove(rail) 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)
self.supply_rail_wires[name].pop(rail_index)
# Make the supply rails into a big giant set of grids
# Must be done after determine which ones are connected)
self.create_supply_track_set(name)
def add_supply_rails(self, name): def add_supply_rails(self, name):
""" """
@ -227,9 +232,17 @@ class supply_router(router):
This is after the paths have been pruned and only include rails that are This is after the paths have been pruned and only include rails that are
connected with vias. connected with vias.
""" """
for wave_path in self.supply_rails: for rail in self.supply_rails[name]:
if wave_path.name == name: ll = grid_utils.get_lower_left(rail)
self.add_wavepath(name, wave_path) ur = grid_utils.get_upper_right(rail)
z = ll.z
pin = self.compute_wide_enclosure(ll, ur, z, name)
debug.info(1,"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_rail_dimensions(self): def compute_supply_rail_dimensions(self):
""" """
@ -275,6 +288,10 @@ class supply_router(router):
Go in a raster order from bottom to the top (for horizontal) and left to right 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. (for vertical). Start with an initial start_offset in x and y direction.
""" """
self.supply_rails[name]=[]
self.supply_rail_wires[name]=[]
start_offset = supply_number*self.supply_rail_width start_offset = supply_number*self.supply_rail_width
# Horizontal supply rails # Horizontal supply rails
@ -283,7 +300,11 @@ class supply_router(router):
wave = [vector3d(0,offset+i,0) for i in range(self.supply_rail_width)] wave = [vector3d(0,offset+i,0) for i in range(self.supply_rail_width)]
# While we can keep expanding east in this horizontal track # While we can keep expanding east in this horizontal track
while wave and wave[0].x < self.max_xoffset: while wave and wave[0].x < self.max_xoffset:
wave = self.find_supply_rail(name, wave, direction.EAST) added_rail = self.find_supply_rail(name, wave, direction.EAST)
if added_rail:
wave = added_rail.neighbor(direction.EAST)
else:
wave = None
# Vertical supply rails # Vertical supply rails
@ -293,10 +314,41 @@ class supply_router(router):
wave = [vector3d(offset+i,0,1) for i in range(self.supply_rail_width)] wave = [vector3d(offset+i,0,1) for i in range(self.supply_rail_width)]
# While we can keep expanding north in this vertical track # While we can keep expanding north in this vertical track
while wave and wave[0].y < self.max_yoffset: while wave and wave[0].y < self.max_yoffset:
wave = self.find_supply_rail(name, wave, direction.NORTH) added_rail = self.find_supply_rail(name, wave, direction.NORTH)
if added_rail:
wave = added_rail.neighbor(direction.NORTH)
else:
wave = None
def find_supply_rail(self, name, seed_wave, direct): 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.
"""
start_wave = self.find_supply_rail_start(name, seed_wave, direct)
if not start_wave:
return None
wave_path = self.probe_supply_rail(name, start_wave, direct)
if self.approve_supply_rail(name, wave_path):
return wave_path
else:
return None
def find_supply_rail_start(self, name, seed_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.
"""
# Sweep to find an initial unblocked valid wave
start_wave = self.rg.find_start_wave(seed_wave, len(seed_wave), direct)
return start_wave
def probe_supply_rail(self, name, start_wave, direct):
""" """
This finds the first valid starting location and routes a supply rail This finds the first valid starting location and routes a supply rail
in the given direction. in the given direction.
@ -304,33 +356,41 @@ class supply_router(router):
supply rails in the same "track" when there is a blockage. supply rails in the same "track" when there is a blockage.
""" """
# Sweep to find an initial unblocked valid wave
start_wave = self.rg.find_start_wave(seed_wave, len(seed_wave), direct)
if not start_wave:
return None
# Expand the wave to the right # Expand the wave to the right
wave_path = self.rg.probe(start_wave, direct) wave_path = self.rg.probe(start_wave, direct)
if not wave_path: if not wave_path:
return None 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 # We must have at least 2 tracks to drop plus 2 tracks for a via
if len(wave_path)>=4*self.rail_track_width: if len(wave_path)>=4*self.rail_track_width:
# drop the first and last steps to leave escape routing room grid_set = wave_path.get_grids()
# around the blockage that stopped the probe self.supply_rails[name].append(grid_set)
# except, don't drop the first if it is the first in a row/column start_wire_index = self.supply_rail_space_width
if (direct==direction.NORTH and seed_wave[0].y>0): end_wire_index = self.supply_rail_width - self.supply_rail_space_width
wave_path.trim_first() wire_set = wave_path.get_wire_grids(start_wire_index,end_wire_index)
elif (direct == direction.EAST and seed_wave[0].x>0): self.supply_rail_wires[name].append(wire_set)
wave_path.trim_first() return True
wave_path.trim_last() return False
wave_path.name = name
self.supply_rails.append(wave_path)
# seed the next start wave location
wave_end = wave_path[-1]
return wave_path.neighbor(direct)
@ -348,36 +408,25 @@ class supply_router(router):
self.compute_supply_rails(name, supply_number) self.compute_supply_rails(name, supply_number)
# Add the supply rail vias (and prune disconnected rails) # Add the supply rail vias (and prune disconnected rails)
self.connect_supply_rails(name) self.finalize_supply_rails(name)
# Add the rails themselves # Add the rails themselves
self.add_supply_rails(name) self.add_supply_rails(name)
# Make the supply rails into a big giant set of grids
self.create_supply_track_set(name)
def create_supply_track_set(self, pin_name): def create_supply_track_set(self, pin_name):
""" """
Take the remaining supply rails and put the middle grids into a set. Make a single set of all the tracks for the rail and wire itself.
The middle grids will be guaranteed to have the wire.
FIXME: Do this instead with the supply_rail_pitch and width
""" """
rail_set = set() rail_set = set()
wire_set = set() for rail in self.supply_rails[pin_name]:
for rail in self.supply_rails: rail_set.update(rail)
if rail.name != pin_name:
continue
# FIXME: Select based on self.supply_rail_wire_width and self.supply_rail_width
start_wire_index = self.supply_rail_space_width
end_wire_index = self.supply_rail_width - self.supply_rail_space_width
for wave_index in range(len(rail)):
rail_set.update(rail[wave_index])
wire_set.update(rail[wave_index][start_wire_index:end_wire_index])
self.supply_rail_tracks[pin_name] = rail_set self.supply_rail_tracks[pin_name] = rail_set
self.supply_rail_wire_tracks[pin_name] = wire_set
wire_set = set()
for rail in self.supply_rail_wires[pin_name]:
wire_set.update(rail)
self.supply_rail_wire_tracks[pin_name] = wire_set
def route_pins_to_rails(self, pin_name, remaining_component_indices): def route_pins_to_rails(self, pin_name, remaining_component_indices):
@ -424,17 +473,10 @@ class supply_router(router):
Add the supply rails of given name as a routing target. Add the supply rails of given name as a routing target.
""" """
debug.info(2,"Add supply rail target {}".format(pin_name)) debug.info(2,"Add supply rail target {}".format(pin_name))
for rail in self.supply_rails: # Add the wire itself as the target
if rail.name != pin_name: self.rg.set_target(self.supply_rail_wire_tracks[pin_name])
continue # But unblock all the rail tracks including the space
# Set the middle track only as the target since wide metal self.rg.set_blocked(self.supply_rail_tracks[pin_name],False)
# spacings may mean the other grids are actually empty space
middle_index = math.floor(len(rail[0])/2)
for wave_index in range(len(rail)):
pin_in_tracks = rail[wave_index]
#debug.info(1,"Set target: " + str(pin_name) + " " + str(pin_in_tracks))
self.rg.set_target(pin_in_tracks[middle_index])
self.rg.set_blocked(pin_in_tracks,False)
def set_supply_rail_blocked(self, value=True): def set_supply_rail_blocked(self, value=True):
@ -442,9 +484,6 @@ class supply_router(router):
Add the supply rails of given name as a routing target. Add the supply rails of given name as a routing target.
""" """
debug.info(3,"Blocking supply rail") debug.info(3,"Blocking supply rail")
for rail in self.supply_rails: for rail_name in self.supply_rail_tracks:
for wave_index in range(len(rail)): self.rg.set_blocked(self.supply_rail_tracks[rail_name])
pin_in_tracks = rail[wave_index]
#debug.info(1,"Set target: " + str(pin_name) + " " + str(pin_in_tracks))
self.rg.set_blocked(pin_in_tracks,value)