Fix unconnected supply pin bug in supply router.

Simplified some of the supply router pin groups so that it assumes
each group is fully connected. When computing enclosures of the
pins on the routing grid, it will remove disconnected enclosure
shapes to keep things connected.
This commit is contained in:
Matt Guthaus 2019-04-24 10:54:22 -07:00
parent 2e353639f7
commit 7f5e6dd6f8
3 changed files with 161 additions and 229 deletions

View File

@ -25,7 +25,7 @@ class pin_group:
# 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(irredundant_pin_set)]
self.pins = set(irredundant_pin_set)
self.router = router
# These are the corresponding pin grids for each pin group.
@ -55,7 +55,7 @@ class pin_group:
total_string += grids_string
if self.enclosed:
enlosure_string = "\n enclose={}".format(self.enclosures)
enclosure_string = "\n enclose={}".format(self.enclosures)
total_string += enclosure_string
total_string += ")"
@ -74,25 +74,6 @@ class pin_group:
def is_routed(self):
return self.routed
def pins_enclosed(self):
"""
Check if all of the pin shapes are enclosed.
Does not check if the DRC is correct, but just touching.
"""
for pin_list in self.pins:
pin_is_enclosed=False
for pin in pin_list:
if pin_is_enclosed:
break
for encosure in self.enclosures:
if pin.overlaps(enclosure):
pin_is_enclosed=True
break
else:
return False
return True
def remove_redundant_shapes(self, pin_list):
"""
Remove any pin layout that is contained within another.
@ -135,7 +116,6 @@ class pin_group:
return new_pin_list
# FIXME: This relies on some technology parameters from router which is not clean.
def compute_enclosures(self):
"""
Find the minimum rectangle enclosures of the given tracks.
@ -406,8 +386,8 @@ class pin_group:
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 right until blocked
and then up until blocked.
starting with the seed and expanding dir1 until blocked
and then dir2 until blocked.
dir1 and dir2 should be two orthogonal directions.
"""
@ -458,61 +438,86 @@ class pin_group:
# Compute the enclosure pin_layout list of the set of tracks
self.enclosures = self.compute_enclosures()
for pin_list in self.pins:
for pin in pin_list:
# 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
# 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)
# 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
for pin_list in self.pins:
if not self.overlap_any_shape(pin_list, self.enclosures):
connector = self.find_smallest_connector(pin_list, self.enclosures)
if connector==None:
debug.error("Could not find a connector for {} with {}".format(pin_list, self.enclosures))
self.router.write_debug_gds("no_connector.gds")
self.enclosures.append(connector)
if not self.overlap_any_shape(self.pins, self.enclosures):
connector = self.find_smallest_connector(pin_list, self.enclosures)
if connector==None:
debug.error("Could not find a connector for {} with {}".format(pin_list, 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 combine_groups(self, pg1, pg2):
def transitive_overlap(self, shape, shape_list):
"""
Combine two pin groups into one.
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.
"""
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_group(self, pg):
"""
Combine the pin group into this one. This will add to the first item in the pins
so this should be used before there are disconnected pins.
"""
debug.check(len(self.pins)==1,"Don't know which group to add pins to.")
self.pins[0].update(*pg.pins) # Join the two lists of pins
self.grids |= pg.grids # OR the set of grid locations
self.secondary_grids |= pg.secondary_grids
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):
"""
@ -579,17 +584,16 @@ class pin_group:
partial_set = set()
blockage_set = set()
for pin_list in self.pins:
for pin in pin_list:
debug.info(2," 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)
for pin in self.pins:
debug.info(2," 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)
blockage_set.update(blockage_in_tracks)
# Blockages will be a super-set of pins since it uses the inflated pin shape.
blockage_in_tracks = self.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
@ -610,13 +614,12 @@ class pin_group:
if (len(pin_set)==0 and len(partial_set)==0 and len(blockage_set)==0):
#debug.warning("Pin is very close to metal blockage.\nAttempting to expand blocked pin {}".format(self.pins))
for pin_list in self.pins:
for pin in pin_list:
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)
pin_set.update(sufficient)
partial_set.update(insufficient)
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)
pin_set.update(sufficient)
partial_set.update(insufficient)
if len(pin_set)==0 and len(partial_set)==0:
debug.error("Unable to find unblocked pin {} {}".format(self.name, self.pins))
@ -630,62 +633,5 @@ class pin_group:
debug.info(2," pins {}".format(self.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)
# # Block the grids
# self.blockages.update(new_set)
# # Add the polygon enclosures and set this pin group as routed
# self.set_routed()
# self.enclosures = self.compute_enclosures()

View File

@ -187,60 +187,64 @@ class router(router_tech):
start_time = datetime.now()
self.enclose_pins()
print_time("Enclosing pins",datetime.now(), start_time, 4)
# MRG: Removing this code for now. The later compute enclosure code
# assumes that all pins are touching and this may produce sets of pins
# that are not connected.
# def combine_adjacent_pins(self, pin_name):
# """
# Find pins that have adjacent routing tracks and merge them into a
# single pin_group. The pins themselves may not be touching, but
# enclose_pins in the next step will ensure they are touching.
# """
# debug.info(1,"Combining adjacent pins for {}.".format(pin_name))
# # Find all adjacencies
# adjacent_pins = {}
# for index1,pg1 in enumerate(self.pin_groups[pin_name]):
# for index2,pg2 in enumerate(self.pin_groups[pin_name]):
# # Cannot combine with yourself, also don't repeat
# if index1<=index2:
# continue
# # Combine if at least 1 grid cell is adjacent
# if pg1.adjacent(pg2):
# if not index1 in adjacent_pins:
# adjacent_pins[index1] = set([index2])
# else:
# adjacent_pins[index1].add(index2)
# # Make a list of indices to ensure every group gets in the new set
# all_indices = set([x for x in range(len(self.pin_groups[pin_name]))])
def combine_adjacent_pins(self, pin_name):
"""
Find pins that have adjacent routing tracks and merge them into a
single pin_group. The pins themselves may not be touching, but
enclose_pins in the next step will ensure they are touching.
"""
debug.info(1,"Combining adjacent pins for {}.".format(pin_name))
# Find all adjacencies
adjacent_pins = {}
for index1,pg1 in enumerate(self.pin_groups[pin_name]):
for index2,pg2 in enumerate(self.pin_groups[pin_name]):
# Cannot combine with yourself, also don't repeat
if index1<=index2:
continue
# Combine if at least 1 grid cell is adjacent
if pg1.adjacent(pg2):
if not index1 in adjacent_pins:
adjacent_pins[index1] = set([index2])
else:
adjacent_pins[index1].add(index2)
# # Now reconstruct the new groups
# new_pin_groups = []
# for index1,index2_set in adjacent_pins.items():
# # Remove the indices if they are added to the new set
# all_indices.discard(index1)
# all_indices.difference_update(index2_set)
# Make a list of indices to ensure every group gets in the new set
all_indices = set([x for x in range(len(self.pin_groups[pin_name]))])
# # Create the combined group starting with the first item
# combined = self.pin_groups[pin_name][index1]
# # Add all of the other items that overlapped
# for index2 in index2_set:
# pg = self.pin_groups[pin_name][index2]
# combined.add_group(pg)
# debug.info(3,"Combining {0} {1}:".format(pin_name, index2))
# debug.info(3, " {0}\n {1}".format(combined.pins, pg.pins))
# debug.info(3," --> {0}\n {1}".format(combined.pins,combined.grids))
# new_pin_groups.append(combined)
# # Add the pin groups that weren't added to the new set
# for index in all_indices:
# new_pin_groups.append(self.pin_groups[pin_name][index])
# old_size = len(self.pin_groups[pin_name])
# # Use the new pin group!
# self.pin_groups[pin_name] = new_pin_groups
# removed_pairs = old_size - len(new_pin_groups)
# debug.info(1, "Combined {0} pin groups for {1}".format(removed_pairs,pin_name))
# Now reconstruct the new groups
new_pin_groups = []
for index1,index2_set in adjacent_pins.items():
# Remove the indices if they are added to the new set
all_indices.discard(index1)
all_indices.difference_update(index2_set)
# Create the combined group starting with the first item
combined = self.pin_groups[pin_name][index1]
# Add all of the other items that overlapped
for index2 in index2_set:
pg = self.pin_groups[pin_name][index2]
combined.add_group(pg)
debug.info(3,"Combining {0} {1}:".format(pin_name, index2))
debug.info(3, " {0}\n {1}".format(combined.pins, pg.pins))
debug.info(3," --> {0}\n {1}".format(combined.pins,combined.grids))
new_pin_groups.append(combined)
# Add the pin groups that weren't added to the new set
for index in all_indices:
new_pin_groups.append(self.pin_groups[pin_name][index])
old_size = len(self.pin_groups[pin_name])
# Use the new pin group!
self.pin_groups[pin_name] = new_pin_groups
removed_pairs = old_size - len(new_pin_groups)
debug.info(1, "Combined {0} pin groups for {1}".format(removed_pairs,pin_name))
return removed_pairs
# return removed_pairs
def separate_adjacent_pins(self, separation):
@ -748,44 +752,10 @@ class router(router_tech):
if gid not in group_map:
group_map[gid] = pin_group(name=pin_name, pin_set=[], router=self)
# We always add it to the first set since they are touching
group_map[gid].pins[0].add(pin)
group_map[gid].pins.add(pin)
self.pin_groups[pin_name] = list(group_map.values())
# This is the old O(n^2) implementation
# def analyze_pins(self, pin_name):
# """
# Analyze the shapes of a pin and combine them into pin_groups which are connected.
# """
# debug.info(2,"Analyzing pin groups for {}.".format(pin_name))
# pin_set = self.pins[pin_name]
# # Put each pin in an equivalence class of it's own
# equiv_classes = [set([x]) for x in pin_set]
# def combine_classes(equiv_classes):
# for class1 in equiv_classes:
# for class2 in equiv_classes:
# if class1 == class2:
# continue
# # Compare each pin in each class,
# # and if any overlap, update equiv_classes to include the combined the class
# for p1 in class1:
# for p2 in class2:
# if p1.overlaps(p2):
# combined_class = class1 | class2
# equiv_classes.remove(class1)
# equiv_classes.remove(class2)
# equiv_classes.append(combined_class)
# return(equiv_classes)
# return(equiv_classes)
# old_length = math.inf
# while (len(equiv_classes)<old_length):
# old_length = len(equiv_classes)
# equiv_classes = combine_classes(equiv_classes)
# self.pin_groups[pin_name] = [pin_group(name=pin_name, pin_set=x, router=self) for x in equiv_classes]
def convert_pins(self, pin_name):
"""

View File

@ -81,7 +81,7 @@ class supply_router(router):
# 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)
@ -94,10 +94,23 @@ class supply_router(router):
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 check_all_routed(self, pin_name):
"""
Check that all pin groups are routed.
"""
for pg in self.pin_groups[pin_name]:
if not pg.is_routed():
return False
def route_simple_overlaps(self, pin_name):
"""
@ -146,7 +159,7 @@ class supply_router(router):
# 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:
@ -202,7 +215,7 @@ class supply_router(router):
ur = grid_utils.get_upper_right(rail)
z = ll.z
pin = self.compute_pin_enclosure(ll, ur, z, name)
debug.info(2,"Adding supply rail {0} {1}->{2} {3}".format(name,ll,ur,pin))
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(),
@ -379,7 +392,10 @@ class supply_router(router):
# Actually run the A* router
if not self.run_router(detour_scale=5):
self.write_debug_gds()
self.write_debug_gds("debug_route.gds",False)
#if index==3 and pin_name=="vdd":
# self.write_debug_gds("route.gds",False)
def add_supply_rail_target(self, pin_name):