diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index e603bdc3..839c9a4b 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -41,7 +41,8 @@ class layout(): self.width = None self.height = None - self.bounding_box = None + self.bounding_box = None # The rectangle shape + self.bbox = None # The ll, ur coords # Holds module/cell layout instances self.insts = [] # Set of names to check for duplicates @@ -1163,6 +1164,57 @@ class layout(): self.bbox = [self.bounding_box.ll(), self.bounding_box.ur()] + def get_bbox(self, side="all", big_margin=0, little_margin=0): + """ + Get the bounding box from the GDS + """ + gds_filename = OPTS.openram_temp + "temp.gds" + # If didn't specify a gds blockage file, write it out to read the gds + # This isn't efficient, but easy for now + # Load the gds file and read in all the shapes + self.gds_write(gds_filename) + layout = gdsMill.VlsiLayout(units=GDS["unit"]) + reader = gdsMill.Gds2reader(layout) + reader.loadFromFile(gds_filename) + top_name = layout.rootStructureName + + if not self.bbox: + # The boundary will determine the limits to the size + # of the routing grid + boundary = layout.measureBoundary(top_name) + # These must be un-indexed to get rid of the matrix type + ll = vector(boundary[0][0], boundary[0][1]) + ur = vector(boundary[1][0], boundary[1][1]) + else: + ll, ur = self.bbox + + ll_offset = vector(0, 0) + ur_offset = vector(0, 0) + if side in ["ring", "top"]: + ur_offset += vector(0, big_margin) + else: + ur_offset += vector(0, little_margin) + if side in ["ring", "bottom"]: + ll_offset += vector(0, big_margin) + else: + ll_offset += vector(0, little_margin) + if side in ["ring", "left"]: + ll_offset += vector(big_margin, 0) + else: + ll_offset += vector(little_margin, 0) + if side in ["ring", "right"]: + ur_offset += vector(big_margin, 0) + else: + ur_offset += vector(little_margin, 0) + bbox = (ll - ll_offset, ur + ur_offset) + size = ur - ll + debug.info(1, "Size: {0} x {1} with perimeter big margin {2} little margin {3}".format(size.x, + size.y, + big_margin, + little_margin)) + + return bbox + def add_enclosure(self, insts, layer="nwell", extend=0, leftx=None, rightx=None, topy=None, boty=None): """ Add a layer that surrounds the given instances. Useful diff --git a/compiler/base/lef.py b/compiler/base/lef.py index ce1eef1c..1d86a63e 100644 --- a/compiler/base/lef.py +++ b/compiler/base/lef.py @@ -112,23 +112,25 @@ class lef: for pin_name in self.pins: pins = self.get_pins(pin_name) for pin in pins: - inflated_pin = pin.inflated_pin(multiple=1) - another_iteration_needed = True - while another_iteration_needed: - another_iteration_needed = False + inflated_pin = pin.inflated_pin(multiple=2) + continue_fragmenting = True + while continue_fragmenting: + continue_fragmenting = False old_blockages = list(self.blockages[pin.layer]) for blockage in old_blockages: if blockage.overlaps(inflated_pin): intersection_shape = blockage.intersection(inflated_pin) - # If it is zero area, don't add the pin + # If it is zero area, don't split the blockage if intersection_shape[0][0]==intersection_shape[1][0] or intersection_shape[0][1]==intersection_shape[1][1]: continue - another_iteration_needed = True + # Remove the old blockage and add the new ones self.blockages[pin.layer].remove(blockage) intersection_pin = pin_layout("", intersection_shape, inflated_pin.layer) new_blockages = blockage.cut(intersection_pin) self.blockages[pin.layer].extend(new_blockages) + # We split something so make another pass + continue_fragmenting = True def lef_write_header(self): """ Header of LEF file """ diff --git a/compiler/base/pin_layout.py b/compiler/base/pin_layout.py index e6baa4fc..f27990f5 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -606,7 +606,9 @@ class pin_layout: # Don't add the existing shape in if it overlaps the pin shape if new_shape.contains(shape): continue - new_shapes.append(new_shape) + # Only add non-zero shapes + if new_shape.area() > 0: + new_shapes.append(new_shape) return new_shapes diff --git a/compiler/example_configs/sky130_sram_common.py b/compiler/example_configs/sky130_sram_common.py index 8efc8f10..a827b5a9 100644 --- a/compiler/example_configs/sky130_sram_common.py +++ b/compiler/example_configs/sky130_sram_common.py @@ -9,8 +9,8 @@ nominal_corner_only = True # Local wordlines have issues with met3 power routing for now #local_array_size = 16 -#route_supplies = "ring" -route_supplies = "left" +route_supplies = "ring" +#route_supplies = "left" check_lvsdrc = True #perimeter_pins = False #netlist_only = True diff --git a/compiler/router/grid.py b/compiler/router/grid.py index 404a716f..843fb3ed 100644 --- a/compiler/router/grid.py +++ b/compiler/router/grid.py @@ -37,6 +37,8 @@ class grid: # 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={} @@ -127,33 +129,47 @@ class grid: Side specifies which side. Layer specifies horizontal (0) or vertical (1) Width specifies how wide the perimter "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 side=="left": - for x in range(self.ll.x + offset, self.ll.x + width + offset, 1): - for y in range(self.ll.y + offset + margin, self.ur.y - offset - margin, 1): + 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 side=="right": - for x in range(self.ur.x - width - offset, self.ur.x - offset, 1): - for y in range(self.ll.y + offset + margin, self.ur.y - offset - margin, 1): + 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 side=="bottom": - for y in range(self.ll.y + offset, self.ll.y + width + offset, 1): - for x in range(self.ll.x + offset + margin, self.ur.x - offset - margin, 1): + 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 side=="top": - for y in range(self.ur.y - width - offset, self.ur.y - offset, 1): - for x in range(self.ll.x + offset + margin, self.ur.x - offset - margin, 1): + 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]): diff --git a/compiler/router/router.py b/compiler/router/router.py index dcfde6cf..d9903e49 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -82,31 +82,13 @@ class router(router_tech): """ Initialize the ll,ur values with the paramter or using the layout boundary. """ - - # If didn't specify a gds blockage file, write it out to read the gds - # This isn't efficient, but easy for now - # Load the gds file and read in all the shapes - self.cell.gds_write(self.gds_filename) - self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) - self.reader = gdsMill.Gds2reader(self.layout) - self.reader.loadFromFile(self.gds_filename) - self.top_name = self.layout.rootStructureName - if not bbox: - # The boundary will determine the limits to the size - # of the routing grid - self.boundary = self.layout.measureBoundary(self.top_name) - # These must be un-indexed to get rid of the matrix type - self.ll = vector(self.boundary[0][0], self.boundary[0][1]) - self.ur = vector(self.boundary[1][0], self.boundary[1][1]) + self.bbox = self.cell.get_bbox(margin) else: - self.ll, self.ur = bbox + self.bbox = bbox + + (self.ll, self.ur) = self.bbox - margin_offset = vector(margin, margin) - self.bbox = (self.ll - margin_offset, self.ur + margin_offset) - size = self.ur - self.ll - debug.info(1, "Size: {0} x {1} with perimeter margin {2}".format(size.x, size.y, margin)) - def get_bbox(self): return self.bbox @@ -893,19 +875,21 @@ class router(router_tech): # Clearing the blockage of this pin requires the inflated pins self.clear_blockages(pin_name) - def add_side_supply_pin(self, name, side="left", width=2): + def add_side_supply_pin(self, name, side="left", width=3, space=2): """ Adds a supply pin to the perimeter and resizes the bounding box. """ pg = pin_group(name, [], self) - if name == "gnd": - offset = width + 1 + # Offset two spaces inside and one between the rings + if name == "vdd": + offset = width + 2 * space else: - offset = 1 + offset = space if side in ["left", "right"]: layers = [1] else: layers = [0] + pg.grids = set(self.rg.get_perimeter_list(side=side, width=width, margin=self.margin, @@ -920,39 +904,39 @@ class router(router_tech): self.new_pins[name] = pg.pins - def add_ring_supply_pin(self, name, width=2): + def add_ring_supply_pin(self, name, width=3, space=2): """ Adds a ring supply pin that goes inside the given bbox. """ pg = pin_group(name, [], self) - # Offset the vdd inside one ring width + # Offset two spaces inside and one between the rings # Units are in routing grids - if name == "gnd": - offset = width + 1 + if name == "vdd": + offset = width + 2 * space else: - offset = 1 + offset = space # LEFT - left_grids = set(self.rg.get_perimeter_list(side="left", + left_grids = set(self.rg.get_perimeter_list(side="left_ring", width=width, margin=self.margin, offset=offset, layers=[1])) # RIGHT - right_grids = set(self.rg.get_perimeter_list(side="right", + right_grids = set(self.rg.get_perimeter_list(side="right_ring", width=width, margin=self.margin, offset=offset, layers=[1])) # TOP - top_grids = set(self.rg.get_perimeter_list(side="top", + top_grids = set(self.rg.get_perimeter_list(side="top_ring", width=width, margin=self.margin, offset=offset, layers=[0])) # BOTTOM - bottom_grids = set(self.rg.get_perimeter_list(side="bottom", + bottom_grids = set(self.rg.get_perimeter_list(side="bottom_ring", width=width, margin=self.margin, offset=offset, diff --git a/compiler/router/supply_tree_router.py b/compiler/router/supply_tree_router.py index e95cdee1..282adc4c 100644 --- a/compiler/router/supply_tree_router.py +++ b/compiler/router/supply_tree_router.py @@ -34,7 +34,7 @@ class supply_tree_router(router): # 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", "tree", "ring"], + 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, diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 327ce209..3bfe3648 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -329,13 +329,21 @@ class sram_1bank(sram_base): # Some technologies have an isolation self.add_dnwell(inflate=2) + # We need the initial bbox for the supply rings later + # because the perimeter pins will change the bbox # Route the pins to the perimeter + pre_bbox = None if OPTS.perimeter_pins: - self.route_escape_pins() + pre_bbox = self.get_bbox(side="ring", + big_margin=self.m3_pitch) + bbox = self.get_bbox(side=OPTS.route_supplies, + big_margin=14 * self.m3_pitch, + little_margin=4 * self.m3_pitch) + self.route_escape_pins(bbox) # Route the supplies first since the MST is not blockage aware # and signals can route to anywhere on sides (it is flexible) - self.route_supplies() + self.route_supplies(pre_bbox) def route_dffs(self, add_routes=True): diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index a7530e0b..5163ef27 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -230,7 +230,7 @@ class sram_base(design, verilog, lef): def create_modules(self): debug.error("Must override pure virtual function.", -1) - def route_supplies(self): + def route_supplies(self, bbox=None): """ Route the supply grid and connect the pins to them. """ # Copy the pins to the top level @@ -252,11 +252,14 @@ class sram_base(design, verilog, lef): return elif OPTS.route_supplies == "grid": from supply_grid_router import supply_grid_router as router - rtr=router(grid_stack, self) + rtr=router(layers=grid_stack, + design=self, + bbox=bbox) else: from supply_tree_router import supply_tree_router as router - rtr=router(grid_stack, - self, + rtr=router(layers=grid_stack, + design=self, + bbox=bbox, pin_type=OPTS.route_supplies) rtr.route() @@ -283,7 +286,7 @@ class sram_base(design, verilog, lef): pin.width(), pin.height()) - elif OPTS.route_supplies: + elif OPTS.route_supplies or OPTS.route_supplies == "single": # Update these as we may have routed outside the region (perimeter pins) lowest_coord = self.find_lowest_coords() @@ -321,7 +324,7 @@ class sram_base(design, verilog, lef): # Grid is left with many top level pins pass - def route_escape_pins(self): + def route_escape_pins(self, bbox): """ Add the top-level pins for a single bank SRAM with control. """ @@ -364,7 +367,7 @@ class sram_base(design, verilog, lef): from signal_escape_router import signal_escape_router as router rtr=router(layers=self.m3_stack, design=self, - margin=8 * self.m3_pitch) + bbox=bbox) rtr.escape_route(pins_to_route) def compute_bus_sizes(self):