diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..b080fd3e5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Version** +Which commit are you using? + +**To Reproduce** +What did you do to demonstrate the bug? +Please include your configuration file used. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Logs** +If applicable, add logs or output to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index 3ca87e2e7..ff9920cff 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -501,8 +501,8 @@ def __init__(self, lpp, offset, width, height): self.name = "rect" self.offset = vector(offset).snap_to_grid() self.size = vector(width, height).snap_to_grid() - self.width = round_to_grid(self.size.x) - self.height = round_to_grid(self.size.y) + self.width = self.size.x # round_to_grid(self.size.x) # NOTE: snap_to_grid already do this! + self.height = self.size.y # round_to_grid(self.size.y) self.compute_boundary(offset, "", 0) debug.info(4, "creating rectangle (" + str(self.layerNumber) + "): " diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index fe108c01d..4e163eb14 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -1915,6 +1915,16 @@ def add_power_pin(self, name, loc, directions=None, start_layer="m1"): min_area = drc["minarea_{}".format(self.pwr_grid_layers[1])] width = round_to_grid(sqrt(min_area)) height = round_to_grid(min_area / width) + elif OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + min_area = drc["minarea_{}".format(self.pwr_grid_layer)] + min_width = drc["minwidth_{}".format(self.pwr_grid_layer)] + dir = preferred_directions[self.pwr_grid_layer] + if dir == "V": + width = round_to_grid(min_width) + height = round_to_grid(min_area / width) + else: + height = round_to_grid(min_width) + width = round_to_grid(min_area / height) else: width = None height = None @@ -1961,6 +1971,16 @@ def copy_power_pin(self, pin, loc=None, directions=None, new_name=""): min_area = drc["minarea_{}".format(self.pwr_grid_layers[1])] width = round_to_grid(sqrt(min_area)) height = round_to_grid(min_area / width) + elif OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + min_area = drc["minarea_{}".format(self.pwr_grid_layer)] + min_width = drc["minwidth_{}".format(self.pwr_grid_layer)] + dir = preferred_directions[self.pwr_grid_layer] + if dir == "V": + width = round_to_grid(min_width) + height = round_to_grid(min_area / width) + else: + height = round_to_grid(min_width) + width = round_to_grid(min_area / height) else: width = None height = None diff --git a/compiler/base/lef.py b/compiler/base/lef.py index 799890e09..b7fc2779f 100644 --- a/compiler/base/lef.py +++ b/compiler/base/lef.py @@ -97,7 +97,8 @@ def compute_abstract_blockages(self): # Start with blockages on all layers the size of the block # minus the pin escape margin (hard coded to 4 x m3 pitch) # These are a pin_layout to use their geometric functions - perimeter_margin = self.m3_pitch + # TODO: The escape is already set to 0. Is not anymore 4xm3. Perimeter is now 0 + perimeter_margin = 0 # self.m3_pitch self.blockages = {} for layer_name in self.lef_layers: self.blockages[layer_name]=[] diff --git a/compiler/base/pin_layout.py b/compiler/base/pin_layout.py index f9b666120..ea75c8241 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -33,6 +33,12 @@ def __init__(self, name, rect, layer_name_pp): # These are the valid pin layers valid_layers = {x: layer[x] for x in layer_indices.keys()} + # search for the "+p" pins also + valid_pin_layers = {} + for x in layer_indices.keys(): + y = x + "p" + if y in layer: + valid_pin_layers[x] = layer[y] # if it's a string, use the name if type(layer_name_pp) == str: @@ -47,17 +53,24 @@ def __init__(self, name, rect, layer_name_pp): break else: - try: - from tech import layer_override - from tech import layer_override_name - if layer_override[name]: - self.lpp = layer_override[name] - self.layer = "pwellp" - self._recompute_hash() - return - except: - debug.error("Layer {} is not a valid routing layer in the tech file.".format(layer_name_pp), -1) - + for (layer_name, lpp) in valid_pin_layers.items(): + if not lpp: + continue + if self.same_lpp(layer_name_pp, lpp): + self._layer = layer_name + break + else: + try: + from tech import layer_override + from tech import layer_override_name + if layer_override[name]: + self.lpp = layer_override[name] + self.layer = "pwellp" + self._recompute_hash() + return + except: + debug.error("Layer {} is not a valid routing layer in the tech file.".format(layer_name_pp), -1) + self.lpp = layer[self.layer] self._recompute_hash() @@ -193,32 +206,15 @@ def intersection(self, other): def xoverlaps(self, other): """ Check if shape has x overlap """ - (ll, ur) = self.rect - (oll, our) = other.rect - x_overlaps = False - # check if self is within other x range - if (ll.x >= oll.x and ll.x <= our.x) or (ur.x >= oll.x and ur.x <= our.x): - x_overlaps = True - # check if other is within self x range - if (oll.x >= ll.x and oll.x <= ur.x) or (our.x >= ll.x and our.x <= ur.x): - x_overlaps = True - - return x_overlaps + a = self.rect + b = other.rect + return a[0].x < b[1].x and a[1].x > b[0].x def yoverlaps(self, other): """ Check if shape has x overlap """ - (ll, ur) = self.rect - (oll, our) = other.rect - y_overlaps = False - - # check if self is within other y range - if (ll.y >= oll.y and ll.y <= our.y) or (ur.y >= oll.y and ur.y <= our.y): - y_overlaps = True - # check if other is within self y range - if (oll.y >= ll.y and oll.y <= ur.y) or (our.y >= ll.y and our.y <= ur.y): - y_overlaps = True - - return y_overlaps + a = self.rect + b = other.rect + return a[0].y < b[1].y and a[1].y > b[0].y def xcontains(self, other): """ Check if shape contains the x overlap """ @@ -423,6 +419,7 @@ def gds_write_file(self, newLayout): except ImportError: label_purpose = purpose + newLayout.addBox(layerNumber=layer_num, purposeNumber=purpose, offsetInMicrons=self.ll(), @@ -444,11 +441,19 @@ def gds_write_file(self, newLayout): zoom = GDS["zoom"] except KeyError: zoom = None - newLayout.addText(text=self.name, - layerNumber=layer_num, - purposeNumber=label_purpose, - magnification=zoom, - offsetInMicrons=self.center()) + # Draw a second pin text too if it is different + if pin_layer_num != layer_num: + newLayout.addText(text=self.name, + layerNumber=pin_layer_num, + purposeNumber=pin_purpose, + magnification=zoom, + offsetInMicrons=self.center()) + else: + newLayout.addText(text=self.name, + layerNumber=layer_num, + purposeNumber=label_purpose, + magnification=zoom, + offsetInMicrons=self.center()) def compute_overlap(self, other): """ Calculate the rectangular overlap of two rectangles. """ @@ -525,7 +530,7 @@ def overlap_length(self, other): new_shape = pin_layout("", [ll, ur], self.lpp) return max(new_shape.height(), new_shape.width()) else: - # This is where we had a corner intersection or none + # This is where we had a corner intersection or none (1 or 0) return 0 @@ -580,9 +585,9 @@ def on_segment(self, p, q, r): Given three co-linear points, determine if q lies on segment pr """ if q.x <= max(p.x, r.x) and \ - q.x >= min(p.x, r.x) and \ - q.y <= max(p.y, r.y) and \ - q.y >= min(p.y, r.y): + q.x >= min(p.x, r.x) and \ + q.y <= max(p.y, r.y) and \ + q.y >= min(p.y, r.y): return True return False diff --git a/compiler/base/utils.py b/compiler/base/utils.py index f80b23e35..97cac5ae5 100644 --- a/compiler/base/utils.py +++ b/compiler/base/utils.py @@ -47,6 +47,25 @@ def snap_to_grid(offset): return [round_to_grid(offset[0]), round_to_grid(offset[1])] +# NOTE: To snap to the unit instead of the grid. Grid is for geometries, unit is for routes or wires. +def round_to_unit(number): + """ + Rounds an arbitrary number to the unit. + """ + grid = tech.GDS["unit"][0] + # this gets the nearest integer value + number_grid = int(round(round((number / grid), 2), 0)) + number_off = number_grid * grid + return number_off + + +def snap_to_unit(offset): + """ + Changes the coodrinate to match the unit settings + """ + return [round_to_unit(offset[0]), + round_to_unit(offset[1])] + def pin_center(boundary): """ diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 88d8abc70..b9c48dfb6 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -239,10 +239,14 @@ def compute_instance_port0_offsets(self): # control logic to allow control signals to easily pass over in M3 # by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs # may be routed in M3 or M4 + # TODO: In tsmc180, is not 1.25 (dff is 6.72. crosses with the first gnd) + mult = 1.25 + if OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + mult = 1.35 x_offset = self.central_bus_width[port] + self.port_address[port].wordline_driver_array.width if self.col_addr_size > 0: x_offset += self.column_decoder.width + self.col_addr_bus_width - y_offset = 1.25 * self.dff.height + self.column_decoder.height + y_offset = mult * self.dff.height + self.column_decoder.height else: y_offset = 0 self.column_decoder_offsets[port] = vector(-x_offset, -y_offset) @@ -283,10 +287,14 @@ def compute_instance_port1_offsets(self): # control logic to allow control signals to easily pass over in M3 # by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs # may be routed in M3 or M4 + # TODO: In tsmc180, is not 1.25 (dff is 6.72. crosses with the first gnd) + mult = 1.25 + if OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + mult = 1.5 x_offset = self.bitcell_array_right + self.central_bus_width[port] + self.port_address[port].wordline_driver_array.width if self.col_addr_size > 0: x_offset += self.column_decoder.width + self.col_addr_bus_width - y_offset = self.bitcell_array_top + 1.25 * self.dff.height + self.column_decoder.height + y_offset = self.bitcell_array_top + mult * self.dff.height + self.column_decoder.height else: y_offset = self.bitcell_array_top self.column_decoder_offsets[port] = vector(x_offset, y_offset) diff --git a/compiler/modules/column_mux.py b/compiler/modules/column_mux.py index 67c448947..3f67f650c 100644 --- a/compiler/modules/column_mux.py +++ b/compiler/modules/column_mux.py @@ -13,6 +13,7 @@ from sram_factory import factory from tech import cell_properties as cell_props from globals import OPTS +from utils import round_to_grid class column_mux(pgate): @@ -232,7 +233,7 @@ def add_pn_wells(self): active_pos = vector(rbc_width, self.nmos_upper.by() - 0.5 * self.poly_space) - self.add_via_center(layers=self.active_stack, + self.well_contact = self.add_via_center(layers=self.active_stack, offset=active_pos, implant_type="p", well_type="p") @@ -251,3 +252,27 @@ def add_pn_wells(self): offset=vector(0, 0), width=rbc_width, height=self.height) + + # TSMC18 gate port hack + if OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + # Body connection + min_area = drc["minarea_{}".format(self.active_stack[0])] + width = round_to_grid(self.well_contact.mod.first_layer_width) + height = round_to_grid(min_area / width) + width_impl = width + 2 * drc("implant_enclose_active") + height_impl = height + 2 * drc("implant_enclose_active") # contact.py:250 + self.add_rect_center(layer=self.active_stack[0], + offset=active_pos, + width=width, + height=height) + self.add_rect_center(layer="pimplant", + offset=active_pos, + width=width_impl, + height=height_impl) + if "pwell" in layer: + width_well = width + 2 * self.well_contact.mod.well_enclose_active + height_well = height + 2 * self.well_contact.mod.well_enclose_active # contact.py:264 + self.add_rect_center(layer="pwell", + offset=active_pos, + width=width_well, + height=height_well) diff --git a/compiler/modules/pand2.py b/compiler/modules/pand2.py index 8bd175890..838520ea3 100644 --- a/compiler/modules/pand2.py +++ b/compiler/modules/pand2.py @@ -9,6 +9,8 @@ from base import vector from .pgate import * from sram_factory import factory +from globals import OPTS +from tech import drc class pand2(pgate): @@ -82,6 +84,31 @@ def place_insts(self): # Add INV to the right self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + # Extension of the imp in both n and p for tsmc18 + if OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + pmos_rightmost_nand = self.nand_inst.mod.pmos2_pos + pmos_leftmost_inv = self.inv_inst.mod.inv_inst_list[0].mod.pmos_pos + vector(self.nand_inst.rx(), 0) + nmos_rightmost_nand = self.nand_inst.mod.nmos2_pos + nmos_leftmost_inv = self.inv_inst.mod.inv_inst_list[0].mod.nmos_pos + vector(self.nand_inst.rx(), 0) + pleft = pmos_rightmost_nand.x + self.nand_inst.mod.pmos_right.active_width + drc("implant_enclose_active") + pright = pmos_leftmost_inv.x - drc("implant_enclose_active") + ptop = pmos_rightmost_nand.y + self.nand_inst.mod.pmos_right.active_height + \ + max(drc("implant_enclose_active"), drc("implant_to_channel")) + pbottom = pmos_rightmost_nand.y - max(drc("implant_enclose_active"), drc("implant_to_channel")) + self.add_rect(layer="pimplant", + offset=vector(pleft, pbottom), + width=pright - pleft, + height=ptop - pbottom) + nleft = nmos_rightmost_nand.x + self.nand_inst.mod.nmos_right.active_width + drc("implant_enclose_active") + nright = nmos_leftmost_inv.x - drc("implant_enclose_active") + ntop = nmos_rightmost_nand.y + self.nand_inst.mod.nmos_right.active_height + \ + max(drc("implant_enclose_active"), drc("implant_to_channel")) + nbottom = nmos_rightmost_nand.y - max(drc("implant_enclose_active"), drc("implant_to_channel")) + self.add_rect(layer="nimplant", + offset=vector(nleft, nbottom), + width=nright - nleft, + height=ntop - nbottom) + def route_supply_rails(self): """ Add vdd/gnd rails to the top, (middle), and bottom. """ self.add_layout_pin_rect_center(text="gnd", diff --git a/compiler/modules/pand3.py b/compiler/modules/pand3.py index f63b8c414..a44ed885b 100644 --- a/compiler/modules/pand3.py +++ b/compiler/modules/pand3.py @@ -9,6 +9,8 @@ from base import vector from .pgate import * from sram_factory import factory +from globals import OPTS +from tech import drc class pand3(pgate): @@ -87,6 +89,31 @@ def place_insts(self): # Add INV to the right self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + # Extension of the imp in both n and p for tsmc18 + if OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + pmos_rightmost_nand = self.nand_inst.mod.pmos3_pos + pmos_leftmost_inv = self.inv_inst.mod.inv_inst_list[0].mod.pmos_pos + vector(self.nand_inst.rx(), 0) + nmos_rightmost_nand = self.nand_inst.mod.nmos3_pos + nmos_leftmost_inv = self.inv_inst.mod.inv_inst_list[0].mod.nmos_pos + vector(self.nand_inst.rx(), 0) + pleft = pmos_rightmost_nand.x + self.nand_inst.mod.pmos_right.active_width + drc("implant_enclose_active") + pright = pmos_leftmost_inv.x - drc("implant_enclose_active") + ptop = pmos_rightmost_nand.y + self.nand_inst.mod.pmos_right.active_height + \ + max(drc("implant_enclose_active"), drc("implant_to_channel")) + pbottom = pmos_rightmost_nand.y - max(drc("implant_enclose_active"), drc("implant_to_channel")) + self.add_rect(layer="pimplant", + offset=vector(pleft, pbottom), + width=pright - pleft, + height=ptop - pbottom) + nleft = nmos_rightmost_nand.x + self.nand_inst.mod.nmos_right.active_width + drc("implant_enclose_active") + nright = nmos_leftmost_inv.x - drc("implant_enclose_active") + ntop = nmos_rightmost_nand.y + self.nand_inst.mod.nmos_right.active_height + \ + max(drc("implant_enclose_active"), drc("implant_to_channel")) + nbottom = nmos_rightmost_nand.y - max(drc("implant_enclose_active"), drc("implant_to_channel")) + self.add_rect(layer="nimplant", + offset=vector(nleft, nbottom), + width=nright - nleft, + height=ntop - nbottom) + def route_supply_rails(self): """ Add vdd/gnd rails to the top, (middle), and bottom. """ self.add_layout_pin_rect_center(text="gnd", diff --git a/compiler/modules/pand4.py b/compiler/modules/pand4.py index 9b5a31d68..32c3b0723 100644 --- a/compiler/modules/pand4.py +++ b/compiler/modules/pand4.py @@ -9,6 +9,8 @@ from base import vector from .pgate import * from sram_factory import factory +from globals import OPTS +from tech import drc class pand4(pgate): @@ -88,6 +90,31 @@ def place_insts(self): # Add INV to the right self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + # Extension of the imp in both n and p for tsmc18 + if OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + pmos_rightmost_nand = self.nand_inst.mod.pmos4_pos + pmos_leftmost_inv = self.inv_inst.mod.inv_inst_list[0].mod.pmos_pos + vector(self.nand_inst.rx(), 0) + nmos_rightmost_nand = self.nand_inst.mod.nmos4_pos + nmos_leftmost_inv = self.inv_inst.mod.inv_inst_list[0].mod.nmos_pos + vector(self.nand_inst.rx(), 0) + pleft = pmos_rightmost_nand.x + self.nand_inst.mod.pmos_right.active_width + drc("implant_enclose_active") + pright = pmos_leftmost_inv.x - drc("implant_enclose_active") + ptop = pmos_rightmost_nand.y + self.nand_inst.mod.pmos_right.active_height + \ + max(drc("implant_enclose_active"), drc("implant_to_channel")) + pbottom = pmos_rightmost_nand.y - max(drc("implant_enclose_active"), drc("implant_to_channel")) + self.add_rect(layer="pimplant", + offset=vector(pleft, pbottom), + width=pright - pleft, + height=ptop - pbottom) + nleft = nmos_rightmost_nand.x + self.nand_inst.mod.nmos_right.active_width + drc("implant_enclose_active") + nright = nmos_leftmost_inv.x - drc("implant_enclose_active") + ntop = nmos_rightmost_nand.y + self.nand_inst.mod.nmos_right.active_height + \ + max(drc("implant_enclose_active"), drc("implant_to_channel")) + nbottom = nmos_rightmost_nand.y - max(drc("implant_enclose_active"), drc("implant_to_channel")) + self.add_rect(layer="nimplant", + offset=vector(nleft, nbottom), + width=nright - nleft, + height=ntop - nbottom) + def route_supply_rails(self): """ Add vdd/gnd rails to the top, (middle), and bottom. """ self.add_layout_pin_rect_center(text="gnd", diff --git a/compiler/modules/pgate.py b/compiler/modules/pgate.py index ad61cb680..d229d5e54 100644 --- a/compiler/modules/pgate.py +++ b/compiler/modules/pgate.py @@ -11,6 +11,10 @@ import math from bisect import bisect_left from tech import layer, drc +from tech import layer_indices +from tech import layer_stacks, preferred_directions +from base import vector +from utils import round_to_grid from globals import OPTS from tech import cell_properties as cell_props @@ -135,6 +139,43 @@ def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left", di offset=contact_offset, directions=directions) + # TSMC18 gate port hack + width = via.mod.second_layer_width + height = via.mod.second_layer_height + offset = contact_offset + # TODO: Put this in a function? + if OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + cur_layer = "poly" + while cur_layer != self.route_layer: + from_id = layer_indices[cur_layer] + to_id = layer_indices[self.route_layer] + + if from_id < to_id: # grow the stack up + search_id = 0 + next_id = 2 + else: # grow the stack down + search_id = 2 + next_id = 0 + + curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, layer_stacks), None) + cur_layer = curr_stack[next_id] + # print("Putting {}".format(cur_layer)) + if cur_layer != "poly": + min_area = drc["minarea_{}".format(cur_layer)] + min_width = drc["minwidth_{}".format(cur_layer)] + width = round_to_grid(math.sqrt(min_area) * 1.2) + height = round_to_grid(min_area / width) + dir = preferred_directions[cur_layer] + # TODO: This is very hackish. We literally just put the rect a little up + # This is caused by the nand3 and 4, whose pitches are a mess + offset = contact_offset + if(dir == "H"): + offset = contact_offset + vector(0.0, -(height - min_width)/2) + self.add_rect_center(layer=cur_layer, + offset=offset, + width=width, + height=height) + self.add_layout_pin_rect_center(text=name, layer=self.route_layer, offset=contact_offset, @@ -201,8 +242,18 @@ def add_nwell_contact(self, pmos, pmos_pos): layer_stack = self.active_stack # To the right a spacing away from the pmos right active edge + # Also, avoid to do intersection of nimp and pimp + # The pimplant_to_nimplant is new + pimp_to_nimp = 0 + imp_to_active = 0 + try: + pimp_to_nimp = drc("pimplant_to_nimplant") + imp_to_active = drc("implant_to_active") + except: + pass contact_xoffset = pmos_pos.x + pmos.active_width \ - + self.active_space + + max(self.active_space, 2*drc("implant_enclose_active") + pimp_to_nimp, + imp_to_active + drc("implant_enclose_active")) # Must be at least an well enclosure of active down # from the top of the well @@ -250,6 +301,28 @@ def add_nwell_contact(self, pmos, pmos_pos): # width=implant_width, # height=implant_height) + # TSMC18 gate port hack + if OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + min_area = drc["minarea_{}".format(self.active_stack[0])] + width = round_to_grid(self.nwell_contact.mod.first_layer_width) + height = round_to_grid(min_area / width) + width_impl = width + 2 * drc("implant_enclose_active") + height_impl = height + 2 * drc("implant_enclose_active") # contact.py:250 + width_well = width + 2 * self.nwell_contact.mod.well_enclose_active + height_well = height + 2 * self.nwell_contact.mod.well_enclose_active # contact.py:264 + self.add_rect_center(layer=self.active_stack[0], + offset=contact_offset, + width=width, + height=height) + self.add_rect_center(layer="nimplant", + offset=contact_offset, + width=width_impl, + height=height_impl) + self.add_rect_center(layer="nwell", + offset=contact_offset, + width=width_well, + height=height_well) + # Return the top of the well def extend_implants(self): @@ -314,8 +387,20 @@ def add_pwell_contact(self, nmos, nmos_pos): layer_stack = self.active_stack # To the right a spacing away from the nmos right active edge + # Also, avoid to do intersection of nimp and pimp + # Also, there should be a distance between channel and implant + # The pimplant_to_nimplant is new. + pimp_to_nimp = 0 + imp_to_active = 0 + try: + pimp_to_nimp = drc("pimplant_to_nimplant") + imp_to_active = drc("implant_to_active") + except: + pass contact_xoffset = nmos_pos.x + nmos.active_width \ - + self.active_space + + max(self.active_space, + 2*drc("implant_enclose_active") + pimp_to_nimp, + imp_to_active + drc("implant_enclose_active")) # Allow an nimplant below it under the rail contact_yoffset = max(0.5 * self.implant_width + self.implant_enclose_active, self.get_tx_insts("nmos")[0].by()) @@ -355,6 +440,28 @@ def add_pwell_contact(self, nmos, nmos_pos): # width=implant_width, # height=implant_height) + # TSMC18 gate port hack + if OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + min_area = drc["minarea_{}".format(self.active_stack[0])] + width = round_to_grid(self.pwell_contact.mod.first_layer_width) + height = round_to_grid(min_area / width) + width_impl = width + 2 * drc("implant_enclose_active") + height_impl = height + 2 * drc("implant_enclose_active") # contact.py:250 + width_well = width + 2 * self.pwell_contact.mod.well_enclose_active + height_well = height + 2 * self.pwell_contact.mod.well_enclose_active # contact.py:264 + self.add_rect_center(layer=self.active_stack[0], + offset=contact_offset, + width=width, + height=height) + self.add_rect_center(layer="pimplant", + offset=contact_offset, + width=width_impl, + height=height_impl) + self.add_rect_center(layer="pwell", + offset=contact_offset, + width=width_well, + height=height_well) + def route_supply_rails(self): """ Add vdd/gnd rails to the top and bottom. """ self.add_layout_pin_rect_center(text="gnd", diff --git a/compiler/modules/pinv.py b/compiler/modules/pinv.py index a60bed13d..be87f57da 100644 --- a/compiler/modules/pinv.py +++ b/compiler/modules/pinv.py @@ -251,6 +251,33 @@ def place_ptx(self): nmos_drain_pos = self.nmos_inst.get_pin("D").ul() self.output_pos = vector(0, 0.5 * (pmos_drain_pos.y + nmos_drain_pos.y)) + # Special requirement for rohm180 (Possibly for another ones is ok to put this) + # We need to extend the implants to the same point as the well + # Now, this function is called before the extend_wells + # So we need to do the same as the extends well would do + if OPTS.tech_name == "rohm180": + mos_list = [(self.pmos, self.pmos_pos.snap_to_grid(), self.nmos, self.nmos_pos.snap_to_grid())] + for pmos,pmos_pos,nmos,nmos_pos in mos_list: + nwell_yoffset = round_to_grid(0.48 * self.height) + # PMOS phase + r1 = pmos.implant + pimp_width = r1.width + pimp_position = vector(r1.offset.x + pmos_pos.x, nwell_yoffset) + pimp_height = pmos_pos.y + r1.offset.y - nwell_yoffset + self.add_rect(layer="pimplant", + offset=pimp_position, + width=pimp_width, + height=pimp_height) + # NMOS phase + r1 = nmos.implant + nimp_width = r1.width + nimp_position = nmos_pos + r1.offset + vector(0, r1.height) + nimp_height = nwell_yoffset - nimp_position.y + self.add_rect(layer="nimplant", + offset=nimp_position, + width=nimp_width, + height=nimp_height) + def route_outputs(self): """ Route the output (drains) together. diff --git a/compiler/modules/pnand2.py b/compiler/modules/pnand2.py index 9262b2f8f..e148c004d 100644 --- a/compiler/modules/pnand2.py +++ b/compiler/modules/pnand2.py @@ -12,6 +12,8 @@ from base import logical_effort from sram_factory import factory from tech import cell_properties as cell_props +from globals import OPTS +from utils import round_to_grid class pnand2(pgate): @@ -150,6 +152,34 @@ def place_ptx(self): self.nmos2_pos = nmos1_pos + self.overlap_offset self.nmos2_inst.place(self.nmos2_pos) + # Special requirement for rohm180 (Possibly for another ones is ok to put this) + # We need to extend the implants to the same point as the well + # Now, this function is called before the extend_wells + # So we need to do the same as the extends well would do + if OPTS.tech_name == "rohm180": + mos_list = [(self.pmos_left, pmos1_pos.snap_to_grid(), self.nmos_left, nmos1_pos.snap_to_grid()), + (self.pmos_right, self.pmos2_pos.snap_to_grid(), self.nmos_right, self.nmos2_pos.snap_to_grid())] + for pmos,pmos_pos,nmos,nmos_pos in mos_list: + nwell_yoffset = round_to_grid(0.48 * self.height) + # PMOS phase + r1 = pmos.implant + pimp_width = r1.width + pimp_position = vector(r1.offset.x + pmos_pos.x, nwell_yoffset) + pimp_height = pmos_pos.y + r1.offset.y - nwell_yoffset + self.add_rect(layer="pimplant", + offset=pimp_position, + width=pimp_width, + height=pimp_height) + # NMOS phase + r1 = nmos.implant + nimp_width = r1.width + nimp_position = nmos_pos + r1.offset + vector(0, r1.height) + nimp_height = nwell_yoffset - nimp_position.y + self.add_rect(layer="nimplant", + offset=nimp_position, + width=nimp_width, + height=nimp_height) + def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies diff --git a/compiler/modules/pnand3.py b/compiler/modules/pnand3.py index 31a1b400c..f8f791186 100644 --- a/compiler/modules/pnand3.py +++ b/compiler/modules/pnand3.py @@ -12,6 +12,8 @@ from base import logical_effort from sram_factory import factory from tech import cell_properties as cell_props +from globals import OPTS +from utils import round_to_grid class pnand3(pgate): @@ -182,6 +184,35 @@ def place_ptx(self): self.nmos3_pos = nmos2_pos + self.ptx_offset self.nmos3_inst.place(self.nmos3_pos) + # Special requirement for rohm180 (Possibly for another ones is ok to put this) + # We need to extend the implants to the same point as the well + # Now, this function is called before the extend_wells + # So we need to do the same as the extends well would do + if OPTS.tech_name == "rohm180": + mos_list = [(self.pmos_left, pmos1_pos.snap_to_grid(), self.nmos_left, nmos1_pos.snap_to_grid()), + (self.pmos_center, pmos2_pos.snap_to_grid(), self.nmos_center, nmos2_pos.snap_to_grid()), + (self.pmos_right, self.pmos3_pos.snap_to_grid(), self.nmos_right, self.nmos3_pos.snap_to_grid())] + for pmos,pmos_pos,nmos,nmos_pos in mos_list: + nwell_yoffset = round_to_grid(0.48 * self.height) + # PMOS phase + r1 = pmos.implant + pimp_width = r1.width + pimp_position = vector(r1.offset.x + pmos_pos.x, nwell_yoffset) + pimp_height = pmos_pos.y + r1.offset.y - nwell_yoffset + self.add_rect(layer="pimplant", + offset=pimp_position, + width=pimp_width, + height=pimp_height) + # NMOS phase + r1 = nmos.implant + nimp_width = r1.width + nimp_position = nmos_pos + r1.offset + vector(0, r1.height) + nimp_height = nwell_yoffset - nimp_position.y + self.add_rect(layer="nimplant", + offset=nimp_position, + width=nimp_width, + height=nimp_height) + def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ @@ -219,6 +250,12 @@ def route_inputs(self): active_to_poly_contact, active_to_poly_contact2) + # TODO: There has to be a better way for this + if OPTS.tech_name in ["tsmc18", "lapis20"]: + self.inputA_yoffset += self.m1_pitch + elif OPTS.tech_name in ["rohm180"]: + self.inputA_yoffset += self.m1_width + apin = self.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.inputA_yoffset, diff --git a/compiler/modules/pnand4.py b/compiler/modules/pnand4.py index 906cb0e85..86dd9e140 100644 --- a/compiler/modules/pnand4.py +++ b/compiler/modules/pnand4.py @@ -12,6 +12,8 @@ from base import logical_effort from sram_factory import factory from tech import cell_properties as cell_props +from globals import OPTS +from utils import round_to_grid class pnand4(pgate): @@ -30,8 +32,8 @@ def __init__(self, name, size=1, height=None, add_wells=True): # We have trouble pitch matching a 3x sizes to the bitcell... # If we relax this, we could size this better. self.size = size - self.nmos_size = 2 * size - self.pmos_size = parameter["beta"] * size + self.nmos_size = size + self.pmos_size = parameter["beta"] * size / 2.0 self.nmos_width = self.nmos_size * drc("minwidth_tx") self.pmos_width = self.pmos_size * drc("minwidth_tx") @@ -196,6 +198,36 @@ def place_ptx(self): self.nmos4_pos = nmos3_pos + self.ptx_offset self.nmos4_inst.place(self.nmos4_pos) + # Special requirement for rohm180 (Possibly for another ones is ok to put this) + # We need to extend the implants to the same point as the well + # Now, this function is called before the extend_wells + # So we need to do the same as the extends well would do + if OPTS.tech_name == "rohm180": + mos_list = [(self.pmos_left, pmos1_pos.snap_to_grid(), self.nmos_left, nmos1_pos.snap_to_grid()), + (self.pmos_center, pmos2_pos.snap_to_grid(), self.nmos_center, nmos2_pos.snap_to_grid()), + (self.pmos_center, pmos3_pos.snap_to_grid(), self.nmos_center, nmos3_pos.snap_to_grid()), + (self.pmos_right, self.pmos4_pos.snap_to_grid(), self.nmos_right, self.nmos4_pos.snap_to_grid())] + for pmos,pmos_pos,nmos,nmos_pos in mos_list: + nwell_yoffset = round_to_grid(0.48 * self.height) + # PMOS phase + r1 = pmos.implant + pimp_width = r1.width + pimp_position = vector(r1.offset.x + pmos_pos.x, nwell_yoffset) + pimp_height = pmos_pos.y + r1.offset.y - nwell_yoffset + self.add_rect(layer="pimplant", + offset=pimp_position, + width=pimp_width, + height=pimp_height) + # NMOS phase + r1 = nmos.implant + nimp_width = r1.width + nimp_position = nmos_pos + r1.offset + vector(0, r1.height) + nimp_height = nwell_yoffset - nimp_position.y + self.add_rect(layer="nimplant", + offset=nimp_position, + width=nimp_width, + height=nimp_height) + def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ @@ -234,6 +266,10 @@ def route_inputs(self): active_to_poly_contact, active_to_poly_contact2) + # TODO: There has to be a better way for this + if OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + self.inputA_yoffset += self.m1_pitch + apin = self.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.inputA_yoffset, diff --git a/compiler/modules/pnor2.py b/compiler/modules/pnor2.py index 35df000fc..a59b8d25b 100644 --- a/compiler/modules/pnor2.py +++ b/compiler/modules/pnor2.py @@ -11,6 +11,8 @@ from base import vector from sram_factory import factory from tech import cell_properties as cell_props +from globals import OPTS +from utils import round_to_grid class pnor2(pgate): @@ -166,6 +168,34 @@ def place_ptx(self): self.nmos2_pos = nmos1_pos + self.overlap_offset self.nmos2_inst.place(self.nmos2_pos) + # Special requirement for rohm180 (Possibly for another ones is ok to put this) + # We need to extend the implants to the same point as the well + # Now, this function is called before the extend_wells + # So we need to do the same as the extends well would do + if OPTS.tech_name == "rohm180": + mos_list = [(self.pmos_left, pmos1_pos.snap_to_grid(), self.nmos_left, nmos1_pos.snap_to_grid()), + (self.pmos_right, self.pmos2_pos.snap_to_grid(), self.nmos_right, self.nmos2_pos.snap_to_grid())] + for pmos,pmos_pos,nmos,nmos_pos in mos_list: + nwell_yoffset = round_to_grid(0.48 * self.height) + # PMOS phase + r1 = pmos.implant + pimp_width = r1.width + pimp_position = vector(r1.offset.x + pmos_pos.x, nwell_yoffset) + pimp_height = pmos_pos.y + r1.offset.y - nwell_yoffset + self.add_rect(layer="pimplant", + offset=pimp_position, + width=pimp_width, + height=pimp_height) + # NMOS phase + r1 = nmos.implant + nimp_width = r1.width + nimp_position = nmos_pos + r1.offset + vector(0, r1.height) + nimp_height = nwell_yoffset - nimp_position.y + self.add_rect(layer="nimplant", + offset=nimp_position, + width=nimp_width, + height=nimp_height) + def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ diff --git a/compiler/modules/precharge.py b/compiler/modules/precharge.py index 4a1267e54..10002a82b 100644 --- a/compiler/modules/precharge.py +++ b/compiler/modules/precharge.py @@ -7,12 +7,15 @@ # from base import design import debug +import math +from tech import layer_indices, layer_stacks from .pgate import * from tech import parameter, drc from base import vector from globals import OPTS from sram_factory import factory from tech import cell_properties as cell_props +from utils import round_to_grid class precharge(design): @@ -142,8 +145,8 @@ def place_ptx(self): self.upper_pmos1_inst.place(self.upper_pmos1_pos) # Second pmos to the right of the first - upper_pmos2_pos = self.upper_pmos1_pos + overlap_offset - self.upper_pmos2_inst.place(upper_pmos2_pos) + self.upper_pmos2_pos = self.upper_pmos1_pos + overlap_offset + self.upper_pmos2_inst.place(self.upper_pmos2_pos) def connect_poly(self): """ @@ -217,6 +220,47 @@ def place_nwell_and_contact(self): width=self.width, height=self.height) + # TSMC18 gate port hack + if OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + # Body connection + min_area = drc["minarea_{}".format(self.active_stack[0])] + height = round_to_grid(self.well_contact.mod.first_layer_width) + width = round_to_grid(min_area / height) + width_impl = width + 2 * drc("implant_enclose_active") + height_impl = height + 2 * drc("implant_enclose_active") # contact.py:250 + width_well = width + 2 * self.well_contact.mod.well_enclose_active + height_well = height + 2 * self.well_contact.mod.well_enclose_active # contact.py:264 + self.add_rect_center(layer=self.active_stack[0], + offset=self.well_contact_pos, + width=width, + height=height) + self.add_rect_center(layer="nimplant", + offset=self.well_contact_pos, + width=width_impl, + height=height_impl) + self.add_rect_center(layer="nwell", + offset=self.well_contact_pos, + width=width_well, + height=height_well) + + # Span of the whole pimp + left = min(self.lower_pmos_position.x, + self.upper_pmos1_pos.x , + self.upper_pmos2_pos.x) - drc("implant_enclose_active") + right = max(self.lower_pmos_position.x + self.pmos.active_width, + self.upper_pmos1_pos.x + self.pmos.active_width, + self.upper_pmos2_pos.x + self.pmos.active_width) + drc("implant_enclose_active") + top = max(self.lower_pmos_position.y + self.pmos.active_height, + self.upper_pmos1_pos.y + self.pmos.active_height, + self.upper_pmos2_pos.y + self.pmos.active_height) + drc("implant_enclose_active") + bottom = min(self.lower_pmos_position.y, + self.upper_pmos1_pos.y, + self.upper_pmos2_pos.y) - drc("implant_enclose_active") + self.add_rect(layer="pimplant", + offset=vector(left, bottom), + width=right - left, + height=top - bottom) + def route_bitlines(self): """ Adds both bit-line and bit-line-bar to the module @@ -258,6 +302,32 @@ def connect_to_bitlines(self): self.connect_pmos(self.upper_pmos2_inst.get_pin("D"), self.br_xoffset) + # Helper function for adding minarea rectangles when via (only for tsmc18 for now) + def helper_areas(self, from_layer, to_layer, contact_offset): + cur_layer = from_layer + while cur_layer != to_layer: + from_id = layer_indices[cur_layer] + to_id = layer_indices[to_layer] + + if from_id < to_id: # grow the stack up + search_id = 0 + next_id = 2 + else: # grow the stack down + search_id = 2 + next_id = 0 + + #vprint("Putting {}".format(cur_layer)) + min_area = drc["minarea_{}".format(cur_layer)] + width = round_to_grid(math.sqrt(min_area)) + height = round_to_grid(min_area / width) + self.add_rect_center(layer=cur_layer, + offset=contact_offset, + width=width, + height=height) + + curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, layer_stacks), None) + cur_layer = curr_stack[next_id] + def add_bitline_contacts(self): """ Adds contacts/via from metal1 to metal2 for bit-lines @@ -269,6 +339,8 @@ def add_bitline_contacts(self): to_layer=self.bitline_layer, offset=lower_pin.center(), directions=("V", "V")) + if OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + self.helper_areas(lower_pin.layer, self.bitline_layer, lower_pin.center()) # BR for upper_pin in [self.upper_pmos1_inst.get_pin("S"), self.upper_pmos2_inst.get_pin("D")]: @@ -276,6 +348,8 @@ def add_bitline_contacts(self): to_layer=self.bitline_layer, offset=upper_pin.center(), directions=("V", "V")) + if OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + self.helper_areas(lower_pin.layer, self.bitline_layer, upper_pin.center()) def connect_pmos(self, pmos_pin, bit_xoffset): """ diff --git a/compiler/modules/ptx.py b/compiler/modules/ptx.py index d1bd13c83..6bb52409f 100644 --- a/compiler/modules/ptx.py +++ b/compiler/modules/ptx.py @@ -148,6 +148,12 @@ def create_netlist(self): self.spice_device = main_str + area_str self.spice.append("\n* spice ptx " + self.spice_device) + # For LVS devices, the name may change + try: + from tech import lvs_spice + except: + lvs_spice = spice + if cell_props.ptx.model_is_subckt and OPTS.lvs_exe and OPTS.lvs_exe[0] == "calibre": # sky130 requires mult parameter too. It is not the same as m, but I don't understand it. # self.lvs_device = "X{{0}} {{1}} {0} m={1} w={2} l={3} mult=1".format(spice[self.tx_type], @@ -166,7 +172,7 @@ def create_netlist(self): self.tx_width, drc("minwidth_poly")) else: - self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type], + self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(lvs_spice[self.tx_type], self.mults, self.tx_width, drc("minwidth_poly")) @@ -368,11 +374,12 @@ def add_active(self): # If the implant must enclose the active, shift offset # and increase width/height enclose_width = self.implant_enclose_active - enclose_offset = [enclose_width] * 2 + enclose_height = max(self.implant_enclose_active, drc("implant_to_channel")) + enclose_offset = vector(enclose_width, enclose_height) self.implant = self.add_rect(layer="{}implant".format(self.implant_type), offset=self.active_offset - enclose_offset, width=self.active_width + 2 * enclose_width, - height=self.active_height + 2 * enclose_width) + height=self.active_height + 2 * enclose_height) def add_well_implant(self): """ diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index a26ea7e84..18ee4e77a 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -346,6 +346,7 @@ def route_layout(self): # because the perimeter pins will change the bbox # Route the pins to the perimeter if OPTS.perimeter_pins: + # We now route the escape routes far enough out so that they will # reach past the power ring or stripes on the sides bbox = self.get_bbox(side="ring", diff --git a/compiler/modules/sram_base.py b/compiler/modules/sram_base.py index 722a649d1..cbaf5133c 100644 --- a/compiler/modules/sram_base.py +++ b/compiler/modules/sram_base.py @@ -41,6 +41,8 @@ def __init__(self, name, sram_config): if not self.num_spare_cols: self.num_spare_cols = 0 + # For assigning only once the bbox + self.bbox = None def add_pins(self): """ Add pins for entire SRAM. """ @@ -369,6 +371,7 @@ def route_escape_pins(self, bbox): design=self, bbox=bbox) rtr.escape_route(pins_to_route) + self.bbox = (rtr.ll, rtr.ur) # Capture the bbox after done with the escape routes, as can increase def compute_bus_sizes(self): """ Compute the independent bus widths shared between two and four bank SRAMs """ diff --git a/compiler/opennvram.py b/compiler/opennvram.py new file mode 100644 index 000000000..83ec5e49c --- /dev/null +++ b/compiler/opennvram.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2021 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +""" +NVRAM Compiler + +Based on the SRAM compiler + +The output files append the given suffixes to the output name: +a spice (.sp) file for circuit simulation +a GDS2 (.gds) file containing the layout +a LEF (.lef) file for preliminary P&R (real one should be from layout) +""" + +import sys +import datetime +import globals as g + +(OPTS, args) = g.parse_args() + +# Check that we are left with a single configuration file as argument. +if len(args) != 1: + print(g.USAGE) + sys.exit(2) + + + diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py index 0819d62af..c196dd2ed 100644 --- a/compiler/router/pin_group.py +++ b/compiler/router/pin_group.py @@ -465,6 +465,12 @@ def enclose_pin(self): # Compute the enclosure pin_layout list of the set of tracks self.enclosures = self.compute_enclosures() + # for pin in self.pins: + # lx = pin.lx() + # ly = pin.by() + # if lx > 61.1 and lx < 62.0 and ly > 56.2 and ly < 56.7: + # breakpoint() + # Find a connector to every pin and add it to the enclosures for pin in self.pins: @@ -546,7 +552,11 @@ def transitive_overlap(self, shape, shape_list): connected_set.add(cur_shape) # Remove the original shape - connected_set.remove(shape) + # NOTE: Normally the shape_list has only 1 shape, and "shape" is the same + # That means we do not need to remove the original shape + # Is highly improbable, but has happened in tsmc18 and lapis20 + if shape in connected_set and len(connected_set) > 1: + connected_set.remove(shape) # if len(connected_set) 87.9 and lx < 87.99 and ly > 18.56 and ly < 18.6: + # if lx > 61.1 and lx < 62.0 and ly > 56.2 and ly < 56.7: # breakpoint() for pin in self.pins: debug.info(4, " Converting {0}".format(pin)) diff --git a/compiler/router/router.py b/compiler/router/router.py index 756047e9b..882ce58f7 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -34,7 +34,7 @@ def __init__(self, layers, design, bbox=None, margin=0, route_track_width=1): route on top of this. The blockages from the gds/module will be considered. """ - + router_tech.__init__(self, layers, route_track_width) self.cell = design @@ -352,12 +352,16 @@ def remove_adjacent_grid(self, pg1, pg2, adj_grids): bigger.grids.remove(adj) bigger.secondary_grids.remove(adj) self.blocked_grids.add(adj) + if adj in bigger.blockages: + bigger.blockages.remove(adj) elif adj in smaller.secondary_grids: debug.info(3,"Removing {} from smaller secondary {}".format(adj, smaller)) smaller.grids.remove(adj) smaller.secondary_grids.remove(adj) self.blocked_grids.add(adj) + if adj in smaller.blockages: + smaller.blockages.remove(adj) else: # If we couldn't remove from a secondary grid, # we must remove from the primary @@ -366,10 +370,14 @@ def remove_adjacent_grid(self, pg1, pg2, adj_grids): debug.info(3,"Removing {} from bigger primary {}".format(adj, bigger)) bigger.grids.remove(adj) + if adj in bigger.blockages: + bigger.blockages.remove(adj) elif adj in smaller.grids: debug.info(3,"Removing {} from smaller primary {}".format(adj, smaller)) smaller.grids.remove(adj) + if adj in smaller.blockages: + smaller.blockages.remove(adj) def set_supply_rail_blocked(self, value): # This is just a virtual function @@ -384,7 +392,7 @@ def prepare_blockages(self, src=None, dest=None): # 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) @@ -457,7 +465,7 @@ def clear_blockages(self, pin_name): """ 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. @@ -518,7 +526,7 @@ def retrieve_blockages(self, lpp): new_shape = 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_shape not in self.all_pins and not self.pin_contains(new_shape): @@ -529,7 +537,7 @@ def pin_contains(self, shape): if pin.contains(shape): return True return False - + def convert_point_to_units(self, p): """ Convert a path set of tracks to center line path. @@ -543,7 +551,7 @@ def convert_wave_to_units(self, wave): Convert a wave to a set of center points """ return [self.convert_point_to_units(i) for i in wave] - + def convert_shape_to_tracks(self, shape): """ Convert a rectangular shape into track units. @@ -719,12 +727,13 @@ def convert_pin_coord_to_tracks(self, pin, coord): pin.inflate(0.5 * self.track_space), pin.layer) - overlap_length = pin.overlap_length(track_pin) + inter=lambda a,b: math.inf if a[0].x < b[1].x and a[1].x > b[0].x and a[0].y < b[1].y and a[1].y > b[0].y else 0 + overlap_length = inter(pin.rect, track_pin.rect) #pin.overlap_length(track_pin) debug.info(4,"Check overlap: {0} {1} . {2} = {3}".format(coord, pin.rect, track_pin, overlap_length)) - inflated_overlap_length = inflated_pin.overlap_length(track_pin) + inflated_overlap_length = inter(inflated_pin.rect, track_pin.rect) #inflated_pin.overlap_length(track_pin) debug.info(4,"Check overlap: {0} {1} . {2} = {3}".format(coord, inflated_pin.rect, track_pin, @@ -1404,7 +1413,7 @@ def annotate_grid(self, g): self.cell.add_label(text="{0},{1}".format(g[0], g[1]), layer="text", offset=shape[0]) - + def del_router_info(self): """ Erase all of the comments on the current level. @@ -1489,7 +1498,7 @@ def get_perimeter_pin(self): # Else if we came from a different layer, we can only add # a signle grid return self.convert_track_to_pin(v) - + return None def get_ll_pin(self, pin_name): @@ -1503,7 +1512,7 @@ def get_ll_pin(self, pin_name): else: if pin.lx() <= keep_pin.lx() and pin.by() <= keep_pin.by(): keep_pin = pin - + return keep_pin def check_all_routed(self, pin_name): @@ -1513,8 +1522,8 @@ def check_all_routed(self, pin_name): for pg in self.pin_groups[pin_name]: if not pg.is_routed(): return False - - + + # FIXME: This should be replaced with vector.snap_to_grid at some point def snap_to_grid(offset): """ diff --git a/compiler/router/supply_grid_router.py b/compiler/router/supply_grid_router.py index e592e02d7..96e824ae2 100644 --- a/compiler/router/supply_grid_router.py +++ b/compiler/router/supply_grid_router.py @@ -21,7 +21,7 @@ class supply_grid_router(router): routes a grid to connect the supply on the two layers. """ - def __init__(self, layers, design, bbox=None, pin_type=None): + def __init__(self, layers, design, bbox=None, pin_type=None, margin=0): """ 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). @@ -31,7 +31,16 @@ def __init__(self, layers, design, bbox=None, pin_type=None): # Power rail width in minimum wire widths self.route_track_width = 1 - router.__init__(self, layers, design, bbox=bbox, margin=margin, route_track_width=self.route_track_width) + # The pin escape router already made the bounding box big enough, + # so we can use the regular bbox here. + if pin_type: + debug.check(pin_type in ["left", "right", "top", "bottom", "single", "ring", "multiple"], + "Invalid pin type {}".format(pin_type)) + self.pin_type = pin_type + if not bbox: + router.__init__(self, layers, design, bbox=bbox, margin=margin, route_track_width=self.route_track_width) + else: + router.__init__(self, layers, design, bbox=bbox, margin=margin) # The bbox should be already aligned. # The list of supply rails (grid sets) that may be routed self.supply_rails = {} @@ -61,19 +70,28 @@ def route(self, vdd_name="vdd", gnd_name="gnd"): 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 side pins if enabled + if self.pin_type in ["left", "right", "top", "bottom"]: + self.add_side_supply_pin(self.vdd_name, side=self.pin_type) + self.add_side_supply_pin(self.gnd_name, side=self.pin_type) + elif self.pin_type == "ring": + self.add_ring_supply_pin(self.vdd_name) + self.add_ring_supply_pin(self.gnd_name) + # Add the supply rails in a mesh network and connect H/V with vias start_time = datetime.now() # Block everything 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.clear_blockages(self.vdd_name) + 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) @@ -83,11 +101,16 @@ def route(self, vdd_name="vdd", gnd_name="gnd"): self.route_simple_overlaps(gnd_name) print_time("Simple overlap routing", datetime.now(), start_time, 3) + start_time = datetime.now() + self.route_1step(vdd_name) + self.route_1step(gnd_name) + print_time("1 step routing", datetime.now(), start_time, 3) + # Route the supply pins to the supply rails # Route vdd first since we want it to be shorter start_time = datetime.now() - self.route_pins_to_rails(vdd_name) - self.route_pins_to_rails(gnd_name) + #self.route_pins_to_rails(vdd_name) + #self.route_pins_to_rails(gnd_name) print_time("Maze routing supplies", datetime.now(), start_time, 3) # self.write_debug_gds("final.gds", False) @@ -126,6 +149,46 @@ def route_simple_overlaps(self, pin_name): debug.info(1, "Routed {} simple overlap pins".format(routed_count)) + def route_1step(self, pin_name): + """ + This will check 1-step easy routes. This is mainly created for faster routes and avoid the full-fledged + router. + """ + debug.info(1, "Routing 1 step pins for {0}".format(pin_name)) + + dirs = [vector3d(1, 0, 0), vector3d(-1, 0, 0), vector3d(0, 1, 0), vector3d(0, -1, 0), vector3d(0, 0, 1), vector3d(0, 0, -1)] + # These are the wire tracks + wire_tracks = self.supply_rail_tracks[pin_name] + routed_count=0 + for i, pg in enumerate(self.pin_groups[pin_name]): + if pg.is_routed(): + continue + + # Explore all grids + for grid in pg.grids: + # Explore all directions + for dir in dirs: + pt = grid + dir + debug.info(3, " trying pt {0}".format(pt)) + if pt in wire_tracks and pt not in self.blocked_grids: + # Contruct the path + path = [grid, pt] + abs_path = [self.convert_point_to_units(x) for x in path] + debug.info(3, " Adding easy route {0} {1}->{2}, {3}".format(pin_name, grid, pt, abs_path)) + routed_count += 1 + pg.set_routed() + self.cell.add_route(layers=self.layers, + coordinates=abs_path, + layer_widths=self.layer_widths) + # Add just the grid here in the pingroup + self.pin_groups[pin_name][i].grids.add(pt) + break + else: + continue + break + + debug.info(1, "Routed {} simple overlap pins".format(routed_count)) + def finalize_supply_rails(self, name): """ Determine which supply rails overlap and can accomodate a via. @@ -196,17 +259,39 @@ def add_supply_rails(self, name): This is after the paths have been pruned and only include rails that are connected with vias. """ + + max_yoffset = self.rg.ur.y + max_xoffset = self.rg.ur.x + min_yoffset = self.rg.ll.y + min_xoffset = self.rg.ll.x + + # Remove the current pins in the layout. Only leave the new ones + self.cell.remove_layout_pin(name) + for rail in self.supply_rails[name]: ll = grid_utils.get_lower_left(rail) ur = grid_utils.get_upper_right(rail) z = ll.z pin = self.compute_pin_enclosure(ll, ur, z, name) - debug.info(3, "Adding supply rail {0} {1}->{2} {3}".format(name, ll, ur, pin)) - self.cell.add_layout_pin(text=name, - layer=pin.layer, - offset=pin.ll(), - width=pin.width(), - height=pin.height()) + # Determinate if this rail is actually part of the very corner. This is to avoid + # large intersections, but only keeping the short ones + # TODO: The way to know that is horizontal or vertical, is just looking at z + avoid = (z == 0 and (ll.y <= min_yoffset or ur.y >= max_yoffset)) or \ + (z == 1 and (ll.x <= min_xoffset or ur.x >= max_xoffset)) + # Add the ones only in the perimeter + if (ll.x <= min_xoffset or ll.y <= min_yoffset or ur.x >= max_xoffset or ur.y >= max_yoffset) and not avoid: + debug.info(3, "Adding supply pin rail {0} {1}->{2} {3}".format(name, ll, ur, pin)) + self.cell.add_layout_pin(text=name, + layer=pin.layer, + offset=pin.ll(), + width=pin.width(), + height=pin.height()) + else: + debug.info(3, "Adding supply norm rail {0} {1}->{2} {3}".format(name, ll, ur, pin)) + self.cell.add_rect(layer=pin.layer, + offset=pin.ll(), + width=pin.width(), + height=pin.height()) def compute_supply_rails(self, name, supply_number): """ @@ -228,7 +313,7 @@ def compute_supply_rails(self, name, supply_number): # Seed the function at the location with the given width wave = [vector3d(min_xoffset, offset, 0)] # While we can keep expanding east in this horizontal track - while wave and wave[0].x < max_xoffset: + while wave and wave[0].x <= max_xoffset: added_rail = self.find_supply_rail(name, wave, direction.EAST) if not added_rail: # Just seed with the next one @@ -243,7 +328,7 @@ def compute_supply_rails(self, name, supply_number): # Seed the function at the location with the given width wave = [vector3d(offset, min_yoffset, 1)] # While we can keep expanding north in this vertical track - while wave and wave[0].y < max_yoffset: + while wave and wave[0].y <= max_yoffset: added_rail = self.find_supply_rail(name, wave, direction.NORTH) if not added_rail: # Just seed with the next one @@ -286,15 +371,19 @@ def probe_supply_rail(self, name, start_wave, direct): if not wave_path: return None + # NOTE: Why? The trimmings are kinda useless for escape routing + # The escape routing is already done at this point + # this will be ignored for now. + # drop the first and last steps to leave escape routing room # around the blockage that stopped the probe # except, don't drop the first if it is the first in a row/column - if (direct==direction.NORTH and start_wave[0].y>0): - wave_path.trim_first() - elif (direct == direction.EAST and start_wave[0].x>0): - wave_path.trim_first() + #if (direct==direction.NORTH and start_wave[0].y>0): + # wave_path.trim_first() + #elif (direct == direction.EAST and start_wave[0].x>0): + # wave_path.trim_first() - wave_path.trim_last() + #wave_path.trim_last() return wave_path @@ -359,7 +448,6 @@ def route_pins_to_rails(self, pin_name): # easier to debug. self.prepare_blockages() self.clear_blockages(self.vdd_name) - # Add the single component of the pin as the source # which unmarks it as a blockage too self.add_pin_component_source(pin_name, index) @@ -370,6 +458,7 @@ def route_pins_to_rails(self, pin_name): # Actually run the A* router if not self.run_router(detour_scale=5): + debug.warning("Component not routed. Location: {0}. Writting a debug gds.".format(str(pg.grids))) self.write_debug_gds("debug_route.gds") # if index==3 and pin_name=="vdd": diff --git a/compiler/tests/21_model_delay_test.py b/compiler/tests/21_model_delay_test.py index ab6f38885..104a25b37 100755 --- a/compiler/tests/21_model_delay_test.py +++ b/compiler/tests/21_model_delay_test.py @@ -89,6 +89,8 @@ def runTest(self): error_tolerance = 0.30 elif OPTS.tech_name == "scn4m_subm": error_tolerance = 0.30 + elif OPTS.tech_name in ["tsmc18", "lapis20", "rohm180"]: + error_tolerance = 0.25 else: self.assertTrue(False) # other techs fail