From 504f9aa8928dcea308ef4f56afa8ba8e2ee0919f Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 8 Jan 2021 11:34:58 -0800 Subject: [PATCH 01/15] Space tx in pinv_dec for power routing. --- compiler/pgates/pinv_dec.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/pgates/pinv_dec.py b/compiler/pgates/pinv_dec.py index b355b28f..b3d6c47f 100644 --- a/compiler/pgates/pinv_dec.py +++ b/compiler/pgates/pinv_dec.py @@ -131,7 +131,10 @@ class pinv_dec(pinv.pinv): self.nmos_inst.place(self.nmos_pos, rotate=270) # place PMOS so it is half a poly spacing down from the top - xoffset = self.nmos_inst.rx() + 2 * self.poly_extend_active + 2 * self.well_extend_active + drc("pwell_to_nwell") + well_offsets = 2 * self.poly_extend_active + 2 * self.well_extend_active + drc("pwell_to_nwell") + # This is to provide spacing for the vdd rails + metal_offsets = 2 * self.m3_pitch + xoffset = self.nmos_inst.rx() + max(well_offsets, metal_offsets) self.pmos_pos = vector(xoffset, y_offset) self.pmos_inst.place(self.pmos_pos, rotate=270) From 7506ba81be975b7657879d71adf4149503145aca Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 11 Jan 2021 11:12:45 -0800 Subject: [PATCH 02/15] Refactor how blocked_grids work. Must still calculate blockages based on enclosed pins. --- compiler/router/pin_group.py | 34 +++++------ compiler/router/router.py | 81 +++++++++++++++---------- compiler/router/signal_escape_router.py | 8 ++- compiler/router/supply_grid_router.py | 7 ++- compiler/router/supply_tree_router.py | 9 +-- 5 files changed, 78 insertions(+), 61 deletions(-) diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py index 474702a4..0c5221e5 100644 --- a/compiler/router/pin_group.py +++ b/compiler/router/pin_group.py @@ -41,10 +41,7 @@ class pin_group: # 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. These are also blockages if we - # used a simple enclosure to route to a rail. + # The set of blocked grids due to this pin self.blockages = set() # This is a set of pin_layout shapes to cover the grids @@ -421,16 +418,16 @@ class pin_group: while True: next_cell = row[-1] + offset1 # Can't move if not in the pin shape - if next_cell in self.grids and next_cell not in self.router.blocked_grids: + if next_cell in self.grids and next_cell not in self.router.get_blocked_grids(): row.append(next_cell) else: break # Move in dir2 while we can while True: - next_row = [x+offset2 for x in row] + next_row = [x + offset2 for x in row] for cell in next_row: # Can't move if any cell is not in the pin shape - if cell not in self.grids or cell in self.router.blocked_grids: + if cell not in self.grids or cell in self.router.get_blocked_grids(): break else: row = next_row @@ -606,9 +603,10 @@ class pin_group: The secondary set of grids are "optional" pin shapes that should be either blocked or part of the pin. """ + # Set of tracks that overlap a pin pin_set = set() + # Set of track adjacent to or paritally overlap a pin (not full DRC connection) partial_set = set() - blockage_set = set() for pin in self.pins: debug.info(4, " Converting {0}".format(pin)) @@ -621,25 +619,20 @@ class pin_group: # 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) + self.blockages.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 & self.router.blocked_grids + shared_set = pin_set & self.router.get_blocked_grids() if len(shared_set) > 0: debug.info(4, "Removing pins {}".format(shared_set)) pin_set.difference_update(shared_set) - shared_set = partial_set & self.router.blocked_grids + shared_set = partial_set & self.router.get_blocked_grids() if len(shared_set) > 0: debug.info(4, "Removing pins {}".format(shared_set)) - partial_set.difference_update(shared_set) - shared_set = blockage_set & self.router.blocked_grids - if len(shared_set) > 0: - debug.info(4, "Removing blocks {}".format(shared_set)) - blockage_set.difference_update(shared_set) # At least one of the groups must have some valid tracks - if (len(pin_set) == 0 and len(partial_set) == 0 and len(blockage_set) == 0): + if (len(pin_set) == 0 and len(partial_set) == 0): # debug.warning("Pin is very close to metal blockage.\nAttempting to expand blocked pin {}".format(self.pins)) for pin in self.pins: @@ -656,8 +649,11 @@ class pin_group: self.pins)) self.router.write_debug_gds("blocked_pin.gds") - # Consider all the grids that would be blocked - self.grids = pin_set | partial_set + # Consider the fully connected set first and if not the partial set + if len(pin_set) > 0: + self.grids = pin_set + else: + self.grids = partial_set if len(self.grids) < 0: debug.error("Did not find any unblocked grids: {}".format(str(self.pins))) self.router.write_debug_gds("blocked_pin.gds") diff --git a/compiler/router/router.py b/compiler/router/router.py index d666fcb5..8cc8f5d8 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -69,9 +69,12 @@ class router(router_tech): # The blockage data structures # A list of metal shapes (using the same pin_layout structure) - # that are not pins but blockages. + # that could be blockages. + # This will include the pins above as well. self.blockages = [] - # The corresponding set of blocked grids for above pin shapes + # The corresponding set of blocked grids for above blockage pin_layout shapes + # It is a cached set of grids that *could* be blocked, but may be unblocked + # depending on which pin we are routing. self.blocked_grids = set() # The routed data structures @@ -345,15 +348,14 @@ class router(router_tech): # This is just a virtual function pass - def prepare_blockages(self, pin_name): + def prepare_blockages(self): """ Reset and add all of the blockages in the design. - Names is a list of pins to add as a blockage. """ debug.info(3, "Preparing blockages.") # Start fresh. Not the best for run-time, but simpler. - self.clear_blockages() + self.clear_all_blockages() # This adds the initial blockges of the design # print("BLOCKING:", self.blocked_grids) self.set_blockages(self.blocked_grids, True) @@ -370,22 +372,17 @@ class router(router_tech): # (some will be unblocked if they're a source/target) # Also block the previous routes for name in self.pin_groups: - blockage_grids = {y for x in self.pin_groups[name] for y in x.grids} - self.set_blockages(blockage_grids, True) + # This should be a superset of the grids... blockage_grids = {y for x in self.pin_groups[name] for y in x.blockages} self.set_blockages(blockage_grids, True) + # But do the grids just in case + blockage_grids = {y for x in self.pin_groups[name] for y in x.grids} + self.set_blockages(blockage_grids, True) # FIXME: These duplicate a bit of work # These are the paths that have already been routed. self.set_blockages(self.path_blockages) - # 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 - # route over them - # 1/6/21: This would cause things that looked like loops in the supply tree router - # blockage_grids = {y for x in self.pin_groups[pin_name] for y in x.grids} - # self.set_blockages(blockage_grids, False) - def convert_shape_to_units(self, shape): """ Scale a shape (two vector list) to user units @@ -421,7 +418,18 @@ class router(router_tech): # z direction return 2 - def clear_blockages(self): + def clear_blockages(self, pin_name): + """ + This function clears a given pin and all of its components from being blockages. + """ + # This should be a superset of the grids... + blockage_grids = {y for x in self.pin_groups[pin_name] for y in x.blockages} + self.set_blockages(blockage_grids, False) + # But do the grids just in case + blockage_grids = {y for x in self.pin_groups[pin_name] for y in x.grids} + self.set_blockages(blockage_grids, False) + + def clear_all_blockages(self): """ Clear all blockages on the grid. """ @@ -432,24 +440,24 @@ class router(router_tech): """ Flag the blockages in the grid """ self.rg.set_blocked(blockages, value) - 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)) + def convert_to_tracks(self, ll, ur, z): + debug.info(3, "Converting ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) - block_list = [] + grid_list = [] for x in range(int(ll[0]), int(ur[0])+1): for y in range(int(ll[1]), int(ur[1])+1): - block_list.append(vector3d(x, y, z)) + grid_list.append(vector3d(x, y, z)) - return set(block_list) + return set(grid_list) def convert_blockage(self, blockage): """ Convert a pin layout blockage shape to routing grid tracks. """ # Inflate the blockage by half a spacing rule - [ll, ur] = self.convert_blockage_to_tracks(blockage.inflate()) + [ll, ur] = self.convert_shape_to_tracks(blockage.inflate()) zlayer = self.get_zindex(blockage.lpp) - blockage_tracks = self.get_blockage_tracks(ll, ur, zlayer) + blockage_tracks = self.convert_to_tracks(ll, ur, zlayer) return blockage_tracks def convert_blockages(self): @@ -460,6 +468,12 @@ class router(router_tech): blockage_list = self.convert_blockage(blockage) self.blocked_grids.update(blockage_list) + def get_blocked_grids(self): + """ + Return the blocked grids with their flag set + """ + return set([x for x in self.blocked_grids if self.rg.is_blocked(x)]) + def retrieve_blockages(self, lpp): """ Recursive find boundaries as blockages to the routing grid. @@ -473,11 +487,7 @@ class router(router_tech): new_pin = pin_layout("blockage{}".format(len(self.blockages)), rect, lpp) - - # If there is a rectangle that is the same in the pins, - # it isn't a blockage! - if new_pin not in self.all_pins: - self.blockages.append(new_pin) + self.blockages.append(new_pin) def convert_point_to_units(self, p): """ @@ -492,10 +502,10 @@ class router(router_tech): Convert a wave to a set of center points """ return [self.convert_point_to_units(i) for i in wave] - - def convert_blockage_to_tracks(self, shape): + + def convert_shape_to_tracks(self, shape): """ - Convert a rectangular blockage shape into track units. + Convert a rectangular shape into track units. """ (ll, ur) = shape ll = snap_to_grid(ll) @@ -531,8 +541,8 @@ class router(router_tech): insufficient_list = set() zindex = self.get_zindex(pin.lpp) - for x in range(int(ll[0]) + expansion, int(ur[0]) + 1 + expansion): - for y in range(int(ll[1] + expansion), int(ur[1]) + 1 + expansion): + for x in range(int(ll[0]) - expansion, int(ur[0]) + 1 + expansion): + for y in range(int(ll[1] - expansion), int(ur[1]) + 1 + expansion): (full_overlap, partial_overlap) = self.convert_pin_coord_to_tracks(pin, vector3d(x, y, @@ -813,6 +823,7 @@ class router(router_tech): for pin_name in self.pin_groups: debug.info(2, "Enclosing pins for {}".format(pin_name)) for pg in self.pin_groups[pin_name]: + self.clear_blockages(pin_name) pg.enclose_pin() pg.add_enclosure(self.cell) @@ -824,6 +835,9 @@ class router(router_tech): for i in range(self.num_pin_components(pin_name)): self.add_pin_component_source(pin_name, i) + # Clearing the blockage of this pin requires the inflated pins + self.clear_blockages(pin_name) + def add_target(self, pin_name): """ This will mark the grids for all pin components as a target. @@ -832,6 +846,9 @@ class router(router_tech): for i in range(self.num_pin_components(pin_name)): self.add_pin_component_target(pin_name, i) + # Clearing the blockage of this pin requires the inflated pins + self.clear_blockages(pin_name) + def add_perimeter_target(self, side="all"): """ This will mark all the cells on the perimeter of the original layout as a target. diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index 2c06d619..134a2ee3 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -62,6 +62,8 @@ class signal_escape_router(router): start_time = datetime.now() for pin_name in ordered_pin_names: self.route_signal(pin_name) + #if pin_name == "dout1[1]": + # self.write_debug_gds("postroute.gds", False) print_time("Maze routing pins",datetime.now(), start_time, 3) @@ -79,7 +81,8 @@ class signal_escape_router(router): # This is inefficient since it is non-incremental, but it was # easier to debug. - self.prepare_blockages(pin_name) + self.prepare_blockages() + self.clear_blockages(pin_name) # Add the single component of the pin as the source # which unmarks it as a blockage too @@ -88,6 +91,9 @@ class signal_escape_router(router): # Marks the grid cells all along the perimeter as a target self.add_perimeter_target(side) + #if pin_name == "dout1[1]": + # self.write_debug_gds("preroute.gds", False) + # Actually run the A* router if self.run_router(detour_scale=detour_scale): new_pin = self.get_perimeter_pin() diff --git a/compiler/router/supply_grid_router.py b/compiler/router/supply_grid_router.py index 53758d9b..6876329f 100644 --- a/compiler/router/supply_grid_router.py +++ b/compiler/router/supply_grid_router.py @@ -73,13 +73,16 @@ class supply_grid_router(router): # Add the supply rails in a mesh network and connect H/V with vias start_time = datetime.now() # Block everything - self.prepare_blockages(self.gnd_name) + self.prepare_blockages() + self.clear_blockages(self.gnd_name) + # Determine the rail locations self.route_supply_rails(self.gnd_name, 0) # Block everything - self.prepare_blockages(self.vdd_name) + self.prepare_blockages() + self.clear_blockages(self.vdd_name) # Determine the rail locations self.route_supply_rails(self.vdd_name, 1) print_time("Routing supply rails", datetime.now(), start_time, 3) diff --git a/compiler/router/supply_tree_router.py b/compiler/router/supply_tree_router.py index d2db1e29..73378b2d 100644 --- a/compiler/router/supply_tree_router.py +++ b/compiler/router/supply_tree_router.py @@ -61,12 +61,6 @@ class supply_tree_router(router): self.find_pins_and_blockages([self.vdd_name, self.gnd_name]) print_time("Finding pins and blockages",datetime.now(), start_time, 3) - # Add the supply rails in a mesh network and connect H/V with vias - start_time = datetime.now() - # Block everything - self.prepare_blockages(self.gnd_name) - self.prepare_blockages(self.vdd_name) - # Route the supply pins to the supply rails # Route vdd first since we want it to be shorter start_time = datetime.now() @@ -136,7 +130,8 @@ class supply_tree_router(router): # This is inefficient since it is non-incremental, but it was # easier to debug. - self.prepare_blockages(pin_name) + self.prepare_blockages() + self.clear_blockages(pin_name) # Add the single component of the pin as the source # which unmarks it as a blockage too From 6f5b7c02643eea5a8d512784e914d89ec1e43058 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 12 Jan 2021 16:20:03 -0800 Subject: [PATCH 03/15] Flatten bug fixed in Magic so don't flatten routes. --- compiler/sram/sram_1bank.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index dc8917b6..d96f9336 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -11,6 +11,7 @@ from contact import m2_via from channel_route import channel_route from signal_escape_router import signal_escape_router as router from globals import OPTS +import debug class sram_1bank(sram_base): @@ -436,9 +437,9 @@ class sram_1bank(sram_base): if add_routes: # This causes problem in magic since it sometimes cannot extract connectivity of isntances # with no active devices. - # self.add_inst(cr.name, cr) - # self.connect_inst([]) - self.add_flat_inst(cr.name, cr) + self.add_inst(cr.name, cr) + self.connect_inst([]) + #self.add_flat_inst(cr.name, cr) else: self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap else: @@ -451,9 +452,9 @@ class sram_1bank(sram_base): if add_routes: # This causes problem in magic since it sometimes cannot extract connectivity of isntances # with no active devices. - # self.add_inst(cr.name, cr) - # self.connect_inst([]) - self.add_flat_inst(cr.name, cr) + self.add_inst(cr.name, cr) + self.connect_inst([]) + #self.add_flat_inst(cr.name, cr) else: self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap From 408ea15228f7d49a4584ee2cdb6a7eed4723bf7d Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 12 Jan 2021 16:20:26 -0800 Subject: [PATCH 04/15] Ordering bug fixed in Magic. --- compiler/verify/magic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py index 21e7592e..6cd68cf9 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -91,8 +91,8 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa # These two options are temporarily disabled until Tim fixes a bug in magic related # to flattening channel routes and vias (hierarchy with no devices in it). Otherwise, # they appear to be disconnected. - f.write("#gds flatten true\n") - f.write("#gds ordering true\n") + f.write("gds flatten true\n") + f.write("gds ordering true\n") f.write("gds readonly true\n") f.write("gds read {}\n".format(gds_name)) f.write('puts "Finished reading gds {}"\n'.format(gds_name)) From 01d312d65c1586bfa552fb76fc53437a0a13228d Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 13 Jan 2021 10:57:12 -0800 Subject: [PATCH 05/15] Refactor add power pins --- compiler/base/hierarchy_layout.py | 57 ++++++++++++++++------ compiler/modules/bank.py | 4 +- compiler/modules/col_cap_array.py | 4 +- compiler/modules/delay_chain.py | 8 +-- compiler/modules/dff_array.py | 4 +- compiler/modules/dff_buf_array.py | 4 +- compiler/modules/dff_inv_array.py | 5 +- compiler/modules/hierarchical_decoder.py | 11 +---- compiler/modules/hierarchical_predecode.py | 11 +---- compiler/modules/local_bitcell_array.py | 4 +- compiler/modules/port_address.py | 4 +- compiler/modules/replica_bitcell_array.py | 4 +- compiler/modules/replica_column.py | 3 +- compiler/modules/row_cap_array.py | 4 +- compiler/modules/sense_amp_array.py | 10 +--- compiler/modules/wordline_buffer_array.py | 2 +- compiler/modules/wordline_driver_array.py | 2 +- compiler/modules/write_driver_array.py | 6 +-- compiler/modules/write_mask_and_array.py | 4 +- 19 files changed, 70 insertions(+), 81 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 1ec5a2f0..ef077c8d 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -56,6 +56,8 @@ class layout(): self.visited = [] # Flag for library cells self.is_library_cell = False + # Flag for top level (used for min area metal pins etc.) + self.is_top_level = False self.gds_read() @@ -1220,30 +1222,21 @@ class layout(): pin.height()) elif add_vias: - self.add_power_pin(name, pin.center(), start_layer=pin.layer) + self.add_power_pin(pin) - def add_io_pin(self, instance, pin_name, new_name="", start_layer=None): + def add_io_pin(self, instance, pin_name, new_name, start_layer=None): """ Add a signle input or output pin up to metal 3. """ pin = instance.get_pin(pin_name) - if new_name == "": - new_name = pin_name - if not start_layer: start_layer = pin.layer - + # Just use the power pin function for now to save code - self.add_power_pin(name=new_name, loc=pin.center(), start_layer=start_layer) - - def add_power_pin(self, name, loc, size=[1, 1], directions=None, start_layer="m1"): - """ - Add a single power pin from the lowest power_grid layer down to M1 (or li) at - the given center location. The starting layer is specified to determine - which vias are needed. - """ + self.add_power_pin(new_name, pin.center(), start_layer=start_layer) + def add_power_pin(self, name, loc, directions=None, start_layer="m1"): if start_layer == self.pwr_grid_layer: self.add_layout_pin_rect_center(text=name, layer=self.pwr_grid_layer, @@ -1251,12 +1244,11 @@ class layout(): else: via = self.add_via_stack_center(from_layer=start_layer, to_layer=self.pwr_grid_layer, - size=size, offset=loc, directions=directions) # Hack for min area - if OPTS.tech_name == "sky130": + if OPTS.tech_name == "sky130" and self.is_top_level: width = round_to_grid(sqrt(drc["minarea_m3"])) height = round_to_grid(drc["minarea_m3"] / width) else: @@ -1268,6 +1260,39 @@ class layout(): width=width, height=height) + def copy_power_pin(self, pin, loc=None, directions=None): + """ + Add a single power pin from the lowest power_grid layer down to M1 (or li) at + the given center location. The starting layer is specified to determine + which vias are needed. + """ + + if not loc: + loc = pin.center() + + if pin.layer == self.pwr_grid_layer: + self.add_layout_pin_rect_center(text=pin.name, + layer=self.pwr_grid_layer, + offset=loc) + else: + via = self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.pwr_grid_layer, + offset=loc, + directions=directions) + + # Hack for min area + if OPTS.tech_name == "sky130" and self.is_top_level: + width = round_to_grid(sqrt(drc["minarea_m3"])) + height = round_to_grid(drc["minarea_m3"] / width) + else: + width = via.width + height = via.height + self.add_layout_pin_rect_center(text=pin.name, + layer=self.pwr_grid_layer, + offset=loc, + width=width, + height=height) + def add_perimeter_pin(self, name, pin, side, bbox): """ Add a pin along the perimeter side specified by the bbox with diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index fbe60ff0..1e821e99 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -615,9 +615,7 @@ class bank(design.design): for pin_name in ["vdd", "gnd"]: pin_list = inst.get_pins(pin_name) for pin in pin_list: - self.add_power_pin(pin_name, - pin.center(), - start_layer=pin.layer) + self.copy_power_pin(pin, pin.center()) def route_bank_select(self, port): """ Route the bank select logic. """ diff --git a/compiler/modules/col_cap_array.py b/compiler/modules/col_cap_array.py index 1be29327..498f5076 100644 --- a/compiler/modules/col_cap_array.py +++ b/compiler/modules/col_cap_array.py @@ -100,7 +100,5 @@ class col_cap_array(bitcell_base_array): inst = self.cell_inst[row, col] for pin_name in ["vdd", "gnd"]: for pin in inst.get_pins(pin_name): - self.add_power_pin(name=pin_name, - loc=pin.center(), - start_layer=pin.layer) + self.copy_power_pin(pin) diff --git a/compiler/modules/delay_chain.py b/compiler/modules/delay_chain.py index fa6df322..54fbf414 100644 --- a/compiler/modules/delay_chain.py +++ b/compiler/modules/delay_chain.py @@ -177,14 +177,10 @@ class delay_chain(design.design): load_list = self.load_inst_map[inst] for pin_name in ["vdd", "gnd"]: pin = load_list[0].get_pin(pin_name) - self.add_power_pin(pin_name, - pin.rc() - vector(self.m1_pitch, 0), - start_layer=pin.layer) + self.copy_power_pin(pin, loc=pin.rc() - vector(self.m1_pitch, 0)) pin = load_list[-2].get_pin(pin_name) - self.add_power_pin(pin_name, - pin.rc() - vector(self.m1_pitch, 0), - start_layer=pin.layer) + self.copy_power_pin(pin, loc=pin.rc() - vector(self.m1_pitch, 0)) def add_layout_pins(self): diff --git a/compiler/modules/dff_array.py b/compiler/modules/dff_array.py index 82746a06..e60ed146 100644 --- a/compiler/modules/dff_array.py +++ b/compiler/modules/dff_array.py @@ -112,11 +112,11 @@ class dff_array(design.design): for col in range(self.columns): # Continous vdd rail along with label. vdd_pin=self.dff_insts[row, col].get_pin("vdd") - self.add_power_pin("vdd", vdd_pin.center(), start_layer=vdd_pin.layer) + self.copy_power_pin(vdd_pin) # Continous gnd rail along with label. gnd_pin=self.dff_insts[row, col].get_pin("gnd") - self.add_power_pin("gnd", gnd_pin.center(), start_layer=gnd_pin.layer) + self.copy_power_pin(gnd_pin) for row in range(self.rows): for col in range(self.columns): diff --git a/compiler/modules/dff_buf_array.py b/compiler/modules/dff_buf_array.py index ea811e76..1ce78f3a 100644 --- a/compiler/modules/dff_buf_array.py +++ b/compiler/modules/dff_buf_array.py @@ -159,11 +159,11 @@ class dff_buf_array(design.design): for col in range(self.columns): # Continous vdd rail along with label. vdd_pin=self.dff_insts[row, col].get_pin("vdd") - self.add_power_pin("vdd", vdd_pin.lc(), start_layer=vdd_pin.layer) + self.copy_power_pin(vdd_pin, loc=vdd_pin.lc()) # Continous gnd rail along with label. gnd_pin=self.dff_insts[row, col].get_pin("gnd") - self.add_power_pin("gnd", gnd_pin.lc(), start_layer=gnd_pin.layer) + self.copy_power_pin(gnd_pin, loc=gnd_pin.lc()) def add_layout_pins(self): diff --git a/compiler/modules/dff_inv_array.py b/compiler/modules/dff_inv_array.py index 59fdfade..aba5e34b 100644 --- a/compiler/modules/dff_inv_array.py +++ b/compiler/modules/dff_inv_array.py @@ -130,12 +130,11 @@ class dff_inv_array(design.design): for col in range(self.columns): # Adds power pin on left of row vdd_pin=self.dff_insts[row,col].get_pin("vdd") - self.add_power_pin("vdd", vdd_pin.lc()) + self.add_power_pin(vdd_pin, loc=vdd_pin.lc()) # Adds gnd pin on left of row gnd_pin=self.dff_insts[row,col].get_pin("gnd") - self.add_power_pin("gnd", gnd_pin.lc()) - + self.add_power_pin(gnd_pin, loc=gnd_pin.lc()) for row in range(self.rows): for col in range(self.columns): diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 372e9792..9a5127e2 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -611,12 +611,7 @@ class hierarchical_decoder(design.design): for i in self.and_inst[:-1]: pins = i.get_pins(n) for pin in pins: - self.add_power_pin(name=n, - loc=pin.uc(), - start_layer=pin.layer) - self.add_power_pin(name=n, - loc=pin.uc(), - start_layer=pin.layer) + self.copy_power_pin(pin, loc=pin.uc()) for i in self.pre2x4_inst + self.pre3x8_inst: self.copy_layout_pin(i, n) @@ -628,9 +623,7 @@ class hierarchical_decoder(design.design): # The nand and inv are the same height rows... supply_pin = self.and_inst[row].get_pin(pin_name) pin_pos = vector(xoffset, supply_pin.cy()) - self.add_power_pin(name=pin_name, - loc=pin_pos, - start_layer=supply_pin.layer) + self.copy_power_pin(supply_pin, loc=pin_pos) # Copy the pins from the predecoders for pre in self.pre2x4_inst + self.pre3x8_inst + self.pre4x16_inst: diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index 72fb2a6d..b9d71a2c 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -330,12 +330,7 @@ class hierarchical_predecode(design.design): for i in self.inv_inst[:-1:2] + self.and_inst[:-1:2]: pins = i.get_pins(n) for pin in pins: - self.add_power_pin(name=n, - loc=pin.uc(), - start_layer=pin.layer) - self.add_power_pin(name=n, - loc=pin.uc(), - start_layer=pin.layer) + self.copy_power_pin(pin, loc=pin.uc()) # In other techs, we are using standard cell decoder cells with horizontal power else: @@ -353,9 +348,7 @@ class hierarchical_predecode(design.design): for xoffset in [self.inv_inst[0].lx() - self.bus_space, self.and_inst[0].lx() - self.bus_space]: pin_pos = vector(xoffset, and_pin.cy()) - self.add_power_pin(name=n, - loc=pin_pos, - start_layer=and_pin.layer) + self.copy_power_pin(and_pin, loc=pin_pos) diff --git a/compiler/modules/local_bitcell_array.py b/compiler/modules/local_bitcell_array.py index 5ff91268..b29cacb1 100644 --- a/compiler/modules/local_bitcell_array.py +++ b/compiler/modules/local_bitcell_array.py @@ -186,9 +186,7 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array): for inst in supply_insts: pin_list = inst.get_pins(pin_name) for pin in pin_list: - self.add_power_pin(name=pin_name, - loc=pin.center(), - start_layer=pin.layer) + self.copy_power_pin(pin) def route(self): diff --git a/compiler/modules/port_address.py b/compiler/modules/port_address.py index 382aca56..d4546178 100644 --- a/compiler/modules/port_address.py +++ b/compiler/modules/port_address.py @@ -82,9 +82,9 @@ class port_address(design.design): for rbl_vdd_pin in self.rbl_driver_inst.get_pins("vdd"): if layer_props.port_address.supply_offset: - self.add_power_pin("vdd", rbl_vdd_pin.center()) + self.copy_power_pin(rbl_vdd_pin) else: - self.add_power_pin("vdd", rbl_vdd_pin.lc()) + self.copy_power_pin(rbl_vdd_pin, loc=rbl_vdd_pin.lc()) # Also connect the B input of the RBL and_dec to vdd if OPTS.local_array_size == 0: diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index 6e1bca40..85225292 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -473,9 +473,7 @@ class replica_bitcell_array(bitcell_base_array): for inst in supply_insts: pin_list = inst.get_pins(pin_name) for pin in pin_list: - self.add_power_pin(name=pin_name, - loc=pin.center(), - start_layer=pin.layer) + self.copy_power_pin(pin) for inst in self.replica_col_insts: if inst: diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index 15d71685..94de906e 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -190,7 +190,8 @@ class replica_column(bitcell_base_array): for (index, inst) in enumerate(self.cell_inst): for pin_name in ["vdd", "gnd"]: if inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]: - self.copy_power_pins(inst, pin_name) + for pin in inst.get_pins(pin_name): + self.copy_power_pin(pin) else: self.copy_layout_pin(inst, pin_name) diff --git a/compiler/modules/row_cap_array.py b/compiler/modules/row_cap_array.py index 850dd5f9..59da5fc2 100644 --- a/compiler/modules/row_cap_array.py +++ b/compiler/modules/row_cap_array.py @@ -113,7 +113,5 @@ class row_cap_array(bitcell_base_array): inst = self.cell_inst[row, col] for pin_name in ["vdd", "gnd"]: for pin in inst.get_pins(pin_name): - self.add_power_pin(name=pin_name, - loc=pin.center(), - start_layer=pin.layer) + self.add_power_pin(pin) diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index d3ab94a5..e4975574 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -146,16 +146,10 @@ class sense_amp_array(design.design): inst = self.local_insts[i] for gnd_pin in inst.get_pins("gnd"): - self.add_power_pin(name="gnd", - loc=gnd_pin.center(), - start_layer=gnd_pin.layer, - directions=("V", "V")) + self.copy_power_pin(gnd_pin, directions=("V", "V")) for vdd_pin in inst.get_pins("vdd"): - self.add_power_pin(name="vdd", - loc=vdd_pin.center(), - start_layer=vdd_pin.layer, - directions=("V", "V")) + self.copy_power_pin(vdd_pin, directions=("V", "V")) bl_pin = inst.get_pin(inst.mod.get_bl_names()) br_pin = inst.get_pin(inst.mod.get_br_names()) diff --git a/compiler/modules/wordline_buffer_array.py b/compiler/modules/wordline_buffer_array.py index d3132861..24529ff7 100644 --- a/compiler/modules/wordline_buffer_array.py +++ b/compiler/modules/wordline_buffer_array.py @@ -96,7 +96,7 @@ class wordline_buffer_array(design.design): # Add pins in two locations for xoffset in xoffset_list: pin_pos = vector(xoffset, supply_pin.cy()) - self.add_power_pin(name, pin_pos) + self.copy_power_pin(supply_pin, loc=pin_pos) def create_drivers(self): self.wld_inst = [] diff --git a/compiler/modules/wordline_driver_array.py b/compiler/modules/wordline_driver_array.py index a310a26f..10816020 100644 --- a/compiler/modules/wordline_driver_array.py +++ b/compiler/modules/wordline_driver_array.py @@ -97,7 +97,7 @@ class wordline_driver_array(design.design): # Add pins in two locations for xoffset in xoffset_list: pin_pos = vector(xoffset, supply_pin.cy()) - self.add_power_pin(name, pin_pos) + self.copy_power_pin(supply_pin, loc=pin_pos) def create_drivers(self): self.wld_inst = [] diff --git a/compiler/modules/write_driver_array.py b/compiler/modules/write_driver_array.py index afd21de5..8ba0d1d1 100644 --- a/compiler/modules/write_driver_array.py +++ b/compiler/modules/write_driver_array.py @@ -208,10 +208,8 @@ class write_driver_array(design.design): for n in ["vdd", "gnd"]: pin_list = self.driver_insts[i].get_pins(n) for pin in pin_list: - self.add_power_pin(name=n, - loc=pin.center(), - directions=("V", "V"), - start_layer=pin.layer) + self.copy_power_pin(pin, directions=("V", "V")) + if self.write_size: for bit in range(self.num_wmasks): inst = self.driver_insts[bit * self.write_size] diff --git a/compiler/modules/write_mask_and_array.py b/compiler/modules/write_mask_and_array.py index f25a71c5..cb6c1db4 100644 --- a/compiler/modules/write_mask_and_array.py +++ b/compiler/modules/write_mask_and_array.py @@ -145,6 +145,6 @@ class write_mask_and_array(design.design): left_loc = vector(0, supply_pin_yoffset) right_loc = vector(self.width, supply_pin_yoffset) self.add_path(supply_pin.layer, [left_loc, right_loc]) - self.add_power_pin(supply, left_loc, start_layer=supply_pin.layer) - self.add_power_pin(supply, right_loc, start_layer=supply_pin.layer) + self.copy_power_pin(supply_pin, loc=left_loc) + self.copy_power_pin(supply_pin, loc=right_loc) From 4991693f1a9860a04e96a76ce6e933dc0cbcb7e1 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 13 Jan 2021 12:32:17 -0800 Subject: [PATCH 06/15] Clean up min area --- compiler/base/hierarchy_layout.py | 41 ++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index ef077c8d..da6cbf9b 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -1237,22 +1237,31 @@ class layout(): self.add_power_pin(new_name, pin.center(), start_layer=start_layer) def add_power_pin(self, name, loc, directions=None, start_layer="m1"): + + # Hack for min area + if OPTS.tech_name == "sky130" and self.is_top_level: + min_area = drc["minarea_{}".format(self.pwr_grid_layer)] + width = round_to_grid(sqrt(min_area)) + height = round_to_grid(min_area / width) + else: + width = None + height = None + if start_layer == self.pwr_grid_layer: self.add_layout_pin_rect_center(text=name, layer=self.pwr_grid_layer, - offset=loc) + offset=loc, + width=width, + height=height) else: via = self.add_via_stack_center(from_layer=start_layer, to_layer=self.pwr_grid_layer, offset=loc, directions=directions) - # Hack for min area - if OPTS.tech_name == "sky130" and self.is_top_level: - width = round_to_grid(sqrt(drc["minarea_m3"])) - height = round_to_grid(drc["minarea_m3"] / width) - else: + if not width: width = via.width + if not height: height = via.height self.add_layout_pin_rect_center(text=name, layer=self.pwr_grid_layer, @@ -1270,22 +1279,30 @@ class layout(): if not loc: loc = pin.center() + # Hack for min area + if OPTS.tech_name == "sky130" and self.is_top_level: + min_area = drc["minarea_{}".format(self.pwr_grid_layer)] + width = round_to_grid(sqrt(min_area)) + height = round_to_grid(min_area / width) + else: + width = None + height = None + if pin.layer == self.pwr_grid_layer: self.add_layout_pin_rect_center(text=pin.name, layer=self.pwr_grid_layer, - offset=loc) + offset=loc, + width=width, + height=height) else: via = self.add_via_stack_center(from_layer=pin.layer, to_layer=self.pwr_grid_layer, offset=loc, directions=directions) - # Hack for min area - if OPTS.tech_name == "sky130" and self.is_top_level: - width = round_to_grid(sqrt(drc["minarea_m3"])) - height = round_to_grid(drc["minarea_m3"] / width) - else: + if not width: width = via.width + if not height: height = via.height self.add_layout_pin_rect_center(text=pin.name, layer=self.pwr_grid_layer, From 78966824dbc65432ca6fd31040b2b5573fabcc5c Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 13 Jan 2021 12:37:29 -0800 Subject: [PATCH 07/15] Second iteration try unblocking partial blocked grids. --- compiler/router/supply_tree_router.py | 42 ++++++++++++++++----------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/compiler/router/supply_tree_router.py b/compiler/router/supply_tree_router.py index 73378b2d..a433271e 100644 --- a/compiler/router/supply_tree_router.py +++ b/compiler/router/supply_tree_router.py @@ -121,28 +121,36 @@ class supply_tree_router(router): #return def route_signal(self, pin_name, src_idx, dest_idx): - - for detour_scale in [5 * pow(2, x) for x in range(5)]: - debug.info(2, "Routing {0} to {1} with scale {2}".format(src_idx, dest_idx, detour_scale)) + + # First pass, try to route normally + # Second pass, clear prior pin blockages so that you can route over other metal + # of the same supply. Otherwise, this can create a lot of circular routes due to accidental overlaps. + for unblock_routes in [False, True]: + for detour_scale in [5 * pow(2, x) for x in range(5)]: + debug.info(2, "Routing {0} to {1} with scale {2}".format(src_idx, dest_idx, detour_scale)) - # Clear everything in the routing grid. - self.rg.reinit() + # Clear everything in the routing grid. + self.rg.reinit() - # This is inefficient since it is non-incremental, but it was - # easier to debug. - self.prepare_blockages() - self.clear_blockages(pin_name) + # This is inefficient since it is non-incremental, but it was + # easier to debug. + self.prepare_blockages() + if unblock_routes: + self.set_blockages(self.path_blockages, False) - # Add the single component of the pin as the source - # which unmarks it as a blockage too - self.add_pin_component_source(pin_name, src_idx) + # Add the single component of the pin as the source + # which unmarks it as a blockage too + self.add_pin_component_source(pin_name, src_idx) - # Marks all pin components except index as target - self.add_pin_component_target(pin_name, dest_idx) + # Marks all pin components except index as target + self.add_pin_component_target(pin_name, dest_idx) - # Actually run the A* router - if self.run_router(detour_scale=detour_scale): - return + # Actually run the A* router + if self.run_router(detour_scale=detour_scale): + return + + debug.warning("Unblocking supply self blockages to improve access (may cause DRC errors):\n{0}\n{1})".format(pin_name, + self.pin_groups[pin_name][src_idx].pins)) self.write_debug_gds("debug_route.gds", True) From bc9ab086e586448fa8b2da8547aeff043f93b754 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 13 Jan 2021 13:01:33 -0800 Subject: [PATCH 08/15] Clean up imports --- compiler/modules/dff_inv_array.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/modules/dff_inv_array.py b/compiler/modules/dff_inv_array.py index aba5e34b..c16fe0b4 100644 --- a/compiler/modules/dff_inv_array.py +++ b/compiler/modules/dff_inv_array.py @@ -7,11 +7,10 @@ # import debug import design -from tech import drc -from math import log from vector import vector from globals import OPTS -import dff_inv +from sram_factory import factory + class dff_inv_array(design.design): """ From 1b31afd773306a31411ccedb2f95f3aa7d56b47b Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 13 Jan 2021 13:01:55 -0800 Subject: [PATCH 09/15] Use partial grids for enclosure with note --- compiler/router/pin_group.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py index 0c5221e5..cb55f193 100644 --- a/compiler/router/pin_group.py +++ b/compiler/router/pin_group.py @@ -650,10 +650,13 @@ class pin_group: self.router.write_debug_gds("blocked_pin.gds") # Consider the fully connected set first and if not the partial set - if len(pin_set) > 0: - self.grids = pin_set - else: - self.grids = partial_set + # if len(pin_set) > 0: + # self.grids = pin_set + # else: + # self.grids = partial_set + # Just using the full set simplifies the enclosures, otherwise + # we get some pin enclose DRC errors due to off grid pins + self.grids = pin_set | partial_set if len(self.grids) < 0: debug.error("Did not find any unblocked grids: {}".format(str(self.pins))) self.router.write_debug_gds("blocked_pin.gds") From 3ef56a29ea370741b931875e8254b7c40b8f058a Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 13 Jan 2021 13:56:22 -0800 Subject: [PATCH 10/15] Bug fix --- compiler/modules/row_cap_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/modules/row_cap_array.py b/compiler/modules/row_cap_array.py index 59da5fc2..04845ad5 100644 --- a/compiler/modules/row_cap_array.py +++ b/compiler/modules/row_cap_array.py @@ -113,5 +113,5 @@ class row_cap_array(bitcell_base_array): inst = self.cell_inst[row, col] for pin_name in ["vdd", "gnd"]: for pin in inst.get_pins(pin_name): - self.add_power_pin(pin) + self.copy_power_pin(pin) From 88f2198524ccd17580425ab81a42031f2c05ef62 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 13 Jan 2021 13:56:46 -0800 Subject: [PATCH 11/15] Always use min area power/IO pins --- compiler/base/hierarchy_layout.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index da6cbf9b..4dc464e1 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -56,8 +56,6 @@ class layout(): self.visited = [] # Flag for library cells self.is_library_cell = False - # Flag for top level (used for min area metal pins etc.) - self.is_top_level = False self.gds_read() @@ -1222,7 +1220,7 @@ class layout(): pin.height()) elif add_vias: - self.add_power_pin(pin) + self.copy_power_pin(pin) def add_io_pin(self, instance, pin_name, new_name, start_layer=None): """ @@ -1239,7 +1237,7 @@ class layout(): def add_power_pin(self, name, loc, directions=None, start_layer="m1"): # Hack for min area - if OPTS.tech_name == "sky130" and self.is_top_level: + if OPTS.tech_name == "sky130": min_area = drc["minarea_{}".format(self.pwr_grid_layer)] width = round_to_grid(sqrt(min_area)) height = round_to_grid(min_area / width) @@ -1280,7 +1278,7 @@ class layout(): loc = pin.center() # Hack for min area - if OPTS.tech_name == "sky130" and self.is_top_level: + if OPTS.tech_name == "sky130": min_area = drc["minarea_{}".format(self.pwr_grid_layer)] width = round_to_grid(sqrt(min_area)) height = round_to_grid(min_area / width) From e3a888e0f7287bb30f8dcec7f8bd13d0504b21d5 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 13 Jan 2021 13:57:49 -0800 Subject: [PATCH 12/15] Only unblock blockages not grids --- compiler/router/router.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/compiler/router/router.py b/compiler/router/router.py index 8cc8f5d8..0e77f939 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -422,12 +422,8 @@ class router(router_tech): """ This function clears a given pin and all of its components from being blockages. """ - # This should be a superset of the grids... blockage_grids = {y for x in self.pin_groups[pin_name] for y in x.blockages} self.set_blockages(blockage_grids, False) - # But do the grids just in case - blockage_grids = {y for x in self.pin_groups[pin_name] for y in x.grids} - self.set_blockages(blockage_grids, False) def clear_all_blockages(self): """ From 683f4214b281d54e00641a64247f3bacda6e32a0 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 14 Jan 2021 15:58:37 -0800 Subject: [PATCH 13/15] Differentiate pin and other blockages for easier to understand blockage processing. --- compiler/router/pin_group.py | 23 ++++++++++++--------- compiler/router/router.py | 29 +++++++++++++++------------ compiler/router/supply_tree_router.py | 12 +++++++---- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py index cb55f193..4458fdfd 100644 --- a/compiler/router/pin_group.py +++ b/compiler/router/pin_group.py @@ -8,6 +8,7 @@ from direction import direction from pin_layout import pin_layout from vector import vector +from vector3d import vector3d import debug @@ -619,18 +620,16 @@ class pin_group: # Blockages will be a super-set of pins since # it uses the inflated pin shape. blockage_in_tracks = self.router.convert_blockage(pin) - self.blockages.update(blockage_in_tracks) - + # Must include the pins here too because these are computed in a different + # way than blockages. + self.blockages.update(sufficient | insufficient | blockage_in_tracks) + # If we have a blockage, we must remove the grids # Remember, this excludes the pin blockages already - shared_set = pin_set & self.router.get_blocked_grids() - if len(shared_set) > 0: - debug.info(4, "Removing pins {}".format(shared_set)) - pin_set.difference_update(shared_set) - shared_set = partial_set & self.router.get_blocked_grids() - if len(shared_set) > 0: - debug.info(4, "Removing pins {}".format(shared_set)) - + blocked_grids = self.router.get_blocked_grids() + pin_set.difference_update(blocked_grids) + partial_set.difference_update(blocked_grids) + # At least one of the groups must have some valid tracks if (len(pin_set) == 0 and len(partial_set) == 0): # debug.warning("Pin is very close to metal blockage.\nAttempting to expand blocked pin {}".format(self.pins)) @@ -641,9 +640,13 @@ class pin_group: (sufficient, insufficient) = self.router.convert_pin_to_tracks(self.name, pin, expansion=1) + + # This time, don't remove blockages in the hopes that it might be ok. + # Could cause DRC problems! pin_set.update(sufficient) partial_set.update(insufficient) + # If it's still empty, we must bail. if len(pin_set) == 0 and len(partial_set) == 0: debug.error("Unable to find unblocked pin {} {}".format(self.name, self.pins)) diff --git a/compiler/router/router.py b/compiler/router/router.py index 0e77f939..ce0a739a 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -356,7 +356,9 @@ class router(router_tech): # Start fresh. Not the best for run-time, but simpler. self.clear_all_blockages() + # This adds the initial blockges of the design + # which includes all blockages due to non-pin shapes # print("BLOCKING:", self.blocked_grids) self.set_blockages(self.blocked_grids, True) @@ -368,19 +370,16 @@ class router(router_tech): # If function doesn't exist, it isn't a supply router pass - # Block all of the pin components - # (some will be unblocked if they're a source/target) - # Also block the previous routes + # Now go and block all of the blockages due to pin shapes. + # Some of these will get unblocked later if they are the source/target. for name in self.pin_groups: # This should be a superset of the grids... blockage_grids = {y for x in self.pin_groups[name] for y in x.blockages} self.set_blockages(blockage_grids, True) - # But do the grids just in case - blockage_grids = {y for x in self.pin_groups[name] for y in x.grids} - self.set_blockages(blockage_grids, True) - # FIXME: These duplicate a bit of work - # These are the paths that have already been routed. + # If we have paths that were recently routed, add them as blockages as well. + # We might later do rip-up and reroute so they might not be metal shapes in the design yet. + # Also, this prevents having to reload an entire GDS and find the blockage shapes. self.set_blockages(self.path_blockages) def convert_shape_to_units(self, shape): @@ -468,7 +467,9 @@ class router(router_tech): """ Return the blocked grids with their flag set """ - return set([x for x in self.blocked_grids if self.rg.is_blocked(x)]) + #return set([x for x in self.blocked_grids if self.rg.is_blocked(x)]) + # These are all the non-pin blockages + return self.blocked_grids def retrieve_blockages(self, lpp): """ @@ -483,7 +484,10 @@ class router(router_tech): new_pin = pin_layout("blockage{}".format(len(self.blockages)), rect, lpp) - self.blockages.append(new_pin) + # If there is a rectangle that is the same in the pins, + # it isn't a blockage! + if new_pin not in self.all_pins: + self.blockages.append(new_pin) def convert_point_to_units(self, p): """ @@ -1031,7 +1035,6 @@ class router(router_tech): self.paths.append(grid_utils.flatten_set(path)) self.add_route(path) - self.path_blockages.append(self.paths[-1]) return True else: @@ -1120,7 +1123,7 @@ class router(router_tech): """ Erase all of the comments on the current level. """ - debug.info(0, "Erasing router info") + debug.info(2, "Erasing router info") lpp = techlayer["text"] self.cell.objs = [x for x in self.cell.objs if x.lpp != lpp] @@ -1130,7 +1133,7 @@ class router(router_tech): the boundary layer for debugging purposes. This can only be called once or the labels will overlap. """ - debug.info(0, "Adding router info") + debug.info(2, "Adding router info") show_blockages = False show_blockage_grids = False diff --git a/compiler/router/supply_tree_router.py b/compiler/router/supply_tree_router.py index a433271e..31311550 100644 --- a/compiler/router/supply_tree_router.py +++ b/compiler/router/supply_tree_router.py @@ -13,7 +13,7 @@ import grid_utils from scipy.sparse import csr_matrix from scipy.sparse.csgraph import minimum_spanning_tree from signal_grid import signal_grid - +from vector3d import vector3d class supply_tree_router(router): """ @@ -116,6 +116,10 @@ class supply_tree_router(router): # Route MST components for (src, dest) in connections: self.route_signal(pin_name, src, dest) + # if pin_name == "gnd": + # print("\nSRC {}: ".format(src) + str(self.pin_groups[pin_name][src].grids) + str(self.pin_groups[pin_name][src].blockages)) + # print("DST {}: ".format(dest) + str(self.pin_groups[pin_name][dest].grids) + str(self.pin_groups[pin_name][dest].blockages)) + # self.write_debug_gds("post_{0}_{1}.gds".format(src, dest), False) #self.write_debug_gds("final.gds", True) #return @@ -136,6 +140,9 @@ class supply_tree_router(router): # easier to debug. self.prepare_blockages() if unblock_routes: + msg = "Unblocking supply self blockages to improve access (may cause DRC errors):\n{0}\n{1})" + debug.warning(msg.format(pin_name, + self.pin_groups[pin_name][src_idx].pins)) self.set_blockages(self.path_blockages, False) # Add the single component of the pin as the source @@ -149,9 +156,6 @@ class supply_tree_router(router): if self.run_router(detour_scale=detour_scale): return - debug.warning("Unblocking supply self blockages to improve access (may cause DRC errors):\n{0}\n{1})".format(pin_name, - self.pin_groups[pin_name][src_idx].pins)) - self.write_debug_gds("debug_route.gds", True) def add_io_pin(self, instance, pin_name, new_name=""): From 69fe050bade334681cdc874e128ae923083e48ad Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 15 Jan 2021 13:25:57 -0800 Subject: [PATCH 14/15] Refactor and cleanup router grids. --- compiler/router/grid.py | 4 +-- compiler/router/router.py | 37 ++++++++++++++++---- compiler/router/signal_escape_router.py | 23 ++++++------- compiler/router/signal_router.py | 18 ++-------- compiler/router/supply_grid_router.py | 17 +++------- compiler/router/supply_tree_router.py | 22 +++++------- compiler/sram/sram_1bank.py | 45 ------------------------- compiler/sram/sram_base.py | 44 ++++++++++++++++++++++++ 8 files changed, 101 insertions(+), 109 deletions(-) diff --git a/compiler/router/grid.py b/compiler/router/grid.py index ae6e04ee..e619bf4d 100644 --- a/compiler/router/grid.py +++ b/compiler/router/grid.py @@ -36,7 +36,7 @@ class grid: # The bounds are in grids for this # 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, 1).scale(self.track_factor).round() + self.ur = vector3d(ur.x, ur.y, 0).scale(self.track_factor).round() # let's leave the map sparse, cells are created on demand to reduce memory self.map={} @@ -124,7 +124,7 @@ class grid: def add_perimeter_target(self, side="all"): debug.info(3, "Adding perimeter target") - + print(self.ll, self.ur) perimeter_list = [] # Add the left/right columns if side=="all" or side=="left": diff --git a/compiler/router/router.py b/compiler/router/router.py index ce0a739a..30ae76e7 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -28,7 +28,7 @@ class router(router_tech): route on a given layer. This is limited to two layer routes. It populates blockages on a grid class. """ - def __init__(self, layers, design, gds_filename=None, route_track_width=1): + def __init__(self, layers, design, gds_filename=None, bbox=None, route_track_width=1): """ This will instantiate a copy of the gds file or the module at (0,0) and route on top of this. The blockages from the gds/module will be @@ -83,12 +83,35 @@ class router(router_tech): # 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 - 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.init_bbox(bbox) + + def init_bbox(self, bbox=None): + """ + Initialize the ll,ur values with the paramter or using the layout boundary. + """ + 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]) + else: + self.ll, self.ur = bbox + + self.bbox = (self.ll, self.ur) + size = self.ur - self.ll + debug.info(1, "Size: {0} x {1}".format(size.x, size.y)) + + def get_bbox(self): + return self.bbox + + def create_routing_grid(self, router_type, bbox=None): + """ + Create a sprase routing grid with A* expansion functions. + """ + self.init_bbox(bbox) + self.rg = router_type(self.ll, self.ur, self.track_width) def clear_pins(self): """ diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index 134a2ee3..2352bbc9 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -17,20 +17,12 @@ class signal_escape_router(router): A router that routes signals to perimeter and makes pins. """ - def __init__(self, layers, design, gds_filename=None): + def __init__(self, layers, design, bbox=None, gds_filename=None): """ This will route on layers in design. It will get the blockages from either the gds file name or the design itself (by saving to a gds file). """ - router.__init__(self, layers, design, gds_filename, 1) - - def create_routing_grid(self): - """ - Create a sprase routing grid with A* expansion functions. - """ - size = self.ur - self.ll - debug.info(1,"Size: {0} x {1}".format(size.x, size.y)) - self.rg = signal_grid(self.ll, self.ur, self.track_width) + router.__init__(self, layers, design, gds_filename, bbox) def perimeter_dist(self, pin_name): """ @@ -47,7 +39,7 @@ class signal_escape_router(router): Takes a list of tuples (name, side) and routes them. After routing, it removes the old pin and places a new one on the perimeter. """ - self.create_routing_grid() + self.create_routing_grid(signal_grid) start_time = datetime.now() self.find_pins_and_blockages(pin_names) @@ -91,8 +83,9 @@ class signal_escape_router(router): # Marks the grid cells all along the perimeter as a target self.add_perimeter_target(side) - #if pin_name == "dout1[1]": - # self.write_debug_gds("preroute.gds", False) + # if pin_name == "dout0[3]": + # self.write_debug_gds("pre_route.gds", False) + # breakpoint() # Actually run the A* router if self.run_router(detour_scale=detour_scale): @@ -100,6 +93,10 @@ class signal_escape_router(router): self.cell.replace_layout_pin(pin_name, new_pin) return + # if pin_name == "dout0[3]": + # self.write_debug_gds("pre_route.gds", False) + # breakpoint() + self.write_debug_gds("debug_route.gds", True) diff --git a/compiler/router/signal_router.py b/compiler/router/signal_router.py index fda92cd8..2a34bc50 100644 --- a/compiler/router/signal_router.py +++ b/compiler/router/signal_router.py @@ -15,24 +15,12 @@ class signal_router(router): route on a given layer. This is limited to two layer routes. """ - def __init__(self, layers, design, gds_filename=None): + def __init__(self, layers, design, gds_filename=None, bbox=None): """ This will route on layers in design. It will get the blockages from either the gds file name or the design itself (by saving to a gds file). """ - router.__init__(self, layers, design, gds_filename) - - def create_routing_grid(self): - """ - Create a sprase routing grid with A* expansion functions. - """ - # We will add a halo around the boundary - # of this many tracks - size = self.ur - self.ll - debug.info(1, "Size: {0} x {1}".format(size.x, size.y)) - - import signal_grid - self.rg = signal_grid.signal_grid(self.ll, self.ur, self.route_track_width) + router.__init__(self, layers, design, gds_filename, bbox) def route(self, src, dest, detour_scale=5): """ @@ -52,7 +40,7 @@ class signal_router(router): # Creat a routing grid over the entire area # FIXME: This could be created only over the routing region, # but this is simplest for now. - self.create_routing_grid() + self.create_routing_grid(signal_grid) # Get the pin shapes self.find_pins_and_blockages([src, dest]) diff --git a/compiler/router/supply_grid_router.py b/compiler/router/supply_grid_router.py index 6876329f..8332a212 100644 --- a/compiler/router/supply_grid_router.py +++ b/compiler/router/supply_grid_router.py @@ -11,6 +11,7 @@ from vector3d import vector3d from router import router from direction import direction from datetime import datetime +from supply_grid import supply_grid import grid_utils @@ -20,7 +21,7 @@ class supply_grid_router(router): routes a grid to connect the supply on the two layers. """ - def __init__(self, layers, design, gds_filename=None): + def __init__(self, layers, design, gds_filename=None, bbox=None): """ This will route on layers in design. It will get the blockages from either the gds file name or the design itself (by saving to a gds file). @@ -30,7 +31,7 @@ class supply_grid_router(router): # Power rail width in minimum wire widths self.route_track_width = 2 - router.__init__(self, layers, design, gds_filename, self.route_track_width) + router.__init__(self, layers, design, gds_filename, bbox, self.route_track_width) # The list of supply rails (grid sets) that may be routed self.supply_rails = {} @@ -39,16 +40,6 @@ class supply_grid_router(router): print_time("Init supply router", datetime.now(), start_time, 3) - def create_routing_grid(self): - """ - Create a sprase routing grid with A* expansion functions. - """ - size = self.ur - self.ll - debug.info(1, "Size: {0} x {1}".format(size.x, size.y)) - - import supply_grid - self.rg = supply_grid.supply_grid(self.ll, self.ur, self.route_track_width) - def route(self, vdd_name="vdd", gnd_name="gnd"): """ Add power supply rails and connect all pins to these rails. @@ -64,7 +55,7 @@ class supply_grid_router(router): # Creat a routing grid over the entire area # FIXME: This could be created only over the routing region, # but this is simplest for now. - self.create_routing_grid() + self.create_routing_grid(supply_grid) # Get the pin shapes start_time = datetime.now() diff --git a/compiler/router/supply_tree_router.py b/compiler/router/supply_tree_router.py index 31311550..2bae39e3 100644 --- a/compiler/router/supply_tree_router.py +++ b/compiler/router/supply_tree_router.py @@ -13,7 +13,7 @@ import grid_utils from scipy.sparse import csr_matrix from scipy.sparse.csgraph import minimum_spanning_tree from signal_grid import signal_grid -from vector3d import vector3d + class supply_tree_router(router): """ @@ -21,23 +21,17 @@ class supply_tree_router(router): routes a grid to connect the supply on the two layers. """ - def __init__(self, layers, design, gds_filename=None): + def __init__(self, layers, design, gds_filename=None, bbox=None): """ This will route on layers in design. It will get the blockages from either the gds file name or the design itself (by saving to a gds file). """ # Power rail width in minimum wire widths - self.route_track_width = 2 + # This is set to match the signal router so that the grids are aligned + # for prettier routes. + self.route_track_width = 1 - router.__init__(self, layers, design, gds_filename, self.route_track_width) - - def create_routing_grid(self): - """ - Create a sprase routing grid with A* expansion functions. - """ - size = self.ur - self.ll - debug.info(1,"Size: {0} x {1}".format(size.x,size.y)) - self.rg = signal_grid(self.ll, self.ur, self.route_track_width) + router.__init__(self, layers, design, gds_filename, bbox, self.route_track_width) def route(self, vdd_name="vdd", gnd_name="gnd"): """ @@ -54,7 +48,7 @@ class supply_tree_router(router): # Creat a routing grid over the entire area # FIXME: This could be created only over the routing region, # but this is simplest for now. - self.create_routing_grid() + self.create_routing_grid(signal_grid) # Get the pin shapes start_time = datetime.now() @@ -122,7 +116,7 @@ class supply_tree_router(router): # self.write_debug_gds("post_{0}_{1}.gds".format(src, dest), False) #self.write_debug_gds("final.gds", True) - #return + #return def route_signal(self, pin_name, src_idx, dest_idx): diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index d96f9336..c34b4b3c 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -9,9 +9,7 @@ from vector import vector from sram_base import sram_base from contact import m2_via from channel_route import channel_route -from signal_escape_router import signal_escape_router as router from globals import OPTS -import debug class sram_1bank(sram_base): @@ -247,49 +245,6 @@ class sram_1bank(sram_base): self.data_pos[port] = vector(x_offset, y_offset) self.spare_wen_pos[port] = vector(x_offset, y_offset) - def route_escape_pins(self): - """ - Add the top-level pins for a single bank SRAM with control. - """ - - # List of pin to new pin name - pins_to_route = [] - for port in self.all_ports: - # Connect the control pins as inputs - for signal in self.control_logic_inputs[port]: - if signal.startswith("rbl"): - continue - if signal=="clk": - pins_to_route.append("{0}{1}".format(signal, port)) - else: - pins_to_route.append("{0}{1}".format(signal, port)) - - if port in self.write_ports: - for bit in range(self.word_size + self.num_spare_cols): - pins_to_route.append("din{0}[{1}]".format(port, bit)) - - if port in self.readwrite_ports or port in self.read_ports: - for bit in range(self.word_size + self.num_spare_cols): - pins_to_route.append("dout{0}[{1}]".format(port, bit)) - - for bit in range(self.col_addr_size): - pins_to_route.append("addr{0}[{1}]".format(port, bit)) - - for bit in range(self.row_addr_size): - pins_to_route.append("addr{0}[{1}]".format(port, bit + self.col_addr_size)) - - if port in self.write_ports: - if self.write_size: - for bit in range(self.num_wmasks): - pins_to_route.append("wmask{0}[{1}]".format(port, bit)) - - if port in self.write_ports: - for bit in range(self.num_spare_cols): - pins_to_route.append("spare_wen{0}[{1}]".format(port, bit)) - - rtr=router(self.m3_stack, self) - rtr.escape_route(pins_to_route) - def add_layout_pins(self, add_vias=True): """ Add the top-level pins for a single bank SRAM with control. diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index 94fa245b..a3beacfa 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -264,6 +264,50 @@ class sram_base(design, verilog, lef): pin.width(), pin.height()) + def route_escape_pins(self): + """ + Add the top-level pins for a single bank SRAM with control. + """ + + # List of pin to new pin name + pins_to_route = [] + for port in self.all_ports: + # Connect the control pins as inputs + for signal in self.control_logic_inputs[port]: + if signal.startswith("rbl"): + continue + if signal=="clk": + pins_to_route.append("{0}{1}".format(signal, port)) + else: + pins_to_route.append("{0}{1}".format(signal, port)) + + if port in self.write_ports: + for bit in range(self.word_size + self.num_spare_cols): + pins_to_route.append("din{0}[{1}]".format(port, bit)) + + if port in self.readwrite_ports or port in self.read_ports: + for bit in range(self.word_size + self.num_spare_cols): + pins_to_route.append("dout{0}[{1}]".format(port, bit)) + + for bit in range(self.col_addr_size): + pins_to_route.append("addr{0}[{1}]".format(port, bit)) + + for bit in range(self.row_addr_size): + pins_to_route.append("addr{0}[{1}]".format(port, bit + self.col_addr_size)) + + if port in self.write_ports: + if self.write_size: + for bit in range(self.num_wmasks): + pins_to_route.append("wmask{0}[{1}]".format(port, bit)) + + if port in self.write_ports: + for bit in range(self.num_spare_cols): + pins_to_route.append("spare_wen{0}[{1}]".format(port, bit)) + + from signal_escape_router import signal_escape_router as router + rtr=router(self.m3_stack, self) + rtr.escape_route(pins_to_route) + def compute_bus_sizes(self): """ Compute the independent bus widths shared between two and four bank SRAMs """ From e8239c5e77c819da073314295e1fc8a3882c4de3 Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 15 Jan 2021 14:27:54 -0800 Subject: [PATCH 15/15] Remove debug print statement --- compiler/router/grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/router/grid.py b/compiler/router/grid.py index e619bf4d..6c336efd 100644 --- a/compiler/router/grid.py +++ b/compiler/router/grid.py @@ -124,7 +124,7 @@ class grid: def add_perimeter_target(self, side="all"): debug.info(3, "Adding perimeter target") - print(self.ll, self.ur) + perimeter_list = [] # Add the left/right columns if side=="all" or side=="left":