From 504f9aa8928dcea308ef4f56afa8ba8e2ee0919f Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 8 Jan 2021 11:34:58 -0800 Subject: [PATCH 01/17] 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/17] 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 1c6d4eedd152c4dc7ea35a1cec5787bfa93aa43f Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 11 Jan 2021 13:52:41 -0800 Subject: [PATCH 03/17] Add new empty debug function. --- compiler/debug.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/compiler/debug.py b/compiler/debug.py index 3d51a227..78c80172 100644 --- a/compiler/debug.py +++ b/compiler/debug.py @@ -107,3 +107,18 @@ def info(lev, str): class_name = mod.__name__ print_raw("[{0}/{1}]: {2}".format(class_name, frm[0].f_code.co_name, str)) + + +def bp(): + """ + An empty function so you can set soft breakpoints in pdb. + Usage: + 1) Add a breakpoint anywhere in your code with "import debug; debug.bp()". + 2) Run "python3 -m pdb openram.py config.py" or "python3 -m pdb 05_bitcell_array.test" (for example) + 3) When pdb starts, run "break debug.py" to set a SOFT breakpoint. (Or you can add this to your ~/.pdbrc) + 4) Then run "cont" to continue. + 5) You can now set additional breakpoints or display commands + and whenever you encounter the debug.bp() they won't be "reset". + """ + pass + From 3d7bed0641b1c0828c384f72eb1f5d116ead7ed8 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 12 Jan 2021 11:22:11 -0800 Subject: [PATCH 04/17] Fix typo in comment --- compiler/debug.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/debug.py b/compiler/debug.py index 78c80172..8d0d662e 100644 --- a/compiler/debug.py +++ b/compiler/debug.py @@ -115,7 +115,7 @@ def bp(): Usage: 1) Add a breakpoint anywhere in your code with "import debug; debug.bp()". 2) Run "python3 -m pdb openram.py config.py" or "python3 -m pdb 05_bitcell_array.test" (for example) - 3) When pdb starts, run "break debug.py" to set a SOFT breakpoint. (Or you can add this to your ~/.pdbrc) + 3) When pdb starts, run "break debug.bp" to set a SOFT breakpoint. (Or you can add this to your ~/.pdbrc) 4) Then run "cont" to continue. 5) You can now set additional breakpoints or display commands and whenever you encounter the debug.bp() they won't be "reset". From 6f5b7c02643eea5a8d512784e914d89ec1e43058 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 12 Jan 2021 16:20:03 -0800 Subject: [PATCH 05/17] 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 06/17] 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 07/17] 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 08/17] 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 09/17] 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 10/17] 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 11/17] 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 12/17] 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 13/17] 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 14/17] 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 15/17] 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 16/17] 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 17/17] 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":