diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 1ec5a2f0..4dc464e1 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -1220,49 +1220,89 @@ class layout(): pin.height()) elif add_vias: - self.add_power_pin(name, pin.center(), start_layer=pin.layer) + self.copy_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"): + # Just use the power pin function for now to save code + 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": + 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, + 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) + + 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, + offset=loc, + 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 start_layer == self.pwr_grid_layer: - self.add_layout_pin_rect_center(text=name, - layer=self.pwr_grid_layer, - offset=loc) + if not loc: + loc = pin.center() + + # Hack for min area + 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) else: - via = self.add_via_stack_center(from_layer=start_layer, + 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, + width=width, + height=height) + else: + via = self.add_via_stack_center(from_layer=pin.layer, to_layer=self.pwr_grid_layer, - size=size, offset=loc, directions=directions) - # Hack for min area - if OPTS.tech_name == "sky130": - 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, + self.add_layout_pin_rect_center(text=pin.name, layer=self.pwr_grid_layer, offset=loc, width=width, 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..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): """ @@ -130,12 +129,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..04845ad5 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.copy_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) 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) diff --git a/compiler/router/grid.py b/compiler/router/grid.py index ae6e04ee..6c336efd 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={} diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py index 474702a4..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 @@ -41,10 +42,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 +419,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 +604,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 +620,18 @@ 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) - + # 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.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 - 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) - + 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 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: @@ -648,15 +640,25 @@ 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)) self.router.write_debug_gds("blocked_pin.gds") - # Consider all the grids that would be blocked + # 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 + # 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))) diff --git a/compiler/router/router.py b/compiler/router/router.py index d666fcb5..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 @@ -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 @@ -80,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): """ @@ -345,16 +371,17 @@ 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 + # which includes all blockages due to non-pin shapes # print("BLOCKING:", self.blocked_grids) self.set_blockages(self.blocked_grids, True) @@ -366,26 +393,18 @@ 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: - 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) - # 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) - # 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 +440,14 @@ 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. + """ + blockage_grids = {y for x in self.pin_groups[pin_name] for y in x.blockages} + self.set_blockages(blockage_grids, False) + + def clear_all_blockages(self): """ Clear all blockages on the grid. """ @@ -432,24 +458,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 +486,14 @@ 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)]) + # These are all the non-pin blockages + return self.blocked_grids + def retrieve_blockages(self, lpp): """ Recursive find boundaries as blockages to the routing grid. @@ -473,7 +507,6 @@ 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: @@ -492,10 +525,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 +564,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 +846,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 +858,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 +869,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. @@ -1018,7 +1058,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: @@ -1107,7 +1146,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] @@ -1117,7 +1156,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/signal_escape_router.py b/compiler/router/signal_escape_router.py index 2c06d619..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) @@ -62,6 +54,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 +73,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,12 +83,20 @@ class signal_escape_router(router): # Marks the grid cells all along the perimeter as a target self.add_perimeter_target(side) + # 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): new_pin = self.get_perimeter_pin() 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 53758d9b..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() @@ -73,13 +64,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..2bae39e3 100644 --- a/compiler/router/supply_tree_router.py +++ b/compiler/router/supply_tree_router.py @@ -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,19 +48,13 @@ 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() 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() @@ -122,32 +110,45 @@ 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 + #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(pin_name) + # This is inefficient since it is non-incremental, but it was + # 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 - # 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 self.write_debug_gds("debug_route.gds", True) diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index dc8917b6..c34b4b3c 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -9,7 +9,6 @@ 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 @@ -246,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. @@ -436,9 +392,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 +407,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 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 """ 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))