Add remove adjacent feature for wide metal spacing

This commit is contained in:
Matt Guthaus 2018-10-30 12:24:13 -07:00
parent c4163d3401
commit 1344a8f7f1
6 changed files with 197 additions and 65 deletions

View File

@ -30,3 +30,10 @@ class direction(Enum):
debug.error("Invalid direction {}".format(dirct))
return offset
def cardinal_directions():
return [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST]
def cardinal_offsets():
return [direction.get_offset(d) for d in direction.cardinal_directions()]

View File

@ -172,20 +172,7 @@ class grid_path:
return neighbors
def neighbor(self, d):
if d==direction.EAST:
offset = vector3d(1,0,0)
elif d==direction.WEST:
offset = vector3d(-1,0,0)
elif d==direction.NORTH:
offset = vector3d(0,1,0)
elif d==direction.SOUTH:
offset = vector3d(0,-1,0)
elif d==direction.UP:
offset = vector3d(0,0,1)
elif d==direction.DOWN:
offset = vector3d(0,0,-1)
else:
debug.error("Invalid direction {}".format(d),-1)
offset = direction.get_offset(d)
newwave = [point + offset for point in self.pathlist[-1]]

View File

@ -15,6 +15,9 @@ class pin_group:
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
if pin_shapes:
@ -25,10 +28,40 @@ class pin_group:
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 corresponding set of partially blocked grids for each pin group.
# These are blockages for other nets but unblocked for routing this group.
self.blockages = 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:
enlosure_string = "\n enclose={}".format(self.enclosures)
total_string += enclosure_string
total_string += ")"
return total_string
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
@ -277,22 +310,41 @@ class pin_group:
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()
# A single set of connected pins is easy, so use the optimized set
if len(self.pins)==1:
enclosure_list = self.enclosures
smallest = self.find_smallest_overlapping(self.pins[0],enclosure_list)
if smallest:
self.enclosures=[smallest]
debug.info(2,"Computed enclosure(s) {0}\n {1}\n {2}\n {3}".format(self.name,
# if len(self.pins)==1:
# enclosure_list = self.enclosures
# smallest = self.find_smallest_overlapping(self.pins[0],enclosure_list)
# if smallest:
# self.enclosures=[smallest]
# Save the list of all grids
#self.all_grids = self.grids.copy()
# Remove the grids that are not covered by the enclosures
# FIXME: We could probably just store what grids each enclosure overlaps when
# it was created.
#for enclosure in self.enclosures:
# enclosure_in_tracks=router.convert_pin_to_tracks(self.name, enclosure)
# self.grids.difference_update(enclosure_in_tracks)
debug.info(3,"Computed enclosure(s) {0}\n {1}\n {2}\n {3}".format(self.name,
self.pins,
self.grids,
self.enclosures))
def combine_pins(self, pg1, pg2):
"""
Combine two pin groups into one.
"""
self.pins = [*pg1.pins, *pg2.pins] # Join the two lists of pins
self.grids = pg1.grids | pg2.grids # OR the set of grid locations
self.secondary_grids = pg1.secondary_grids | pg2.secondary_grids
def add_enclosure(self, cell):
"""
Add the enclosure shape to the given cell.
@ -305,23 +357,57 @@ class pin_group:
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.grids:
for g2 in other.grids:
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
g1_grids = set()
g2_grids = set()
for g1 in self.grids:
for g2 in other.grids:
if g1.distance(g2) <= separation:
g1_grids.add(g1)
g2_grids.add(g2)
return g1_grids,g2_grids
def convert_pin(self, router):
#print("PG ",pg)
# Keep the same groups for each pin
"""
Convert the list of pin shapes into sets of routing grids.
The secondary set of grids are "optional" pin shapes that could be
should be either blocked or part of the pin.
"""
pin_set = set()
blockage_set = set()
@ -334,19 +420,6 @@ class pin_group:
# Blockages will be a super-set of pins since it uses the inflated pin shape.
blockage_in_tracks = router.convert_blockage(pin)
blockage_set.update(blockage_in_tracks)
# If we have a blockage, we must remove the grids
# Remember, this excludes the pin blockages already
shared_set = pin_set & router.blocked_grids
if len(shared_set)>0:
debug.info(2,"Removing pins {}".format(shared_set))
shared_set = blockage_set & router.blocked_grids
if len(shared_set)>0:
debug.info(2,"Removing blocks {}".format(shared_set))
pin_set.difference_update(router.blocked_grids)
blockage_set.difference_update(router.blocked_grids)
debug.info(2," pins {}".format(pin_set))
debug.info(2," blocks {}".format(blockage_set))
# At least one of the groups must have some valid tracks
if (len(pin_set)==0 and len(blockage_set)==0):
@ -355,14 +428,9 @@ class pin_group:
# We need to route each of the components, so don't combine the groups
self.grids = pin_set | blockage_set
# Remember the secondary grids for removing adjacent pins in wide metal spacing
self.secondary_grids = blockage_set - pin_set
# Add all of the partial blocked grids to the set for the design
# if they are not blocked by other metal
#partial_set = blockage_set - pin_set
#self.blockages = partial_set
# We should not have added the pins to the blockages,
# but remove them just in case
# Partial set may still be in the blockages if there were
# other shapes disconnected from the pins that were also overlapping
#route.blocked_grids.difference_update(pin_set)
debug.info(2," pins {}".format(self.grids))
debug.info(2," secondary {}".format(self.secondary_grids))

View File

@ -48,7 +48,6 @@ class router(router_tech):
self.all_pins = set()
# A map of pin names to a list of pin groups
# A pin group is a set overlapping pin shapes on the same layer.
self.pin_groups = {}
### The blockage data structures
@ -157,10 +156,14 @@ class router(router_tech):
#self.write_debug_gds("debug_combine_pins.gds",stop_program=True)
# Separate any adjacent grids of differing net names to prevent wide metal DRC violations
self.separate_adjacent_pins(pin)
# Must be done before enclosing pins
self.separate_adjacent_pins(self.supply_rail_space_width)
# For debug
#self.separate_adjacent_pins(1)
# Enclose the continguous grid units in a metal rectangle to fix some DRCs
self.enclose_pins()
#self.write_debug_gds("debug_enclose_pins.gds",stop_program=True)
def combine_adjacent_pins_pass(self, pin_name):
@ -191,13 +194,13 @@ class router(router_tech):
# Combine if at least 1 grid cell is adjacent
if pg1.adjacent(pg2):
combined = pin_group(pin_name, [], self)
combined.pins = [*pg1.pins, *pg2.pins] # Join the two lists of pins
combined.grids = pg1.grids | pg2.grids # OR the set of grid locations
debug.info(2,"Combining {0}:\n {1}\n {2}".format(pin_name, pg1.pins, pg2.pins))
debug.info(2," --> {0}\n {1}\n".format(combined.pins,combined.grids))
combined.combine_pins(pg1, pg2)
debug.info(2,"Combining {0} {1} {2}:".format(pin_name, index1, index2))
debug.info(2, " {0}\n {1}".format(pg1.pins, pg2.pins))
debug.info(2," --> {0}\n {1}".format(combined.pins,combined.grids))
remove_indices.update([index1,index2])
pin_groups.append(combined)
break
# Remove them in decreasing order to not invalidate the indices
debug.info(2,"Removing {}".format(sorted(remove_indices)))
@ -207,7 +210,7 @@ class router(router_tech):
# Use the new pin group!
self.pin_groups[pin_name] = pin_groups
removed_pairs = len(remove_indices)/2
removed_pairs = int(len(remove_indices)/2)
debug.info(1, "Combined {0} pin pairs for {1}".format(removed_pairs,pin_name))
return removed_pairs
@ -229,17 +232,77 @@ class router(router_tech):
else:
debug.warning("Did not converge combining adjacent pins in supply router.")
def separate_adjacent_pins(self, pin_name, separation=1):
def separate_adjacent_pins(self, separation):
"""
This will try to separate all grid pins by the supplied number of separation
tracks (default is to prevent adjacency).
"""
# Commented out to debug with SCMOS
#if separation==0:
# return
pin_names = self.pin_groups.keys()
for pin_name1 in pin_names:
for pin_name2 in pin_names:
if pin_name1==pin_name2:
continue
self.separate_adjacent_pin(pin_name1, pin_name2, separation)
def separate_adjacent_pin(self, pin_name1, pin_name2, separation):
"""
Go through all of the pin groups and check if any other pin group is
within a separation of it.
If so, reduce the pin group grid to not include the adjacent grid.
Try to do this intelligently to keep th pins enclosed.
"""
pass
debug.info(1,"Comparing {0} and {1} adjacency".format(pin_name1, pin_name2))
for index1,pg1 in enumerate(self.pin_groups[pin_name1]):
for index2,pg2 in enumerate(self.pin_groups[pin_name2]):
# FIXME: Use separation distance and edge grids only
grids_g1, grids_g2 = pg1.adjacent_grids(pg2, separation)
# These should have the same length, so...
if len(grids_g1)>0:
debug.info(1,"Adjacent grids {0} {1} {2} {3}".format(index1,grids_g1,index2,grids_g2))
self.remove_adjacent_grid(pg1, grids_g1, pg2, grids_g2)
def remove_adjacent_grid(self, pg1, grids1, pg2, grids2):
"""
Remove one of the adjacent grids in a heuristic manner.
"""
# Determine the bigger and smaller group
if pg1.size()>pg2.size():
bigger = pg1
bigger_grids = grids1
smaller = pg2
smaller_grids = grids2
else:
bigger = pg2
bigger_grids = grids2
smaller = pg1
smaller_grids = grids1
# First, see if we can remove grids that are in the secondary grids
# i.e. they aren't necessary to the pin grids
if bigger_grids.issubset(bigger.secondary_grids):
debug.info(1,"Removing {} from bigger {}".format(str(bigger_grids), bigger))
bigger.grids.difference_update(bigger_grids)
self.blocked_grids.update(bigger_grids)
return
elif smaller_grids.issubset(smaller.secondary_grids):
debug.info(1,"Removing {} from smaller {}".format(str(smaller_grids), smaller))
smaller.grids.difference_update(smaller_grids)
self.blocked_grids.update(smaller_grids)
return
# If that fails, just randomly remove from the bigger one and give a warning.
# This might fail later.
debug.warning("Removing arbitrary grids from a pin group {} {}".format(bigger, bigger_grids))
debug.check(len(bigger.grids)>len(bigger_grids),"Zero size pin group after adjacency removal {} {}".format(bigger, bigger_grids))
bigger.grids.difference_update(bigger_grids)
self.blocked_grids.update(bigger_grids)
def prepare_blockages(self, pin_name):
"""
Reset and add all of the blockages in the design.
@ -975,6 +1038,8 @@ class router(router_tech):
if show_enclosures:
for key in self.pin_groups.keys():
for pg in self.pin_groups[key]:
if not pg.enclosed:
continue
for pin in pg.enclosures:
#print("enclosure: ",pin.name,pin.ll(),pin.width(),pin.height())
self.cell.add_rect(layer="text",

View File

@ -41,6 +41,7 @@ class supply_router(router):
# Power rail width in grid units.
self.rail_track_width = 2
def create_routing_grid(self):
"""
@ -69,6 +70,9 @@ class supply_router(router):
# but this is simplest for now.
self.create_routing_grid()
# Compute the grid dimensions
self.compute_supply_rail_dimensions()
# Get the pin shapes
self.find_pins_and_blockages([self.vdd_name, self.gnd_name])
#self.write_debug_gds("pin_enclosures.gds",stop_program=True)
@ -87,7 +91,7 @@ class supply_router(router):
self.route_simple_overlaps(vdd_name)
self.route_simple_overlaps(gnd_name)
#self.write_debug_gds("debug_simple_route.gds",stop_program=True)
self.write_debug_gds("debug_simple_route.gds",stop_program=False)
# Route the supply pins to the supply rails
# Route vdd first since we want it to be shorter
@ -436,9 +440,6 @@ class supply_router(router):
Must be done with lower left at 0,0
"""
# Compute the grid dimensions
self.compute_supply_rail_dimensions()
# Compute the grid locations of the supply rails
self.compute_supply_rails(name, supply_number)

View File

@ -163,6 +163,10 @@ class vector3d():
""" Min of both values """
return vector3d(min(self.x,other.x),min(self.y,other.y),min(self.z,other.z))
def distance(self, other):
""" Return the planar distance between two values """
return abs(self.x-other.x)+abs(self.y-other.y)
def adjacent(self, other):
""" Is the one grid adjacent in any planar direction to the other """