diff --git a/compiler/base/contact.py b/compiler/base/contact.py index 9e947082..1ffdc7bf 100644 --- a/compiler/base/contact.py +++ b/compiler/base/contact.py @@ -7,8 +7,11 @@ # import hierarchy_design import debug -from tech import drc, layer +from tech import drc +import tech from vector import vector +from sram_factory import factory +import sys class contact(hierarchy_design.hierarchy_design): @@ -26,29 +29,36 @@ class contact(hierarchy_design.hierarchy_design): """ - def __init__(self, layer_stack, dimensions=(1, 1), directions=("V", "V"), + def __init__(self, layer_stack, dimensions=(1, 1), directions=None, implant_type=None, well_type=None, name=""): # This will ignore the name parameter since # we can guarantee a unique name here hierarchy_design.hierarchy_design.__init__(self, name) debug.info(4, "create contact object {0}".format(name)) + self.add_comment("layers: {0}".format(layer_stack)) self.add_comment("dimensions: {0}".format(dimensions)) if implant_type or well_type: self.add_comment("implant type: {}\n".format(implant_type)) self.add_comment("well_type: {}\n".format(well_type)) + + self.is_well_contact = implant_type == well_type self.layer_stack = layer_stack self.dimensions = dimensions - self.directions = directions + if directions: + self.directions = directions + else: + self.directions = (tech.preferred_directions[layer_stack[0]], + tech.preferred_directions[layer_stack[2]]) self.offset = vector(0, 0) self.implant_type = implant_type self.well_type = well_type # Module does not have pins, but has empty pin list. self.pins = [] self.create_layout() - + def create_layout(self): self.setup_layers() @@ -56,15 +66,19 @@ class contact(hierarchy_design.hierarchy_design): self.create_contact_array() self.create_first_layer_enclosure() self.create_second_layer_enclosure() + self.create_nitride_cut_enclosure() - self.height = max(obj.offset.y + obj.height for obj in self.objs) - self.width = max(obj.offset.x + obj.width for obj in self.objs) + self.height = max(self.first_layer_position.y + self.first_layer_height, + self.second_layer_position.y + self.second_layer_height) + self.width = max(self.first_layer_position.x + self.first_layer_width, + self.second_layer_position.x + self.second_layer_width) # Do not include the select layer in the height/width if self.implant_type and self.well_type: self.create_implant_well_enclosures() elif self.implant_type or self.well_type: - debug.error(-1, "Must define both implant and well type or none at all.") + debug.error(-1, + "Must define both implant and well type or none.") def setup_layers(self): """ Locally assign the layer names. """ @@ -72,14 +86,19 @@ class contact(hierarchy_design.hierarchy_design): (first_layer, via_layer, second_layer) = self.layer_stack self.first_layer_name = first_layer self.second_layer_name = second_layer + # Contacts will have unique per first layer - if via_layer == "contact": + if via_layer in tech.layer: + self.via_layer_name = via_layer + elif via_layer == "contact": if first_layer in ("active", "poly"): self.via_layer_name = first_layer + "_" + via_layer + elif second_layer in ("active", "poly"): + self.via_layer_name = second_layer + "_" + via_layer else: - self.via_layer_name = second_layer + "_" + via_layer + debug.error("Invalid via layer {}".format(via_layer), -1) else: - self.via_layer_name = via_layer + debug.error("Invalid via layer {}".format(via_layer), -1) def setup_layout_constants(self): """ Determine the design rules for the enclosure layers """ @@ -95,42 +114,48 @@ class contact(hierarchy_design.hierarchy_design): # The extend rule applies to asymmetric enclosures in one direction. # The enclosure rule applies to symmetric enclosure component. - first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name)) - first_layer_enclosure = drc("{0}_enclosure_{1}".format(self.first_layer_name, self.via_layer_name)) - first_layer_extend = drc("{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name)) + self.first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name)) + self.first_layer_enclosure = drc("{0}_enclose_{1}".format(self.first_layer_name, self.via_layer_name)) + # If there's a different rule for active + # FIXME: Make this more elegant + if self.is_well_contact and self.first_layer_name == "active" and "tap_extend_contact" in drc.keys(): + self.first_layer_extend = drc("tap_extend_contact") + else: + self.first_layer_extend = drc("{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name)) - second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name)) - second_layer_enclosure = drc("{0}_enclosure_{1}".format(self.second_layer_name, self.via_layer_name)) - second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name)) + self.second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name)) + self.second_layer_enclosure = drc("{0}_enclose_{1}".format(self.second_layer_name, self.via_layer_name)) + self.second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name)) # In some technologies, the minimum width may be larger # than the overlap requirement around the via, so # check this for each dimension. if self.directions[0] == "V": - self.first_layer_horizontal_enclosure = max(first_layer_enclosure, - (first_layer_minwidth - self.contact_array_width) / 2) - self.first_layer_vertical_enclosure = max(first_layer_extend, - (first_layer_minwidth - self.contact_array_height) / 2) + self.first_layer_horizontal_enclosure = max(self.first_layer_enclosure, + (self.first_layer_minwidth - self.contact_array_width) / 2) + self.first_layer_vertical_enclosure = max(self.first_layer_extend, + (self.first_layer_minwidth - self.contact_array_height) / 2) elif self.directions[0] == "H": - self.first_layer_horizontal_enclosure = max(first_layer_extend, - (first_layer_minwidth - self.contact_array_width) / 2) - self.first_layer_vertical_enclosure = max(first_layer_enclosure, - (first_layer_minwidth - self.contact_array_height) / 2) + self.first_layer_horizontal_enclosure = max(self.first_layer_extend, + (self.first_layer_minwidth - self.contact_array_width) / 2) + self.first_layer_vertical_enclosure = max(self.first_layer_enclosure, + (self.first_layer_minwidth - self.contact_array_height) / 2) else: debug.error("Invalid first layer direction.", -1) - # In some technologies, the minimum width may be larger than the overlap requirement around the via, so + # In some technologies, the minimum width may be larger + # than the overlap requirement around the via, so # check this for each dimension. if self.directions[1] == "V": - self.second_layer_horizontal_enclosure = max(second_layer_enclosure, - (second_layer_minwidth - self.contact_array_width) / 2) - self.second_layer_vertical_enclosure = max(second_layer_extend, - (second_layer_minwidth - self.contact_array_height) / 2) + self.second_layer_horizontal_enclosure = max(self.second_layer_enclosure, + (self.second_layer_minwidth - self.contact_array_width) / 2) + self.second_layer_vertical_enclosure = max(self.second_layer_extend, + (self.second_layer_minwidth - self.contact_array_height) / 2) elif self.directions[1] == "H": - self.second_layer_horizontal_enclosure = max(second_layer_extend, - (second_layer_minwidth - self.contact_array_height) / 2) - self.second_layer_vertical_enclosure = max(second_layer_enclosure, - (second_layer_minwidth - self.contact_array_width) / 2) + self.second_layer_horizontal_enclosure = max(self.second_layer_extend, + (self.second_layer_minwidth - self.contact_array_height) / 2) + self.second_layer_vertical_enclosure = max(self.second_layer_enclosure, + (self.second_layer_minwidth - self.contact_array_width) / 2) else: debug.error("Invalid second layer direction.", -1) @@ -138,11 +163,14 @@ class contact(hierarchy_design.hierarchy_design): """ Create the contact array at the origin""" # offset for the via array self.via_layer_position = vector( - max(self.first_layer_horizontal_enclosure, self.second_layer_horizontal_enclosure), - max(self.first_layer_vertical_enclosure, self.second_layer_vertical_enclosure)) + max(self.first_layer_horizontal_enclosure, + self.second_layer_horizontal_enclosure), + max(self.first_layer_vertical_enclosure, + self.second_layer_vertical_enclosure)) for i in range(self.dimensions[1]): - offset = self.via_layer_position + vector(0, self.contact_pitch * i) + offset = self.via_layer_position + vector(0, + self.contact_pitch * i) for j in range(self.dimensions[0]): self.add_rect(layer=self.via_layer_name, offset=offset, @@ -150,14 +178,34 @@ class contact(hierarchy_design.hierarchy_design): height=self.contact_width) offset = offset + vector(self.contact_pitch, 0) + def create_nitride_cut_enclosure(self): + """ Special layer that encloses poly contacts in some processes """ + # Check if there is a special poly nitride cut layer + if "npc" not in tech.layer: + return + + # Only add for poly layers + if self.first_layer_name == "poly": + self.add_rect(layer="npc", + offset=self.first_layer_position, + width=self.first_layer_width, + height=self.first_layer_height) + elif self.second_layer_name == "poly": + self.add_rect(layer="npc", + offset=self.second_layer_position, + width=self.second_layer_width, + height=self.second_layer_height) + def create_first_layer_enclosure(self): # this is if the first and second layers are different self.first_layer_position = vector( max(self.second_layer_horizontal_enclosure - self.first_layer_horizontal_enclosure, 0), max(self.second_layer_vertical_enclosure - self.first_layer_vertical_enclosure, 0)) - self.first_layer_width = self.contact_array_width + 2 * self.first_layer_horizontal_enclosure - self.first_layer_height = self.contact_array_height + 2 * self.first_layer_vertical_enclosure + self.first_layer_width = max(self.contact_array_width + 2 * self.first_layer_horizontal_enclosure, + self.first_layer_minwidth) + self.first_layer_height = max(self.contact_array_height + 2 * self.first_layer_vertical_enclosure, + self.first_layer_minwidth) self.add_rect(layer=self.first_layer_name, offset=self.first_layer_position, width=self.first_layer_width, @@ -169,57 +217,72 @@ class contact(hierarchy_design.hierarchy_design): max(self.first_layer_horizontal_enclosure - self.second_layer_horizontal_enclosure, 0), max(self.first_layer_vertical_enclosure - self.second_layer_vertical_enclosure, 0)) - self.second_layer_width = self.contact_array_width + 2 * self.second_layer_horizontal_enclosure - self.second_layer_height = self.contact_array_height + 2 * self.second_layer_vertical_enclosure + self.second_layer_width = max(self.contact_array_width + 2 * self.second_layer_horizontal_enclosure, + self.second_layer_minwidth) + self.second_layer_height = max(self.contact_array_height + 2 * self.second_layer_vertical_enclosure, + self.second_layer_minwidth) self.add_rect(layer=self.second_layer_name, offset=self.second_layer_position, width=self.second_layer_width, height=self.second_layer_height) def create_implant_well_enclosures(self): - implant_position = self.first_layer_position - [drc("implant_enclosure_active")] * 2 - implant_width = self.first_layer_width + 2 * drc("implant_enclosure_active") - implant_height = self.first_layer_height + 2 * drc("implant_enclosure_active") + implant_position = self.first_layer_position - [drc("implant_enclose_active")] * 2 + implant_width = self.first_layer_width + 2 * drc("implant_enclose_active") + implant_height = self.first_layer_height + 2 * drc("implant_enclose_active") self.add_rect(layer="{}implant".format(self.implant_type), offset=implant_position, width=implant_width, height=implant_height) - well_position = self.first_layer_position - [drc("well_enclosure_active")] * 2 - well_width = self.first_layer_width + 2 * drc("well_enclosure_active") - well_height = self.first_layer_height + 2 * drc("well_enclosure_active") - self.add_rect(layer="{}well".format(self.well_type), - offset=well_position, - width=well_width, - height=well_height) + + # Optionally implant well if layer exists + well_layer = "{}well".format(self.well_type) + if well_layer in tech.layer: + well_width_rule = drc("minwidth_" + well_layer) + self.well_enclose_active = drc(well_layer + "_enclose_active") + self.well_width = max(self.first_layer_width + 2 * self.well_enclose_active, + well_width_rule) + self.well_height = max(self.first_layer_height + 2 * self.well_enclose_active, + well_width_rule) + center_pos = vector(0.5*self.width, 0.5*self.height) + well_position = center_pos - vector(0.5*self.well_width, 0.5*self.well_height) + self.add_rect(layer=well_layer, + offset=well_position, + width=self.well_width, + height=self.well_height) def analytical_power(self, corner, load): """ Get total power of a module """ return self.return_power() -from sram_factory import factory +# Set up a static for each layer to be used for measurements +for layer_stack in tech.layer_stacks: + (layer1, via, layer2) = layer_stack + cont = factory.create(module_type="contact", + layer_stack=layer_stack) + module = sys.modules[__name__] + # Also create a contact that is just the first layer + if layer1 == "poly" or layer1 == "active": + setattr(module, layer1 + "_contact", cont) + else: + setattr(module, layer1 + "_via", cont) + +# Set up a static for each well contact for measurements +if "nwell" in tech.layer: + cont = factory.create(module_type="contact", + layer_stack=tech.active_stack, + implant_type="n", + well_type="n") + module = sys.modules[__name__] + setattr(module, "nwell_contact", cont) + +if "pwell" in tech.layer: + cont = factory.create(module_type="contact", + layer_stack=tech.active_stack, + implant_type="p", + well_type="p") + module = sys.modules[__name__] + setattr(module, "pwell_contact", cont) -# This is not instantiated and used for calculations only. -# These are static 1x1 contacts to reuse in all the design modules. -well = factory.create(module_type="contact", - layer_stack=("active", "contact", "metal1"), - directions=("H", "V")) -active = factory.create(module_type="contact", - layer_stack=("active", "contact", "metal1"), - directions=("H", "V")) -poly = factory.create(module_type="contact", - layer_stack=("poly", "contact", "metal1"), - directions=("V", "H")) -m1m2 = factory.create(module_type="contact", - layer_stack=("metal1", "via1", "metal2"), - directions=("H", "V")) -m2m3 = factory.create(module_type="contact", - layer_stack=("metal2", "via2", "metal3"), - directions=("V", "H")) -if "metal4" in layer.keys(): - m3m4 = factory.create(module_type="contact", - layer_stack=("metal3", "via3", "metal4"), - directions=("H", "V")) -else: - m3m4 = None diff --git a/compiler/base/custom_cell_properties.py b/compiler/base/custom_cell_properties.py new file mode 100644 index 00000000..45887561 --- /dev/null +++ b/compiler/base/custom_cell_properties.py @@ -0,0 +1,134 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2020 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. +# + +class _pins: + def __init__(self, pin_dict): + # make the pins elements of the class to allow "." access. + # For example: props.bitcell.cell_6t.pin.bl = "foobar" + for k,v in pin_dict.items(): + self.__dict__[k] = v + +class _cell: + def __init__(self, pin_dict): + pin_dict.update(self._default_power_pins()) + self._pins = _pins(pin_dict) + + @property + def pin(self): + return self._pins + + def _default_power_pins(self): + return { 'vdd' : 'vdd', 'gnd' : 'gnd' } + +class _mirror_axis: + def __init__(self, x, y): + self.x = x + self.y = y + +class _bitcell: + def __init__(self, mirror, split_wl, cell_6t, cell_1rw1r, cell_1w1r): + self.mirror = mirror + self.split_wl = split_wl + self._6t = cell_6t + self._1rw1r = cell_1rw1r + self._1w1r = cell_1w1r + + def _default(): + axis = _mirror_axis(True, False) + cell_6t = _cell({'bl' : 'bl', + 'br' : 'br', + 'wl' : 'wl'}) + + cell_1rw1r = _cell({'bl0' : 'bl0', + 'br0' : 'br0', + 'bl1' : 'bl1', + 'br1' : 'br1', + 'wl0' : 'wl0', + 'wl1' : 'wl1'}) + cell_1w1r = _cell({'bl0' : 'bl0', + 'br0' : 'br0', + 'bl1' : 'bl1', + 'br1' : 'br1', + 'wl0' : 'wl0', + 'wl1' : 'wl1'}) + return _bitcell(cell_6t=cell_6t, + cell_1rw1r=cell_1rw1r, + cell_1w1r=cell_1w1r, + split_wl = False, + mirror=axis) + + @property + def cell_6t(self): + return self._6t + + @property + def cell_1rw1r(self): + return self._1rw1r + + @property + def cell_1w1r(self): + return self._1w1r + + +class _dff: + def __init__(self, use_custom_ports, custom_port_list, custom_type_list, clk_pin): + self.use_custom_ports = use_custom_ports + self.custom_port_list = custom_port_list + self.custom_type_list = custom_type_list + self.clk_pin = clk_pin + +class _dff_buff: + def __init__(self, use_custom_ports, custom_buff_ports, add_body_contacts): + self.use_custom_ports = use_custom_ports + self.buf_ports = custom_buff_ports + self.add_body_contacts = add_body_contacts + +class _dff_buff_array: + def __init__(self, use_custom_ports, add_body_contacts): + self.use_custom_ports = use_custom_ports + self.add_body_contacts = add_body_contacts + +class cell_properties(): + """ + This contains meta information about the custom designed cells. For + instance, pin names, or the axis on which they need to be mirrored. These + can be overriden in the tech.py file. + """ + def __init__(self): + self.names = {} + + self._bitcell = _bitcell._default() + + self._dff = _dff(use_custom_ports = False, + custom_port_list = ["D", "Q", "clk", "vdd", "gnd"], + custom_type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"], + clk_pin= "clk") + + self._dff_buff = _dff_buff(use_custom_ports = False, + custom_buff_ports = ["D", "qint", "clk", "vdd", "gnd"], + add_body_contacts = False) + + self._dff_buff_array = _dff_buff_array(use_custom_ports = False, + add_body_contacts = False) + + @property + def bitcell(self): + return self._bitcell + + @property + def dff(self): + return self._dff + + @property + def dff_buff(self): + return self._dff_buff + + @property + def dff_buff_array(self): + return self._dff_buff_array + diff --git a/compiler/base/design.py b/compiler/base/design.py index c6385243..c2ef8340 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -8,12 +8,13 @@ from hierarchy_design import hierarchy_design import contact from globals import OPTS +import re class design(hierarchy_design): """ This is the same as the hierarchy_design class except it contains - some DRC constants and analytical models for other modules to reuse. + some DRC/layer constants and analytical models for other modules to reuse. """ @@ -21,42 +22,130 @@ class design(hierarchy_design): hierarchy_design.__init__(self, name) self.setup_drc_constants() + self.setup_layer_constants() self.setup_multiport_constants() - from tech import layer - self.m1_pitch = max(contact.m1m2.width, contact.m1m2.height) + max(self.m1_space, self.m2_space) - self.m2_pitch = max(contact.m2m3.width, contact.m2m3.height) + max(self.m2_space, self.m3_space) - if "metal4" in layer: - self.m3_pitch = max(contact.m3m4.width, contact.m3m4.height) + max(self.m3_space, self.m4_space) + def setup_layer_constants(self): + """ + These are some layer constants used + in many places in the compiler. + """ + + import tech + for key in dir(tech): + # Single layer width rules + match = re.match(r".*_stack$", key) + if match: + layer_stack = getattr(tech, key) + + # Set the stack as a local helper + setattr(self, key, layer_stack) + + # Add the pitch + setattr(self, + "{}_pitch".format(layer_stack[0]), + self.compute_pitch(layer_stack)) + + if False: + print("m1_pitch", self.m1_pitch) + print("m2_pitch", self.m2_pitch) + print("m3_pitch", self.m3_pitch) + import sys + sys.exit(1) + + def compute_pitch(self, layer_stack): + + """ + This is contact direction independent pitch, + i.e. we take the maximum contact dimension + """ + (layer1, via, layer2) = layer_stack + + if layer1 == "poly" or layer1 == "active": + contact1 = getattr(contact, layer1 + "_contact") else: - self.m3_pitch = self.m2_pitch + contact1 = getattr(contact, layer1 + "_via") + max_contact = max(contact1.width, contact1.height) + + layer1_space = getattr(self, layer1 + "_space") + layer2_space = getattr(self, layer2 + "_space") + pitch = max_contact + max(layer1_space, layer2_space) + return pitch + def setup_drc_constants(self): - """ These are some DRC constants used in many places in the compiler.""" - from tech import drc, layer - self.well_width = drc("minwidth_well") - self.poly_width = drc("minwidth_poly") - self.poly_space = drc("poly_to_poly") - self.m1_width = drc("minwidth_metal1") - self.m1_space = drc("metal1_to_metal1") - self.m2_width = drc("minwidth_metal2") - self.m2_space = drc("metal2_to_metal2") - self.m3_width = drc("minwidth_metal3") - self.m3_space = drc("metal3_to_metal3") - if "metal4" in layer: - self.m4_width = drc("minwidth_metal4") - self.m4_space = drc("metal4_to_metal4") - self.active_width = drc("minwidth_active") - self.active_space = drc("active_to_body_active") - self.contact_width = drc("minwidth_active_contact") + """ + These are some DRC constants used in many places + in the compiler. + """ + # Make some local rules for convenience + from tech import drc + for rule in drc.keys(): + # Single layer width rules + match = re.search(r"minwidth_(.*)", rule) + if match: + if match.group(1) == "active_contact": + setattr(self, "contact_width", drc(match.group(0))) + else: + setattr(self, match.group(1) + "_width", drc(match.group(0))) - self.poly_to_active = drc("poly_to_active") - self.poly_extend_active = drc("poly_extend_active") - self.poly_to_poly_contact = drc("poly_to_poly_contact") - self.active_contact_to_gate = drc("active_contact_to_gate") - self.well_enclose_active = drc("well_enclosure_active") - self.implant_enclose_active = drc("implant_enclosure_active") - self.implant_space = drc("implant_to_implant") + # Single layer area rules + match = re.search(r"minarea_(.*)", rule) + if match: + setattr(self, match.group(0), drc(match.group(0))) + + # Single layer spacing rules + match = re.search(r"(.*)_to_(.*)", rule) + if match and match.group(1) == match.group(2): + setattr(self, match.group(1) + "_space", drc(match.group(0))) + elif match and match.group(1) != match.group(2): + if match.group(2) == "poly_active": + setattr(self, match.group(1) + "_to_contact", + drc(match.group(0))) + else: + setattr(self, match.group(0), drc(match.group(0))) + + match = re.search(r"(.*)_enclose_(.*)", rule) + if match: + setattr(self, match.group(0), drc(match.group(0))) + + match = re.search(r"(.*)_extend_(.*)", rule) + if match: + setattr(self, match.group(0), drc(match.group(0))) + + # Create the maximum well extend active that gets used + # by cells to extend the wells for interaction with other cells + from tech import layer + self.well_extend_active = 0 + if "nwell" in layer: + self.well_extend_active = max(self.well_extend_active, self.nwell_extend_active) + if "pwell" in layer: + self.well_extend_active = max(self.well_extend_active, self.pwell_extend_active) + + # These are for debugging previous manual rules + if False: + print("poly_width", self.poly_width) + print("poly_space", self.poly_space) + print("m1_width", self.m1_width) + print("m1_space", self.m1_space) + print("m2_width", self.m2_width) + print("m2_space", self.m2_space) + print("m3_width", self.m3_width) + print("m3_space", self.m3_space) + print("m4_width", self.m4_width) + print("m4_space", self.m4_space) + print("active_width", self.active_width) + print("active_space", self.active_space) + print("contact_width", self.contact_width) + print("poly_to_active", self.poly_to_active) + print("poly_extend_active", self.poly_extend_active) + print("poly_to_contact", self.poly_to_contact) + print("contact_to_gate", self.contact_to_gate) + print("well_enclose_active", self.well_enclose_active) + print("implant_enclose_active", self.implant_enclose_active) + print("implant_space", self.implant_space) + import sys + sys.exit(1) def setup_multiport_constants(self): """ diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index 46cfe7c7..f354cf02 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -69,7 +69,9 @@ class geometry: """ Transform with offset, mirror and rotation to get the absolute pin location. We must then re-find the ll and ur. The master is the cell instance. """ if OPTS.netlist_only: + self.boundary = [vector(0,0), vector(0,0)] return + (ll, ur) = [vector(0, 0), vector(self.width, self.height)] if mirror == "MX": diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 494ab4ca..451d1356 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -5,18 +5,18 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import itertools import collections import geometry import gdsMill import debug from tech import drc, GDS from tech import layer as techlayer +from tech import layer_stacks import os from globals import OPTS from vector import vector from pin_layout import pin_layout -import lef + class layout(): """ @@ -32,13 +32,19 @@ class layout(): self.name = name self.width = None self.height = None - self.boundary = None + self.bounding_box = None self.insts = [] # Holds module/cell layout instances self.objs = [] # Holds all other objects (labels, geometries, etc) self.pin_map = {} # Holds name->pin_layout map for all pins self.visited = [] # List of modules we have already visited self.is_library_cell = False # Flag for library cells self.gds_read() + try: + from tech import power_grid + self.pwr_grid_layer = power_grid[0] + except ImportError: + self.pwr_grid_layer = "m3" + ############################################################ # GDS layout @@ -57,64 +63,116 @@ class layout(): """ if (inv_num % 2 == 0): - base_offset=vector(x_offset, inv_num * height) + base_offset = vector(x_offset, inv_num * height) y_dir = 1 else: - # we lose a rail after every 2 gates - base_offset=vector(x_offset, (inv_num+1) * height - (inv_num%2)*drc["minwidth_metal1"]) + # we lose a rail after every 2 gates + base_offset = vector(x_offset, + (inv_num + 1) * height - \ + (inv_num % 2) * drc["minwidth_m1"]) y_dir = -1 - return (base_offset,y_dir) - + return (base_offset, y_dir) def find_lowest_coords(self): - """Finds the lowest set of 2d cartesian coordinates within - this layout""" + """ + Finds the lowest set of 2d cartesian coordinates within + this layout + """ - if len(self.objs)>0: - lowestx1 = min(obj.lx() for obj in self.objs if obj.name!="label") - lowesty1 = min(obj.by() for obj in self.objs if obj.name!="label") + if len(self.objs) > 0: + lowestx1 = min(obj.lx() for obj in self.objs if obj.name != "label") + lowesty1 = min(obj.by() for obj in self.objs if obj.name != "label") else: - lowestx1=lowesty1=None - if len(self.insts)>0: + lowestx1 = lowesty1 = None + if len(self.insts) > 0: lowestx2 = min(inst.lx() for inst in self.insts) lowesty2 = min(inst.by() for inst in self.insts) else: - lowestx2=lowesty2=None + lowestx2 = lowesty2 = None - if lowestx1==None and lowestx2==None: + if lowestx1 == None and lowestx2 == None: return None - elif lowestx1==None: - return vector(lowestx2,lowesty2) - elif lowestx2==None: - return vector(lowestx1,lowesty1) + elif lowestx1 == None: + return vector(lowestx2, lowesty2) + elif lowestx2 == None: + return vector(lowestx1, lowesty1) else: return vector(min(lowestx1, lowestx2), min(lowesty1, lowesty2)) def find_highest_coords(self): - """Finds the highest set of 2d cartesian coordinates within - this layout""" - - if len(self.objs)>0: - highestx1 = max(obj.rx() for obj in self.objs if obj.name!="label") - highesty1 = max(obj.uy() for obj in self.objs if obj.name!="label") + """ + Finds the highest set of 2d cartesian coordinates within + this layout + """ + if len(self.objs) > 0: + highestx1 = max(obj.rx() for obj in self.objs if obj.name != "label") + highesty1 = max(obj.uy() for obj in self.objs if obj.name != "label") else: - highestx1=highesty1=None - if len(self.insts)>0: + highestx1 = highesty1 = None + if len(self.insts) > 0: highestx2 = max(inst.rx() for inst in self.insts) highesty2 = max(inst.uy() for inst in self.insts) else: - highestx2=highesty2=None - if highestx1==None and highestx2==None: + highestx2 = highesty2 = None + if highestx1 == None and highestx2 == None: return None - elif highestx1==None: - return vector(highestx2,highesty2) - elif highestx2==None: - return vector(highestx1,highesty1) + elif highestx1 == None: + return vector(highestx2, highesty2) + elif highestx2 == None: + return vector(highestx1, highesty1) else: - return vector(max(highestx1, highestx2), max(highesty1, highesty2)) + return vector(max(highestx1, highestx2), + max(highesty1, highesty2)) + def find_highest_layer_coords(self, layer): + """ + Finds the highest set of 2d cartesian coordinates within + this layout on a layer + """ + # Only consider the layer not the purpose for now + layerNumber = techlayer[layer][0] + try: + highestx = max(obj.rx() for obj in self.objs if obj.layerNumber == layerNumber) + except ValueError: + highestx =0 + try: + highesty = max(obj.uy() for obj in self.objs if obj.layerNumber == layerNumber) + except ValueError: + highesty = 0 + for inst in self.insts: + # This really should be rotated/mirrored etc... + subcoord = inst.mod.find_highest_layer_coords(layer) + inst.offset + highestx = max(highestx, subcoord.x) + highesty = max(highesty, subcoord.y) + + return vector(highestx, highesty) + + def find_lowest_layer_coords(self, layer): + """ + Finds the highest set of 2d cartesian coordinates within + this layout on a layer + """ + # Only consider the layer not the purpose for now + layerNumber = techlayer[layer][0] + try: + lowestx = min(obj.lx() for obj in self.objs if obj.layerNumber == layerNumber) + except ValueError: + lowestx = 0 + try: + lowesty = min(obj.by() for obj in self.objs if obj.layerNumber == layerNumber) + except ValueError: + lowesty = 0 + + for inst in self.insts: + # This really should be rotated/mirrored etc... + subcoord = inst.mod.find_lowest_layer_coords(layer) + inst.offset + lowestx = min(lowestx, subcoord.x) + lowesty = min(lowesty, subcoord.y) + + return vector(lowestx, lowesty) + def translate_all(self, offset): """ Translates all objects, instances, and pins by the given (x,y) offset @@ -132,8 +190,8 @@ class layout(): for pin in pin_list: pin.rect = [pin.ll() - offset, pin.ur() - offset] - def add_inst(self, name, mod, offset=[0,0], mirror="R0",rotate=0): - """Adds an instance of a mod to this module""" + def add_inst(self, name, mod, offset=[0, 0], mirror="R0", rotate=0): + """ Adds an instance of a mod to this module """ self.insts.append(geometry.instance(name, mod, offset, mirror, rotate)) debug.info(3, "adding instance {}".format(self.insts[-1])) # This is commented out for runtime reasons @@ -141,7 +199,7 @@ class layout(): return self.insts[-1] def get_inst(self, name): - """Retrieve an instance by name""" + """ Retrieve an instance by name """ for inst in self.insts: if inst.name == name: return inst @@ -152,9 +210,9 @@ class layout(): Adds a rectangle on a given layer,offset with width and height """ if not width: - width=drc["minwidth_{}".format(layer)] + width = drc["minwidth_{}".format(layer)] if not height: - height=drc["minwidth_{}".format(layer)] + height = drc["minwidth_{}".format(layer)] # negative layers indicate "unused" layers in a given technology lpp = techlayer[layer] if lpp[0] >= 0: @@ -164,58 +222,63 @@ class layout(): def add_rect_center(self, layer, offset, width=None, height=None): """ - Adds a rectangle on a given layer at the center point with width and height + Adds a rectangle on a given layer at the center + point with width and height """ if not width: - width=drc["minwidth_{}".format(layer)] + width = drc["minwidth_{}".format(layer)] if not height: - height=drc["minwidth_{}".format(layer)] + height = drc["minwidth_{}".format(layer)] # negative layers indicate "unused" layers in a given technology lpp = techlayer[layer] - corrected_offset = offset - vector(0.5*width,0.5*height) + corrected_offset = offset - vector(0.5 * width, 0.5 * height) if lpp[0] >= 0: - self.objs.append(geometry.rectangle(lpp, corrected_offset, width, height)) + self.objs.append(geometry.rectangle(lpp, + corrected_offset, + width, + height)) return self.objs[-1] return None - def add_segment_center(self, layer, start, end): - """ - Add a min-width rectanglular segment using center line on the start to end point """ - minwidth_layer = drc["minwidth_{}".format(layer)] - if start.x!=end.x and start.y!=end.y: - debug.error("Nonrectilinear center rect!",-1) - elif start.x!=end.x: - offset = vector(0,0.5*minwidth_layer) - return self.add_rect(layer,start-offset,end.x-start.x,minwidth_layer) + Add a min-width rectanglular segment using center + line on the start to end point + """ + minwidth_layer = drc["minwidth_{}".format(layer)] + if start.x != end.x and start.y != end.y: + debug.error("Nonrectilinear center rect!", -1) + elif start.x != end.x: + offset = vector(0, 0.5 * minwidth_layer) + return self.add_rect(layer, + start-offset, + end.x-start.x, + minwidth_layer) else: - offset = vector(0.5*minwidth_layer,0) - return self.add_rect(layer,start-offset,minwidth_layer,end.y-start.y) - - + offset = vector(0.5 * minwidth_layer, 0) + return self.add_rect(layer, + start-offset, + minwidth_layer, + end.y-start.y) def get_pin(self, text): - """ - Return the pin or list of pins + """ + Return the pin or list of pins """ try: - if len(self.pin_map[text])>1: + if len(self.pin_map[text]) > 1: debug.error("Should use a pin iterator since more than one pin {}".format(text),-1) # If we have one pin, return it and not the list. # Otherwise, should use get_pins() any_pin = next(iter(self.pin_map[text])) return any_pin - except Exception as e: - #print e + except Exception: self.gds_write("missing_pin.gds") debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(text,self.name),-1) - - def get_pins(self, text): - """ - Return a pin list (instead of a single pin) + """ + Return a pin list (instead of a single pin) """ if text in self.pin_map.keys(): return self.pin_map[text] @@ -223,87 +286,97 @@ class layout(): return set() def get_pin_names(self): - """ + """ Return a pin list of all pins """ return self.pin_map.keys() def copy_layout_pin(self, instance, pin_name, new_name=""): - """ - Create a copied version of the layout pin at the current level. - You can optionally rename the pin to a new name. """ - pins=instance.get_pins(pin_name) + Create a copied version of the layout pin at the current level. + You can optionally rename the pin to a new name. + """ + pins = instance.get_pins(pin_name) - debug.check(len(pins)>0,"Could not find pin {}".format(pin_name)) + debug.check(len(pins) > 0, + "Could not find pin {}".format(pin_name)) for pin in pins: - if new_name=="": + if new_name == "": new_name = pin.name - self.add_layout_pin(new_name, pin.layer, pin.ll(), pin.width(), pin.height()) + self.add_layout_pin(new_name, + pin.layer, + pin.ll(), + pin.width(), + pin.height()) def copy_layout_pins(self, instance, prefix=""): - """ + """ Create a copied version of the layout pin at the current level. - You can optionally rename the pin to a new name. + You can optionally rename the pin to a new name. """ for pin_name in self.pin_map.keys(): self.copy_layout_pin(instance, pin_name, prefix+pin_name) def add_layout_pin_segment_center(self, text, layer, start, end): - """ - Creates a path like pin with center-line convention + """ + Creates a path like pin with center-line convention """ - debug.check(start.x==end.x or start.y==end.y,"Cannot have a non-manhatten layout pin.") + debug.check(start.x == end.x or start.y == end.y, + "Cannot have a non-manhatten layout pin.") minwidth_layer = drc["minwidth_{}".format(layer)] # one of these will be zero - width = max(start.x,end.x) - min(start.x,end.x) - height = max(start.y,end.y) - min(start.y,end.y) - ll_offset = vector(min(start.x,end.x),min(start.y,end.y)) + width = max(start.x, end.x) - min(start.x, end.x) + height = max(start.y, end.y) - min(start.y, end.y) + ll_offset = vector(min(start.x, end.x), min(start.y, end.y)) # Shift it down 1/2 a width in the 0 dimension - if height==0: - ll_offset -= vector(0,0.5*minwidth_layer) - if width==0: - ll_offset -= vector(0.5*minwidth_layer,0) + if height == 0: + ll_offset -= vector(0, 0.5 * minwidth_layer) + if width == 0: + ll_offset -= vector(0.5 * minwidth_layer, 0) # This makes sure it is long enough, but also it is not 0 width! - height = max(minwidth_layer,height) - width = max(minwidth_layer,width) + height = max(minwidth_layer, height) + width = max(minwidth_layer, width) - - return self.add_layout_pin(text, layer, ll_offset, width, height) + return self.add_layout_pin(text, + layer, + ll_offset, + width, + height) def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None): """ Creates a path like pin with center-line convention """ if not width: - width=drc["minwidth_{0}".format(layer)] + width = drc["minwidth_{0}".format(layer)] if not height: - height=drc["minwidth_{0}".format(layer)] + height = drc["minwidth_{0}".format(layer)] - ll_offset = offset - vector(0.5*width,0.5*height) + ll_offset = offset - vector(0.5 * width, 0.5 * height) return self.add_layout_pin(text, layer, ll_offset, width, height) - def remove_layout_pin(self, text): """ Delete a labeled pin (or all pins of the same name) """ - self.pin_map[text]=set() + self.pin_map[text] = set() def add_layout_pin(self, text, layer, offset, width=None, height=None): """ - Create a labeled pin + Create a labeled pin """ if not width: - width=drc["minwidth_{0}".format(layer)] + width = drc["minwidth_{0}".format(layer)] if not height: - height=drc["minwidth_{0}".format(layer)] + height = drc["minwidth_{0}".format(layer)] - new_pin = pin_layout(text, [offset,offset+vector(width,height)], layer) + new_pin = pin_layout(text, + [offset, offset+vector(width, height)], + layer) try: # Check if there's a duplicate! @@ -324,42 +397,41 @@ class layout(): in LVS. """ if not width: - width=drc["minwidth_{0}".format(layer)] + width = drc["minwidth_{0}".format(layer)] if not height: - height=drc["minwidth_{0}".format(layer)] + height = drc["minwidth_{0}".format(layer)] self.add_rect(layer=layer, offset=offset, width=width, height=height) self.add_label(text=text, layer=layer, - offset=offset+vector(0.5*width,0.5*height)) + offset=offset + vector(0.5 * width, + 0.5 * height)) - - def add_label(self, text, layer, offset=[0,0],zoom=-1): + def add_label(self, text, layer, offset=[0, 0], zoom=-1): """Adds a text label on the given layer,offset, and zoom level""" # negative layers indicate "unused" layers in a given technology - debug.info(5,"add label " + str(text) + " " + layer + " " + str(offset)) + debug.info(5, "add label " + str(text) + " " + layer + " " + str(offset)) lpp = techlayer[layer] if lpp[0] >= 0: self.objs.append(geometry.label(text, lpp, offset, zoom)) return self.objs[-1] return None - def add_path(self, layer, coordinates, width=None): """Connects a routing path on given layer,coordinates,width.""" - debug.info(4,"add path " + str(layer) + " " + str(coordinates)) + debug.info(4, "add path " + str(layer) + " " + str(coordinates)) import wire_path # NOTE: (UNTESTED) add_path(...) is currently not used # negative layers indicate "unused" layers in a given technology - #lpp = techlayer[layer] - #if lpp[0] >= 0: - # self.objs.append(geometry.path(lpp, coordinates, width)) + # lpp = techlayer[layer] + # if lpp[0] >= 0: + # self.objs.append(geometry.path(lpp, coordinates, width)) wire_path.wire_path(obj=self, - layer=layer, - position_list=coordinates, + layer=layer, + position_list=coordinates, width=width) def add_route(self, layers, coordinates, layer_widths): @@ -369,38 +441,34 @@ class layout(): the coordinates. """ import route - debug.info(4,"add route " + str(layers) + " " + str(coordinates)) + debug.info(4, "add route " + str(layers) + " " + str(coordinates)) # add an instance of our path that breaks down into rectangles and contacts route.route(obj=self, - layer_stack=layers, + layer_stack=layers, path=coordinates, layer_widths=layer_widths) - def add_wire(self, layers, coordinates): """Connects a routing path on given layer,coordinates,width. The layers are the (horizontal, via, vertical). """ import wire - # add an instance of our path that breaks down into rectangles and contacts + # add an instance of our path that breaks down + # into rectangles and contacts wire.wire(obj=self, - layer_stack=layers, + layer_stack=layers, position_list=coordinates) def get_preferred_direction(self, layer): """ Return the preferred routing directions """ - if layer in ["metal1", "metal3", "metal5"]: - return "H" - elif layer in ["active", "poly", "metal2", "metal4"]: - return "V" - else: - return "N" - + from tech import preferred_directions + return preferred_directions[layer] def add_via(self, layers, offset, size=[1,1], directions=None, implant_type=None, well_type=None): """ Add a three layer via structure. """ - if directions==None: - directions = (self.get_preferred_direction(layers[0]), self.get_preferred_direction(layers[2])) + if not directions: + directions = (self.get_preferred_direction(layers[0]), + self.get_preferred_direction(layers[2])) from sram_factory import factory via = factory.create(module_type="contact", @@ -410,18 +478,22 @@ class layout(): implant_type=implant_type, well_type=well_type) self.add_mod(via) - inst=self.add_inst(name=via.name, - mod=via, - offset=offset) + inst = self.add_inst(name=via.name, + mod=via, + offset=offset) # We don't model the logical connectivity of wires/paths self.connect_inst([]) return inst def add_via_center(self, layers, offset, directions=None, size=[1,1], implant_type=None, well_type=None): - """ Add a three layer via structure by the center coordinate accounting for mirroring and rotation. """ + """ + Add a three layer via structure by the center coordinate + accounting for mirroring and rotation. + """ - if directions==None: - directions = (self.get_preferred_direction(layers[0]), self.get_preferred_direction(layers[2])) + if not directions: + directions = (self.get_preferred_direction(layers[0]), + self.get_preferred_direction(layers[2])) from sram_factory import factory via = factory.create(module_type="contact", @@ -433,15 +505,81 @@ class layout(): height = via.height width = via.width - corrected_offset = offset + vector(-0.5*width,-0.5*height) + corrected_offset = offset + vector(-0.5 * width, + -0.5 * height) self.add_mod(via) - inst=self.add_inst(name=via.name, - mod=via, - offset=corrected_offset) + inst = self.add_inst(name=via.name, + mod=via, + offset=corrected_offset) # We don't model the logical connectivity of wires/paths self.connect_inst([]) return inst + + def add_via_stack(self, offset, direction, from_layer, to_layer, + size=[1,1]): + """ + Punch a stack of vias from a start layer to a target layer. + """ + + return self.__add_via_stack_internal(offset=offset, + direction=direction, + from_layer=from_layer, + to_layer=to_layer, + via_func=self.add_via, + last_via=None, + size=size) + + def add_via_stack_center(self, offset, direction, from_layer, to_layer, + size=[1,1]): + """ + Punch a stack of vias from a start layer to a target layer by the center + coordinate accounting for mirroring and rotation. + """ + return self.__add_via_stack_internal(offset=offset, + direction=direction, + from_layer=from_layer, + to_layer=to_layer, + via_func=self.add_via_center, + last_via=None, + size=size) + + + def __add_via_stack_internal(self, offset, direction, from_layer, to_layer, + via_func, last_via, size): + """ + Punch a stack of vias from a start layer to a target layer. Here we + figure out whether to punch it up or down the stack. + """ + + if from_layer == to_layer: + return last_via + + from_id = int(from_layer[1]) + to_id = int(to_layer[1]) + + 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] == from_layer, layer_stacks), None) + if curr_stack is None: + raise ValueError("Cannot create via from '{0}' to '{1}'." \ + "Layer '{0}' not defined" + .format(from_layer, to_layer)) + + via = via_func(layers=curr_stack, size=size, offset=offset, directions=direction) + return self.__add_via_stack_internal(offset=offset, + direction=direction, + from_layer=curr_stack[next_id], + to_layer=to_layer, + via_func=via_func, + last_via=via, + size=size) + def add_ptx(self, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"): """Adds a ptx module to the design.""" @@ -450,22 +588,20 @@ class layout(): mults=mults, tx_type=tx_type) self.add_mod(mos) - inst=self.add_inst(name=mos.name, - mod=mos, - offset=offset, - mirror=mirror, - rotate=rotate) + inst = self.add_inst(name=mos.name, + mod=mos, + offset=offset, + mirror=mirror, + rotate=rotate) return inst - - def gds_read(self): """Reads a GDSII file in the library and checks if it exists Otherwise, start a new layout for dynamic generation.""" # This must be done for netlist only mode too if os.path.isfile(self.gds_file): - self.is_library_cell=True + self.is_library_cell = True if OPTS.netlist_only: self.gds = None @@ -483,7 +619,7 @@ class layout(): def print_gds(self, gds_file=None): """Print the gds file (not the vlsi class) to the terminal """ - if gds_file == None: + if not gds_file: gds_file = self.gds_file debug.info(4, "Printing {}".format(gds_file)) arrayCellLayout = gdsMill.VlsiLayout(units=GDS["unit"]) @@ -510,11 +646,12 @@ class layout(): # If it's not a premade cell # and we didn't add our own boundary, # we should add a boundary just for DRC in some technologies - if not self.is_library_cell and not self.boundary: + if not self.is_library_cell and not self.bounding_box: # If there is a boundary layer, and we didn't create one, add one. if "stdc" in techlayer.keys(): boundary_layer = "stdc" - boundary = [self.find_lowest_coords(), self.find_highest_coords()] + boundary = [self.find_lowest_coords(), + self.find_highest_coords()] height = boundary[1][1] - boundary[0][1] width = boundary[1][0] - boundary[0][0] (layer_number, layer_purpose) = techlayer[boundary_layer] @@ -525,7 +662,6 @@ class layout(): height=height, center=False) debug.info(2, "Adding {0} boundary {1}".format(self.name, boundary)) - self.visited.append(self.name) @@ -552,20 +688,21 @@ class layout(): # populates the xyTree data structure for gds # self.gds.prepareForWrite() writer.writeToFile(gds_name) - debug.info(3, "Done writing to {}".format(gds_name)) + debug.info(3, "Done writing to {}".format(gds_name)) def get_boundary(self): """ Return the lower-left and upper-right coordinates of boundary """ # This assumes nothing spans outside of the width and height! - return [vector(0,0), vector(self.width, self.height)] + return [vector(0, 0), vector(self.width, self.height)] #return [self.find_lowest_coords(), self.find_highest_coords()] def get_blockages(self, layer, top_level=False): - """ - Write all of the obstacles in the current (and children) modules to the lef file + """ + Write all of the obstacles in the current (and children) + modules to the lef file. Do not write the pins since they aren't obstructions. """ - if type(layer)==str: + if type(layer) == str: lpp = techlayer[layer] else: lpp = layer @@ -637,22 +774,21 @@ class layout(): vertical=False, make_pins=False) - def create_bus(self, layer, pitch, offset, names, length, vertical, make_pins): """ Create a horizontal or vertical bus. It can be either just rectangles, or actual - layout pins. It returns an map of line center line positions indexed by name. + layout pins. It returns an map of line center line positions indexed by name. The other coordinate is a 0 since the bus provides a range. TODO: combine with channel router. """ # half minwidth so we can return the center line offsets - half_minwidth = 0.5*drc["minwidth_{}".format(layer)] + half_minwidth = 0.5 * drc["minwidth_{}".format(layer)] line_positions = {} if vertical: for i in range(len(names)): - line_offset = offset + vector(i*pitch,0) + line_offset = offset + vector(i * pitch, 0) if make_pins: self.add_layout_pin(text=names[i], layer=layer, @@ -662,11 +798,13 @@ class layout(): self.add_rect(layer=layer, offset=line_offset, height=length) - # Make this the center of the rail - line_positions[names[i]]=line_offset+vector(half_minwidth,0.5*length) + # Make this the center of the rail + line_positions[names[i]] = line_offset + vector(half_minwidth, + 0.5 * length) else: for i in range(len(names)): - line_offset = offset + vector(0,i*pitch + half_minwidth) + line_offset = offset + vector(0, + i * pitch + half_minwidth) if make_pins: self.add_layout_pin(text=names[i], layer=layer, @@ -677,27 +815,29 @@ class layout(): offset=line_offset, width=length) # Make this the center of the rail - line_positions[names[i]]=line_offset+vector(0.5*length,half_minwidth) + line_positions[names[i]] = line_offset + vector(0.5 * length, + half_minwidth) return line_positions def connect_horizontal_bus(self, mapping, inst, bus_offsets, - layer_stack=("metal1","via1","metal2")): + layer_stack=("m1", "via1", "m2")): """ Horizontal version of connect_bus. """ self.connect_bus(mapping, inst, bus_offsets, layer_stack, True) def connect_vertical_bus(self, mapping, inst, bus_offsets, - layer_stack=("metal1","via1","metal2")): + layer_stack=("m1", "via1", "m2")): """ Vertical version of connect_bus. """ self.connect_bus(mapping, inst, bus_offsets, layer_stack, False) def connect_bus(self, mapping, inst, bus_offsets, layer_stack, horizontal): - """ - Connect a mapping of pin -> name for a bus. This could be - replaced with a channel router in the future. - NOTE: This has only really been tested with point-to-point connections (not multiple pins on a net). """ - (horizontal_layer, via_layer, vertical_layer)=layer_stack + Connect a mapping of pin -> name for a bus. This could be + replaced with a channel router in the future. + NOTE: This has only really been tested with point-to-point + connections (not multiple pins on a net). + """ + (horizontal_layer, via_layer, vertical_layer) = layer_stack if horizontal: route_layer = vertical_layer else: @@ -715,37 +855,48 @@ class layout(): # left/right then up/down mid_pos = vector(bus_pos.x, pin_pos.y) - self.add_wire(layer_stack,[bus_pos, mid_pos, pin_pos]) + self.add_wire(layer_stack, + [bus_pos, mid_pos, pin_pos]) # Connect to the pin on the instances with a via if it is # not on the right layer if pin.layer != route_layer: self.add_via_center(layers=layer_stack, offset=pin_pos) - # FIXME: output pins tend to not be rotate, but supply pins are. Make consistent? + # FIXME: output pins tend to not be rotate, + # but supply pins are. Make consistent? - - # We only need a via if they happened to align perfectly # so the add_wire didn't add a via if (horizontal and bus_pos.y == pin_pos.y) or (not horizontal and bus_pos.x == pin_pos.x): self.add_via_center(layers=layer_stack, offset=bus_pos, rotate=90) + def get_layer_pitch(self, layer): """ Return the track pitch on a given layer """ - if layer=="metal1": - return (self.m1_pitch,self.m1_pitch-self.m1_space,self.m1_space) - elif layer=="metal2": - return (self.m2_pitch,self.m2_pitch-self.m2_space,self.m2_space) - elif layer=="metal3": - return (self.m3_pitch,self.m3_pitch-self.m3_space,self.m3_space) - elif layer=="metal4": + if layer == "m1": + return (self.m1_pitch, + self.m1_pitch - self.m1_space, + self.m1_space) + elif layer == "m2": + return (self.m2_pitch, + self.m2_pitch - self.m2_space, + self.m2_space) + elif layer == "m3": + return (self.m3_pitch, + self.m3_pitch - self.m3_space, + self.m3_space) + elif layer == "m4": from tech import layer as tech_layer - if "metal4" in tech_layer: - return (self.m3_pitch,self.m3_pitch-self.m4_space,self.m4_space) + if "m4" in tech_layer: + return (self.m3_pitch, + self.m3_pitch - self.m4_space, + self.m4_space) else: - return (self.m3_pitch,self.m3_pitch-self.m3_space,self.m3_space) + return (self.m3_pitch, + self.m3_pitch - self.m3_space, + self.m3_space) else: debug.error("Cannot find layer pitch.") @@ -755,18 +906,20 @@ class layout(): layer_stack, pitch): """ - Create a trunk route for all pins with the trunk located at the given y offset. + Create a trunk route for all pins with + the trunk located at the given y offset. """ max_x = max([pin.center().x for pin in pins]) min_x = min([pin.center().x for pin in pins]) # if we are less than a pitch, just create a non-preferred layer jog if max_x-min_x <= pitch: - - half_layer_width = 0.5*drc["minwidth_{0}".format(self.vertical_layer)] + half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] # Add the horizontal trunk on the vertical layer! - self.add_path(self.vertical_layer,[vector(min_x-half_layer_width,trunk_offset.y), vector(max_x+half_layer_width,trunk_offset.y)]) + self.add_path(self.vertical_layer, + [vector(min_x - half_layer_width, trunk_offset.y), + vector(max_x + half_layer_width, trunk_offset.y)]) # Route each pin to the trunk for pin in pins: @@ -775,8 +928,9 @@ class layout(): self.add_path(self.vertical_layer, [pin.center(), mid]) else: # Add the horizontal trunk - self.add_path(self.horizontal_layer,[vector(min_x,trunk_offset.y), vector(max_x,trunk_offset.y)]) - trunk_mid = vector(0.5*(max_x+min_x),trunk_offset.y) + self.add_path(self.horizontal_layer, + [vector(min_x, trunk_offset.y), + vector(max_x, trunk_offset.y)]) # Route each pin to the trunk for pin in pins: @@ -791,7 +945,8 @@ class layout(): layer_stack, pitch): """ - Create a trunk route for all pins with the trunk located at the given x offset. + Create a trunk route for all pins with the + trunk located at the given x offset. """ max_y = max([pin.center().y for pin in pins]) min_y = min([pin.center().y for pin in pins]) @@ -799,10 +954,12 @@ class layout(): # if we are less than a pitch, just create a non-preferred layer jog if max_y-min_y <= pitch: - half_layer_width = 0.5*drc["minwidth_{0}".format(self.horizontal_layer)] + half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)] # Add the vertical trunk on the horizontal layer! - self.add_path(self.horizontal_layer,[vector(trunk_offset.x,min_y-half_layer_width), vector(trunk_offset.x,max_y+half_layer_width)]) + self.add_path(self.horizontal_layer, + [vector(trunk_offset.x, min_y - half_layer_width), + vector(trunk_offset.x,max_y + half_layer_width)]) # Route each pin to the trunk for pin in pins: @@ -811,8 +968,9 @@ class layout(): self.add_path(self.horizontal_layer, [pin.center(), mid]) else: # Add the vertical trunk - self.add_path(self.vertical_layer,[vector(trunk_offset.x,min_y), vector(trunk_offset.x,max_y)]) - trunk_mid = vector(trunk_offset.x,0.5*(max_y+min_y),) + self.add_path(self.vertical_layer, + [vector(trunk_offset.x, min_y), + vector(trunk_offset.x, max_y)]) # Route each pin to the trunk for pin in pins: @@ -820,11 +978,10 @@ class layout(): self.add_path(self.horizontal_layer, [pin.center(), mid]) self.add_via_center(layers=layer_stack, offset=mid) - def create_channel_route(self, netlist, - offset, - layer_stack=("metal1", "via1", "metal2"), + offset, + layer_stack, vertical=False): """ The net list is a list of the nets. Each net is a list of pins @@ -838,7 +995,7 @@ class layout(): """ Remove the pin from the graph and all conflicts """ - g.pop(pin,None) + g.pop(pin, None) # Remove the pin from all conflicts # FIXME: This is O(n^2), so maybe optimize it. @@ -849,9 +1006,9 @@ class layout(): return g def vcg_nets_overlap(net1, net2, vertical, pitch): - """ + """ Check all the pin pairs on two nets and return a pin - overlap if any pin overlaps + overlap if any pin overlaps. """ for pin1 in net1: @@ -864,7 +1021,8 @@ class layout(): def vcg_pin_overlap(pin1, pin2, vertical, pitch): """ Check for vertical or horizontal overlap of the two pins """ # FIXME: If the pins are not in a row, this may break. - # However, a top pin shouldn't overlap another top pin, for example, so the + # However, a top pin shouldn't overlap another top pin, + # for example, so the # extra comparison *shouldn't* matter. # Pin 1 must be in the "BOTTOM" set @@ -875,121 +1033,135 @@ class layout(): overlaps = (not vertical and x_overlap) or (vertical and y_overlap) return overlaps - if self.get_preferred_direction(layer_stack[0])=="V": + if self.get_preferred_direction(layer_stack[0]) == "V": self.vertical_layer = layer_stack[0] self.horizontal_layer = layer_stack[2] else: self.vertical_layer = layer_stack[2] self.horizontal_layer = layer_stack[0] - (self.vertical_pitch,self.vertical_width,self.vertical_space) = self.get_layer_pitch(self.vertical_layer) - (self.horizontal_pitch,self.horizontal_width,self.horizontal_space) = self.get_layer_pitch(self.horizontal_layer) + layer_stuff = self.get_layer_pitch(self.vertical_layer) + (self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff + layer_stuff = self.get_layer_pitch(self.horizontal_layer) + (self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff - - # FIXME: Must extend this to a horizontal conflict graph too if we want to minimize the + # FIXME: Must extend this to a horizontal conflict graph + # too if we want to minimize the # number of tracks! - #hcg = {} + # hcg = {} - # Initialize the vertical conflict graph (vcg) and make a list of all pins + # Initialize the vertical conflict graph (vcg) + # and make a list of all pins vcg = collections.OrderedDict() # Create names for the nets for the graphs nets = collections.OrderedDict() index = 0 - #print(netlist) + # print(netlist) for pin_list in netlist: - net_name = "n{}".format(index) - index += 1 - nets[net_name] = pin_list + net_name = "n{}".format(index) + index += 1 + nets[net_name] = pin_list # Find the vertical pin conflicts # FIXME: O(n^2) but who cares for now for net_name1 in nets: if net_name1 not in vcg.keys(): - vcg[net_name1]=[] + vcg[net_name1] = [] for net_name2 in nets: if net_name2 not in vcg.keys(): - vcg[net_name2]=[] + vcg[net_name2] = [] # Skip yourself if net_name1 == net_name2: continue - if vertical and vcg_nets_overlap(nets[net_name1], nets[net_name2], vertical, self.vertical_pitch): + if vertical and vcg_nets_overlap(nets[net_name1], + nets[net_name2], + vertical, + self.vertical_pitch): vcg[net_name2].append(net_name1) - elif not vertical and vcg_nets_overlap(nets[net_name1], nets[net_name2], vertical, self.horizontal_pitch): + elif not vertical and vcg_nets_overlap(nets[net_name1], + nets[net_name2], + vertical, + self.horizontal_pitch): vcg[net_name2].append(net_name1) # list of routes to do while vcg: - #from pprint import pformat - #print("VCG:\n",pformat(vcg)) + # from pprint import pformat + # print("VCG:\n",pformat(vcg)) # get a route from conflict graph with empty fanout set - net_name=None - for net_name,conflicts in vcg.items(): - if len(conflicts)==0: - vcg=remove_net_from_graph(net_name,vcg) + net_name = None + for net_name, conflicts in vcg.items(): + if len(conflicts) == 0: + vcg = remove_net_from_graph(net_name, vcg) break else: # FIXME: We don't support cyclic VCGs right now. - debug.error("Cyclic VCG in channel router.",-1) - - + debug.error("Cyclic VCG in channel router.", -1) # These are the pins we'll have to connect pin_list = nets[net_name] - #print("Routing:",net_name,[x.name for x in pin_list]) + # print("Routing:", net_name, [x.name for x in pin_list]) # Remove the net from other constriants in the VCG - vcg=remove_net_from_graph(net_name, vcg) + vcg = remove_net_from_graph(net_name, vcg) - # Add the trunk routes from the bottom up for horizontal or the left to right for vertical + # Add the trunk routes from the bottom up for + # horizontal or the left to right for vertical if vertical: - self.add_vertical_trunk_route(pin_list, offset, layer_stack, self.vertical_pitch) - offset += vector(self.vertical_pitch,0) + self.add_vertical_trunk_route(pin_list, + offset, + layer_stack, + self.vertical_pitch) + offset += vector(self.vertical_pitch, 0) else: - self.add_horizontal_trunk_route(pin_list, offset, layer_stack, self.horizontal_pitch) - offset += vector(0,self.horizontal_pitch) + self.add_horizontal_trunk_route(pin_list, + offset, + layer_stack, + self.horizontal_pitch) + offset += vector(0, self.horizontal_pitch) - - def create_vertical_channel_route(self, netlist, offset, - layer_stack=("metal1", "via1", "metal2")): + def create_vertical_channel_route(self, netlist, offset, layer_stack): """ Wrapper to create a vertical channel route """ self.create_channel_route(netlist, offset, layer_stack, vertical=True) - def create_horizontal_channel_route(self, netlist, offset, - layer_stack=("metal1", "via1", "metal2")): + def create_horizontal_channel_route(self, netlist, offset, layer_stack): """ Wrapper to create a horizontal channel route """ self.create_channel_route(netlist, offset, layer_stack, vertical=False) - def add_boundary(self, ll=vector(0,0), ur=None): + def add_boundary(self, ll=vector(0, 0), ur=None): """ Add boundary for debugging dimensions """ + if OPTS.netlist_only: + return + if "stdc" in techlayer.keys(): boundary_layer = "stdc" else: boundary_layer = "boundary" - if ur == None: - self.boundary = self.add_rect(layer=boundary_layer, - offset=ll, - height=self.height, - width=self.width) + if not ur: + self.bounding_box = self.add_rect(layer=boundary_layer, + offset=ll, + height=self.height, + width=self.width) else: - self.boundary = self.add_rect(layer=boundary_layer, - offset=ll, - height=ur.y-ll.y, - width=ur.x-ll.x) + self.bounding_box = self.add_rect(layer=boundary_layer, + offset=ll, + height=ur.y-ll.y, + width=ur.x-ll.x) def add_enclosure(self, insts, layer="nwell"): """ Add a layer that surrounds the given instances. Useful for creating wells, for example. Doesn't check for minimum widths or spacings.""" - xmin=insts[0].lx() - ymin=insts[0].by() - xmax=insts[0].rx() - ymax=insts[0].uy() + xmin = insts[0].lx() + ymin = insts[0].by() + xmax = insts[0].rx() + ymax = insts[0].uy() for inst in insts: xmin = min(xmin, inst.lx()) ymin = min(ymin, inst.by()) @@ -997,64 +1169,67 @@ class layout(): ymax = max(ymax, inst.uy()) self.add_rect(layer=layer, - offset=vector(xmin,ymin), + offset=vector(xmin, ymin), width=xmax-xmin, height=ymax-ymin) - def copy_power_pins(self, inst, name): """ - This will copy a power pin if it is on M3. If it is on M1, it will add a power via too. + This will copy a power pin if it is on the lowest power_grid layer. + If it is on M1, it will add a power via too. """ - pins=inst.get_pins(name) + pins = inst.get_pins(name) for pin in pins: - if pin.layer=="metal3": - self.add_layout_pin(name, pin.layer, pin.ll(), pin.width(), pin.height()) - elif pin.layer=="metal1": + if pin.layer == self.pwr_grid_layer: + self.add_layout_pin(name, + pin.layer, + pin.ll(), + pin.width(), + pin.height()) + elif pin.layer == "m1": self.add_power_pin(name, pin.center()) else: - debug.warning("{0} pins of {1} should be on metal3 or metal1 for supply router.".format(name,inst.name)) + debug.warning("{0} pins of {1} should be on {2} or metal1 for "\ + "supply router." + .format(name,inst.name,self.pwr_grid_layer)) - def add_power_pin(self, name, loc, vertical=False, start_layer="metal1"): - """ - Add a single power pin from M3 down to M1 at the given center location. - The starting layer is specified to determine which vias are needed. + def add_power_pin(self, name, loc, size=[1, 1], vertical=False, start_layer="m1"): + """ + Add a single power pin from the lowest power_grid layer down to M1 at + the given center location. The starting layer is specified to determine + which vias are needed. """ if vertical: - direction=("V","V") + direction = ("V", "V") else: - direction=("H","H") + direction = ("H", "H") - if start_layer=="metal1": - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=loc, - directions=direction) + via = self.add_via_stack_center(from_layer=start_layer, + to_layer=self.pwr_grid_layer, + size=size, + offset=loc, + direction=direction) - - if start_layer=="metal1" or start_layer=="metal2": - via=self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=loc, - directions=direction) - - if start_layer=="metal3": + if start_layer == self.pwr_grid_layer: self.add_layout_pin_rect_center(text=name, - layer="metal3", + layer=self.pwr_grid_layer, offset=loc) else: self.add_layout_pin_rect_center(text=name, - layer="metal3", + layer=self.pwr_grid_layer, offset=loc, width=via.width, height=via.height) def add_power_ring(self, bbox): """ - Create vdd and gnd power rings around an area of the bounding box argument. Must - have a supply_rail_width and supply_rail_pitch defined as a member variable. - Defines local variables of the left/right/top/bottom vdd/gnd center offsets - for use in other modules.. + Create vdd and gnd power rings around an area of the bounding box + argument. Must have a supply_rail_width and supply_rail_pitch + defined as a member variable. Defines local variables of the + left/right/top/bottom vdd/gnd center offsets for use in other + modules.. """ [ll, ur] = bbox @@ -1064,65 +1239,71 @@ class layout(): width = (ur.x-ll.x) + 3 * self.supply_rail_pitch - supply_rail_spacing # LEFT vertical rails - offset = ll + vector(-2*self.supply_rail_pitch, -2*self.supply_rail_pitch) - left_gnd_pin=self.add_layout_pin(text="gnd", - layer="metal2", - offset=offset, - width=self.supply_rail_width, - height=height) + offset = ll + vector(-2 * self.supply_rail_pitch, + -2 * self.supply_rail_pitch) + left_gnd_pin = self.add_layout_pin(text="gnd", + layer="m2", + offset=offset, + width=self.supply_rail_width, + height=height) + offset = ll + vector(-1 * self.supply_rail_pitch, + -1 * self.supply_rail_pitch) + left_vdd_pin = self.add_layout_pin(text="vdd", + layer="m2", + offset=offset, + width=self.supply_rail_width, + height=height) - offset = ll + vector(-1*self.supply_rail_pitch, -1*self.supply_rail_pitch) - left_vdd_pin=self.add_layout_pin(text="vdd", - layer="metal2", - offset=offset, - width=self.supply_rail_width, - height=height) - - # RIGHT vertical rails - offset = vector(ur.x,ll.y) + vector(0,-2*self.supply_rail_pitch) + # RIGHT vertical rails + offset = vector(ur.x, ll.y) + vector(0, -2 * self.supply_rail_pitch) right_gnd_pin = self.add_layout_pin(text="gnd", - layer="metal2", + layer="m2", offset=offset, width=self.supply_rail_width, height=height) - offset = vector(ur.x,ll.y) + vector(self.supply_rail_pitch,-1*self.supply_rail_pitch) - right_vdd_pin=self.add_layout_pin(text="vdd", - layer="metal2", - offset=offset, - width=self.supply_rail_width, - height=height) + offset = vector(ur.x, ll.y) + vector(self.supply_rail_pitch, + -1 * self.supply_rail_pitch) + right_vdd_pin = self.add_layout_pin(text="vdd", + layer="m2", + offset=offset, + width=self.supply_rail_width, + height=height) # BOTTOM horizontal rails - offset = ll + vector(-2*self.supply_rail_pitch, -2*self.supply_rail_pitch) - bottom_gnd_pin=self.add_layout_pin(text="gnd", - layer="metal1", - offset=offset, - width=width, - height=self.supply_rail_width) + offset = ll + vector(-2 * self.supply_rail_pitch, + -2 * self.supply_rail_pitch) + bottom_gnd_pin = self.add_layout_pin(text="gnd", + layer="m1", + offset=offset, + width=width, + height=self.supply_rail_width) - offset = ll + vector(-1*self.supply_rail_pitch, -1*self.supply_rail_pitch) - bottom_vdd_pin=self.add_layout_pin(text="vdd", - layer="metal1", - offset=offset, - width=width, - height=self.supply_rail_width) + offset = ll + vector(-1 * self.supply_rail_pitch, + -1 * self.supply_rail_pitch) + bottom_vdd_pin = self.add_layout_pin(text="vdd", + layer="m1", + offset=offset, + width=width, + height=self.supply_rail_width) - # TOP horizontal rails - offset = vector(ll.x, ur.y) + vector(-2*self.supply_rail_pitch,0) - top_gnd_pin=self.add_layout_pin(text="gnd", - layer="metal1", - offset=offset, - width=width, - height=self.supply_rail_width) + # TOP horizontal rails + offset = vector(ll.x, ur.y) + vector(-2 * self.supply_rail_pitch, + 0) + top_gnd_pin = self.add_layout_pin(text="gnd", + layer="m1", + offset=offset, + width=width, + height=self.supply_rail_width) - offset = vector(ll.x, ur.y) + vector(-1*self.supply_rail_pitch, self.supply_rail_pitch) - top_vdd_pin=self.add_layout_pin(text="vdd", - layer="metal1", - offset=offset, - width=width, - height=self.supply_rail_width) + offset = vector(ll.x, ur.y) + vector(-1 * self.supply_rail_pitch, + self.supply_rail_pitch) + top_vdd_pin = self.add_layout_pin(text="vdd", + layer="m1", + offset=offset, + width=width, + height=self.supply_rail_width) # Remember these for connecting things in the design self.left_gnd_x_center = left_gnd_pin.cx() @@ -1135,14 +1316,13 @@ class layout(): self.top_gnd_y_center = top_gnd_pin.cy() self.top_vdd_y_center = top_vdd_pin.cy() - # Find the number of vias for this pitch self.supply_vias = 1 from sram_factory import factory while True: - c=factory.create(module_type="contact", - layer_stack=("metal1","via1","metal2"), - dimensions=(self.supply_vias, self.supply_vias)) + c = factory.create(module_type="contact", + layer_stack=self.m1_stack, + dimensions=(self.supply_vias, self.supply_vias)) if c.second_layer_width < self.supply_rail_width and c.second_layer_height < self.supply_rail_width: self.supply_vias += 1 else: @@ -1159,15 +1339,17 @@ class layout(): vector(self.right_vdd_x_center, self.top_vdd_y_center)] for pt in via_points: - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=pt, - size = (self.supply_vias, self.supply_vias)) + size=(self.supply_vias, + self.supply_vias)) - - def pdf_write(self, pdf_name): - # NOTE: Currently does not work (Needs further research) - #self.pdf_name = self.name + ".pdf" + """ + Display the layout to a PDF file. + """ + debug.error("NOTE: Currently does not work (Needs further research)") + # self.pdf_name = self.name + ".pdf" debug.info(0, "Writing to {}".format(pdf_name)) pdf = gdsMill.pdfLayout(self.gds) @@ -1187,22 +1369,22 @@ class layout(): def print_attr(self): """Prints a list of attributes for the current layout object""" - debug.info(0, + debug.info(0, "|==============================================================================|") - debug.info(0, + debug.info(0, "|========= LIST OF OBJECTS (Rects) FOR: " + self.name) - debug.info(0, + debug.info(0, "|==============================================================================|") for obj in self.objs: debug.info(0, "layer={0} : offset={1} : size={2}".format(obj.layerNumber, obj.offset, obj.size)) - debug.info(0, + debug.info(0, "|==============================================================================|") - debug.info(0, + debug.info(0, "|========= LIST OF INSTANCES FOR: " + self.name) - debug.info(0, + debug.info(0, "|==============================================================================|") for inst in self.insts: debug.info(0, "name={0} : mod={1} : offset={2}".format(inst.name, diff --git a/compiler/base/pin_layout.py b/compiler/base/pin_layout.py index ea114ac6..a057f3e0 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -22,30 +22,53 @@ class pin_layout: self.name = name # repack the rect as a vector, just in case if type(rect[0]) == vector: - self.rect = rect + self._rect = rect else: - self.rect = [vector(rect[0]), vector(rect[1])] + self._rect = [vector(rect[0]), vector(rect[1])] # snap the rect to the grid - self.rect = [x.snap_to_grid() for x in self.rect] + self._rect = [x.snap_to_grid() for x in self.rect] debug.check(self.width() > 0, "Zero width pin.") debug.check(self.height() > 0, "Zero height pin.") # if it's a string, use the name if type(layer_name_pp) == str: - self.layer = layer_name_pp + self._layer = layer_name_pp # else it is required to be a lpp else: for (layer_name, lpp) in layer.items(): if not lpp: continue if self.same_lpp(layer_name_pp, lpp): - self.layer = layer_name + self._layer = layer_name break else: debug.error("Couldn't find layer {}".format(layer_name_pp), -1) self.lpp = layer[self.layer] + self._recompute_hash() + + @property + def layer(self): + return self._layer + + @layer.setter + def layer(self, l): + self._layer = l + self._recompute_hash() + + @property + def rect(self): + return self._rect + + @rect.setter + def rect(self, r): + self._rect = r + self._recompute_hash() + + def _recompute_hash(self): + """ Recompute the hash for our hash cache """ + self._hash = hash(repr(self)) def __str__(self): """ override print function output """ @@ -64,8 +87,12 @@ class pin_layout: self.rect[1]) def __hash__(self): - """ Implement the hash function for sets etc. """ - return hash(repr(self)) + """ + Implement the hash function for sets etc. We only return a cached + value, that is updated when either 'rect' or 'layer' are changed. This + is a major speedup, if pin_layout is used as a key for dicts. + """ + return self._hash def __lt__(self, other): """ Provide a function for ordering items by the ll point """ @@ -101,7 +128,14 @@ class pin_layout: max_y = max(max_y, pin.ur().y) self.rect = [vector(min_x, min_y), vector(max_x, max_y)] - + + def fix_minarea(self): + """ + Try to fix minimum area rule. + """ + min_area = drc("{}_minarea".format(self.layer)) + pass + def inflate(self, spacing=None): """ Inflate the rectangle by the spacing (or other rule) diff --git a/compiler/base/utils.py b/compiler/base/utils.py index f02b2b85..b6a62788 100644 --- a/compiler/base/utils.py +++ b/compiler/base/utils.py @@ -96,10 +96,12 @@ def get_libcell_size(name, units, lpp): Open a GDS file and return the library cell size from either the bounding box or a border layer. """ + cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds" return(get_gds_size(name, cell_gds, units, lpp)) + def get_gds_pins(pin_names, name, gds_filename, units): """ Open a GDS file and find the pins in pin_names as text on a given layer. @@ -128,6 +130,7 @@ def get_libcell_pins(pin_list, name, units): Open a GDS file and find the pins in pin_list as text on a given layer. Return these as a rectangle layer pair for each pin. """ + cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds" return(get_gds_pins(pin_list, name, cell_gds, units)) diff --git a/compiler/base/vector.py b/compiler/base/vector.py index 8bf09f7d..582b9391 100644 --- a/compiler/base/vector.py +++ b/compiler/base/vector.py @@ -28,6 +28,7 @@ class vector(): else: self.x = float(x) self.y = float(y) + self._hash = hash((self.x,self.y)) def __str__(self): """ override print function output """ @@ -49,7 +50,7 @@ class vector(): else: self.x=float(value[0]) self.y=float(value[1]) - + def __getitem__(self, index): """ override getitem function @@ -97,7 +98,7 @@ class vector(): Note: This assumes that you DON'T CHANGE THE VECTOR or it will break things. """ - return hash((self.x,self.y)) + return self._hash def snap_to_grid(self): self.x = self.snap_offset_to_grid(self.x) diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index b22c9a46..68e70e7e 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -7,7 +7,8 @@ # import debug import utils -from tech import GDS, layer +from tech import GDS, layer, parameter +from tech import cell_properties as props import bitcell_base @@ -19,9 +20,21 @@ class bitcell(bitcell_base.bitcell_base): library. """ - pin_names = ["bl", "br", "wl", "vdd", "gnd"] + # If we have a split WL bitcell, if not be backwards + # compatible in the tech file + + if props.bitcell.split_wl: + pin_names = ["bl", "br", "wl0", "wl1", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] + else: + pin_names = [props.bitcell.cell_6t.pin.bl, + props.bitcell.cell_6t.pin.br, + props.bitcell.cell_6t.pin.wl, + props.bitcell.cell_6t.pin.vdd, + props.bitcell.cell_6t.pin.gnd] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] storage_nets = ['Q', 'Qbar'] - type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] + (width, height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"]) @@ -37,42 +50,47 @@ class bitcell(bitcell_base.bitcell_base): self.pin_map = bitcell.pin_map self.add_pin_types(self.type_list) self.nets_match = self.do_nets_exist(self.storage_nets) - + def get_all_wl_names(self): """ Creates a list of all wordline pin names """ - row_pins = ["wl"] + if props.bitcell.split_wl: + row_pins = ["wl0", "wl1"] + else: + row_pins = [props.bitcell.cell_6t.pin.wl] return row_pins - + def get_all_bitline_names(self): """ Creates a list of all bitline pin names (both bl and br) """ - column_pins = ["bl", "br"] + pin = props.bitcell.cell_6t.pin + column_pins = [pin.bl, pin.br] return column_pins - + def get_all_bl_names(self): """ Creates a list of all bl pins names """ - column_pins = ["bl"] - return column_pins - + return [props.bitcell.cell_6t.pin.bl] + def get_all_br_names(self): """ Creates a list of all br pins names """ - column_pins = ["br"] - return column_pins - + return [props.bitcell.cell_6t.pin.br] + def get_bl_name(self, port=0): """Get bl name""" debug.check(port == 0, "One port for bitcell only.") - return "bl" - + return props.bitcell.cell_6t.pin.bl + def get_br_name(self, port=0): """Get bl name""" debug.check(port == 0, "One port for bitcell only.") - return "br" + return props.bitcell.cell_6t.pin.br def get_wl_name(self, port=0): """Get wl name""" - debug.check(port == 0, "One port for bitcell only.") - return "wl" - + if props.bitcell.split_wl: + return "wl{}".format(port) + else: + debug.check(port == 0, "One port for bitcell only.") + return props.bitcell.cell_6t.pin.wl + def build_graph(self, graph, inst_name, port_nets): """ Adds edges based on inputs/outputs. diff --git a/compiler/bitcells/bitcell_1rw_1r.py b/compiler/bitcells/bitcell_1rw_1r.py index 0280f3cb..e5eb3043 100644 --- a/compiler/bitcells/bitcell_1rw_1r.py +++ b/compiler/bitcells/bitcell_1rw_1r.py @@ -8,6 +8,7 @@ import debug import utils from tech import GDS, layer, parameter, drc +from tech import cell_properties as props import logical_effort import bitcell_base @@ -20,7 +21,15 @@ class bitcell_1rw_1r(bitcell_base.bitcell_base): library. """ - pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + pin_names = [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.bl1, + props.bitcell.cell_1rw1r.pin.br1, + props.bitcell.cell_1rw1r.pin.wl0, + props.bitcell.cell_1rw1r.pin.wl1, + props.bitcell.cell_1rw1r.pin.vdd, + props.bitcell.cell_1rw1r.pin.gnd] + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] storage_nets = ['Q', 'Q_bar'] @@ -39,85 +48,92 @@ class bitcell_1rw_1r(bitcell_base.bitcell_base): self.pin_map = bitcell_1rw_1r.pin_map self.add_pin_types(self.type_list) self.nets_match = self.do_nets_exist(self.storage_nets) - + + pin_names = bitcell_1rw_1r.pin_names + self.bl_names = [pin_names[0], pin_names[2]] + self.br_names = [pin_names[1], pin_names[3]] + self.wl_names = [pin_names[4], pin_names[5]] + def get_bitcell_pins(self, col, row): """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ - bitcell_pins = ["bl0_{0}".format(col), - "br0_{0}".format(col), - "bl1_{0}".format(col), - "br1_{0}".format(col), - "wl0_{0}".format(row), - "wl1_{0}".format(row), + pin_name = props.bitcell.cell_1rw1r.pin + bitcell_pins = ["{0}_{1}".format(pin_name.bl0, col), + "{0}_{1}".format(pin_name.br0, col), + "{0}_{1}".format(pin_name.bl1, col), + "{0}_{1}".format(pin_name.br1, col), + "{0}_{1}".format(pin_name.wl0, row), + "{0}_{1}".format(pin_name.wl1, row), "vdd", "gnd"] return bitcell_pins - + def get_all_wl_names(self): """ Creates a list of all wordline pin names """ - row_pins = ["wl0", "wl1"] - return row_pins - + return [props.bitcell.cell_1rw1r.pin.wl0, + props.bitcell.cell_1rw1r.pin.wl1] + def get_all_bitline_names(self): """ Creates a list of all bitline pin names (both bl and br) """ - column_pins = ["bl0", "br0", "bl1", "br1"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.bl1, + props.bitcell.cell_1rw1r.pin.br1] + def get_all_bl_names(self): """ Creates a list of all bl pins names """ - column_pins = ["bl0", "bl1"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.bl1] + def get_all_br_names(self): """ Creates a list of all br pins names """ - column_pins = ["br0", "br1"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.br1] + def get_read_bl_names(self): """ Creates a list of bl pin names associated with read ports """ - column_pins = ["bl0", "bl1"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.bl1] + def get_read_br_names(self): """ Creates a list of br pin names associated with read ports """ - column_pins = ["br0", "br1"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.br1] + def get_write_bl_names(self): """ Creates a list of bl pin names associated with write ports """ - column_pins = ["bl0"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.bl0] + def get_write_br_names(self): """ Creates a list of br pin names asscociated with write ports""" - column_pins = ["br0"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.br1] + def get_bl_name(self, port=0): """Get bl name by port""" debug.check(port < 2, "Two ports for bitcell_1rw_1r only.") - return "bl{}".format(port) - + return self.bl_names[port] + def get_br_name(self, port=0): """Get bl name by port""" debug.check(port < 2, "Two ports for bitcell_1rw_1r only.") - return "br{}".format(port) + return self.br_names[port] def get_wl_name(self, port=0): """Get wl name by port""" debug.check(port < 2, "Two ports for bitcell_1rw_1r only.") - return "wl{}".format(port) - + return self.wl_names[port] + def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph. Multiport bitcell timing graph is too complex to use the add_graph_edges function.""" pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)} # Edges hardcoded here. Essentially wl->bl/br for both ports. # Port 0 edges - graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self) - graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self) + pins = props.bitcell.cell_1rw1r.pin + graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.bl0], self) + graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.br0], self) # Port 1 edges - graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) - graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self) + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self) diff --git a/compiler/bitcells/bitcell_1w_1r.py b/compiler/bitcells/bitcell_1w_1r.py index a92dc75d..56bd0b45 100644 --- a/compiler/bitcells/bitcell_1w_1r.py +++ b/compiler/bitcells/bitcell_1w_1r.py @@ -8,6 +8,7 @@ import debug import utils from tech import GDS, layer +from tech import cell_properties as props import bitcell_base @@ -19,7 +20,14 @@ class bitcell_1w_1r(bitcell_base.bitcell_base): library. """ - pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + pin_names = [props.bitcell.cell_1w1r.pin.bl0, + props.bitcell.cell_1w1r.pin.br0, + props.bitcell.cell_1w1r.pin.bl1, + props.bitcell.cell_1w1r.pin.br1, + props.bitcell.cell_1w1r.pin.wl0, + props.bitcell.cell_1w1r.pin.wl1, + props.bitcell.cell_1w1r.pin.vdd, + props.bitcell.cell_1w1r.pin.gnd] type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] storage_nets = ['Q', 'Q_bar'] @@ -39,80 +47,88 @@ class bitcell_1w_1r(bitcell_base.bitcell_base): self.add_pin_types(self.type_list) self.nets_match = self.do_nets_exist(self.storage_nets) + pin_names = bitcell_1w_1r.pin_names + self.bl_names = [pin_names[0], pin_names[2]] + self.br_names = [pin_names[1], pin_names[3]] + self.wl_names = [pin_names[4], pin_names[5]] + + def get_bitcell_pins(self, col, row): """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ - bitcell_pins = ["bl0_{0}".format(col), - "br0_{0}".format(col), - "bl1_{0}".format(col), - "br1_{0}".format(col), - "wl0_{0}".format(row), - "wl1_{0}".format(row), + pin_name = props.bitcell.cell_1w1r.pin + bitcell_pins = ["{0}_{1}".format(pin_name.bl0, col), + "{0}_{1}".format(pin_name.br0, col), + "{0}_{1}".format(pin_name.bl1, col), + "{0}_{1}".format(pin_name.br1, col), + "{0}_{1}".format(pin_name.wl0, row), + "{0}_{1}".format(pin_name.wl1, row), "vdd", "gnd"] return bitcell_pins - + def get_all_wl_names(self): """ Creates a list of all wordline pin names """ - row_pins = ["wl0", "wl1"] - return row_pins - + return [props.bitcell.cell_1w1r.pin.wl0, + props.bitcell.cell_1w1r.pin.wl1] + def get_all_bitline_names(self): """ Creates a list of all bitline pin names (both bl and br) """ - column_pins = ["bl0", "br0", "bl1", "br1"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.bl0, + props.bitcell.cell_1w1r.pin.br0, + props.bitcell.cell_1w1r.pin.bl1, + props.bitcell.cell_1w1r.pin.br1] + def get_all_bl_names(self): """ Creates a list of all bl pins names """ - column_pins = ["bl0", "bl1"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.bl0, + props.bitcell.cell_1w1r.pin.bl1] + def get_all_br_names(self): """ Creates a list of all br pins names """ - column_pins = ["br0", "br1"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.br0, + props.bitcell.cell_1w1r.pin.br1] + def get_read_bl_names(self): """ Creates a list of bl pin names associated with read ports """ - column_pins = ["bl0", "bl1"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.bl0, + props.bitcell.cell_1w1r.pin.bl1] + def get_read_br_names(self): """ Creates a list of br pin names associated with read ports """ - column_pins = ["br0", "br1"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.br0, + props.bitcell.cell_1w1r.pin.br1] + def get_write_bl_names(self): """ Creates a list of bl pin names associated with write ports """ - column_pins = ["bl0"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.bl0] + def get_write_br_names(self): """ Creates a list of br pin names asscociated with write ports""" - column_pins = ["br0"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.br1] + def get_bl_name(self, port=0): """Get bl name by port""" - return "bl{}".format(port) - + return self.bl_names[port] + def get_br_name(self, port=0): """Get bl name by port""" - return "br{}".format(port) - + return self.br_names[port] + def get_wl_name(self, port=0): """Get wl name by port""" debug.check(port < 2, "Two ports for bitcell_1rw_1r only.") - return "wl{}".format(port) - + return self.wl_names[port] + def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph. Multiport bitcell timing graph is too complex to use the add_graph_edges function.""" pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)} + pins = props.bitcell.cell_1w1r.pin # Edges hardcoded here. Essentially wl->bl/br for both ports. # Port 0 edges - graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) - graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self) + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self) # Port 1 is a write port, so its timing is not considered here. diff --git a/compiler/bitcells/dummy_bitcell.py b/compiler/bitcells/dummy_bitcell.py index 98da96e2..116ea3ed 100644 --- a/compiler/bitcells/dummy_bitcell.py +++ b/compiler/bitcells/dummy_bitcell.py @@ -8,6 +8,7 @@ import debug import utils from tech import GDS, layer +from tech import cell_properties as props import bitcell_base @@ -18,8 +19,12 @@ class dummy_bitcell(bitcell_base.bitcell_base): the layout and netlist should be available in the technology library. """ + pin_names = [props.bitcell.cell_6t.pin.bl, + props.bitcell.cell_6t.pin.br, + props.bitcell.cell_6t.pin.wl, + props.bitcell.cell_6t.pin.vdd, + props.bitcell.cell_6t.pin.gnd] - pin_names = ["bl", "br", "wl", "vdd", "gnd"] (width, height) = utils.get_libcell_size("dummy_cell_6t", GDS["unit"], layer["boundary"]) diff --git a/compiler/bitcells/dummy_bitcell_1rw_1r.py b/compiler/bitcells/dummy_bitcell_1rw_1r.py index 401e9f85..d29c804f 100644 --- a/compiler/bitcells/dummy_bitcell_1rw_1r.py +++ b/compiler/bitcells/dummy_bitcell_1rw_1r.py @@ -8,6 +8,7 @@ import debug import utils from tech import GDS, layer +from tech import cell_properties as props import bitcell_base @@ -18,7 +19,15 @@ class dummy_bitcell_1rw_1r(bitcell_base.bitcell_base): is a hand-made cell, so the layout and netlist should be available in the technology library. """ - pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + pin_names = [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.bl1, + props.bitcell.cell_1rw1r.pin.br1, + props.bitcell.cell_1rw1r.pin.wl0, + props.bitcell.cell_1rw1r.pin.wl1, + props.bitcell.cell_1rw1r.pin.vdd, + props.bitcell.cell_1rw1r.pin.gnd] + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width, height) = utils.get_libcell_size("dummy_cell_1rw_1r", diff --git a/compiler/bitcells/dummy_bitcell_1w_1r.py b/compiler/bitcells/dummy_bitcell_1w_1r.py index 54192f71..758a5b16 100644 --- a/compiler/bitcells/dummy_bitcell_1w_1r.py +++ b/compiler/bitcells/dummy_bitcell_1w_1r.py @@ -8,6 +8,7 @@ import debug import utils from tech import GDS, layer +from tech import cell_properties as props import bitcell_base @@ -18,7 +19,14 @@ class dummy_bitcell_1w_1r(bitcell_base.bitcell_base): is a hand-made cell, so the layout and netlist should be available in the technology library. """ - pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + pin_names = [props.bitcell.cell_1w1r.pin.bl0, + props.bitcell.cell_1w1r.pin.br0, + props.bitcell.cell_1w1r.pin.bl1, + props.bitcell.cell_1w1r.pin.br1, + props.bitcell.cell_1w1r.pin.wl0, + props.bitcell.cell_1w1r.pin.wl1, + props.bitcell.cell_1w1r.pin.vdd, + props.bitcell.cell_1w1r.pin.gnd] type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width, height) = utils.get_libcell_size("dummy_cell_1w_1r", diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index 0e0e1bf7..61bd9710 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -199,7 +199,6 @@ class pbitcell(bitcell_base.bitcell_base): def calculate_spacing(self): """ Calculate transistor spacings """ - # calculate metal contact extensions over transistor active readwrite_nmos_contact_extension = 0.5 * \ (self.readwrite_nmos.active_contact.height - self.readwrite_nmos.active_height) @@ -213,20 +212,20 @@ class pbitcell(bitcell_base.bitcell_base): # y-offset for the access transistor's gate contact self.gate_contact_yoffset = max_contact_extension + self.m2_space \ - + 0.5 * max(contact.poly.height, contact.m1m2.height) + + 0.5 * max(contact.poly_contact.height, contact.m1_via.height) # y-position of access transistors - self.port_ypos = self.m1_space + 0.5 * contact.m1m2.height + self.gate_contact_yoffset + self.port_ypos = self.m1_space + 0.5 * contact.m1_via.height + self.gate_contact_yoffset # y-position of inverter nmos self.inverter_nmos_ypos = self.port_ypos # spacing between ports (same for read/write and write ports) self.bitline_offset = -0.5 * self.readwrite_nmos.active_width \ - + 0.5 * contact.m1m2.height \ + + 0.5 * contact.m1_via.height \ + self.m2_space + self.m2_width m2_constraint = self.bitline_offset + self.m2_space \ - + 0.5 * contact.m1m2.height \ + + 0.5 * contact.m1_via.height \ - 0.5 * self.readwrite_nmos.active_width self.write_port_spacing = max(self.active_space, self.m1_space, @@ -234,7 +233,7 @@ class pbitcell(bitcell_base.bitcell_base): self.read_port_spacing = self.bitline_offset + self.m2_space # spacing between cross coupled inverters - self.inverter_to_inverter_spacing = contact.poly.width + self.m1_space + self.inverter_to_inverter_spacing = contact.poly_contact.width + self.m1_space # calculations related to inverter connections inverter_pmos_contact_extension = 0.5 * \ @@ -243,19 +242,19 @@ class pbitcell(bitcell_base.bitcell_base): (self.inverter_nmos.active_contact.height - self.inverter_nmos.active_height) self.inverter_gap = max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \ - + self.poly_to_poly_contact + 2 * contact.poly.width \ + + self.poly_to_contact + 2 * contact.poly_contact.width \ + self.m1_space + inverter_pmos_contact_extension self.cross_couple_lower_ypos = self.inverter_nmos_ypos \ + self.inverter_nmos.active_height \ + max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \ - + 0.5 * contact.poly.width + + 0.5 * contact.poly_contact.width self.cross_couple_upper_ypos = self.inverter_nmos_ypos \ + self.inverter_nmos.active_height \ + max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \ - + self.poly_to_poly_contact \ - + 1.5 * contact.poly.width + + self.poly_to_contact \ + + 1.5 * contact.poly_contact.width # spacing between wordlines (and gnd) self.m1_offset = -0.5 * self.m1_width @@ -263,7 +262,7 @@ class pbitcell(bitcell_base.bitcell_base): # spacing for vdd implant_constraint = max(inverter_pmos_contact_extension, 0) \ + 2 * self.implant_enclose_active \ - + 0.5 * (contact.well.width - self.m1_width) + + 0.5*(self.inverter_pmos.active_contact.height - self.m1_width) metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space self.vdd_offset = max(implant_constraint, metal1_constraint) + 0.5*self.m1_width @@ -291,7 +290,7 @@ class pbitcell(bitcell_base.bitcell_base): (self.write_nmos.active_width + self.write_port_spacing) \ - self.num_r_ports * \ (self.read_port_width + self.read_port_spacing) \ - - self.bitline_offset - 0.5 * contact.m1m2.width + - self.bitline_offset - 0.5 * contact.m1_via.width self.width = -2 * self.leftmost_xpos self.height = self.topmost_ypos - self.botmost_ypos @@ -364,31 +363,29 @@ class pbitcell(bitcell_base.bitcell_base): self.inverter_pmos_right.get_pin("G").bc()]) # connect output (drain/source) of inverters - self.add_path("metal1", + self.add_path("m1", [self.inverter_nmos_left.get_pin("D").uc(), self.inverter_pmos_left.get_pin("D").bc()], - width=contact.active.second_layer_width) - self.add_path("metal1", + width=self.inverter_nmos_left.get_pin("D").width()) + self.add_path("m1", [self.inverter_nmos_right.get_pin("S").uc(), self.inverter_pmos_right.get_pin("S").bc()], - width=contact.active.second_layer_width) + width=self.inverter_nmos_right.get_pin("S").width()) # add contacts to connect gate poly to drain/source # metal1 (to connect Q to Q_bar) contact_offset_left = vector(self.inverter_nmos_left.get_pin("D").rc().x \ - + 0.5 * contact.poly.height, + + 0.5 * contact.poly_contact.height, self.cross_couple_upper_ypos) - self.add_via_center(layers=("poly", "contact", "metal1"), - offset=contact_offset_left, - directions=("H", "H")) + self.add_via_center(layers=self.poly_stack, + offset=contact_offset_left) contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x \ - - 0.5*contact.poly.height, + - 0.5*contact.poly_contact.height, self.cross_couple_lower_ypos) - self.add_via_center(layers=("poly", "contact", "metal1"), - offset=contact_offset_right, - directions=("H", "H")) + self.add_via_center(layers=self.poly_stack, + offset=contact_offset_right) # connect contacts to gate poly (cross couple connections) gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").lc().x, @@ -404,7 +401,7 @@ class pbitcell(bitcell_base.bitcell_base): # Add rails for vdd and gnd gnd_ypos = self.m1_offset - self.total_ports * self.m1_pitch self.gnd_position = vector(0, gnd_ypos) - self.add_rect_center(layer="metal1", + self.add_rect_center(layer="m1", offset=self.gnd_position, width=self.width) self.add_power_pin("gnd", vector(0, gnd_ypos)) @@ -416,7 +413,7 @@ class pbitcell(bitcell_base.bitcell_base): + self.inverter_pmos.active_height \ + self.vdd_offset self.vdd_position = vector(0, vdd_ypos) - self.add_rect_center(layer="metal1", + self.add_rect_center(layer="m1", offset=self.vdd_position, width=self.width) self.add_power_pin("vdd", vector(0, vdd_ypos)) @@ -489,7 +486,7 @@ class pbitcell(bitcell_base.bitcell_base): rwwl_ypos = self.m1_offset - k * self.m1_pitch self.rwwl_positions[k] = vector(0, rwwl_ypos) self.add_layout_pin_rect_center(text=self.rw_wl_names[k], - layer="metal1", + layer="m1", offset=self.rwwl_positions[k], width=self.width) @@ -499,7 +496,7 @@ class pbitcell(bitcell_base.bitcell_base): + 0.5 * self.m2_width self.rwbl_positions[k] = vector(rwbl_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.rw_bl_names[k], - layer="metal2", + layer="m2", offset=self.rwbl_positions[k], height=self.height) @@ -509,7 +506,7 @@ class pbitcell(bitcell_base.bitcell_base): - 0.5 * self.m2_width self.rwbr_positions[k] = vector(rwbr_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.rw_br_names[k], - layer="metal2", + layer="m2", offset=self.rwbr_positions[k], height=self.height) @@ -586,7 +583,7 @@ class pbitcell(bitcell_base.bitcell_base): - k * self.m1_pitch self.wwl_positions[k] = vector(0, wwl_ypos) self.add_layout_pin_rect_center(text=self.w_wl_names[k], - layer="metal1", + layer="m1", offset=self.wwl_positions[k], width=self.width) @@ -596,7 +593,7 @@ class pbitcell(bitcell_base.bitcell_base): + 0.5 * self.m2_width self.wbl_positions[k] = vector(wbl_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.w_bl_names[k], - layer="metal2", + layer="m2", offset=self.wbl_positions[k], height=self.height) @@ -606,7 +603,7 @@ class pbitcell(bitcell_base.bitcell_base): - 0.5 * self.m2_width self.wbr_positions[k] = vector(wbr_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.w_br_names[k], - layer="metal2", + layer="m2", offset=self.wbr_positions[k], height=self.height) @@ -713,7 +710,7 @@ class pbitcell(bitcell_base.bitcell_base): - k * self.m1_pitch self.rwl_positions[k] = vector(0, rwl_ypos) self.add_layout_pin_rect_center(text=self.r_wl_names[k], - layer="metal1", + layer="m1", offset=self.rwl_positions[k], width=self.width) @@ -723,7 +720,7 @@ class pbitcell(bitcell_base.bitcell_base): + 0.5 * self.m2_width self.rbl_positions[k] = vector(rbl_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.r_bl_names[k], - layer="metal2", + layer="m2", offset=self.rbl_positions[k], height=self.height) @@ -733,7 +730,7 @@ class pbitcell(bitcell_base.bitcell_base): - 0.5 * self.m2_width self.rbr_positions[k] = vector(rbr_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.r_br_names[k], - layer="metal2", + layer="m2", offset=self.rbr_positions[k], height=self.height) @@ -771,25 +768,24 @@ class pbitcell(bitcell_base.bitcell_base): # first transistor on either side of the cross coupled inverters # does not need to route to wordline on metal2 if (k == 0) or (k == 1): - self.add_via_center(layers=("poly", "contact", "metal1"), + self.add_via_center(layers=self.poly_stack, offset=port_contact_offset) self.add_path("poly", [gate_offset, port_contact_offset]) - self.add_path("metal1", + self.add_path("m1", [port_contact_offset, wl_contact_offset]) else: - self.add_via_center(layers=("poly", "contact", "metal1"), + self.add_via_center(layers=self.poly_stack, offset=port_contact_offset) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=port_contact_offset) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=wl_contact_offset, - directions=("H", "H")) + self.add_via_center(layers=self.m1_stack, + offset=wl_contact_offset) self.add_path("poly", [gate_offset, port_contact_offset]) - self.add_path("metal2", + self.add_path("m2", [port_contact_offset, wl_contact_offset]) def route_bitlines(self): @@ -824,11 +820,11 @@ class pbitcell(bitcell_base.bitcell_base): # Leave bitline disconnected if a dummy cell if not self.dummy_bitcell: - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=port_contact_offest) - self.add_path("metal2", - [port_contact_offest, bl_offset], width=contact.m1m2.height) + self.add_path("m2", + [port_contact_offest, bl_offset], width=contact.m1_via.height) for k in range(self.total_ports): port_contact_offest = right_port_transistors[k].get_pin("D").center() @@ -836,11 +832,11 @@ class pbitcell(bitcell_base.bitcell_base): # Leave bitline disconnected if a dummy cell if not self.dummy_bitcell: - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=port_contact_offest) - self.add_path("metal2", - [port_contact_offest, br_offset], width=contact.m1m2.height) + self.add_path("m2", + [port_contact_offest, br_offset], width=contact.m1_via.height) def route_supply(self): """ Route inverter nmos and read-access nmos to gnd. Route inverter pmos to vdd. """ @@ -853,30 +849,29 @@ class pbitcell(bitcell_base.bitcell_base): nmos_contact_positions.append(self.read_access_nmos_right[k].get_pin("S").center()) for position in nmos_contact_positions: - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=position) if position.x > 0: - contact_correct = 0.5 * contact.m1m2.height + contact_correct = 0.5 * contact.m1_via.height else: - contact_correct = -0.5 * contact.m1m2.height + contact_correct = -0.5 * contact.m1_via.height supply_offset = vector(position.x + contact_correct, self.gnd_position.y) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=supply_offset, - directions=("H", "H")) + self.add_via_center(layers=self.m1_stack, + offset=supply_offset) - self.add_path("metal2", [position, supply_offset]) + self.add_path("m2", [position, supply_offset]) # route inverter pmos to vdd vdd_pos_left = vector(self.inverter_nmos_left.get_pin("S").uc().x, self.vdd_position.y) - self.add_path("metal1", + self.add_path("m1", [self.inverter_pmos_left.get_pin("S").uc(), vdd_pos_left]) vdd_pos_right = vector(self.inverter_nmos_right.get_pin("D").uc().x, self.vdd_position.y) - self.add_path("metal1", + self.add_path("m1", [self.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right]) def route_readwrite_access(self): @@ -889,14 +884,14 @@ class pbitcell(bitcell_base.bitcell_base): self.cross_couple_lower_ypos) Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos) - self.add_path("metal1", + self.add_path("m1", [self.readwrite_nmos_left[k].get_pin("D").uc(), mid, Q_pos]) mid = vector(self.readwrite_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos) Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos) - self.add_path("metal1", + self.add_path("m1", [self.readwrite_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos]) def route_write_access(self): @@ -909,14 +904,14 @@ class pbitcell(bitcell_base.bitcell_base): self.cross_couple_lower_ypos) Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos) - self.add_path("metal1", + self.add_path("m1", [self.write_nmos_left[k].get_pin("D").uc(), mid, Q_pos]) mid = vector(self.write_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos) Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos) - self.add_path("metal1", + self.add_path("m1", [self.write_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos]) def route_read_access(self): @@ -926,18 +921,16 @@ class pbitcell(bitcell_base.bitcell_base): """ # add poly to metal1 contacts for gates of the inverters left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x \ - - self.poly_to_poly_contact - 0.5*contact.poly.width, + - self.poly_to_contact - 0.5*contact.poly_contact.width, self.cross_couple_upper_ypos) - self.add_via_center(layers=("poly", "contact", "metal1"), - offset=left_storage_contact, - directions=("H", "H")) + self.add_via_center(layers=self.poly_stack, + offset=left_storage_contact) right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x \ - + self.poly_to_poly_contact + 0.5*contact.poly.width, + + self.poly_to_contact + 0.5*contact.poly_contact.width, self.cross_couple_upper_ypos) - self.add_via_center(layers=("poly", "contact", "metal1"), - offset=right_storage_contact, - directions=("H", "H")) + self.add_via_center(layers=self.poly_stack, + offset=right_storage_contact) inverter_gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_upper_ypos) self.add_path("poly", [left_storage_contact, inverter_gate_offset_left]) @@ -952,7 +945,7 @@ class pbitcell(bitcell_base.bitcell_base): + vector(0, self.gate_contact_yoffset - self.poly_extend_active) - self.add_via_center(layers=("poly", "contact", "metal1"), + self.add_via_center(layers=self.poly_stack, offset=port_contact_offset) self.add_path("poly", @@ -960,14 +953,14 @@ class pbitcell(bitcell_base.bitcell_base): mid = vector(self.read_access_nmos_left[k].get_pin("G").uc().x, self.cross_couple_upper_ypos) - self.add_path("metal1", + self.add_path("m1", [port_contact_offset, mid, left_storage_contact]) port_contact_offset = self.read_access_nmos_right[k].get_pin("G").uc() \ + vector(0, self.gate_contact_yoffset - self.poly_extend_active) - self.add_via_center(layers=("poly", "contact", "metal1"), + self.add_via_center(layers=self.poly_stack, offset=port_contact_offset) self.add_path("poly", @@ -975,7 +968,7 @@ class pbitcell(bitcell_base.bitcell_base): mid = vector(self.read_access_nmos_right[k].get_pin("G").uc().x, self.cross_couple_upper_ypos) - self.add_path("metal1", + self.add_path("m1", [port_contact_offset, mid, right_storage_contact]) def extend_well(self): @@ -984,33 +977,36 @@ class pbitcell(bitcell_base.bitcell_base): """ # extend pwell to encompass entire nmos region of the cell up to the # height of the tallest nmos transistor - max_nmos_well_height = max(self.inverter_nmos.cell_well_height, - self.readwrite_nmos.cell_well_height, - self.write_nmos.cell_well_height, - self.read_nmos.cell_well_height) + max_nmos_well_height = max(self.inverter_nmos.well_height, + self.readwrite_nmos.well_height, + self.write_nmos.well_height, + self.read_nmos.well_height) well_height = max_nmos_well_height + self.port_ypos \ - - self.well_enclose_active - self.gnd_position.y - offset = vector(self.leftmost_xpos, self.botmost_ypos) + - self.nwell_enclose_active - self.gnd_position.y + # FIXME fudge factor xpos + well_width = self.width + 2*self.nwell_enclose_active + offset = vector(self.leftmost_xpos - self.nwell_enclose_active, self.botmost_ypos) self.add_rect(layer="pwell", offset=offset, - width=self.width, + width=well_width, height=well_height) # extend nwell to encompass inverter_pmos # calculate offset of the left pmos well inverter_well_xpos = -(self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \ - - self.well_enclose_active + - self.nwell_enclose_active inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \ - + self.inverter_gap - self.well_enclose_active + + self.inverter_gap - self.nwell_enclose_active # calculate width of the two combined nwells # calculate height to encompass nimplant connected to vdd well_width = 2 * (self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \ - + 2 * self.well_enclose_active + + 2 * self.nwell_enclose_active well_height = self.vdd_position.y - inverter_well_ypos \ - + self.well_enclose_active + drc["minwidth_tx"] + + self.nwell_enclose_active + drc["minwidth_tx"] - offset = [inverter_well_xpos, inverter_well_ypos] + # FIXME fudge factor xpos + offset = [inverter_well_xpos + 2*self.nwell_enclose_active, inverter_well_ypos] self.add_rect(layer="nwell", offset=offset, width=well_width, @@ -1019,7 +1015,7 @@ class pbitcell(bitcell_base.bitcell_base): # add well contacts # connect pimplants to gnd offset = vector(0, self.gnd_position.y) - self.add_via_center(layers=("active", "contact", "metal1"), + self.add_via_center(layers=self.active_stack, offset=offset, directions=("H", "H"), implant_type="p", @@ -1027,7 +1023,7 @@ class pbitcell(bitcell_base.bitcell_base): # connect nimplants to vdd offset = vector(0, self.vdd_position.y) - self.add_via_center(layers=("active", "contact", "metal1"), + self.add_via_center(layers=self.active_stack, offset=offset, directions=("H", "H"), implant_type="n", @@ -1076,7 +1072,7 @@ class pbitcell(bitcell_base.bitcell_base): """ Q_bar_pos = self.inverter_pmos_right.get_pin("S").center() vdd_pos = self.inverter_pmos_right.get_pin("D").center() - self.add_path("metal1", [Q_bar_pos, vdd_pos]) + self.add_path("m1", [Q_bar_pos, vdd_pos]) def get_storage_net_names(self): """ diff --git a/compiler/bitcells/replica_bitcell.py b/compiler/bitcells/replica_bitcell.py index 2f804bf0..479883d9 100644 --- a/compiler/bitcells/replica_bitcell.py +++ b/compiler/bitcells/replica_bitcell.py @@ -8,7 +8,10 @@ import design import debug import utils -from tech import GDS,layer,drc,parameter +from tech import GDS,layer,drc,parameter,cell_properties +from tech import cell_properties as props + +from globals import OPTS class replica_bitcell(design.design): """ @@ -17,10 +20,24 @@ class replica_bitcell(design.design): is a hand-made cell, so the layout and netlist should be available in the technology library. """ - pin_names = ["bl", "br", "wl", "vdd", "gnd"] - type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] - (width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"]) - pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"]) + if cell_properties.bitcell.split_wl: + pin_names = ["bl", "br", "wl0", "wl1", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT" , "POWER", "GROUND"] + else: + pin_names = [props.bitcell.cell_6t.pin.bl, + props.bitcell.cell_6t.pin.br, + props.bitcell.cell_6t.pin.wl, + props.bitcell.cell_6t.pin.vdd, + props.bitcell.cell_6t.pin.gnd] + + type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] + + if not OPTS.netlist_only: + (width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"]) + else: + (width,height) = (0,0) + pin_map = [] def __init__(self, name=""): # Ignore the name argument @@ -56,4 +73,4 @@ class replica_bitcell(design.design): def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) \ No newline at end of file + self.add_graph_edges(graph, port_nets) diff --git a/compiler/bitcells/replica_bitcell_1rw_1r.py b/compiler/bitcells/replica_bitcell_1rw_1r.py index 0f56319e..79f16a47 100644 --- a/compiler/bitcells/replica_bitcell_1rw_1r.py +++ b/compiler/bitcells/replica_bitcell_1rw_1r.py @@ -9,6 +9,7 @@ import design import debug import utils from tech import GDS,layer,drc,parameter +from tech import cell_properties as props class replica_bitcell_1rw_1r(design.design): """ @@ -17,7 +18,15 @@ class replica_bitcell_1rw_1r(design.design): is a hand-made cell, so the layout and netlist should be available in the technology library. """ - pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + pin_names = [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.bl1, + props.bitcell.cell_1rw1r.pin.br1, + props.bitcell.cell_1rw1r.pin.wl0, + props.bitcell.cell_1rw1r.pin.wl1, + props.bitcell.cell_1rw1r.pin.vdd, + props.bitcell.cell_1rw1r.pin.gnd] + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("replica_cell_1rw_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1rw_1r", GDS["unit"]) @@ -47,14 +56,15 @@ class replica_bitcell_1rw_1r(design.design): access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin - def build_graph(self, graph, inst_name, port_nets): + def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph. Multiport bitcell timing graph is too complex to use the add_graph_edges function.""" - pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + pins = props.bitcell.cell_1rw1r.pin #Edges hardcoded here. Essentially wl->bl/br for both ports. # Port 0 edges - graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self) - graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self) + graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.bl0], self) + graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.br0], self) # Port 1 edges - graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) - graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) \ No newline at end of file + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self) + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self) diff --git a/compiler/bitcells/replica_bitcell_1w_1r.py b/compiler/bitcells/replica_bitcell_1w_1r.py index b903e0ad..52bea519 100644 --- a/compiler/bitcells/replica_bitcell_1w_1r.py +++ b/compiler/bitcells/replica_bitcell_1w_1r.py @@ -9,6 +9,7 @@ import design import debug import utils from tech import GDS,layer,drc,parameter +from tech import cell_properties as props class replica_bitcell_1w_1r(design.design): """ @@ -17,7 +18,15 @@ class replica_bitcell_1w_1r(design.design): is a hand-made cell, so the layout and netlist should be available in the technology library. """ - pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + pin_names = [props.bitcell.cell_1w1r.pin.bl0, + props.bitcell.cell_1w1r.pin.br0, + props.bitcell.cell_1w1r.pin.bl1, + props.bitcell.cell_1w1r.pin.br1, + props.bitcell.cell_1w1r.pin.wl0, + props.bitcell.cell_1w1r.pin.wl1, + props.bitcell.cell_1w1r.pin.vdd, + props.bitcell.cell_1w1r.pin.gnd] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("replica_cell_1w_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1w_1r", GDS["unit"]) @@ -47,13 +56,14 @@ class replica_bitcell_1w_1r(design.design): access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin - def build_graph(self, graph, inst_name, port_nets): + def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph. Multiport bitcell timing graph is too complex to use the add_graph_edges function.""" debug.info(1,'Adding edges for {}'.format(inst_name)) - pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + pins = props.bitcell.cell_1w1r.pin #Edges hardcoded here. Essentially wl->bl/br for the read port. # Port 1 edges - graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) - graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) - # Port 0 is a write port, so its timing is not considered here. \ No newline at end of file + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self) + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self) + # Port 0 is a write port, so its timing is not considered here. diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 9b59c9e6..7782e532 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -180,30 +180,30 @@ class delay(simulation): def create_read_bit_measures(self): """ Adds bit measurements for read0 and read1 cycles """ - self.bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]} + self.read_bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]} meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE) for cycle in meas_cycles: meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name) single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data) for polarity,meas in single_bit_meas.items(): meas.meta_str = cycle - self.bit_meas[polarity].append(meas) + self.read_bit_meas[polarity].append(meas) # Dictionary values are lists, reduce to a single list of measurements - return [meas for meas_list in self.bit_meas.values() for meas in meas_list] + return [meas for meas_list in self.read_bit_meas.values() for meas in meas_list] def create_write_bit_measures(self): """ Adds bit measurements for write0 and write1 cycles """ - self.bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]} + self.write_bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]} meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE) for cycle in meas_cycles: meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name) single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data) for polarity,meas in single_bit_meas.items(): meas.meta_str = cycle - self.bit_meas[polarity].append(meas) + self.write_bit_meas[polarity].append(meas) # Dictionary values are lists, reduce to a single list of measurements - return [meas for meas_list in self.bit_meas.values() for meas in meas_list] + return [meas for meas_list in self.write_bit_meas.values() for meas in meas_list] def get_bit_measures(self, meas_tag, probe_address, probe_data): """ @@ -649,8 +649,9 @@ class delay(simulation): if (time_out <= 0): debug.error("Timed out, could not find a feasible period.",2) - # Clear any write target ports and set read port - self.targ_write_ports = [port] + # Write ports are assumed non-critical to timing, so the first available is used + self.targ_write_ports = [self.write_ports[0]] + # Set target read port for simulation self.targ_read_ports = [port] debug.info(1, "Trying feasible period: {0}ns on Port {1}".format(feasible_period, port)) @@ -733,7 +734,8 @@ class delay(simulation): # First, check that the memory has the right values at the right times - if not self.check_bit_measures(): + if not self.check_bit_measures(self.read_bit_meas) or \ + not self.check_bit_measures(self.write_bit_meas): return(False,{}) for port in self.targ_write_ports: @@ -824,13 +826,13 @@ class delay(simulation): return dout_success - def check_bit_measures(self): + def check_bit_measures(self, bit_measures): """ Checks the measurements which represent the internal storage voltages at the end of the read cycle. """ success = False - for polarity, meas_list in self.bit_meas.items(): + for polarity, meas_list in bit_measures.items(): for meas in meas_list: val = meas.retrieve_measure() debug.info(2,"{}={}".format(meas.name, val)) @@ -965,7 +967,8 @@ class delay(simulation): # Binary search algorithm to find the min period (max frequency) of input port time_out = 25 - self.targ_write_ports = [port] + # Write ports are assumed non-critical to timing, so the first available is used + self.targ_write_ports = [self.write_ports[0]] self.targ_read_ports = [port] while True: time_out -= 1 @@ -1254,8 +1257,8 @@ class delay(simulation): """ # Using this requires setting at least one port to target for simulation. - if len(self.targ_write_ports) == 0 and len(self.targ_read_ports) == 0: - debug.error("No port selected for characterization.",1) + if len(self.targ_write_ports) == 0 or len(self.targ_read_ports) == 0: + debug.error("Write and read port must be specified for characterization.",1) self.set_stimulus_variables() # Get any available read/write port in case only a single write or read ports is being characterized. diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 6baff201..6d6c6ce5 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -81,17 +81,25 @@ class lib: self.lib_files = [] # Nominal corner - self.add_corner(nom_process, nom_supply, nom_temperature) + corner_set = set() + nom_corner = (nom_process, nom_supply, nom_temperature) + corner_set.add(nom_corner) if not OPTS.nominal_corner_only: # Temperature corners - self.add_corner(nom_process, nom_supply, min_temperature) - self.add_corner(nom_process, nom_supply, max_temperature) + corner_set.add((nom_process, nom_supply, min_temperature)) + corner_set.add((nom_process, nom_supply, max_temperature)) # Supply corners - self.add_corner(nom_process, min_supply, nom_temperature) - self.add_corner(nom_process, max_supply, nom_temperature) + corner_set.add((nom_process, min_supply, nom_temperature)) + corner_set.add((nom_process, max_supply, nom_temperature)) # Process corners - self.add_corner(min_process, nom_supply, nom_temperature) - self.add_corner(max_process, nom_supply, nom_temperature) + corner_set.add((min_process, nom_supply, nom_temperature)) + corner_set.add((max_process, nom_supply, nom_temperature)) + + # Enforce that nominal corner is the first to be characterized + self.add_corner(*nom_corner) + corner_set.remove(nom_corner) + for corner_tuple in corner_set: + self.add_corner(*corner_tuple) def add_corner(self, proc, volt, temp): self.corner_name = "{0}_{1}_{2}V_{3}C".format(self.sram.name, diff --git a/compiler/drc/design_rules.py b/compiler/drc/design_rules.py index 1017aca6..05168b77 100644 --- a/compiler/drc/design_rules.py +++ b/compiler/drc/design_rules.py @@ -9,7 +9,7 @@ import debug from drc_value import * from drc_lut import * -class design_rules(): +class design_rules(dict): """ This is a class that implements the design rules structures. """ @@ -43,6 +43,22 @@ class design_rules(): else: debug.error("Must call complex DRC rule {} with arguments.".format(b),-1) - - + def keys(self): + return self.rules.keys() + + def add_layer(self, name, width, spacing, area=0): + # Minimum width + self.add("minwidth_{}".format(name), width) + # Minimum spacing (could be a table too) + self.add("{0}_to_{0}".format(name), spacing) + # Minimum area + self.add("minarea_{}".format(name), area) + + def add_enclosure(self, name, layer, enclosure, extension=None): + self.add("{0}_enclose_{1}".format(name, layer), enclosure) + # Reserved for asymmetric enclosures + if extension: + self.add("{0}_extend_{1}".format(name, layer), extension) + else: + self.add("{0}_extend_{1}".format(name, layer), enclosure) diff --git a/compiler/example_configs/example_config_1w_1r_scn4m_subm.py b/compiler/example_configs/example_config_1w_1r_scn4m_subm.py index 330d8d46..55ac4016 100644 --- a/compiler/example_configs/example_config_1w_1r_scn4m_subm.py +++ b/compiler/example_configs/example_config_1w_1r_scn4m_subm.py @@ -19,6 +19,3 @@ output_name = "sram_1w_1r_{0}_{1}_{2}".format(word_size, num_words, tech_name) -drc_name = "magic" -lvs_name = "netgen" -pex_name = "magic" diff --git a/compiler/gdsMill/gdsMill/gds2reader.py b/compiler/gdsMill/gdsMill/gds2reader.py index 351df6a4..2f9976a5 100644 --- a/compiler/gdsMill/gdsMill/gds2reader.py +++ b/compiler/gdsMill/gdsMill/gds2reader.py @@ -12,14 +12,14 @@ class Gds2reader: self.fileHandle = None self.layoutObject = layoutObject self.debugToTerminal=debugToTerminal - + #do we dump debug data to the screen - + def print64AsBinary(self,number): for index in range(0,64): print((number>>(63-index))&0x1,eol='') print("\n") - + def stripNonASCII(self,bytestring): string = bytestring.decode('utf-8') return string @@ -29,20 +29,20 @@ class Gds2reader: #(1)sign (7)exponent (56)mantissa #exponent is excess 64, mantissa has no implied 1 #a normal IEEE double is like this: - #(1)sign (11)exponent (52)mantissa + #(1)sign (11)exponent (52)mantissa data = struct.unpack('>q',ibmData)[0] sign = (data >> 63)&0x01 exponent = (data >> 56) & 0x7f mantissa = data<<8 #chop off sign and exponent - + if mantissa == 0: newFloat = 0.0 - else: + else: exponent = ((exponent-64)*4)+1023 #convert to double exponent #re normalize - while mantissa & 0x8000000000000000 == 0: + while mantissa & 0x8000000000000000 == 0: mantissa<<=1 - exponent-=1 + exponent-=1 mantissa<<=1 #remove the assumed high bit exponent-=1 #check for underflow error -- should handle these properly! @@ -56,7 +56,7 @@ class Gds2reader: #convert back to double newFloat = struct.unpack('>d',asciiDouble)[0] return newFloat - + def ieeeFloatCheck(self,aFloat): asciiDouble = struct.pack('>d',aFloat) data = struct.unpack('>q',asciiDouble)[0] @@ -70,12 +70,12 @@ class Gds2reader: asciiDouble = struct.pack('>q',(sign<<63)|(exponent+1023<<52)|(mantissa>>12)) newFloat = struct.unpack('>d',asciiDouble)[0] print("Check:"+str(newFloat)) - + def readNextRecord(self): global offset recordLengthAscii = self.fileHandle.read(2) #first 2 bytes tell us the length of the record if len(recordLengthAscii)==0: - return + return recordLength = struct.unpack(">h",recordLengthAscii) #gives us a tuple with a short int inside offset_int = int(recordLength[0]) # extract length offset += offset_int # count offset @@ -96,20 +96,20 @@ class Gds2reader: else: print("Invalid GDSII Header") return -1 - + #read records until we hit the UNITS section... this is the last part of the header while 1: record = self.readNextRecord() idBits = record[0:2] ## Modified Date if idBits==b'\x01\x02' and len(record)==26: - modYear = struct.unpack(">h",record[2:4])[0] + modYear = struct.unpack(">h",record[2:4])[0] modMonth = struct.unpack(">h",record[4:6])[0] modDay = struct.unpack(">h",record[6:8])[0] modHour = struct.unpack(">h",record[8:10])[0] modMinute = struct.unpack(">h",record[10:12])[0] modSecond = struct.unpack(">h",record[12:14])[0] - lastAccessYear = struct.unpack(">h",record[14:16])[0] + lastAccessYear = struct.unpack(">h",record[14:16])[0] lastAccessMonth = struct.unpack(">h",record[16:18])[0] lastAccessDay = struct.unpack(">h",record[18:20])[0] lastAccessHour = struct.unpack(">h",record[20:22])[0] @@ -169,14 +169,14 @@ class Gds2reader: if(self.debugToTerminal==1): print("Units: 1 user unit="+str(userUnits)+" database units, 1 database unit="+str(dbUnits)+" meters.") break; - if(self.debugToTerminal==1): + if(self.debugToTerminal==1): print("End of GDSII Header Found") return 1 - + def readBoundary(self): ##reads in a boundary type structure = a filled polygon if(self.debugToTerminal==1): - print("\t\t\tBeginBoundary") + print("\t\t\tBeginBoundary") thisBoundary=GdsBoundary() while 1: record = self.readNextRecord() @@ -198,9 +198,9 @@ class Gds2reader: self.layoutObject.layerNumbersInUse += [drawingLayer] if(self.debugToTerminal==1): print("\t\tDrawing Layer: "+str(drawingLayer)) - elif(idBits==b'\x16\x02'): #Purpose + elif(idBits==b'\x0E\x02'): #Purpose DATATYPE purposeLayer = struct.unpack(">h",record[2:4])[0] - thisBoundary.purposeLayer=purposeLayer + thisBoundary.purposeLayer=purposeLayer if(self.debugToTerminal==1): print("\t\tPurpose Layer: "+str(purposeLayer)) elif(idBits==b'\x10\x03'): #XY Data Points @@ -217,11 +217,11 @@ class Gds2reader: print("\t\t\tEndBoundary") break; return thisBoundary - + def readPath(self): #reads in a path structure if(self.debugToTerminal==1): print("\t\t\tBeginPath") - + thisPath=GdsPath() while 1: record = self.readNextRecord() @@ -245,7 +245,7 @@ class Gds2reader: print("\t\t\tDrawing Layer: "+str(drawingLayer)) elif(idBits==b'\x16\x02'): #Purpose purposeLayer = struct.unpack(">h",record[2:4])[0] - thisPath.purposeLayer=purposeLayer + thisPath.purposeLayer=purposeLayer if(self.debugToTerminal==1): print("\t\tPurpose Layer: "+str(purposeLayer)) elif(idBits==b'\x21\x02'): #Path type @@ -253,6 +253,11 @@ class Gds2reader: thisPath.pathType=pathType if(self.debugToTerminal==1): print("\t\t\tPath Type: "+str(pathType)) + elif(idBits==b'\x0E\x02'): #Data type + dataType = struct.unpack(">h",record[2:4])[0] + thisPath.dataType=dataType + if(self.debugToTerminal==1): + print("\t\t\tData Type: "+str(dataType)) elif(idBits==b'\x0F\x03'): #Path width pathWidth = struct.unpack(">i",record[2:6])[0] thisPath.pathWidth=pathWidth @@ -272,7 +277,7 @@ class Gds2reader: print("\t\t\tEndPath") break; return thisPath - + def readSref(self): #reads in a reference to another structure if(self.debugToTerminal==1): print("\t\t\tBeginSref") @@ -313,7 +318,7 @@ class Gds2reader: print("\t\t\tMagnification:"+str(magFactor)) elif(idBits==b'\x1C\x05'): #Rotate Angle rotateAngle=self.ieeeDoubleFromIbmData(record[2:10]) - thisSref.rotateAngle=rotateAngle + thisSref.rotateAngle=rotateAngle if(self.debugToTerminal==1): print("\t\t\tRotate Angle (CCW):"+str(rotateAngle)) elif(idBits==b'\x10\x03'): #XY Data Points @@ -328,11 +333,11 @@ class Gds2reader: print("\t\t\tEndSref") break; return thisSref - + def readAref(self): #an array of references if(self.debugToTerminal==1): print("\t\t\tBeginAref") - + thisAref = GdsAref() while 1: record = self.readNextRecord() @@ -369,7 +374,7 @@ class Gds2reader: print("\t\t\tMagnification:"+str(magFactor)) elif(idBits==b'\x1C\x05'): #Rotate Angle rotateAngle=self.ieeeDoubleFromIbmData(record[2:10]) - thisAref.rotateAngle=rotateAngle + thisAref.rotateAngle=rotateAngle if(self.debugToTerminal==1): print("\t\t\tRotate Angle (CCW):"+str(rotateAngle)) elif(idBits==b'\x10\x03'): #XY Data Points @@ -388,11 +393,11 @@ class Gds2reader: print("\t\t\tEndAref") break; return thisAref - + def readText(self): if(self.debugToTerminal==1): print("\t\t\tBeginText") - + thisText=GdsText() while 1: record = self.readNextRecord() @@ -414,9 +419,9 @@ class Gds2reader: self.layoutObject.layerNumbersInUse += [drawingLayer] if(self.debugToTerminal==1): print("\t\tDrawing Layer: "+str(drawingLayer)) - elif(idBits==b'\x16\x02'): #Purpose + elif(idBits==b'\x16\x02'): #Purpose TEXTTYPE purposeLayer = struct.unpack(">h",record[2:4])[0] - thisText.purposeLayer=purposeLayer + thisText.purposeLayer=purposeLayer if(self.debugToTerminal==1): print("\t\tPurpose Layer: "+str(purposeLayer)) elif(idBits==b'\x1A\x01'): #Transformation @@ -492,11 +497,11 @@ class Gds2reader: print("\t\t\tEndText") break; return thisText - + def readNode(self): if(self.debugToTerminal==1): print("\t\t\tBeginNode") - + ##reads in a node type structure = an electrical net thisNode = GdsNode() while 1: @@ -538,11 +543,11 @@ class Gds2reader: print("\t\t\tEndNode") break; return thisNode - + def readBox(self): if(self.debugToTerminal==1): print("\t\t\tBeginBox") - + ##reads in a gds BOX structure thisBox = GdsBox() while 1: @@ -565,9 +570,9 @@ class Gds2reader: self.layoutObject.layerNumbersInUse += [drawingLayer] if(self.debugToTerminal==1): print("\t\tDrawing Layer: "+str(drawingLayer)) - elif(idBits==b'\x16\x02'): #Purpose + elif(idBits==b'\x16\x02'): #Purpose TEXTYPE purposeLayer = struct.unpack(">h",record[2:4])[0] - thisBox.purposeLayer=purposeLayer + thisBox.purposeLayer=purposeLayer if(self.debugToTerminal==1): print("\t\tPurpose Layer: "+str(purposeLayer)) elif(idBits==b'\x2D\x00'): #Box @@ -589,14 +594,14 @@ class Gds2reader: print("\t\t\tEndBox") break; return thisBox - + def readNextStructure(self): - thisStructure = GdsStructure() + thisStructure = GdsStructure() record = self.readNextRecord() idBits = record[0:2] # Begin structure if(idBits==b'\x05\x02' and len(record)==26): - createYear = struct.unpack(">h",record[2:4])[0] + createYear = struct.unpack(">h",record[2:4])[0] createMonth = struct.unpack(">h",record[4:6])[0] createDay = struct.unpack(">h",record[6:8])[0] createHour = struct.unpack(">h",record[8:10])[0] @@ -623,7 +628,7 @@ class Gds2reader: idBits = record[0:2] if idBits==b'\x07\x00': break; #we've reached the end of the structure elif(idBits==b'\x06\x06'): - structName = self.stripNonASCII(record[2::]) + structName = self.stripNonASCII(record[2::]) thisStructure.name = structName if(self.debugToTerminal==1): print("\tStructure Name: "+structName) @@ -641,11 +646,11 @@ class Gds2reader: thisStructure.nodes+=[self.readNode()] elif(idBits==b'\x2E\x02'): thisStructure.boxes+=[self.readBox()] - if(self.debugToTerminal==1): + if(self.debugToTerminal==1): print("\tEnd of Structure.") self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object return 1 - + def readGds2(self): if(self.readHeader()): #did the header read ok? record = self.readNextStructure() @@ -662,7 +667,7 @@ class Gds2reader: print("There was an error reading the structure list.") else: print("There was an error parsing the GDS header. Aborting...") - + def loadFromFile(self, fileName): self.fileHandle = open(fileName,"rb") self.readGds2() @@ -690,11 +695,11 @@ class Gds2reader: def findStruct_readNextStruct(self,findStructName): self.debugToTerminal=0 - thisStructure = GdsStructure() + thisStructure = GdsStructure() record = self.readNextRecord() idBits = record[0:2] if(idBits==('\x05','\x02') and len(record)==26): - createYear = struct.unpack(">h",record[2]+record[3])[0] + createYear = struct.unpack(">h",record[2]+record[3])[0] createMonth = struct.unpack(">h",record[4]+record[5])[0] createDay = struct.unpack(">h",record[6]+record[7])[0] createHour = struct.unpack(">h",record[8]+record[9])[0] @@ -738,7 +743,7 @@ class Gds2reader: thisStructure.nodes+=[self.readNode()] elif(idBits==('\x2E','\x02')): thisStructure.boxes+=[self.readBox()] - if(self.debugToTerminal==1): + if(self.debugToTerminal==1): print("\tEnd of Structure.") self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object if(wantedStruct == 0): @@ -766,11 +771,11 @@ class Gds2reader: def findLabel_readNextStruct(self,findLabelName): self.debugToTerminal=0 - thisStructure = GdsStructure() + thisStructure = GdsStructure() record = self.readNextRecord() idBits = record[0:2] if(idBits==('\x05','\x02') and len(record)==26): - createYear = struct.unpack(">h",record[2]+record[3])[0] + createYear = struct.unpack(">h",record[2]+record[3])[0] createMonth = struct.unpack(">h",record[4]+record[5])[0] createDay = struct.unpack(">h",record[6]+record[7])[0] createHour = struct.unpack(">h",record[8]+record[9])[0] @@ -820,7 +825,7 @@ class Gds2reader: thisStructure.nodes+=[self.readNode()] elif(idBits==('\x2E','\x02')): thisStructure.boxes+=[self.readBox()] - if(self.debugToTerminal==1): + if(self.debugToTerminal==1): print("\tEnd of Structure.") self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object if(wantedLabel == 0): @@ -828,4 +833,3 @@ class Gds2reader: else: #print("\tDone with collectting bound. Return") return [0,wantedtexts] - diff --git a/compiler/gdsMill/gdsMill/gds2writer.py b/compiler/gdsMill/gdsMill/gds2writer.py index 3a319324..875de831 100644 --- a/compiler/gdsMill/gdsMill/gds2writer.py +++ b/compiler/gdsMill/gdsMill/gds2writer.py @@ -5,37 +5,37 @@ from .gdsPrimitives import * class Gds2writer: """Class to take a populated layout class and write it to a file in GDSII format""" ## Based on info from http://www.rulabinsky.com/cavd/text/chapc.html - + def __init__(self,layoutObject): self.fileHandle = 0 self.layoutObject = layoutObject self.debugToTerminal=0 #do we dump debug data to the screen - + def print64AsBinary(self,number): #debugging method for binary inspection for index in range(0,64): print((number>>(63-index))&0x1,eol='') print("\n") - + def ieeeDoubleFromIbmData(self,ibmData): #the GDS double is in IBM 370 format like this: #(1)sign (7)exponent (56)mantissa #exponent is excess 64, mantissa has no implied 1 #a normal IEEE double is like this: - #(1)sign (11)exponent (52)mantissa + #(1)sign (11)exponent (52)mantissa data = struct.unpack('>q',ibmData)[0] sign = (data >> 63)&0x01 exponent = (data >> 56) & 0x7f mantissa = data<<8 #chop off sign and exponent - + if mantissa == 0: newFloat = 0.0 else: exponent = ((exponent-64)*4)+1023 #convert to double exponent #re normalize - while mantissa & 0x8000000000000000 == 0: + while mantissa & 0x8000000000000000 == 0: mantissa<<=1 - exponent-=1 + exponent-=1 mantissa<<=1 #remove the assumed high bit exponent-=1 #check for underflow error -- should handle these properly! @@ -49,12 +49,12 @@ class Gds2writer: #convert back to double newFloat = struct.unpack('>d',asciiDouble)[0] return newFloat - + def ibmDataFromIeeeDouble(self,ieeeDouble): asciiDouble = struct.pack('>d',ieeeDouble) data = struct.unpack('>q',asciiDouble)[0] sign = (data >> 63) & 0x01 - exponent = ((data >> 52) & 0x7ff)-1023 + exponent = ((data >> 52) & 0x7ff)-1023 mantissa = data << 12 #chop off sign and exponent if(ieeeDouble == 0): mantissa = 0 @@ -70,14 +70,14 @@ class Gds2writer: for index in range (0,-exponent&3): mantissa >>= 1 mantissa = mantissa & 0x7fffffffffffffff - - exponent = (exponent+3) >> 2 + + exponent = (exponent+3) >> 2 exponent+=64 - - newFloat =(sign<<63)|(exponent<<56)|((mantissa>>8)&0xffffffffffffff) - asciiDouble = struct.pack('>q',newFloat) + + newFloat =(sign<<63)|(exponent<<56)|((mantissa>>8)&0xffffffffffffff) + asciiDouble = struct.pack('>q',newFloat) return asciiDouble - + def ieeeFloatCheck(self,aFloat): #debugging method for float construction asciiDouble = struct.pack('>d',aFloat) @@ -90,7 +90,7 @@ class Gds2writer: asciiDouble = struct.pack('>q',(sign<<63)|(exponent+1023<<52)|(mantissa>>12)) newFloat = struct.unpack('>d',asciiDouble)[0] print("Check:"+str(newFloat)) - + def writeRecord(self,record): recordLength = len(record)+2 #make sure to include this in the length recordLengthAscii=struct.pack(">h",recordLength) @@ -127,7 +127,7 @@ class Gds2writer: libraryName = self.layoutObject.info["libraryName"].encode() + "\0" else: libraryName = self.layoutObject.info["libraryName"].encode() - self.writeRecord(idBits+libraryName) + self.writeRecord(idBits+libraryName) ## reference libraries if("referenceLibraries" in self.layoutObject.info): idBits=b'\x1F\x06' @@ -158,11 +158,11 @@ class Gds2writer: mask = self.layoutObject.info["mask"] self.writeRecord(idBits+mask) if("units" in self.layoutObject.info): - idBits=b'\x03\x05' + idBits=b'\x03\x05' userUnits=self.ibmDataFromIeeeDouble(self.layoutObject.info["units"][0]) dbUnits=self.ibmDataFromIeeeDouble((self.layoutObject.info["units"][0]*1e-6/self.layoutObject.info["units"][1])*self.layoutObject.info["units"][1]) - #User Units are hardcoded, since the floating point implementation of gdsMill is not adequate, + #User Units are hardcoded, since the floating point implementation of gdsMill is not adequate, #resulting in a different value being written in output stream. Hardcoded to sram compiler's outputed gds units. #db="39225c17d04dad2a" #uu="3e20c49ba5e353f8" @@ -172,17 +172,17 @@ class Gds2writer: #dbUnits="39225c17d04dad2a".decode("hex") #db=39225c17d04dad2a - - + + self.writeRecord(idBits+userUnits+dbUnits) - if(self.debugToTerminal==1): + if(self.debugToTerminal==1): print("writer: userUnits %s"%(userUnits.encode("hex"))) print("writer: dbUnits %s"%(dbUnits.encode("hex"))) #self.ieeeFloatCheck(1.3e-6) - + print("End of GDSII Header Written") return 1 - + def writeBoundary(self,thisBoundary): idBits=b'\x08\x00' #record Type self.writeRecord(idBits) @@ -216,7 +216,7 @@ class Gds2writer: idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writePath(self,thisPath): #writes out a path structure idBits=b'\x09\x00' #record Type self.writeRecord(idBits) @@ -236,6 +236,10 @@ class Gds2writer: idBits=b'\x16\x02' #purpose layer purposeLayer = struct.pack(">h",thisPath.purposeLayer) self.writeRecord(idBits+purposeLayer) + if(thisPath.dataType is not None): + idBits=b'\x0E\x02' #Data type + dataType = struct.pack(">h",thisPath.dataType) + self.writeRecord(idBits+dataType) if(thisPath.pathType): idBits=b'\x21\x02' #Path type pathType = struct.pack(">h",thisPath.pathType) @@ -256,7 +260,7 @@ class Gds2writer: idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writeSref(self,thisSref): #reads in a reference to another structure idBits=b'\x0A\x00' #record Type self.writeRecord(idBits) @@ -292,7 +296,7 @@ class Gds2writer: magFactor=self.ibmDataFromIeeeDouble(thisSref.magFactor) self.writeRecord(idBits+magFactor) if(thisSref.rotateAngle!=""): - idBits=b'\x1C\x05' + idBits=b'\x1C\x05' rotateAngle=self.ibmDataFromIeeeDouble(thisSref.rotateAngle) self.writeRecord(idBits+rotateAngle) if(thisSref.coordinates!=""): @@ -308,7 +312,7 @@ class Gds2writer: idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writeAref(self,thisAref): #an array of references idBits=b'\x0B\x00' #record Type self.writeRecord(idBits) @@ -344,7 +348,7 @@ class Gds2writer: magFactor=self.ibmDataFromIeeeDouble(thisAref.magFactor) self.writeRecord(idBits+magFactor) if(thisAref.rotateAngle!=""): - idBits=b'\x1C\x05' + idBits=b'\x1C\x05' rotateAngle=self.ibmDataFromIeeeDouble(thisAref.rotateAngle) self.writeRecord(idBits+rotateAngle) if(thisAref.coordinates): @@ -359,7 +363,7 @@ class Gds2writer: idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writeText(self,thisText): idBits=b'\x0C\x00' #record Type self.writeRecord(idBits) @@ -375,9 +379,8 @@ class Gds2writer: idBits=b'\x0D\x02' #drawing layer drawingLayer = struct.pack(">h",thisText.drawingLayer) self.writeRecord(idBits+drawingLayer) - # TextType is always a 0 per GDS specification - idBits=b'\x16\x02' #purpose layer - purposeLayer = struct.pack(">h",0) + idBits=b'\x16\x02' #purpose layer TEXTTYPE + purposeLayer = struct.pack(">h",thisText.purposeLayer) self.writeRecord(idBits+purposeLayer) if(thisText.transFlags != ""): idBits=b'\x1A\x01' @@ -396,7 +399,7 @@ class Gds2writer: magFactor=self.ibmDataFromIeeeDouble(thisText.magFactor) self.writeRecord(idBits+magFactor) if(thisText.rotateAngle!=""): - idBits=b'\x1C\x05' + idBits=b'\x1C\x05' rotateAngle=self.ibmDataFromIeeeDouble(thisText.rotateAngle) self.writeRecord(idBits+rotateAngle) if(thisText.pathType !=""): @@ -408,12 +411,12 @@ class Gds2writer: pathWidth = struct.pack(">i",thisText.pathWidth) self.writeRecord(idBits+pathWidth) if(thisText.presentationFlags!=""): - idBits=b'\x1A\x01' + idBits=b'\x1A\x01' font = thisText.presentationFlags[0]<<4 verticalFlags = int(thisText.presentationFlags[1])<<2 horizontalFlags = int(thisText.presentationFlags[2]) presentationFlags = struct.pack(">H",font|verticalFlags|horizontalFlags) - self.writeRecord(idBits+transFlags) + self.writeRecord(idBits+transFlags) if(thisText.coordinates!=""): idBits=b'\x10\x03' #XY Data Points coordinateRecord = idBits @@ -427,11 +430,11 @@ class Gds2writer: idBits=b'\x19\x06' textString = thisText.textString self.writeRecord(idBits+textString.encode()) - + idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writeNode(self,thisNode): idBits=b'\x15\x00' #record Type self.writeRecord(idBits) @@ -446,11 +449,11 @@ class Gds2writer: if(thisNode.drawingLayer!=""): idBits=b'\x0D\x02' #drawig layer drawingLayer = struct.pack(">h",thisNode.drawingLayer) - self.writeRecord(idBits+drawingLayer) + self.writeRecord(idBits+drawingLayer) if(thisNode.nodeType!=""): idBits=b'\x2A\x02' nodeType = struct.pack(">h",thisNode.nodeType) - self.writeRecord(idBits+nodeType) + self.writeRecord(idBits+nodeType) if(thisText.coordinates!=""): idBits=b'\x10\x03' #XY Data Points coordinateRecord = idBits @@ -460,11 +463,11 @@ class Gds2writer: coordinateRecord+=x coordinateRecord+=y self.writeRecord(coordinateRecord) - + idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writeBox(self,thisBox): idBits=b'\x2E\x02' #record Type self.writeRecord(idBits) @@ -487,7 +490,7 @@ class Gds2writer: if(thisBox.boxValue!=""): idBits=b'\x2D\x00' boxValue = struct.pack(">h",thisBox.boxValue) - self.writeRecord(idBits+boxValue) + self.writeRecord(idBits+boxValue) if(thisBox.coordinates!=""): idBits=b'\x10\x03' #XY Data Points coordinateRecord = idBits @@ -497,11 +500,11 @@ class Gds2writer: coordinateRecord+=x coordinateRecord+=y self.writeRecord(coordinateRecord) - + idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writeNextStructure(self,structureName): #first put in the structure head thisStructure = self.layoutObject.structures[structureName] @@ -528,7 +531,7 @@ class Gds2writer: structureName = structureName + '\x00' self.writeRecord(idBits+structureName.encode()) #now go through all the structure elements and write them in - + for boundary in thisStructure.boundaries: self.writeBoundary(boundary) for path in thisStructure.paths: @@ -546,7 +549,7 @@ class Gds2writer: #put in the structure tail idBits=b'\x07\x00' self.writeRecord(idBits) - + def writeGds2(self): self.writeHeader(); #first, put the header in #go through each structure in the layout and write it to the file @@ -555,7 +558,7 @@ class Gds2writer: #at the end, put in the END LIB record idBits=b'\x04\x00' self.writeRecord(idBits) - + def writeToFile(self,fileName): self.fileHandle = open(fileName,"wb") self.writeGds2() diff --git a/compiler/gdsMill/gdsMill/gdsPrimitives.py b/compiler/gdsMill/gdsMill/gdsPrimitives.py index 09bd85b4..13b8acf9 100644 --- a/compiler/gdsMill/gdsMill/gdsPrimitives.py +++ b/compiler/gdsMill/gdsMill/gdsPrimitives.py @@ -1,5 +1,12 @@ import math +from globals import OPTS +# default purpose layer is used for addText() in vlsiLayout.py +if OPTS.tech_name == "s8": + purposeLayer=20 +else: + purposeLayer=0 + class GdsStructure: """Class represent a GDS Structure Object""" def __init__(self): @@ -9,7 +16,7 @@ class GdsStructure: #these are the primitives defined in GDS2, and we will maintain lists of them all self.boundaries=[] self.paths=[] - self.srefs=[] + self.srefs=[] self.arefs=[] self.texts=[] self.nodes=[] @@ -23,7 +30,7 @@ class GdsBoundary: self.drawingLayer="" self.purposeLayer=0 self.coordinates="" - + class GdsPath: """Class represent a GDS Path Object""" def __init__(self): @@ -32,9 +39,10 @@ class GdsPath: self.drawingLayer="" self.purposeLayer=0 self.pathType="" + self.dataType=None self.pathWidth="" self.coordinates="" - + def equivalentBoundaryCoordinates(self): """Convert the path to a set of boundary coordinates that define it""" halfWidth = (self.pathWidth/2) @@ -61,7 +69,7 @@ class GdsPath: nextX = None; nextY = None; if lastX==None: #start of the path - if nextX>x:#moving right + if nextX>x:#moving right boundaryEquivalent+=[(x,y+halfWidth)] if nextX nextX): boundaryEquivalent+=[(x+halfWidth,y-halfWidth)] - - if nextX == None: #end of path, put in the last 2 points - if lastXx:#moving left boundaryEquivalent+=[(x,y-halfWidth)] @@ -139,7 +147,7 @@ class GdsText: self.elementFlags="" self.plex="" self.drawingLayer="" - self.purposeLayer=0 + self.purposeLayer=purposeLayer self.transFlags=[0,0,0] self.magFactor="" self.rotateAngle="" @@ -148,7 +156,7 @@ class GdsText: self.presentationFlags="" self.coordinates="" self.textString = "" - + class GdsNode: """Class represent a GDS Node Object""" def __init__(self): @@ -157,7 +165,7 @@ class GdsNode: self.drawingLayer="" self.nodeType="" self.coordinates="" - + class GdsBox: """Class represent a GDS Box Object""" def __init__(self): diff --git a/compiler/globals.py b/compiler/globals.py index 69ead34f..c864f53b 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -304,6 +304,7 @@ def read_config(config_file, is_unit_test=True): except: debug.error("Unable to read configuration file: {0}".format(config_file),2) + OPTS.overridden = {} for k, v in config.__dict__.items(): # The command line will over-ride the config file # except in the case of the tech name! This is because the tech name @@ -311,6 +312,7 @@ def read_config(config_file, is_unit_test=True): # Note that if we re-read a config file, nothing will get read again! if k not in OPTS.__dict__ or k == "tech_name": OPTS.__dict__[k] = v + OPTS.overridden[k] = True # Massage the output path to be an absolute one if not OPTS.output_path.endswith('/'): @@ -504,6 +506,11 @@ def import_tech(): except ImportError: debug.error("Could not load tech module.", -1) + # Add custom modules of the technology to the path, if they exist + custom_mod_path = os.path.join(tech_path, "modules/") + if os.path.exists(custom_mod_path): + sys.path.append(custom_mod_path) + def print_time(name, now_time, last_time=None, indentation=2): """ Print a statement about the time delta. """ diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 18ced346..aea4e909 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -5,19 +5,15 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys -from tech import drc, parameter import debug import design -import math -from math import log,sqrt,ceil -import contact -import pgates from sram_factory import factory +from math import log +from tech import drc from vector import vector - from globals import OPTS + class bank(design.design): """ Dynamically generated a single bank including bitcell array, @@ -57,6 +53,7 @@ class bank(design.design): def create_netlist(self): + self.compute_sizes() self.add_modules() self.add_pins() # Must create the replica bitcell array first @@ -334,7 +331,7 @@ class bank(design.design): self.col_addr_bus_width = self.m2_pitch*self.num_col_addr_lines # A space for wells or jogging m2 - self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclosure_active"), + self.m2_gap = max(2*drc("pwell_to_nwell") + drc("nwell_enclose_active"), 3*self.m2_pitch) @@ -500,6 +497,8 @@ class bank(design.design): Create a 2:4 or 3:8 column address decoder. """ + # Height is a multiple of DFF so that it can be staggered + # and rows do not align with the control logic module self.dff = factory.create(module_type="dff") if self.col_addr_size == 0: @@ -606,12 +605,12 @@ class bank(design.design): out_pos = self.bank_select_inst[port].get_pin(gated_bank_sel_signals[signal]).rc() name = self.control_signals[port][signal] bus_pos = vector(self.bus_xoffset[port][name].x, out_pos.y) - self.add_path("metal3",[out_pos, bus_pos]) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_path("m3",[out_pos, bus_pos]) + self.add_via_center(layers=self.m2_stack, offset=bus_pos) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=out_pos) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=out_pos) @@ -649,7 +648,7 @@ class bank(design.design): control_bus_offset = vector(-self.m2_pitch * self.num_control_lines[0] - self.m2_pitch, self.min_y_offset) # The control bus is routed up to two pitches below the bitcell array control_bus_length = self.main_bitcell_array_bottom - self.min_y_offset - 2*self.m1_pitch - self.bus_xoffset[0] = self.create_bus(layer="metal2", + self.bus_xoffset[0] = self.create_bus(layer="m2", pitch=self.m2_pitch, offset=control_bus_offset, names=self.control_signals[0], @@ -664,7 +663,7 @@ class bank(design.design): control_bus_offset = vector(self.bitcell_array_right + self.m2_pitch, self.max_y_offset - control_bus_length) # The bus for the right port is reversed so that the rbl_wl is closest to the array - self.bus_xoffset[1] = self.create_bus(layer="metal2", + self.bus_xoffset[1] = self.create_bus(layer="m2", pitch=self.m2_pitch, offset=control_bus_offset, names=list(reversed(self.control_signals[1])), @@ -681,9 +680,13 @@ class bank(design.design): inst1 = self.bitcell_array_inst inst1_bl_name = self.bl_names[port]+"_{}" inst1_br_name = self.br_names[port]+"_{}" + + inst2_bl_name = inst2.mod.get_bl_names()+"_{}" + inst2_br_name = inst2.mod.get_br_names()+"_{}" self.connect_bitlines(inst1=inst1, inst2=inst2, num_bits=self.num_cols, - inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name) + inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name, + inst2_bl_name=inst2_bl_name, inst2_br_name=inst2_br_name) # Connect the replica bitlines rbl_bl_name=self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]) @@ -757,7 +760,7 @@ class bank(design.design): bottom_names = [bottom_inst.get_pin(bottom_bl_name.format(bit)), bottom_inst.get_pin(bottom_br_name.format(bit))] top_names = [top_inst.get_pin(top_bl_name.format(bit)), top_inst.get_pin(top_br_name.format(bit))] route_map = list(zip(bottom_names, top_names)) - self.create_horizontal_channel_route(route_map, offset) + self.create_horizontal_channel_route(route_map, offset, self.m1_stack) def connect_bitline(self, inst1, inst2, inst1_name, inst2_name): """ @@ -788,8 +791,8 @@ class bank(design.design): def connect_bitlines(self, inst1, inst2, num_bits, - inst1_bl_name="bl_{}", inst1_br_name="br_{}", - inst2_bl_name="bl_{}", inst2_br_name="br_{}"): + inst1_bl_name, inst1_br_name, + inst2_bl_name, inst2_br_name): """ Connect the bl and br of two modules. """ @@ -818,7 +821,7 @@ class bank(design.design): bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port]+"_{}".format(row)).lc() mid1 = driver_wl_pos.scale(0,1) + vector(0.5*self.port_address_inst[port].rx() + 0.5*self.bitcell_array_inst.lx(),0) mid2 = mid1.scale(1,0)+bitcell_wl_pos.scale(0.5,1) - self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + self.add_path("m1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) def route_port_address_right(self, port): @@ -830,7 +833,7 @@ class bank(design.design): bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port]+"_{}".format(row)).rc() mid1 = driver_wl_pos.scale(0,1) + vector(0.5*self.port_address_inst[port].lx() + 0.5*self.bitcell_array_inst.rx(),0) mid2 = mid1.scale(1,0)+bitcell_wl_pos.scale(0,1) - self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + self.add_path("m1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) def route_column_address_lines(self, port): """ Connecting the select lines of column mux to the address bus """ @@ -866,8 +869,7 @@ class bank(design.design): column_mux_pins = [self.port_data_inst[port].get_pin(x) for x in sel_names] route_map = list(zip(decode_pins, column_mux_pins)) - self.create_vertical_channel_route(route_map, offset) - + self.create_vertical_channel_route(route_map, offset, self.m1_stack) def add_lvs_correspondence_points(self): """ This adds some points for easier debugging if LVS goes wrong. @@ -879,7 +881,7 @@ class bank(design.design): wl_name = "wl_{}".format(i) wl_pin = self.bitcell_array_inst.get_pin(wl_name) self.add_label(text=wl_name, - layer="metal1", + layer="m1", offset=wl_pin.center()) # Add the bitline names @@ -889,10 +891,10 @@ class bank(design.design): bl_pin = self.bitcell_array_inst.get_pin(bl_name) br_pin = self.bitcell_array_inst.get_pin(br_name) self.add_label(text=bl_name, - layer="metal2", + layer="m2", offset=bl_pin.center()) self.add_label(text=br_name, - layer="metal2", + layer="m2", offset=br_pin.center()) # # Add the data output names to the sense amp output @@ -900,7 +902,7 @@ class bank(design.design): # data_name = "data_{}".format(i) # data_pin = self.sense_amp_array_inst.get_pin(data_name) # self.add_label(text="sa_out_{}".format(i), - # layer="metal2", + # layer="m2", # offset=data_pin.center()) # Add labels on the decoder @@ -910,10 +912,9 @@ class bank(design.design): pin_name = "in_{}".format(i) data_pin = self.wordline_driver_inst[port].get_pin(pin_name) self.add_label(text=data_name, - layer="metal1", + layer="m1", offset=data_pin.center()) - def route_control_lines(self, port): """ Route the control lines of the entire bank """ @@ -921,29 +922,31 @@ class bank(design.design): # From control signal to the module pin # Connection from the central bus to the main control block crosses # pre-decoder and this connection is in metal3 - write_inst = 0 - read_inst = 0 - connection = [] - connection.append((self.prefix+"p_en_bar{}".format(port), self.port_data_inst[port].get_pin("p_en_bar").lc())) + connection.append((self.prefix + "p_en_bar{}".format(port), + self.port_data_inst[port].get_pin("p_en_bar").lc())) rbl_wl_name = self.bitcell_array.get_rbl_wl_name(self.port_rbl_map[port]) - connection.append((self.prefix+"wl_en{}".format(port), self.bitcell_array_inst.get_pin(rbl_wl_name).lc())) + connection.append((self.prefix + "wl_en{}".format(port), + self.bitcell_array_inst.get_pin(rbl_wl_name).lc())) if port in self.write_ports: if port % 2: - connection.append((self.prefix+"w_en{}".format(port), self.port_data_inst[port].get_pin("w_en").rc())) + connection.append((self.prefix + "w_en{}".format(port), + self.port_data_inst[port].get_pin("w_en").rc())) else: - connection.append((self.prefix+"w_en{}".format(port), self.port_data_inst[port].get_pin("w_en").lc())) + connection.append((self.prefix + "w_en{}".format(port), + self.port_data_inst[port].get_pin("w_en").lc())) if port in self.read_ports: - connection.append((self.prefix+"s_en{}".format(port), self.port_data_inst[port].get_pin("s_en").lc())) + connection.append((self.prefix + "s_en{}".format(port), + self.port_data_inst[port].get_pin("s_en").lc())) for (control_signal, pin_pos) in connection: control_mid_pos = self.bus_xoffset[port][control_signal] control_pos = vector(self.bus_xoffset[port][control_signal].x ,pin_pos.y) - self.add_wire(("metal1","via1","metal2"), [control_mid_pos, control_pos, pin_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_wire(self.m1_stack, [control_mid_pos, control_pos, pin_pos]) + self.add_via_center(layers=self.m1_stack, offset=control_pos) @@ -957,11 +960,11 @@ class bank(design.design): mid_pos = pin_pos - vector(0,2*self.m2_gap) # to route down to the top of the bus control_x_offset = self.bus_xoffset[port][control_signal].x control_pos = vector(control_x_offset, mid_pos.y) - self.add_wire(("metal1","via1","metal2"),[pin_pos, mid_pos, control_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_wire(self.m1_stack,[pin_pos, mid_pos, control_pos]) + self.add_via_center(layers=self.m1_stack, offset=control_pos) - def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True): + def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True): """Get the all the stage efforts for each stage in the path within the bank clk_buf to a wordline""" #Decoder is assumed to have settled before the negative edge of the clock. Delay model relies on this assumption stage_effort_list = [] diff --git a/compiler/modules/bank_select.py b/compiler/modules/bank_select.py index 296cef8b..c3307ece 100644 --- a/compiler/modules/bank_select.py +++ b/compiler/modules/bank_select.py @@ -42,6 +42,10 @@ class bank_select(design.design): self.place_instances() self.route_instances() + self.height = max([x.uy() for x in self.inv_inst]) + self.m1_width + self.width = max([x.rx() for x in self.inv_inst]) + + self.add_boundary() self.DRC_LVS() @@ -96,14 +100,11 @@ class bank_select(design.design): self.xoffset_nand = self.inv4x.width + 2*self.m2_pitch + drc("pwell_to_nwell") self.xoffset_nor = self.inv4x.width + 2*self.m2_pitch + drc("pwell_to_nwell") - self.xoffset_inv = max(self.xoffset_nand + self.nand2.width, self.xoffset_nor + self.nor2.width) self.xoffset_bank_sel_inv = 0 self.xoffset_inputs = 0 self.yoffset_maxpoint = self.num_control_lines * self.inv4x.height - # Include the M1 pitches for the supply rails and spacing - self.height = self.yoffset_maxpoint + 2*self.m1_pitch - self.width = self.xoffset_inv + self.inv4x.width + def create_instances(self): @@ -197,7 +198,7 @@ class bank_select(design.design): mirror=mirror) # They all get inverters on the output - inv_inst.place(offset=[self.xoffset_inv, y_offset], + inv_inst.place(offset=[logic_inst.rx(), y_offset], mirror=mirror) @@ -208,18 +209,18 @@ class bank_select(design.design): xoffset_bank_sel = bank_sel_inv_pin.lx() bank_sel_line_pos = vector(xoffset_bank_sel, 0) bank_sel_line_end = vector(xoffset_bank_sel, self.yoffset_maxpoint) - self.add_path("metal2", [bank_sel_line_pos, bank_sel_line_end]) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_path("m2", [bank_sel_line_pos, bank_sel_line_end]) + self.add_via_center(layers=self.m1_stack, offset=bank_sel_inv_pin.lc()) # Route the pin to the left edge as well bank_sel_pin_pos=vector(0, 0) bank_sel_pin_end=vector(bank_sel_line_pos.x, bank_sel_pin_pos.y) self.add_layout_pin_segment_center(text="bank_sel", - layer="metal3", + layer="m3", start=bank_sel_pin_pos, end=bank_sel_pin_end) - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_via_center(layers=self.m2_stack, offset=bank_sel_pin_end, directions=("H","H")) @@ -227,10 +228,10 @@ class bank_select(design.design): bank_sel_bar_pin = self.bank_sel_inv.get_pin("Z") xoffset_bank_sel_bar = bank_sel_bar_pin.rx() self.add_label_pin(text="bank_sel_bar", - layer="metal2", + layer="m2", offset=vector(xoffset_bank_sel_bar, 0), height=self.inv4x.height) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=bank_sel_bar_pin.rc()) @@ -251,14 +252,14 @@ class bank_select(design.design): out_position = logic_inst.get_pin("Z").rc() + vector(0.5*self.m1_width,0) in_position = inv_inst.get_pin("A").lc() + vector(0.5*self.m1_width,0) post = inv_inst.get_pin("A").rc() - self.add_path("metal1", [pre, out_position, in_position, post]) + self.add_path("m1", [pre, out_position, in_position, post]) # Connect the logic B input to bank_sel/bank_sel_bar - logic_pos = logic_inst.get_pin("B").lc() - vector(0.5*contact.m1m2.height,0) + logic_pos = logic_inst.get_pin("B").lc() - vector(0.5*contact.m1_via.height,0) input_pos = vector(xoffset_bank_signal, logic_pos.y) - self.add_path("metal2",[logic_pos, input_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_path("m2",[logic_pos, input_pos]) + self.add_via_center(layers=self.m1_stack, offset=logic_pos, directions=("H","H")) @@ -266,14 +267,14 @@ class bank_select(design.design): # Connect the logic A input to the input pin logic_pos = logic_inst.get_pin("A").lc() input_pos = vector(0,logic_pos.y) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=logic_pos, directions=("H","H")) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=logic_pos, directions=("H","H")) self.add_layout_pin_segment_center(text=input_name, - layer="metal3", + layer="m3", start=input_pos, end=logic_pos) @@ -294,34 +295,34 @@ class bank_select(design.design): for n in ["vdd", "gnd"]: supply_pin = self.inv_inst[num].get_pin(n) supply_offset = supply_pin.ll().scale(0,1) - self.add_rect(layer="metal1", + self.add_rect(layer="m1", offset=supply_offset, width=self.width) # Add pins in two locations for xoffset in [a_xoffset, b_xoffset]: pin_pos = vector(xoffset, supply_pin.cy()) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=pin_pos, directions=("H","H")) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=pin_pos, directions=("H","H")) self.add_layout_pin_rect_center(text=n, - layer="metal3", + layer="m3", offset=pin_pos) # Add vdd/gnd supply rails - gnd_pin = inv_inst.get_pin("gnd") + gnd_pin = self.inv_inst[num].get_pin("gnd") left_gnd_pos = vector(0, gnd_pin.cy()) self.add_layout_pin_segment_center(text="gnd", - layer="metal1", + layer="m1", start=left_gnd_pos, end=gnd_pin.rc()) - vdd_pin = inv_inst.get_pin("vdd") + vdd_pin = self.inv_inst[num].get_pin("vdd") left_vdd_pos = vector(0, vdd_pin.cy()) self.add_layout_pin_segment_center(text="vdd", - layer="metal1", + layer="m1", start=left_vdd_pos, end=vdd_pin.rc()) diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index b1b61487..60461724 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -5,27 +5,20 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import debug -import design +from bitcell_base_array import bitcell_base_array from tech import drc, spice -from vector import vector from globals import OPTS from sram_factory import factory -import logical_effort -class bitcell_array(design.design): + +class bitcell_array(bitcell_base_array): """ Creates a rows x cols array of memory cells. Assumes bit-lines and word line is connected by abutment. Connects the word lines and bit lines. """ - def __init__(self, cols, rows, name): - design.design.__init__(self, name) - debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) - self.add_comment("rows: {0} cols: {1}".format(rows, cols)) - - self.column_size = cols - self.row_size = rows + def __init__(self, cols, rows, name, column_offset=0): + super().__init__(cols, rows, name, column_offset) self.create_netlist() if not OPTS.netlist_only: @@ -33,7 +26,7 @@ class bitcell_array(design.design): # We don't offset this because we need to align # the replica bitcell in the control logic - #self.offset_all_coordinates() + # self.offset_all_coordinates() def create_netlist(self): @@ -44,27 +37,7 @@ class bitcell_array(design.design): def create_layout(self): - # We increase it by a well enclosure so the precharges don't overlap our wells - self.height = self.row_size*self.cell.height - self.width = self.column_size*self.cell.width - - xoffset = 0.0 - for col in range(self.column_size): - yoffset = 0.0 - for row in range(self.row_size): - name = "bit_r{0}_c{1}".format(row, col) - - if row % 2: - tempy = yoffset + self.cell.height - dir_key = "MX" - else: - tempy = yoffset - dir_key = "" - - self.cell_inst[row,col].place(offset=[xoffset, tempy], - mirror=dir_key) - yoffset += self.cell.height - xoffset += self.cell.width + self.place_array("bit_r{0}_c{1}") self.add_layout_pins() @@ -72,41 +45,11 @@ class bitcell_array(design.design): self.DRC_LVS() - def add_pins(self): - row_list = self.cell.get_all_wl_names() - column_list = self.cell.get_all_bitline_names() - for col in range(self.column_size): - for cell_column in column_list: - self.add_pin(cell_column+"_{0}".format(col), "INOUT") - for row in range(self.row_size): - for cell_row in row_list: - self.add_pin(cell_row+"_{0}".format(row), "INPUT") - self.add_pin("vdd", "POWER") - self.add_pin("gnd", "GROUND") - def add_modules(self): """ Add the modules used in this design """ self.cell = factory.create(module_type="bitcell") self.add_mod(self.cell) - def get_bitcell_pins(self, col, row): - """ Creates a list of connections in the bitcell, - indexed by column and row, for instance use in bitcell_array """ - - bitcell_pins = [] - - pin_names = self.cell.get_all_bitline_names() - for pin in pin_names: - bitcell_pins.append(pin+"_{0}".format(col)) - pin_names = self.cell.get_all_wl_names() - for pin in pin_names: - bitcell_pins.append(pin+"_{0}".format(row)) - bitcell_pins.append("vdd") - bitcell_pins.append("gnd") - - return bitcell_pins - - def create_instances(self): """ Create the module instances used in this design """ self.cell_inst = {} @@ -117,38 +60,6 @@ class bitcell_array(design.design): mod=self.cell) self.connect_inst(self.get_bitcell_pins(col, row)) - def add_layout_pins(self): - """ Add the layout pins """ - - row_list = self.cell.get_all_wl_names() - column_list = self.cell.get_all_bitline_names() - - for col in range(self.column_size): - for cell_column in column_list: - bl_pin = self.cell_inst[0,col].get_pin(cell_column) - self.add_layout_pin(text=cell_column+"_{0}".format(col), - layer=bl_pin.layer, - offset=bl_pin.ll().scale(1,0), - width=bl_pin.width(), - height=self.height) - - for row in range(self.row_size): - for cell_row in row_list: - wl_pin = self.cell_inst[row,0].get_pin(cell_row) - self.add_layout_pin(text=cell_row+"_{0}".format(row), - layer=wl_pin.layer, - offset=wl_pin.ll().scale(0,1), - width=self.width, - height=wl_pin.height()) - - # For every second row and column, add a via for gnd and vdd - for row in range(self.row_size): - for col in range(self.column_size): - 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(), vertical=True, start_layer=pin.layer) - def analytical_power(self, corner, load): """Power of Bitcell array and bitline in nW.""" from tech import drc, parameter @@ -160,10 +71,10 @@ class bitcell_array(design.design): freq = spice["default_event_frequency"] bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing) - #Calculate the bitcell power which currently only includes leakage + # Calculate the bitcell power which currently only includes leakage cell_power = self.cell.analytical_power(corner, load) - #Leakage power grows with entire array and bitlines. + # Leakage power grows with entire array and bitlines. total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size, cell_power.leakage * self.column_size * self.row_size) return total_power @@ -173,7 +84,7 @@ class bitcell_array(design.design): width = 0 else: width = self.width - wl_wire = self.generate_rc_net(int(self.column_size), width, drc("minwidth_metal1")) + wl_wire = self.generate_rc_net(int(self.column_size), width, drc("minwidth_m1")) wl_wire.wire_c = 2*spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell return wl_wire @@ -183,7 +94,7 @@ class bitcell_array(design.design): else: height = self.height bl_pos = 0 - bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), height, drc("minwidth_metal1")) + bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), height, drc("minwidth_m1")) bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell return bl_wire diff --git a/compiler/modules/bitcell_base_array.py b/compiler/modules/bitcell_base_array.py new file mode 100644 index 00000000..45bcfe0c --- /dev/null +++ b/compiler/modules/bitcell_base_array.py @@ -0,0 +1,157 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# +import debug +import design +from tech import cell_properties + +class bitcell_base_array(design.design): + """ + Abstract base class for bitcell-arrays -- bitcell, dummy + """ + def __init__(self, cols, rows, name, column_offset): + design.design.__init__(self, name) + debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) + self.add_comment("rows: {0} cols: {1}".format(rows, cols)) + + self.column_size = cols + self.row_size = rows + self.column_offset = column_offset + + def get_all_bitline_names(self): + + res = list() + bitline_names = self.cell.get_all_bitline_names() + + # We have to keep the order of self.pins, otherwise we connect + # it wrong in the spice netlist + for pin in self.pins: + for bl_name in bitline_names: + if bl_name in pin: + res.append(pin) + return res + + def get_all_wordline_names(self): + + res = list() + wordline_names = self.cell.get_all_wl_names() + + # We have to keep the order of self.pins, otherwise we connect + # it wrong in the spice netlist + for pin in self.pins: + for wl_name in wordline_names: + if wl_name in pin: + res.append(pin) + return res + + def add_pins(self): + row_list = self.cell.get_all_wl_names() + column_list = self.cell.get_all_bitline_names() + for col in range(self.column_size): + for cell_column in column_list: + self.add_pin(cell_column+"_{0}".format(col), "INOUT") + for row in range(self.row_size): + for cell_row in row_list: + self.add_pin(cell_row+"_{0}".format(row), "INPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def get_bitcell_pins(self, col, row): + """ Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array """ + + bitcell_pins = [] + + pin_names = self.cell.get_all_bitline_names() + for pin in pin_names: + bitcell_pins.append(pin+"_{0}".format(col)) + pin_names = self.cell.get_all_wl_names() + for pin in pin_names: + bitcell_pins.append(pin+"_{0}".format(row)) + bitcell_pins.append("vdd") + bitcell_pins.append("gnd") + + return bitcell_pins + + def add_layout_pins(self): + """ Add the layout pins """ + + row_list = self.cell.get_all_wl_names() + column_list = self.cell.get_all_bitline_names() + + for col in range(self.column_size): + for cell_column in column_list: + bl_pin = self.cell_inst[0,col].get_pin(cell_column) + self.add_layout_pin(text=cell_column+"_{0}".format(col), + layer=bl_pin.layer, + offset=bl_pin.ll().scale(1,0), + width=bl_pin.width(), + height=self.height) + + for row in range(self.row_size): + for cell_row in row_list: + wl_pin = self.cell_inst[row,0].get_pin(cell_row) + self.add_layout_pin(text=cell_row+"_{0}".format(row), + layer=wl_pin.layer, + offset=wl_pin.ll().scale(0,1), + width=self.width, + height=wl_pin.height()) + + # For every second row and column, add a via for gnd and vdd + for row in range(self.row_size): + for col in range(self.column_size): + 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(), vertical=True, start_layer=pin.layer) + + def _adjust_x_offset(self, xoffset, col, col_offset): + tempx = xoffset + dir_y = False + # If we mirror the current cell on the y axis adjust the x position + if cell_properties.bitcell.mirror.y and (col + col_offset) % 2: + tempx = xoffset + self.cell.width + dir_y = True + return (tempx, dir_y) + + def _adjust_y_offset(self, yoffset, row, row_offset): + tempy = yoffset + dir_x = False + # If we mirror the current cell on the x axis adjust the y position + if cell_properties.bitcell.mirror.x and (row + row_offset) % 2: + tempy = yoffset + self.cell.height + dir_x = True + return (tempy, dir_x) + + + def place_array(self, name_template, row_offset=0): + # We increase it by a well enclosure so the precharges don't overlap our wells + self.height = self.row_size*self.cell.height + self.width = self.column_size*self.cell.width + + xoffset = 0.0 + for col in range(self.column_size): + yoffset = 0.0 + tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset) + + for row in range(self.row_size): + name = name_template.format(row, col) + tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset) + + if dir_x and dir_y: + dir_key = "XY" + elif dir_x: + dir_key = "MX" + elif dir_y: + dir_key = "MY" + else: + dir_key = "" + + self.cell_inst[row,col].place(offset=[tempx, tempy], + mirror=dir_key) + yoffset += self.cell.height + xoffset += self.cell.width diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 3256c9ac..1612938d 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -8,6 +8,7 @@ from math import log import design from tech import drc, parameter +from tech import cell_properties as props import debug import contact from sram_factory import factory @@ -371,7 +372,7 @@ class control_logic(design.design): height = self.control_logic_center.y - self.m2_pitch offset = vector(self.ctrl_dff_array.width,0) - self.rail_offsets = self.create_vertical_bus("metal2", self.m2_pitch, offset, self.internal_bus_list, height) + self.rail_offsets = self.create_vertical_bus("m2", self.m2_pitch, offset, self.internal_bus_list, height) def create_instances(self): @@ -483,8 +484,8 @@ class control_logic(design.design): vdd_ypos = self.p_en_bar_nand_inst.get_pin("vdd").by() in_pos = vector(self.rail_offsets["rbl_bl_delay"].x,vdd_ypos) mid1 = vector(out_pos.x,in_pos.y) - self.add_wire(("metal1","via1","metal2"),[out_pos, mid1, in_pos]) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_wire(self.m1_stack,[out_pos, mid1, in_pos]) + self.add_via_center(layers=self.m1_stack, offset=in_pos) @@ -509,10 +510,10 @@ class control_logic(design.design): clk_pin = self.clk_buf_inst.get_pin("A") clk_pos = clk_pin.center() self.add_layout_pin_segment_center(text="clk", - layer="metal2", + layer="m2", start=clk_pos, end=clk_pos.scale(1,0)) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=clk_pos) @@ -521,9 +522,9 @@ class control_logic(design.design): mid1 = vector(out_pos.x,2*self.m2_pitch) mid2 = vector(self.rail_offsets["clk_buf"].x, mid1.y) bus_pos = self.rail_offsets["clk_buf"] - self.add_wire(("metal3","via2","metal2"),[out_pos, mid1, mid2, bus_pos]) + self.add_wire(("m3","via2","m2"),[out_pos, mid1, mid2, bus_pos]) # The pin is on M1, so we need another via as well - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=self.clk_buf_inst.get_pin("Z").center()) self.connect_output(self.clk_buf_inst, "Z", "clk_buf") @@ -552,21 +553,21 @@ class control_logic(design.design): out_pos = self.clk_bar_inst.get_pin("Z").center() in_pos = self.gated_clk_bar_inst.get_pin("B").center() mid1 = vector(in_pos.x,out_pos.y) - self.add_path("metal1",[out_pos, mid1, in_pos]) + self.add_path("m1",[out_pos, mid1, in_pos]) # This is the second gate over, so it needs to be on M3 clkbuf_map = zip(["A"], ["cs"]) - self.connect_vertical_bus(clkbuf_map, self.gated_clk_bar_inst, self.rail_offsets, ("metal3", "via2", "metal2")) + self.connect_vertical_bus(clkbuf_map, self.gated_clk_bar_inst, self.rail_offsets, ("m3", "via2", "m2")) # The pin is on M1, so we need another via as well - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=self.gated_clk_bar_inst.get_pin("A").center()) # This is the second gate over, so it needs to be on M3 clkbuf_map = zip(["Z"], ["gated_clk_bar"]) - self.connect_vertical_bus(clkbuf_map, self.gated_clk_bar_inst, self.rail_offsets, ("metal3", "via2", "metal2")) + self.connect_vertical_bus(clkbuf_map, self.gated_clk_bar_inst, self.rail_offsets, ("m3", "via2", "m2")) # The pin is on M1, so we need another via as well - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=self.gated_clk_bar_inst.get_pin("Z").center()) def create_gated_clk_buf_row(self): @@ -587,9 +588,9 @@ class control_logic(design.design): clkbuf_map = zip(["Z"], ["gated_clk_buf"]) - self.connect_vertical_bus(clkbuf_map, self.gated_clk_buf_inst, self.rail_offsets, ("metal3", "via2", "metal2")) + self.connect_vertical_bus(clkbuf_map, self.gated_clk_buf_inst, self.rail_offsets, ("m3", "via2", "m2")) # The pin is on M1, so we need another via as well - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=self.gated_clk_buf_inst.get_pin("Z").center()) def create_wlen_row(self): @@ -637,7 +638,7 @@ class control_logic(design.design): out_pos = self.p_en_bar_nand_inst.get_pin("Z").rc() in_pos = self.p_en_bar_driver_inst.get_pin("A").lc() mid1 = vector(out_pos.x,in_pos.y) - self.add_wire(("metal1","via1","metal2"),[out_pos, mid1,in_pos]) + self.add_wire(self.m1_stack,[out_pos, mid1,in_pos]) self.connect_output(self.p_en_bar_driver_inst, "Z", "p_en_bar") @@ -646,7 +647,7 @@ class control_logic(design.design): if self.port_type=="rw": input_name = "we_bar" else: - input_name = "cs_bar" + input_name = "cs" # GATE FOR S_EN self.s_en_gate_inst = self.add_inst(name="buf_s_en_and", mod=self.sen_and3) @@ -669,7 +670,7 @@ class control_logic(design.design): if self.port_type=="rw": input_name = "we_bar" else: - input_name = "cs_bar" + input_name = "cs" sen_map = zip(["A", "B", "C"], ["rbl_bl_delay", "gated_clk_bar", input_name]) self.connect_vertical_bus(sen_map, self.s_en_gate_inst, self.rail_offsets) @@ -694,9 +695,9 @@ class control_logic(design.design): # Connect to rail rbl_map = zip(["Z"], ["rbl_bl_delay_bar"]) - self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets, ("metal3", "via2", "metal2")) + self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets, ("m3", "via2", "m2")) # The pin is on M1, so we need another via as well - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=self.rbl_bl_delay_inv_inst.get_pin("Z").center()) @@ -742,7 +743,11 @@ class control_logic(design.design): def create_dffs(self): self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs", mod=self.ctrl_dff_array) - self.connect_inst(self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list) + inst_pins = self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list + if props.dff_buff_array.add_body_contacts: + inst_pins.append("vpb") + inst_pins.append("vnb") + self.connect_inst(inst_pins) def place_dffs(self): self.ctrl_dff_inst.place(vector(0,0)) @@ -754,14 +759,14 @@ class control_logic(design.design): dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"]) else: dff_out_map = zip(["dout_bar_0"], ["cs"]) - self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets, ("metal3", "via2", "metal2")) + self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets, ("m3", "via2", "m2")) # Connect the clock rail to the other clock rail in_pos = self.ctrl_dff_inst.get_pin("clk").uc() mid_pos = in_pos + vector(0,2*self.m2_pitch) rail_pos = vector(self.rail_offsets["clk_buf"].x, mid_pos.y) - self.add_wire(("metal1","via1","metal2"),[in_pos, mid_pos, rail_pos]) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_wire(self.m1_stack,[in_pos, mid_pos, rail_pos]) + self.add_via_center(layers=self.m1_stack, offset=rail_pos) self.copy_layout_pin(self.ctrl_dff_inst, "din_0", "csb") @@ -786,7 +791,7 @@ class control_logic(design.design): out_pin = inst.get_pin(pin_name) right_pos=out_pin.center() + vector(self.width-out_pin.cx(),0) self.add_layout_pin_segment_center(text=out_name, - layer="metal1", + layer="m1", start=out_pin.center(), end=right_pos) @@ -799,19 +804,19 @@ class control_logic(design.design): for inst in self.row_end_inst: pins = inst.get_pins("vdd") for pin in pins: - if pin.layer == "metal1": + if pin.layer == "m1": row_loc = pin.rc() pin_loc = vector(max_row_x_loc, pin.rc().y) self.add_power_pin("vdd", pin_loc) - self.add_path("metal1", [row_loc, pin_loc]) + self.add_path("m1", [row_loc, pin_loc]) pins = inst.get_pins("gnd") for pin in pins: - if pin.layer == "metal1": + if pin.layer == "m1": row_loc = pin.rc() pin_loc = vector(max_row_x_loc, pin.rc().y) self.add_power_pin("gnd", pin_loc) - self.add_path("metal1", [row_loc, pin_loc]) + self.add_path("m1", [row_loc, pin_loc]) self.copy_layout_pin(self.delay_inst,"gnd") self.copy_layout_pin(self.delay_inst,"vdd") @@ -828,14 +833,14 @@ class control_logic(design.design): """ # pin=self.clk_inv1.get_pin("Z") # self.add_label_pin(text="clk1_bar", - # layer="metal1", + # layer="m1", # offset=pin.ll(), # height=pin.height(), # width=pin.width()) # pin=self.clk_inv2.get_pin("Z") # self.add_label_pin(text="clk2", - # layer="metal1", + # layer="m1", # offset=pin.ll(), # height=pin.height(), # width=pin.width()) diff --git a/compiler/modules/delay_chain.py b/compiler/modules/delay_chain.py index bc932a26..a07576d4 100644 --- a/compiler/modules/delay_chain.py +++ b/compiler/modules/delay_chain.py @@ -132,11 +132,11 @@ class delay_chain(design.design): pin1_pos = pin1.center() pin2_pos = pin2.center() if pin1_pos.y == pin2_pos.y: - self.add_path("metal2", [pin1_pos, pin2_pos]) + self.add_path("m2", [pin1_pos, pin2_pos]) else: mid_point = vector(pin2_pos.x, 0.5*(pin1_pos.y+pin2_pos.y)) # Written this way to guarantee it goes right first if we are switching rows - self.add_path("metal2", [pin1_pos, vector(pin1_pos.x,mid_point.y), mid_point, vector(mid_point.x,pin2_pos.y), pin2_pos]) + self.add_path("m2", [pin1_pos, vector(pin1_pos.x,mid_point.y), mid_point, vector(mid_point.x,pin2_pos.y), pin2_pos]) def route_inverters(self): """ Add metal routing for each of the fanout stages """ @@ -146,22 +146,22 @@ class delay_chain(design.design): for load in self.load_inst_map[inv]: # Drop a via on each A pin a_pin = load.get_pin("A") - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=a_pin.center()) - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_via_center(layers=self.m2_stack, offset=a_pin.center()) # Route an M3 horizontal wire to the furthest z_pin = inv.get_pin("Z") a_pin = inv.get_pin("A") a_max = self.rightest_load_inst[inv].get_pin("A") - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=a_pin.center()) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=z_pin.center()) - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_via_center(layers=self.m2_stack, offset=z_pin.center()) - self.add_path("metal3",[z_pin.center(), a_max.center()]) + self.add_path("m3",[z_pin.center(), a_max.center()]) # Route Z to the A of the next stage @@ -172,7 +172,7 @@ class delay_chain(design.design): y_mid = (z_pin.cy() + next_a_pin.cy())/2 mid1_point = vector(z_pin.cx(), y_mid) mid2_point = vector(next_a_pin.cx(), y_mid) - self.add_path("metal2",[z_pin.center(), mid1_point, mid2_point, next_a_pin.center()]) + self.add_path("m2",[z_pin.center(), mid1_point, mid2_point, next_a_pin.center()]) def add_layout_pins(self): @@ -205,10 +205,10 @@ class delay_chain(design.design): # input is A pin of first inverter a_pin = self.driver_inst_list[0].get_pin("A") - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=a_pin.center()) self.add_layout_pin(text="in", - layer="metal2", + layer="m2", offset=a_pin.ll().scale(1,0), height=a_pin.cy()) @@ -216,12 +216,12 @@ class delay_chain(design.design): # output is A pin of last load inverter last_driver_inst = self.driver_inst_list[-1] a_pin = self.rightest_load_inst[last_driver_inst].get_pin("A") - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=a_pin.center()) mid_point = vector(a_pin.cx()+3*self.m2_width,a_pin.cy()) - self.add_path("metal2",[a_pin.center(), mid_point, mid_point.scale(1,0)]) + self.add_path("m2",[a_pin.center(), mid_point, mid_point.scale(1,0)]) self.add_layout_pin_segment_center(text="out", - layer="metal2", + layer="m2", start=mid_point, end=mid_point.scale(1,0)) diff --git a/compiler/modules/dff.py b/compiler/modules/dff.py index 8871872d..e319459e 100644 --- a/compiler/modules/dff.py +++ b/compiler/modules/dff.py @@ -7,6 +7,7 @@ # import design from tech import GDS, layer, spice, parameter +from tech import cell_properties as props import utils @@ -14,9 +15,15 @@ class dff(design.design): """ Memory address flip-flop """ + if not props.dff.use_custom_ports: + pin_names = ["D", "Q", "clk", "vdd", "gnd"] + type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] + clk_pin = "clk" + else: + pin_names = props.dff.custom_port_list + type_list = props.dff.custom_type_list + clk_pin = props.dff.clk_pin - pin_names = ["D", "Q", "clk", "vdd", "gnd"] - type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width, height) = utils.get_libcell_size("dff", GDS["unit"], layer["boundary"]) diff --git a/compiler/modules/dff_array.py b/compiler/modules/dff_array.py index 89b29476..62464834 100644 --- a/compiler/modules/dff_array.py +++ b/compiler/modules/dff_array.py @@ -69,11 +69,12 @@ class dff_array(design.design): name = "dff_r{0}_c{1}".format(row,col) self.dff_insts[row,col]=self.add_inst(name=name, mod=self.dff) - self.connect_inst([self.get_din_name(row,col), - self.get_dout_name(row,col), - "clk", - "vdd", - "gnd"]) + instance_ports = [self.get_din_name(row,col), + self.get_dout_name(row,col)] + for port in self.dff.pin_names: + if port != 'D' and port != 'Q': + instance_ports.append(port) + self.connect_inst(instance_ports) def place_dff_array(self): for row in range(self.rows): @@ -124,7 +125,7 @@ class dff_array(design.design): for row in range(self.rows): for col in range(self.columns): din_pin = self.dff_insts[row,col].get_pin("D") - debug.check(din_pin.layer=="metal2","DFF D pin not on metal2") + debug.check(din_pin.layer=="m2","DFF D pin not on metal2") self.add_layout_pin(text=self.get_din_name(row,col), layer=din_pin.layer, offset=din_pin.ll(), @@ -132,7 +133,7 @@ class dff_array(design.design): height=din_pin.height()) dout_pin = self.dff_insts[row,col].get_pin("Q") - debug.check(dout_pin.layer=="metal2","DFF Q pin not on metal2") + debug.check(dout_pin.layer=="m2","DFF Q pin not on metal2") self.add_layout_pin(text=self.get_dout_name(row,col), layer=dout_pin.layer, offset=dout_pin.ll(), @@ -142,22 +143,22 @@ class dff_array(design.design): # Create vertical spines to a single horizontal rail - clk_pin = self.dff_insts[0,0].get_pin("clk") + clk_pin = self.dff_insts[0,0].get_pin(self.dff.clk_pin) clk_ypos = 2*self.m3_pitch+self.m3_width - debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2") + debug.check(clk_pin.layer=="m2","DFF clk pin not on metal2") self.add_layout_pin_segment_center(text="clk", - layer="metal3", + layer="m3", start=vector(0,clk_ypos), end=vector(self.width,clk_ypos)) for col in range(self.columns): - clk_pin = self.dff_insts[0,col].get_pin("clk") + clk_pin = self.dff_insts[0,col].get_pin(self.dff.clk_pin) # Make a vertical strip for each column - self.add_rect(layer="metal2", + self.add_rect(layer="m2", offset=clk_pin.ll().scale(1,0), width=self.m2_width, height=self.height) # Drop a via to the M3 pin - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_via_center(layers=self.m2_stack, offset=vector(clk_pin.cx(),clk_ypos)) def get_clk_cin(self): diff --git a/compiler/modules/dff_buf.py b/compiler/modules/dff_buf.py index f6fc1cf2..6f06b778 100644 --- a/compiler/modules/dff_buf.py +++ b/compiler/modules/dff_buf.py @@ -8,6 +8,7 @@ import debug import design from tech import drc,parameter +from tech import cell_properties as props from math import log from vector import vector from globals import OPTS @@ -49,10 +50,11 @@ class dff_buf(design.design): self.create_instances() def create_layout(self): - self.width = self.dff.width + self.inv1.width + self.inv2.width + self.place_instances() + self.width = self.inv2_inst.rx() self.height = self.dff.height - self.place_instances() + self.route_wires() self.add_layout_pins() self.add_boundary() @@ -82,10 +84,15 @@ class dff_buf(design.design): self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") + if props.dff_buff.add_body_contacts: + self.add_pin("vpb", "INPUT") + self.add_pin("vpn", "INPUT") + def create_instances(self): self.dff_inst=self.add_inst(name="dff_buf_dff", mod=self.dff) - self.connect_inst(["D", "qint", "clk", "vdd", "gnd"]) + self.connect_inst(props.dff_buff.buf_ports) + #self.connect_inst(["D", "qint", "clk", "vdd", "gnd"]) self.inv1_inst=self.add_inst(name="dff_buf_inv1", mod=self.inv1) @@ -100,7 +107,10 @@ class dff_buf(design.design): self.dff_inst.place(vector(0,0)) # Add INV1 to the right - self.inv1_inst.place(vector(self.dff_inst.rx(),0)) + well_spacing = max(self.nwell_space, + self.pwell_space, + self.pwell_to_nwell) + self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing + self.well_extend_active,0)) # Add INV2 to the right self.inv2_inst.place(vector(self.inv1_inst.rx(),0)) @@ -112,12 +122,12 @@ class dff_buf(design.design): mid_x_offset = 0.5*(a1_pin.cx() + q_pin.cx()) mid1 = vector(mid_x_offset, q_pin.cy()) mid2 = vector(mid_x_offset, a1_pin.cy()) - self.add_path("metal3", [q_pin.center(), mid1, mid2, a1_pin.center()]) - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_path("m3", [q_pin.center(), mid1, mid2, a1_pin.center()]) + self.add_via_center(layers=self.m2_stack, offset=q_pin.center()) - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_via_center(layers=self.m2_stack, offset=a1_pin.center()) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=a1_pin.center()) # Route inv1 z to inv2 a @@ -126,14 +136,14 @@ class dff_buf(design.design): mid_x_offset = 0.5*(z1_pin.cx() + a2_pin.cx()) self.mid_qb_pos = vector(mid_x_offset, z1_pin.cy()) mid2 = vector(mid_x_offset, a2_pin.cy()) - self.add_path("metal1", [z1_pin.center(), self.mid_qb_pos, mid2, a2_pin.center()]) + self.add_path("m1", [z1_pin.center(), self.mid_qb_pos, mid2, a2_pin.center()]) def add_layout_pins(self): # Continous vdd rail along with label. vdd_pin=self.dff_inst.get_pin("vdd") self.add_layout_pin(text="vdd", - layer="metal1", + layer="m1", offset=vdd_pin.ll(), width=self.width, height=vdd_pin.height()) @@ -141,7 +151,7 @@ class dff_buf(design.design): # Continous gnd rail along with label. gnd_pin=self.dff_inst.get_pin("gnd") self.add_layout_pin(text="gnd", - layer="metal1", + layer="m1", offset=gnd_pin.ll(), width=self.width, height=vdd_pin.height()) @@ -164,18 +174,18 @@ class dff_buf(design.design): mid_pos = dout_pin.center() + vector(self.m1_pitch,0) q_pos = mid_pos - vector(0,self.m2_pitch) self.add_layout_pin_rect_center(text="Q", - layer="metal2", + layer="m2", offset=q_pos) - self.add_path("metal1", [dout_pin.center(), mid_pos, q_pos]) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_path("m1", [dout_pin.center(), mid_pos, q_pos]) + self.add_via_center(layers=self.m1_stack, offset=q_pos) qb_pos = self.mid_qb_pos + vector(0,self.m2_pitch) self.add_layout_pin_rect_center(text="Qb", - layer="metal2", + layer="m2", offset=qb_pos) - self.add_path("metal1", [self.mid_qb_pos, qb_pos]) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_path("m1", [self.mid_qb_pos, qb_pos]) + self.add_via_center(layers=self.m1_stack, offset=qb_pos) def get_clk_cin(self): diff --git a/compiler/modules/dff_buf_array.py b/compiler/modules/dff_buf_array.py index 2fafee76..1735a7a1 100644 --- a/compiler/modules/dff_buf_array.py +++ b/compiler/modules/dff_buf_array.py @@ -8,6 +8,7 @@ import debug import design from tech import drc +from tech import cell_properties as props from math import log from vector import vector from globals import OPTS @@ -64,12 +65,17 @@ class dff_buf_array(design.design): self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") + if props.dff_buff_array.add_body_contacts: + self.add_pin("vpb", "INPUT") + self.add_pin("vnb", "INPUT") + def add_modules(self): self.dff = factory.create(module_type="dff_buf", inv1_size=self.inv1_size, inv2_size=self.inv2_size) self.add_mod(self.dff) + def create_dff_array(self): self.dff_insts={} for row in range(self.rows): @@ -77,22 +83,33 @@ class dff_buf_array(design.design): name = "dff_r{0}_c{1}".format(row,col) self.dff_insts[row,col]=self.add_inst(name=name, mod=self.dff) - self.connect_inst([self.get_din_name(row,col), + inst_ports = [self.get_din_name(row,col), self.get_dout_name(row,col), self.get_dout_bar_name(row,col), "clk", "vdd", - "gnd"]) + "gnd"] + if props.dff_buff_array.add_body_contacts: + inst_ports.append("vpb") + inst_ports.append("vnb") + self.connect_inst(inst_ports) def place_dff_array(self): + + well_spacing = max(self.nwell_space, + self.pwell_space, + self.pwell_to_nwell) + + dff_pitch = self.dff.width + well_spacing + self.well_extend_active + for row in range(self.rows): for col in range(self.columns): name = "Xdff_r{0}_c{1}".format(row,col) if (row % 2 == 0): - base = vector(col*self.dff.width,row*self.dff.height) + base = vector(col*dff_pitch,row*self.dff.height) mirror = "R0" else: - base = vector(col*self.dff.width,(row+1)*self.dff.height) + base = vector(col*dff_pitch,(row+1)*self.dff.height) mirror = "MX" self.dff_insts[row,col].place(offset=base, mirror=mirror) @@ -142,7 +159,7 @@ class dff_buf_array(design.design): for row in range(self.rows): for col in range(self.columns): din_pin = self.dff_insts[row,col].get_pin("D") - debug.check(din_pin.layer=="metal2","DFF D pin not on metal2") + debug.check(din_pin.layer=="m2","DFF D pin not on metal2") self.add_layout_pin(text=self.get_din_name(row,col), layer=din_pin.layer, offset=din_pin.ll(), @@ -150,7 +167,7 @@ class dff_buf_array(design.design): height=din_pin.height()) dout_pin = self.dff_insts[row,col].get_pin("Q") - debug.check(dout_pin.layer=="metal2","DFF Q pin not on metal2") + debug.check(dout_pin.layer=="m2","DFF Q pin not on metal2") self.add_layout_pin(text=self.get_dout_name(row,col), layer=dout_pin.layer, offset=dout_pin.ll(), @@ -158,7 +175,7 @@ class dff_buf_array(design.design): height=dout_pin.height()) dout_bar_pin = self.dff_insts[row,col].get_pin("Qb") - debug.check(dout_bar_pin.layer=="metal2","DFF Qb pin not on metal2") + debug.check(dout_bar_pin.layer=="m2","DFF Qb pin not on metal2") self.add_layout_pin(text=self.get_dout_bar_name(row,col), layer=dout_bar_pin.layer, offset=dout_bar_pin.ll(), @@ -169,28 +186,28 @@ class dff_buf_array(design.design): # Create vertical spines to a single horizontal rail clk_pin = self.dff_insts[0,0].get_pin("clk") clk_ypos = 2*self.m3_pitch+self.m3_width - debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2") + debug.check(clk_pin.layer=="m2","DFF clk pin not on metal2") if self.columns==1: self.add_layout_pin(text="clk", - layer="metal2", + layer="m2", offset=clk_pin.ll().scale(1,0), width=self.m2_width, height=self.height) else: self.add_layout_pin_segment_center(text="clk", - layer="metal3", + layer="m3", start=vector(0,clk_ypos), end=vector(self.width,clk_ypos)) for col in range(self.columns): clk_pin = self.dff_insts[0,col].get_pin("clk") # Make a vertical strip for each column - self.add_rect(layer="metal2", + self.add_rect(layer="m2", offset=clk_pin.ll().scale(1,0), width=self.m2_width, height=self.height) # Drop a via to the M3 pin - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_via_center(layers=self.m2_stack, offset=vector(clk_pin.cx(),clk_ypos)) def get_clk_cin(self): diff --git a/compiler/modules/dff_inv.py b/compiler/modules/dff_inv.py index 207a5ad0..9dcb84c5 100644 --- a/compiler/modules/dff_inv.py +++ b/compiler/modules/dff_inv.py @@ -97,13 +97,13 @@ class dff_inv(design.design): mid_x_offset = 0.5*(a1_pin.cx() + q_pin.cx()) mid1 = vector(mid_x_offset, q_pin.cy()) mid2 = vector(mid_x_offset, a1_pin.cy()) - self.add_path("metal3", + self.add_path("m3", [q_pin.center(), mid1, mid2, a1_pin.center()]) - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_via_center(layers=self.m2_stack, offset=q_pin.center()) - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_via_center(layers=self.m2_stack, offset=a1_pin.center()) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=a1_pin.center()) @@ -112,7 +112,7 @@ class dff_inv(design.design): # Continous vdd rail along with label. vdd_pin=self.dff_inst.get_pin("vdd") self.add_layout_pin(text="vdd", - layer="metal1", + layer="m1", offset=vdd_pin.ll(), width=self.width, height=vdd_pin.height()) @@ -120,7 +120,7 @@ class dff_inv(design.design): # Continous gnd rail along with label. gnd_pin=self.dff_inst.get_pin("gnd") self.add_layout_pin(text="gnd", - layer="metal1", + layer="m1", offset=gnd_pin.ll(), width=self.width, height=vdd_pin.height()) @@ -146,9 +146,9 @@ class dff_inv(design.design): dout_pin = self.inv1_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Qb", - layer="metal2", + layer="m2", offset=dout_pin.center()) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=dout_pin.center()) def get_clk_cin(self): diff --git a/compiler/modules/dff_inv_array.py b/compiler/modules/dff_inv_array.py index 760a2337..aadb4257 100644 --- a/compiler/modules/dff_inv_array.py +++ b/compiler/modules/dff_inv_array.py @@ -140,7 +140,7 @@ class dff_inv_array(design.design): for row in range(self.rows): for col in range(self.columns): din_pin = self.dff_insts[row,col].get_pin("D") - debug.check(din_pin.layer=="metal2","DFF D pin not on metal2") + debug.check(din_pin.layer=="m2","DFF D pin not on metal2") self.add_layout_pin(text=self.get_din_name(row,col), layer=din_pin.layer, offset=din_pin.ll(), @@ -148,7 +148,7 @@ class dff_inv_array(design.design): height=din_pin.height()) dout_pin = self.dff_insts[row,col].get_pin("Q") - debug.check(dout_pin.layer=="metal2","DFF Q pin not on metal2") + debug.check(dout_pin.layer=="m2","DFF Q pin not on metal2") self.add_layout_pin(text=self.get_dout_name(row,col), layer=dout_pin.layer, offset=dout_pin.ll(), @@ -156,7 +156,7 @@ class dff_inv_array(design.design): height=dout_pin.height()) dout_bar_pin = self.dff_insts[row,col].get_pin("Qb") - debug.check(dout_bar_pin.layer=="metal2","DFF Qb pin not on metal2") + debug.check(dout_bar_pin.layer=="m2","DFF Qb pin not on metal2") self.add_layout_pin(text=self.get_dout_bar_name(row,col), layer=dout_bar_pin.layer, offset=dout_bar_pin.ll(), @@ -167,27 +167,27 @@ class dff_inv_array(design.design): # Create vertical spines to a single horizontal rail clk_pin = self.dff_insts[0,0].get_pin("clk") clk_ypos = 2*self.m3_pitch+self.m3_width - debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2") + debug.check(clk_pin.layer=="m2","DFF clk pin not on metal2") if self.columns==1: self.add_layout_pin(text="clk", - layer="metal2", + layer="m2", offset=clk_pin.ll().scale(1,0), width=self.m2_width, height=self.height) else: self.add_layout_pin_segment_center(text="clk", - layer="metal3", + layer="m3", start=vector(0,clk_ypos), end=vector(self.width,clk_ypos)) for col in range(self.columns): clk_pin = self.dff_insts[0,col].get_pin("clk") # Make a vertical strip for each column - self.add_rect(layer="metal2", + self.add_rect(layer="m2", offset=clk_pin.ll().scale(1,0), width=self.m2_width, height=self.height) # Drop a via to the M3 pin - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_via_center(layers=self.m2_stack, offset=vector(clk_pin.cx(),clk_ypos)) def get_clk_cin(self): diff --git a/compiler/modules/dummy_array.py b/compiler/modules/dummy_array.py index f1f433ce..de15d0ce 100644 --- a/compiler/modules/dummy_array.py +++ b/compiler/modules/dummy_array.py @@ -3,31 +3,22 @@ # Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # -import debug -import design -from tech import drc -import contact +from bitcell_base_array import bitcell_base_array from sram_factory import factory -from vector import vector from globals import OPTS -class dummy_array(design.design): + +class dummy_array(bitcell_base_array): """ Generate a dummy row/column for the replica array. """ - def __init__(self, cols, rows, mirror=0, name=""): - design.design.__init__(self, name) - debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) - self.add_comment("rows: {0} cols: {1}".format(rows, cols)) - - self.column_size = cols - self.row_size = rows + def __init__(self, cols, rows, column_offset=0, mirror=0, name=""): + super().__init__(cols, rows, name, column_offset) self.mirror = mirror self.create_netlist() if not OPTS.netlist_only: self.create_layout() - def create_netlist(self): """ Create and connect the netlist """ @@ -37,27 +28,7 @@ class dummy_array(design.design): def create_layout(self): - # We increase it by a well enclosure so the precharges don't overlap our wells - self.height = self.row_size*self.dummy_cell.height - self.width = self.column_size*self.dummy_cell.width - - xoffset = 0.0 - for col in range(self.column_size): - yoffset = 0.0 - for row in range(self.row_size): - name = "dummy_r{0}_c{1}".format(row, col) - - if (row+self.mirror) % 2: - tempy = yoffset + self.dummy_cell.height - dir_key = "MX" - else: - tempy = yoffset - dir_key = "" - - self.cell_inst[row,col].place(offset=[xoffset, tempy], - mirror=dir_key) - yoffset += self.dummy_cell.height - xoffset += self.dummy_cell.width + self.place_array("dummy_r{0}_c{1}", self.mirror) self.add_layout_pins() @@ -65,18 +36,6 @@ class dummy_array(design.design): self.DRC_LVS() - def add_pins(self): - row_list = self.cell.get_all_wl_names() - column_list = self.cell.get_all_bitline_names() - for col in range(self.column_size): - for cell_column in column_list: - self.add_pin(cell_column+"_{0}".format(col), "INOUT") - for row in range(self.row_size): - for cell_row in row_list: - self.add_pin(cell_row+"_{0}".format(row), "INPUT") - self.add_pin("vdd", "POWER") - self.add_pin("gnd", "GROUND") - def add_modules(self): """ Add the modules used in this design """ self.dummy_cell = factory.create(module_type="dummy_bitcell") @@ -84,23 +43,6 @@ class dummy_array(design.design): self.cell = factory.create(module_type="bitcell") - def get_bitcell_pins(self, col, row): - """ Creates a list of connections in the bitcell, - indexed by column and row, for instance use in bitcell_array """ - - bitcell_pins = [] - - pin_names = self.cell.get_all_bitline_names() - for pin in pin_names: - bitcell_pins.append(pin+"_{0}".format(col)) - pin_names = self.cell.get_all_wl_names() - for pin in pin_names: - bitcell_pins.append(pin+"_{0}".format(row)) - bitcell_pins.append("vdd") - bitcell_pins.append("gnd") - - return bitcell_pins - def create_instances(self): """ Create the module instances used in this design """ @@ -111,39 +53,6 @@ class dummy_array(design.design): self.cell_inst[row,col]=self.add_inst(name=name, mod=self.dummy_cell) self.connect_inst(self.get_bitcell_pins(col, row)) - - def add_layout_pins(self): - """ Add the layout pins """ - - row_list = self.cell.get_all_wl_names() - column_list = self.cell.get_all_bitline_names() - - for col in range(self.column_size): - for cell_column in column_list: - bl_pin = self.cell_inst[0,col].get_pin(cell_column) - self.add_layout_pin(text=cell_column+"_{0}".format(col), - layer="metal2", - offset=bl_pin.ll(), - width=bl_pin.width(), - height=self.height) - - for row in range(self.row_size): - for cell_row in row_list: - wl_pin = self.cell_inst[row,0].get_pin(cell_row) - self.add_layout_pin(text=cell_row+"_{0}".format(row), - layer="metal1", - offset=wl_pin.ll(), - width=self.width, - height=wl_pin.height()) - - # For every second row and column, add a via for gnd and vdd - for row in range(self.row_size): - for col in range(self.column_size): - 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(), vertical=True, start_layer=pin.layer) - def input_load(self): wl_wire = self.gen_wl_wire() diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 4648519a..242ef476 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -5,23 +5,19 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from tech import drc import debug import design -from math import log -from math import sqrt -from math import ceil import math -import contact from sram_factory import factory from vector import vector from globals import OPTS + class hierarchical_decoder(design.design): """ Dynamically generated hierarchical decoder. """ - def __init__(self, name, rows, height=None): + def __init__(self, name, rows): design.design.__init__(self, name) self.NAND_FORMAT = "DEC_NAND_{0}" @@ -30,16 +26,16 @@ class hierarchical_decoder(design.design): self.pre2x4_inst = [] self.pre3x8_inst = [] - self.cell_height = height + b = factory.create(module_type="bitcell") + self.cell_height = b.height self.rows = rows self.num_inputs = math.ceil(math.log(self.rows, 2)) - (self.no_of_pre2x4,self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs) + (self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs) self.create_netlist() if not OPTS.netlist_only: self.create_layout() - def create_netlist(self): self.add_modules() self.setup_netlist_constants() @@ -173,7 +169,7 @@ class hierarchical_decoder(design.design): input_offset=vector(min_x - self.input_routing_width,0) input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)] - self.input_rails = self.create_vertical_pin_bus(layer="metal2", + self.input_rails = self.create_vertical_pin_bus(layer="m2", pitch=self.m2_pitch, offset=input_offset, names=input_bus_names, @@ -221,11 +217,11 @@ class hierarchical_decoder(design.design): def route_input_rail(self, input_offset, output_offset): """ Route a vertical M2 coordinate to another vertical M2 coordinate to the predecode inputs """ - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=input_offset) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=output_offset) - self.add_path(("metal3"), [input_offset, output_offset]) + self.add_path(("m3"), [input_offset, output_offset]) def add_pins(self): @@ -307,7 +303,6 @@ class hierarchical_decoder(design.design): base= vector(-self.pre2_4.width, num * self.pre2_4.height) self.pre2x4_inst[num].place(base) - def place_pre3x8(self,num): """ Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """ @@ -320,7 +315,6 @@ class hierarchical_decoder(design.design): self.pre3x8_inst[num].place(offset) - def create_row_decoder(self): """ Create the row-decoder by placing NAND2/NAND3 and Inverters and add the primary decoder output pins. """ @@ -328,7 +322,6 @@ class hierarchical_decoder(design.design): self.create_decoder_nand_array() self.create_decoder_inv_array() - def create_decoder_nand_array(self): """ Add a column of NAND gates for final decode """ @@ -467,11 +460,11 @@ class hierarchical_decoder(design.design): # ensure the bend is in the middle mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y) mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y) - self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos]) + self.add_path("m1", [zr_pos, mid1_pos, mid2_pos, al_pos]) z_pin = self.inv_inst[row].get_pin("Z") self.add_layout_pin(text="decode_{0}".format(row), - layer="metal1", + layer="m1", offset=z_pin.ll(), width=z_pin.width(), height=z_pin.height()) @@ -485,7 +478,7 @@ class hierarchical_decoder(design.design): if (self.num_inputs >= 4): input_offset = vector(0.5*self.m2_width,0) input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)] - self.predecode_rails = self.create_vertical_pin_bus(layer="metal2", + self.predecode_rails = self.create_vertical_pin_bus(layer="m2", pitch=self.m2_pitch, offset=input_offset, names=input_bus_names, @@ -555,7 +548,7 @@ class hierarchical_decoder(design.design): """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ # The vias will be placed in the center and right of the cells, respectively. - xoffset = self.nand_inst[0].cx() + xoffset = self.nand_inst[0].rx() for num in range(0,self.rows): for pin_name in ["vdd", "gnd"]: # The nand and inv are the same height rows... @@ -570,7 +563,7 @@ class hierarchical_decoder(design.design): start = self.nand_inst[num].get_pin(pin_name).lc() end = self.inv_inst[num].get_pin(pin_name).rc() mid = (start+end).scale(0.5,0.5) - self.add_rect_center(layer="metal1", + self.add_rect_center(layer="m1", offset=mid, width=end.x-start.x) @@ -584,8 +577,8 @@ class hierarchical_decoder(design.design): def route_predecode_rail(self, rail_name, pin): """ Connect the routing rail to the given metal1 pin """ rail_pos = vector(self.predecode_rails[rail_name].x,pin.lc().y) - self.add_path("metal1", [rail_pos, pin.lc()]) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_path("m1", [rail_pos, pin.lc()]) + self.add_via_center(layers=self.m1_stack, offset=rail_pos) @@ -595,10 +588,10 @@ class hierarchical_decoder(design.design): # It would be fixed with a channel router. mid_point = vector(pin.cx(), pin.cy()+self.inv.height/2) rail_pos = vector(self.predecode_rails[rail_name].x,mid_point.y) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=pin.center()) - self.add_wire(("metal3","via2","metal2"), [rail_pos, mid_point, pin.uc()]) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_wire(("m3","via2","m2"), [rail_pos, mid_point, pin.uc()]) + self.add_via_center(layers=self.m2_stack, offset=rail_pos) def input_load(self): diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index bec0ce06..88bae534 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -8,12 +8,11 @@ import debug import design import math -from tech import drc import contact from vector import vector -from globals import OPTS from sram_factory import factory + class hierarchical_predecode(design.design): """ Pre 2x4 and 3x8 decoder shared code. @@ -42,7 +41,7 @@ class hierarchical_predecode(design.design): self.add_nand(self.number_of_inputs) self.add_mod(self.nand) - def add_nand(self,inputs): + def add_nand(self, inputs): """ Create the NAND for the predecode input stage """ if inputs==2: self.nand = factory.create(module_type="pnand2", @@ -51,7 +50,7 @@ class hierarchical_predecode(design.design): self.nand = factory.create(module_type="pnand3", height=self.cell_height) else: - debug.error("Invalid number of predecode inputs: {}".format(inputs),-1) + debug.error("Invalid number of predecode inputs: {}".format(inputs), -1) def setup_layout_constraints(self): @@ -73,7 +72,7 @@ class hierarchical_predecode(design.design): """ Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """ input_names = ["in_{}".format(x) for x in range(self.number_of_inputs)] offset = vector(0.5*self.m2_width,2*self.m1_width) - self.input_rails = self.create_vertical_pin_bus(layer="metal2", + self.input_rails = self.create_vertical_pin_bus(layer="m2", pitch=self.m2_pitch, offset=offset, names=input_names, @@ -83,13 +82,12 @@ class hierarchical_predecode(design.design): non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)] decode_names = invert_names + non_invert_names offset = vector(self.x_off_inv_1 + self.inv.width + 2*self.m2_pitch, 2*self.m1_width) - self.decode_rails = self.create_vertical_bus(layer="metal2", + self.decode_rails = self.create_vertical_bus(layer="m2", pitch=self.m2_pitch, offset=offset, names=decode_names, length=self.height - 2*self.m1_width) - def create_input_inverters(self): """ Create the input inverters to invert input signals for the decode stage. """ self.in_inst = [] @@ -178,15 +176,15 @@ class hierarchical_predecode(design.design): # route one signal next to each vdd/gnd rail since this is # typically where the p/n devices are and there are no # pins in the nand gates. - y_offset = (num+self.number_of_inputs) * self.inv.height + contact.m1m2.width + self.m1_space + y_offset = (num+self.number_of_inputs) * self.inv.height + contact.m1_via.width + self.m1_space in_pin = "in_{}".format(num) a_pin = "A_{}".format(num) in_pos = vector(self.input_rails[in_pin].x,y_offset) a_pos = vector(self.decode_rails[a_pin].x,y_offset) - self.add_path("metal1",[in_pos, a_pos]) - self.add_via_center(layers = ("metal1", "via1", "metal2"), + self.add_path("m1",[in_pos, a_pos]) + self.add_via_center(layers = self.m1_stack, offset=[self.input_rails[in_pin].x, y_offset]) - self.add_via_center(layers = ("metal1", "via1", "metal2"), + self.add_via_center(layers = self.m1_stack, offset=[self.decode_rails[a_pin].x, y_offset]) def route_output_inverters(self): @@ -201,11 +199,11 @@ class hierarchical_predecode(design.design): # ensure the bend is in the middle mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y) mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y) - self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos]) + self.add_path("m1", [zr_pos, mid1_pos, mid2_pos, al_pos]) z_pin = self.inv_inst[num].get_pin("Z") self.add_layout_pin(text="out_{}".format(num), - layer="metal1", + layer="m1", offset=z_pin.ll(), height=z_pin.height(), width=z_pin.width()) @@ -226,16 +224,16 @@ class hierarchical_predecode(design.design): inv_out_pos = self.in_inst[inv_num].get_pin("Z").rc() right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").lx(),0) rail_pos = vector(self.decode_rails[out_pin].x,y_offset) - self.add_path("metal1", [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos]) - self.add_via_center(layers = ("metal1", "via1", "metal2"), + self.add_path("m1", [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos]) + self.add_via_center(layers = self.m1_stack, offset=rail_pos) #route input inv_in_pos = self.in_inst[inv_num].get_pin("A").lc() in_pos = vector(self.input_rails[in_pin].x,inv_in_pos.y) - self.add_path("metal1", [in_pos, inv_in_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_path("m1", [in_pos, inv_in_pos]) + self.add_via_center(layers=self.m1_stack, offset=in_pos) @@ -255,8 +253,8 @@ class hierarchical_predecode(design.design): for rail_pin,gate_pin in zip(index_lst,gate_lst): pin_pos = self.nand_inst[k].get_pin(gate_pin).lc() rail_pos = vector(self.decode_rails[rail_pin].x, pin_pos.y) - self.add_path("metal1", [rail_pos, pin_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_path("m1", [rail_pos, pin_pos]) + self.add_via_center(layers=self.m1_stack, offset=rail_pos) @@ -266,7 +264,7 @@ class hierarchical_predecode(design.design): """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ # Find the x offsets for where the vias/pins should be placed - in_xoffset = self.in_inst[0].rx() + in_xoffset = self.in_inst[0].rx() + self.m1_space out_xoffset = self.inv_inst[0].lx() - self.m1_space for num in range(0,self.number_of_outputs): # this will result in duplicate polygons for rails, but who cares @@ -275,7 +273,7 @@ class hierarchical_predecode(design.design): for n in ["vdd", "gnd"]: nand_pin = self.nand_inst[num].get_pin(n) supply_offset = nand_pin.ll().scale(0,1) - self.add_rect(layer="metal1", + self.add_rect(layer="m1", offset=supply_offset, width=self.inv_inst[num].rx()) diff --git a/compiler/modules/module_type.py b/compiler/modules/module_type.py new file mode 100644 index 00000000..419e6041 --- /dev/null +++ b/compiler/modules/module_type.py @@ -0,0 +1,26 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 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. +# + +class module_type(): + """ + This is a class that maps cell names to python classes implementing them. + """ + def __init__(self): + self.names = {} + + def __setitem__(self, b, c): + self.names[b] = c + + def is_overridden(self, b): + return (b in self.names.keys()) + + def __getitem__(self, b): + if b not in self.names.keys(): + raise KeyError + + return self.names[b] diff --git a/compiler/modules/multibank.py b/compiler/modules/multibank.py index 2f933a2d..bf19954b 100644 --- a/compiler/modules/multibank.py +++ b/compiler/modules/multibank.py @@ -160,7 +160,7 @@ class multibank(design.design): self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width # A space for wells or jogging m2 - self.m2_gap = max(2*drc("pwell_to_nwell"] + drc["well_enclosure_active"), + self.m2_gap = max(2*drc("pwell_to_nwell"] + drc["well_enclose_active"), 2*self.m2_pitch) @@ -451,14 +451,14 @@ class multibank(design.design): # Connect the inverter output to the central bus out_pos = self.bank_select_inst.get_pin(gated_name).rc() bus_pos = vector(self.bus_xoffset[gated_name], out_pos.y) - self.add_path("metal3",[out_pos, bus_pos]) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_path("m3",[out_pos, bus_pos]) + self.add_via_center(layers=self.m2_stack, offset=bus_pos, rotate=90) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=out_pos, rotate=90) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=out_pos, rotate=90) @@ -512,7 +512,7 @@ class multibank(design.design): # 2 pitches on the right for vias/jogs to access the inputs control_bus_offset = vector(-self.m2_pitch * self.num_control_lines - self.m2_width, 0) control_bus_length = self.bitcell_array_inst.uy() - self.bus_xoffset = self.create_vertical_bus(layer="metal2", + self.bus_xoffset = self.create_vertical_bus(layer="m2", pitch=self.m2_pitch, offset=control_bus_offset, names=self.control_signals, @@ -530,9 +530,9 @@ class multibank(design.design): bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).uc() yoffset = 0.5*(precharge_bl.y+bitcell_bl.y) - self.add_path("metal2",[precharge_bl, vector(precharge_bl.x,yoffset), + self.add_path("m2",[precharge_bl, vector(precharge_bl.x,yoffset), vector(bitcell_bl.x,yoffset), bitcell_bl]) - self.add_path("metal2",[precharge_br, vector(precharge_br.x,yoffset), + self.add_path("m2",[precharge_br, vector(precharge_br.x,yoffset), vector(bitcell_br.x,yoffset), bitcell_br]) @@ -550,9 +550,9 @@ class multibank(design.design): bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).bc() yoffset = 0.5*(col_mux_bl.y+bitcell_bl.y) - self.add_path("metal2",[col_mux_bl, vector(col_mux_bl.x,yoffset), + self.add_path("m2",[col_mux_bl, vector(col_mux_bl.x,yoffset), vector(bitcell_bl.x,yoffset), bitcell_bl]) - self.add_path("metal2",[col_mux_br, vector(col_mux_br.x,yoffset), + self.add_path("m2",[col_mux_br, vector(col_mux_br.x,yoffset), vector(bitcell_br.x,yoffset), bitcell_br]) def route_sense_amp_to_col_mux_or_bitcell_array(self): @@ -573,9 +573,9 @@ class multibank(design.design): yoffset = 0.5*(sense_amp_bl.y+connect_bl.y) - self.add_path("metal2",[sense_amp_bl, vector(sense_amp_bl.x,yoffset), + self.add_path("m2",[sense_amp_bl, vector(sense_amp_bl.x,yoffset), vector(connect_bl.x,yoffset), connect_bl]) - self.add_path("metal2",[sense_amp_br, vector(sense_amp_br.x,yoffset), + self.add_path("m2",[sense_amp_br, vector(sense_amp_br.x,yoffset), vector(connect_br.x,yoffset), connect_br]) def route_sense_amp_to_trigate(self): @@ -586,11 +586,11 @@ class multibank(design.design): tri_gate_in = self.tri_gate_array_inst.get_pin("in_{}".format(i)).lc() sa_data_out = self.sense_amp_array_inst.get_pin("data_{}".format(i)).bc() - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=tri_gate_in) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=sa_data_out) - self.add_path("metal3",[sa_data_out,tri_gate_in]) + self.add_path("m3",[sa_data_out,tri_gate_in]) def route_sense_amp_out(self): """ Add pins for the sense amp output """ @@ -644,14 +644,14 @@ class multibank(design.design): driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(i)).lc() mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0) mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1) - self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos]) + self.add_path("m1", [decoder_out_pos, mid1, mid2, driver_in_pos]) # The mid guarantees we exit the input cell to the right. driver_wl_pos = self.wordline_driver_inst.get_pin("wl_{}".format(i)).rc() bitcell_wl_pos = self.bitcell_array_inst.get_pin("wl_{}".format(i)).lc() mid1 = driver_wl_pos.scale(0.5,1)+bitcell_wl_pos.scale(0.5,0) mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1) - self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + self.add_path("m1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) @@ -698,8 +698,8 @@ class multibank(design.design): else: mid1_pos = vector(decode_out_pos.x + delta_offset + (self.num_col_addr_lines-i)*self.m2_pitch,decode_out_pos.y) mid2_pos = vector(mid1_pos.x,mux_addr_pos.y) - #self.add_wire(("metal1","via1","metal2"),[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos]) - self.add_path("metal1",[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos]) + #self.add_wire(self.m1_stack,[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos]) + self.add_path("m1",[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos]) @@ -715,7 +715,7 @@ class multibank(design.design): wl_name = "wl_{}".format(i) wl_pin = self.bitcell_array_inst.get_pin(wl_name) self.add_label(text=wl_name, - layer="metal1", + layer="m1", offset=wl_pin.center()) # Add the bitline names @@ -725,10 +725,10 @@ class multibank(design.design): bl_pin = self.bitcell_array_inst.get_pin(bl_name) br_pin = self.bitcell_array_inst.get_pin(br_name) self.add_label(text=bl_name, - layer="metal2", + layer="m2", offset=bl_pin.center()) self.add_label(text=br_name, - layer="metal2", + layer="m2", offset=br_pin.center()) # # Add the data output names to the sense amp output @@ -736,7 +736,7 @@ class multibank(design.design): # data_name = "data_{}".format(i) # data_pin = self.sense_amp_array_inst.get_pin(data_name) # self.add_label(text="sa_out_{}".format(i), - # layer="metal2", + # layer="m2", # offset=data_pin.center()) # Add labels on the decoder @@ -745,7 +745,7 @@ class multibank(design.design): pin_name = "in_{}".format(i) data_pin = self.wordline_driver_inst.get_pin(pin_name) self.add_label(text=data_name, - layer="metal1", + layer="m1", offset=data_pin.center()) @@ -765,8 +765,8 @@ class multibank(design.design): for (control_signal, pin_pos) in connection: control_pos = vector(self.bus_xoffset[control_signal].x ,pin_pos.y) - self.add_path("metal1", [control_pos, pin_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_path("m1", [control_pos, pin_pos]) + self.add_via_center(layers=self.m1_stack, offset=control_pos, rotate=90) @@ -776,9 +776,9 @@ class multibank(design.design): mid_pos = pin_pos + vector(0,self.m1_pitch) control_x_offset = self.bus_xoffset[control_signal].x control_pos = vector(control_x_offset + self.m1_width, mid_pos.y) - self.add_wire(("metal1","via1","metal2"),[pin_pos, mid_pos, control_pos]) + self.add_wire(self.m1_stack,[pin_pos, mid_pos, control_pos]) control_via_pos = vector(control_x_offset, mid_pos.y) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=control_via_pos, rotate=90) @@ -791,13 +791,13 @@ class multibank(design.design): if self.num_banks > 1: # it's not an input pin if we have multiple banks self.add_label_pin(text=ctrl, - layer="metal2", + layer="m2", offset=vector(x_offset, self.min_y_offset), width=self.m2_width, height=self.max_y_offset-self.min_y_offset) else: self.add_layout_pin(text=ctrl, - layer="metal2", + layer="m2", offset=vector(x_offset, self.min_y_offset), width=self.m2_width, height=self.max_y_offset-self.min_y_offset) @@ -807,12 +807,12 @@ class multibank(design.design): """ Helper routine to connect an unrotated/mirrored oriented instance to the rails """ in_pin = inst.get_pin(pin).lc() rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y) - self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)]) + self.add_wire(("m3","via2","m2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)]) # Bring it up to M2 for M2/M3 routing - self.add_via(layers=("metal1","via1","metal2"), - offset=in_pin + contact.m1m2.offset, + self.add_via(layers=self.m1_stack, + offset=in_pin + contact.m1_via.offset, rotate=90) - self.add_via(layers=("metal2","via2","metal3"), + self.add_via(layers=self.m2_stack, offset=in_pin + self.m2m3_via_offset, rotate=90) @@ -821,10 +821,10 @@ class multibank(design.design): """ Helper routine to connect an unrotated/mirrored oriented instance to the rails """ in_pin = inst.get_pin(pin).rc() rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y) - self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)]) - self.add_via(layers=("metal1","via1","metal2"), - offset=in_pin + contact.m1m2.offset, + self.add_wire(("m3","via2","m2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)]) + self.add_via(layers=self.m1_stack, + offset=in_pin + contact.m1_via.offset, rotate=90) - self.add_via(layers=("metal2","via2","metal3"), + self.add_via(layers=self.m2_stack, offset=in_pin + self.m2m3_via_offset, rotate=90) diff --git a/compiler/modules/port_address.py b/compiler/modules/port_address.py index 11035f30..4cfd8138 100644 --- a/compiler/modules/port_address.py +++ b/compiler/modules/port_address.py @@ -92,7 +92,7 @@ class port_address(design.design): driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(row)).lc() mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0) mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1) - self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos]) + self.add_path("m1", [decoder_out_pos, mid1, mid2, driver_in_pos]) @@ -149,7 +149,7 @@ class port_address(design.design): """ # A space for wells or jogging m2 - self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclosure_active"), + self.m2_gap = max(2*drc("pwell_to_nwell") + drc("nwell_enclose_active"), 3*self.m2_pitch) row_decoder_offset = vector(0,0) diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index c3473803..d1c2c671 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -38,6 +38,15 @@ class port_data(design.design): self.create_layout() self.add_boundary() + def get_bl_names(self): + # bl lines are connect from the precharger + return self.precharge.get_bl_names() + + def get_br_names(self): + # br lines are connect from the precharger + return self.precharge.get_br_names() + + def create_netlist(self): self.precompute_constants() @@ -85,8 +94,10 @@ class port_data(design.design): self.add_pin("rbl_bl","INOUT") self.add_pin("rbl_br","INOUT") for bit in range(self.num_cols): - self.add_pin("{0}_{1}".format(self.bl_names[self.port], bit),"INOUT") - self.add_pin("{0}_{1}".format(self.br_names[self.port], bit),"INOUT") + bl_name = self.precharge_array.get_bl_name(self.port) + br_name = self.precharge_array.get_br_name(self.port) + self.add_pin("{0}_{1}".format(bl_name, bit),"INOUT") + self.add_pin("{0}_{1}".format(br_name, bit),"INOUT") if self.port in self.read_ports: for bit in range(self.word_size): self.add_pin("dout_{}".format(bit),"OUTPUT") @@ -212,7 +223,7 @@ class port_data(design.design): # A space for wells or jogging m2 between modules - self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclosure_active"), + self.m2_gap = max(2*drc("pwell_to_nwell") + drc("nwell_enclose_active"), 3*self.m2_pitch) @@ -221,6 +232,18 @@ class port_data(design.design): self.bl_names = self.bitcell.get_all_bl_names() self.br_names = self.bitcell.get_all_br_names() self.wl_names = self.bitcell.get_all_wl_names() + # used for bl/br names + self.precharge = factory.create(module_type="precharge", + bitcell_bl = self.bl_names[0], + bitcell_br = self.br_names[0]) + # We create a dummy here to get bl/br names to add those pins to this + # module, which happens before we create the real precharge_array + self.precharge_array = factory.create(module_type="precharge_array", + columns=self.num_cols + 1, + bitcell_bl=self.bl_names[self.port], + bitcell_br=self.br_names[self.port]) + + def create_precharge_array(self): """ Creating Precharge """ @@ -230,6 +253,8 @@ class port_data(design.design): self.precharge_array_inst = self.add_inst(name="precharge_array{}".format(self.port), mod=self.precharge_array) + bl_name = self.precharge_array.get_bl_name(self.port) + br_name = self.precharge_array.get_br_name(self.port) temp = [] # Use left BLs for RBL @@ -237,8 +262,9 @@ class port_data(design.design): temp.append("rbl_bl") temp.append("rbl_br") for bit in range(self.num_cols): - temp.append(self.bl_names[self.port]+"_{0}".format(bit)) - temp.append(self.br_names[self.port]+"_{0}".format(bit)) + temp.append("{0}_{1}".format(bl_name, bit)) + temp.append("{0}_{1}".format(br_name, bit)) + # Use right BLs for RBL if self.port==1: temp.append("rbl_bl") @@ -259,15 +285,19 @@ class port_data(design.design): self.column_mux_array_inst = self.add_inst(name="column_mux_array{}".format(self.port), mod=self.column_mux_array) + bl_name = self.column_mux_array.get_bl_name(self.port) + br_name = self.column_mux_array.get_br_name(self.port) temp = [] for col in range(self.num_cols): - temp.append(self.bl_names[self.port]+"_{0}".format(col)) - temp.append(self.br_names[self.port]+"_{0}".format(col)) + temp.append("{0}_{1}".format(bl_name, col)) + temp.append("{0}_{1}".format(br_name, col)) + for word in range(self.words_per_row): temp.append("sel_{}".format(word)) for bit in range(self.word_size): - temp.append(self.bl_names[self.port]+"_out_{0}".format(bit)) - temp.append(self.br_names[self.port]+"_out_{0}".format(bit)) + temp.append("{0}_out_{1}".format(bl_name, bit)) + temp.append("{0}_out_{1}".format(br_name, bit)) + temp.append("gnd") self.connect_inst(temp) @@ -285,15 +315,18 @@ class port_data(design.design): self.sense_amp_array_inst = self.add_inst(name="sense_amp_array{}".format(self.port), mod=self.sense_amp_array) + bl_name = self.sense_amp_array.get_bl_name(self.port) + br_name = self.sense_amp_array.get_br_name(self.port) temp = [] for bit in range(self.word_size): temp.append("dout_{}".format(bit)) if self.words_per_row == 1: - temp.append(self.bl_names[self.port]+"_{0}".format(bit)) - temp.append(self.br_names[self.port]+"_{0}".format(bit)) + temp.append("{0}_{1}".format(bl_name, bit)) + temp.append("{0}_{1}".format(br_name, bit)) else: - temp.append(self.bl_names[self.port]+"_out_{0}".format(bit)) - temp.append(self.br_names[self.port]+"_out_{0}".format(bit)) + temp.append("{0}_out_{1}".format(bl_name, bit)) + temp.append("{0}_out_{1}".format(br_name, bit)) + temp.extend(["s_en", "vdd", "gnd"]) self.connect_inst(temp) @@ -308,6 +341,8 @@ class port_data(design.design): """ Creating Write Driver """ self.write_driver_array_inst = self.add_inst(name="write_driver_array{}".format(self.port), mod=self.write_driver_array) + bl_name = self.write_driver_array.get_bl_name(self.port) + br_name = self.write_driver_array.get_br_name(self.port) temp = [] for bit in range(self.word_size): @@ -315,11 +350,11 @@ class port_data(design.design): for bit in range(self.word_size): if (self.words_per_row == 1): - temp.append(self.bl_names[self.port] + "_{0}".format(bit)) - temp.append(self.br_names[self.port] + "_{0}".format(bit)) + temp.append("{0}_{1}".format(bl_name, bit)) + temp.append("{0}_{1}".format(br_name, bit)) else: - temp.append(self.bl_names[self.port] + "_out_{0}".format(bit)) - temp.append(self.br_names[self.port] + "_out_{0}".format(bit)) + temp.append("{0}_out_{1}".format(bl_name, bit)) + temp.append("{0}_out_{1}".format(br_name, bit)) if self.write_size is not None: for i in range(self.num_wmasks): @@ -475,11 +510,11 @@ class port_data(design.design): end_pos = vector(length, wdriver_en_pin.cy()) # Add via for the write driver array's enable input - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=end_pos) # Route between write mask AND array and write driver array - self.add_wire(("metal1","via1","metal2"), [beg_pos, middle_pos, end_pos]) + self.add_wire(self.m1_stack, [beg_pos, middle_pos, end_pos]) def route_column_mux_to_precharge_array(self, port): @@ -626,7 +661,7 @@ class port_data(design.design): bottom_names = [bottom_inst.get_pin(bottom_bl_name.format(bit+bottom_start_bit)), bottom_inst.get_pin(bottom_br_name.format(bit+bottom_start_bit))] top_names = [top_inst.get_pin(top_bl_name.format(bit+top_start_bit)), top_inst.get_pin(top_br_name.format(bit+top_start_bit))] route_map = list(zip(bottom_names, top_names)) - self.create_horizontal_channel_route(route_map, offset) + self.create_horizontal_channel_route(route_map, offset, self.m1_stack) def connect_bitlines(self, inst1, inst2, num_bits, @@ -655,9 +690,9 @@ class port_data(design.design): top_br = top_inst.get_pin(top_br_name.format(col+top_start_bit)).bc() yoffset = 0.5*(top_bl.y+bottom_bl.y) - self.add_path("metal2",[bottom_bl, vector(bottom_bl.x,yoffset), + self.add_path("m2",[bottom_bl, vector(bottom_bl.x,yoffset), vector(top_bl.x,yoffset), top_bl]) - self.add_path("metal2",[bottom_br, vector(bottom_br.x,yoffset), + self.add_path("m2",[bottom_br, vector(bottom_br.x,yoffset), vector(top_br.x,yoffset), top_br]) def graph_exclude_precharge(self): diff --git a/compiler/modules/precharge_array.py b/compiler/modules/precharge_array.py index 2d98ba14..5ac97b4d 100644 --- a/compiler/modules/precharge_array.py +++ b/compiler/modules/precharge_array.py @@ -32,6 +32,21 @@ class precharge_array(design.design): if not OPTS.netlist_only: self.create_layout() + def get_bl_name(self, port=0): + bl_name = self.pc_cell.get_bl_names() + if len(self.all_ports) == 1: + return bl_name + else: + return bl_name + "{}".format(port) + + def get_br_name(self, port=0): + br_name = self.pc_cell.get_br_names() + if len(self.all_ports) == 1: + return br_name + else: + return br_name + "{}".format(port) + + def add_pins(self): """Adds pins for spice file""" for i in range(self.columns): @@ -68,10 +83,10 @@ class precharge_array(design.design): def add_layout_pins(self): self.add_layout_pin(text="en_bar", - layer="metal1", + layer="m1", offset=self.pc_cell.get_pin("en_bar").ll(), width=self.width, - height=drc("minwidth_metal1")) + height=drc("minwidth_m1")) for inst in self.local_insts: self.copy_layout_pin(inst, "vdd") @@ -80,15 +95,15 @@ class precharge_array(design.design): inst = self.local_insts[i] bl_pin = inst.get_pin("bl") self.add_layout_pin(text="bl_{0}".format(i), - layer="metal2", + layer="m2", offset=bl_pin.ll(), - width=drc("minwidth_metal2"), + width=drc("minwidth_m2"), height=bl_pin.height()) br_pin = inst.get_pin("br") self.add_layout_pin(text="br_{0}".format(i), - layer="metal2", + layer="m2", offset=br_pin.ll(), - width=drc("minwidth_metal2"), + width=drc("minwidth_m2"), height=bl_pin.height()) @@ -107,9 +122,19 @@ class precharge_array(design.design): def place_insts(self): """ Places precharge array by horizontally tiling the precharge cell""" + from tech import cell_properties + xoffset = 0 for i in range(self.columns): - offset = vector(self.pc_cell.width * i, 0) - self.local_insts[i].place(offset) + tempx = xoffset + if cell_properties.bitcell.mirror.y and (i + 1) % 2: + mirror = "MY" + tempx = tempx + self.pc_cell.width + else: + mirror = "" + + offset = vector(tempx, 0) + self.local_insts[i].place(offset=offset, mirror=mirror) + xoffset = xoffset + self.pc_cell.width def get_en_cin(self): """Get the relative capacitance of all the clk connections in the precharge array""" diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index 0f922eb0..e405401a 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -10,10 +10,7 @@ from tech import drc, spice from vector import vector from globals import OPTS from sram_factory import factory -import logical_effort -import bitcell_array -import replica_column -import dummy_array + class replica_bitcell_array(design.design): """ @@ -86,6 +83,7 @@ class replica_bitcell_array(design.design): # Bitcell array self.bitcell_array = factory.create(module_type="bitcell_array", + column_offset=1 + self.left_rbl, cols=self.column_size, rows=self.row_size) self.add_mod(self.bitcell_array) @@ -95,12 +93,17 @@ class replica_bitcell_array(design.design): for bit in range(self.left_rbl+self.right_rbl): if bit1 port) for port in range(self.left_rbl): # Make names for all RBLs - wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.all_ports))] + wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.cell.get_all_wl_names()))] # Keep track of the pin that is the RBL self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]] self.replica_col_wl_names.extend(wl_names) @@ -150,7 +166,7 @@ class replica_bitcell_array(design.design): # Right port WLs (one dummy for each port when we allow >1 port) for port in range(self.left_rbl,self.left_rbl+self.right_rbl): # Make names for all RBLs - wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.all_ports))] + wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.cell.get_all_wl_names()))] # Keep track of the pin that is the RBL self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]] self.replica_col_wl_names.extend(wl_names) @@ -236,10 +252,10 @@ class replica_bitcell_array(design.design): # Left/right Dummy columns self.dummy_col_left_inst=self.add_inst(name="dummy_col_left", - mod=self.dummy_col) + mod=self.dummy_col_left) self.connect_inst([x+"_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) self.dummy_col_right_inst=self.add_inst(name="dummy_col_right", - mod=self.dummy_col) + mod=self.dummy_col_right) self.connect_inst([x+"_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) @@ -300,22 +316,24 @@ class replica_bitcell_array(design.design): # Main array wl and bl/br pin_names = self.bitcell_array.get_pin_names() for pin_name in pin_names: - if pin_name.startswith("wl"): - pin_list = self.bitcell_array_inst.get_pins(pin_name) - for pin in pin_list: - self.add_layout_pin(text=pin_name, - layer=pin.layer, - offset=pin.ll().scale(0,1), - width=self.width, - height=pin.height()) - elif pin_name.startswith("bl") or pin_name.startswith("br"): - pin_list = self.bitcell_array_inst.get_pins(pin_name) - for pin in pin_list: - self.add_layout_pin(text=pin_name, - layer=pin.layer, - offset=pin.ll().scale(1,0), - width=pin.width(), - height=self.height) + for wl in self.bitcell_array_wl_names: + if wl in pin_name: + pin_list = self.bitcell_array_inst.get_pins(pin_name) + for pin in pin_list: + self.add_layout_pin(text=pin_name, + layer=pin.layer, + offset=pin.ll().scale(0,1), + width=self.width, + height=pin.height()) + for bitline in self.bitcell_array_bl_names: + if bitline in pin_name: + pin_list = self.bitcell_array_inst.get_pins(pin_name) + for pin in pin_list: + self.add_layout_pin(text=pin_name, + layer=pin.layer, + offset=pin.ll().scale(1,0), + width=pin.width(), + height=self.height) # Replica wordlines @@ -400,7 +418,7 @@ class replica_bitcell_array(design.design): else: height = self.height bl_pos = 0 - bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), height, drc("minwidth_metal1")) + bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), height, drc("minwidth_m1")) bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell return bl_wire diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index c3f63b19..d874ba8c 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -20,7 +20,8 @@ class replica_column(design.design): replica cell. """ - def __init__(self, name, rows, left_rbl, right_rbl, replica_bit): + def __init__(self, name, rows, left_rbl, right_rbl, replica_bit, + column_offset=0): design.design.__init__(self, name) self.rows = rows @@ -29,6 +30,7 @@ class replica_column(design.design): self.replica_bit = replica_bit # left, right, regular rows plus top/bottom dummy cells self.total_size = self.left_rbl+rows+self.right_rbl+2 + self.column_offset = column_offset debug.check(replica_bit!=0 and replica_bit!=rows,"Replica bit cannot be the dummy row.") debug.check(replica_bit<=left_rbl or replica_bit>=self.total_size-right_rbl-1, @@ -92,18 +94,35 @@ class replica_column(design.design): self.connect_inst(self.get_bitcell_pins(0, row)) def place_instances(self): - + from tech import cell_properties # Flip the mirrors if we have an odd number of replica+dummy rows at the bottom # so that we will start with mirroring rather than not mirroring rbl_offset = (self.left_rbl+1)%2 - + + # if our bitcells are mirrored on the y axis, check if we are in global + # column that needs to be flipped. + dir_y = False + xoffset = 0 + if cell_properties.bitcell.mirror.y and self.column_offset % 2: + dir_y = True + xoffset = self.replica_cell.width + for row in range(self.total_size): + dir_x = False name = "bit_r{0}_{1}".format(row,"rbl") - offset = vector(0,self.cell.height*(row+(row+rbl_offset)%2)) - if (row+rbl_offset)%2: + if cell_properties.bitcell.mirror.x and (row+rbl_offset)%2: + dir_x = True + + offset = vector(xoffset,self.cell.height*(row+(row+rbl_offset)%2)) + + if dir_x and dir_y: + dir_key = "XY" + elif dir_x: dir_key = "MX" + elif dir_y: + dir_key = "MY" else: - dir_key = "R0" + dir_key = "" self.cell_inst[row].place(offset=offset, mirror=dir_key) @@ -116,7 +135,7 @@ class replica_column(design.design): for bl_name in self.cell.get_all_bitline_names(): bl_pin = self.cell_inst[0].get_pin(bl_name) self.add_layout_pin(text=bl_name, - layer="metal2", + layer="m2", offset=bl_pin.ll(), width=bl_pin.width(), height=self.height) @@ -125,7 +144,7 @@ class replica_column(design.design): for wl_name in self.cell.get_all_wl_names(): wl_pin = self.cell_inst[row].get_pin(wl_name) self.add_layout_pin(text="{0}_{1}".format(wl_name,row), - layer="metal1", + layer="m1", offset=wl_pin.ll().scale(0,1), width=self.width, height=wl_pin.height()) diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py index e77d577f..eb624111 100644 --- a/compiler/modules/sense_amp.py +++ b/compiler/modules/sense_amp.py @@ -9,6 +9,7 @@ import design import debug import utils from tech import GDS,layer, parameter,drc +from globals import OPTS import logical_effort class sense_amp(design.design): @@ -21,8 +22,18 @@ class sense_amp(design.design): pin_names = ["bl", "br", "dout", "en", "vdd", "gnd"] type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] - (width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"]) - pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"]) + if not OPTS.netlist_only: + (width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"]) + else: + (width, height) = (0,0) + pin_map = [] + + def get_bl_names(self): + return "bl" + + def get_br_names(self): + return "br" def __init__(self, name): design.design.__init__(self, name) diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index 598886c0..8ac5146c 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -34,6 +34,20 @@ class sense_amp_array(design.design): self.create_layout() + def get_bl_name(self, port=0): + bl_name = self.amp.get_bl_names() + if len(self.all_ports) == 1: + return bl_name + else: + return bl_name + "{}".format(port) + + def get_br_name(self, port=0): + br_name = self.amp.get_br_names() + if len(self.all_ports) == 1: + return br_name + else: + return br_name + "{}".format(port) + def create_netlist(self): self.add_modules() self.add_pins() @@ -84,50 +98,60 @@ class sense_amp_array(design.design): "en", "vdd", "gnd"]) def place_sense_amp_array(self): - + from tech import cell_properties if self.bitcell.width > self.amp.width: amp_spacing = self.bitcell.width * self.words_per_row else: amp_spacing = self.amp.width * self.words_per_row + for i in range(0,self.word_size): - amp_position = vector(amp_spacing * i, 0) - self.local_insts[i].place(amp_position) + xoffset = amp_spacing * i + + # align the xoffset to the grid of bitcells. This way we + # know when to do the mirroring. + grid_x = int(xoffset / self.amp.width) + + if cell_properties.bitcell.mirror.y and grid_x % 2: + mirror = "MY" + xoffset = xoffset + self.amp.width + else: + mirror = "" + + amp_position = vector(xoffset, 0) + self.local_insts[i].place(offset=amp_position,mirror=mirror) def add_layout_pins(self): for i in range(len(self.local_insts)): inst = self.local_insts[i] - gnd_pos = inst.get_pin("gnd").center() - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=gnd_pos) - self.add_layout_pin_rect_center(text="gnd", - layer="metal3", - offset=gnd_pos) - vdd_pos = inst.get_pin("vdd").center() - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=vdd_pos) - self.add_layout_pin_rect_center(text="vdd", - layer="metal3", - offset=vdd_pos) + self.add_power_pin(name = "gnd", + loc = inst.get_pin("gnd").center(), + start_layer="m2", + vertical=True) + + self.add_power_pin(name = "vdd", + loc = inst.get_pin("vdd").center(), + start_layer="m2", + vertical=True) bl_pin = inst.get_pin("bl") br_pin = inst.get_pin("br") dout_pin = inst.get_pin("dout") self.add_layout_pin(text="bl_{0}".format(i), - layer="metal2", + layer="m2", offset=bl_pin.ll(), width=bl_pin.width(), height=bl_pin.height()) self.add_layout_pin(text="br_{0}".format(i), - layer="metal2", + layer="m2", offset=br_pin.ll(), width=br_pin.width(), height=br_pin.height()) self.add_layout_pin(text="data_{0}".format(i), - layer="metal2", + layer="m2", offset=dout_pin.ll(), width=dout_pin.width(), height=dout_pin.height()) @@ -137,10 +161,10 @@ class sense_amp_array(design.design): # add sclk rail across entire array sclk_offset = self.amp.get_pin("en").ll().scale(0,1) self.add_layout_pin(text="en", - layer="metal1", + layer="m1", offset=sclk_offset, width=self.width, - height=drc("minwidth_metal1")) + height=drc("minwidth_m1")) def input_load(self): return self.amp.input_load() diff --git a/compiler/modules/single_level_column_mux_array.py b/compiler/modules/single_level_column_mux_array.py index 7e3beaad..1c181574 100644 --- a/compiler/modules/single_level_column_mux_array.py +++ b/compiler/modules/single_level_column_mux_array.py @@ -37,6 +37,21 @@ class single_level_column_mux_array(design.design): if not OPTS.netlist_only: self.create_layout() + def get_bl_name(self, port=0): + bl_name = self.mux.get_bl_names() + if len(self.all_ports) == 1: + return bl_name + else: + return bl_name + "{}".format(port) + + def get_br_name(self, port=0): + br_name = self.mux.get_br_names() + if len(self.all_ports) == 1: + return br_name + else: + return br_name + "{}".format(port) + + def create_netlist(self): self.add_modules() self.add_pins() @@ -99,11 +114,19 @@ class single_level_column_mux_array(design.design): "gnd"]) def place_array(self): + from tech import cell_properties # For every column, add a pass gate for col_num in range(self.columns): + xoffset = col_num * self.mux.width + if cell_properties.bitcell.mirror.y and col_num % 2: + mirror = "MY" + xoffset = xoffset + self.mux.width + else: + mirror = "" + name = "XMUX{0}".format(col_num) - x_off = vector(col_num * self.mux.width, self.route_height) - self.mux_inst[col_num].place(x_off) + offset = vector(xoffset, self.route_height) + self.mux_inst[col_num].place(offset=offset, mirror=mirror) def add_layout_pins(self): @@ -113,13 +136,13 @@ class single_level_column_mux_array(design.design): mux_inst = self.mux_inst[col_num] offset = mux_inst.get_pin("bl").ll() self.add_layout_pin(text="bl_{}".format(col_num), - layer="metal2", + layer="m2", offset=offset, height=self.height-offset.y) offset = mux_inst.get_pin("br").ll() self.add_layout_pin(text="br_{}".format(col_num), - layer="metal2", + layer="m2", offset=offset, height=self.height-offset.y) @@ -137,7 +160,7 @@ class single_level_column_mux_array(design.design): for j in range(self.words_per_row): offset = vector(0, self.route_height + (j-self.words_per_row)*self.m1_pitch) self.add_layout_pin(text="sel_{}".format(j), - layer="metal1", + layer="m1", offset=offset, width=self.mux.width * self.columns) @@ -155,12 +178,13 @@ class single_level_column_mux_array(design.design): # use the y offset from the sel pin and the x offset from the gate offset = vector(gate_offset.x,self.get_pin("sel_{}".format(sel_index)).cy()) # Add the poly contact with a shift to account for the rotation - self.add_via_center(layers=("metal1", "contact", "poly"), + self.add_via_center(layers=("m1", "contact", "poly"), offset=offset) self.add_path("poly", [offset, gate_offset]) def route_bitlines(self): """ Connect the output bit-lines to form the appropriate width mux """ + from tech import cell_properties for j in range(self.columns): bl_offset = self.mux_inst[j].get_pin("bl_out").bc() br_offset = self.mux_inst[j].get_pin("br_out").bc() @@ -171,43 +195,58 @@ class single_level_column_mux_array(design.design): bl_out_offset_end = bl_out_offset + vector(0,self.route_height) br_out_offset_end = br_out_offset + vector(0,self.route_height) + if cell_properties.bitcell.mirror.y and j % 2: + tmp_bl_out_end = br_out_offset_end + tmp_br_out_end = bl_out_offset_end + else: + tmp_bl_out_end = bl_out_offset_end + tmp_br_out_end = br_out_offset_end + if (j % self.words_per_row) == 0: # Create the metal1 to connect the n-way mux output from the pass gate # These will be located below the select lines. Yes, these are M2 width # to ensure vias are enclosed and M1 min width rules. width = self.m2_width + self.mux.width * (self.words_per_row - 1) - self.add_path("metal1", [bl_out_offset, bl_out_offset+vector(width,0)]) - self.add_path("metal1", [br_out_offset, br_out_offset+vector(width,0)]) + + if cell_properties.bitcell.mirror.y and (j % 2) == 0: + bl = self.mux.get_pin("bl") + br = self.mux.get_pin("br") + dist = abs(bl.ll().x - br.ll().x) + else: + dist = 0 + + self.add_path("m1", [bl_out_offset, bl_out_offset+vector(width+dist,0)]) + self.add_path("m1", [br_out_offset, br_out_offset+vector(width-dist,0)]) # Extend the bitline output rails and gnd downward on the first bit of each n-way mux self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j/self.words_per_row)), - layer="metal2", + layer="m2", start=bl_out_offset, - end=bl_out_offset_end) + end=tmp_bl_out_end) self.add_layout_pin_segment_center(text="br_out_{}".format(int(j/self.words_per_row)), - layer="metal2", + layer="m2", start=br_out_offset, - end=br_out_offset_end) + end=tmp_br_out_end) # This via is on the right of the wire - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=bl_out_offset) # This via is on the left of the wire - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=br_out_offset) else: - self.add_path("metal2", [ bl_out_offset, bl_out_offset_end]) - self.add_path("metal2", [ br_out_offset, br_out_offset_end]) + self.add_path("m2", [ bl_out_offset, tmp_bl_out_end]) + self.add_path("m2", [ br_out_offset, tmp_br_out_end]) # This via is on the right of the wire - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=bl_out_offset) # This via is on the left of the wire - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=br_out_offset) def get_drain_cin(self): diff --git a/compiler/modules/tri_gate_array.py b/compiler/modules/tri_gate_array.py index 7d1c21d0..e7ebd802 100644 --- a/compiler/modules/tri_gate_array.py +++ b/compiler/modules/tri_gate_array.py @@ -83,14 +83,14 @@ class tri_gate_array(design.design): in_pin = self.tri_inst[i].get_pin("in") self.add_layout_pin(text="in_{0}".format(index), - layer="metal2", + layer="m2", offset=in_pin.ll(), width=in_pin.width(), height=in_pin.height()) out_pin = self.tri_inst[i].get_pin("out") self.add_layout_pin(text="out_{0}".format(index), - layer="metal2", + layer="m2", offset=out_pin.ll(), width=out_pin.width(), height=out_pin.height()) @@ -100,24 +100,24 @@ class tri_gate_array(design.design): for n in ["vdd", "gnd"]: for supply_pin in self.tri_inst[i].get_pins(n): pin_pos = supply_pin.center() - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=pin_pos) self.add_layout_pin_rect_center(text=n, - layer="metal3", + layer="m3", offset=pin_pos) width = self.tri.width * self.columns - (self.words_per_row - 1) * self.tri.width en_pin = self.tri_inst[0].get_pin("en") self.add_layout_pin(text="en", - layer="metal1", + layer="m1", offset=en_pin.ll().scale(0, 1), width=width, - height=drc("minwidth_metal1")) + height=drc("minwidth_m1")) enbar_pin = self.tri_inst[0].get_pin("en_bar") self.add_layout_pin(text="en_bar", - layer="metal1", + layer="m1", offset=enbar_pin.ll().scale(0, 1), width=width, - height=drc("minwidth_metal1")) \ No newline at end of file + height=drc("minwidth_m1")) \ No newline at end of file diff --git a/compiler/modules/wordline_driver.py b/compiler/modules/wordline_driver.py index fbd800b2..27671fd2 100644 --- a/compiler/modules/wordline_driver.py +++ b/compiler/modules/wordline_driver.py @@ -56,12 +56,16 @@ class wordline_driver(design.design): self.add_pin("gnd", "GROUND") def add_modules(self): + b = factory.create(module_type="bitcell") + self.inv = factory.create(module_type="pdriver", fanout=self.cols, - neg_polarity=True) + neg_polarity=True, + height=b.height) self.add_mod(self.inv) - self.nand2 = factory.create(module_type="pnand2") + self.nand2 = factory.create(module_type="pnand2", + height=b.height) self.add_mod(self.nand2) def route_vdd_gnd(self): @@ -142,7 +146,7 @@ class wordline_driver(design.design): # Wordline enable connection en_offset = [self.m1_width + 2 * self.m1_space, 0] en_pin = self.add_layout_pin(text="en", - layer="metal2", + layer="m2", offset=en_offset, width=self.m2_width, height=self.height) @@ -155,10 +159,10 @@ class wordline_driver(design.design): a_pin = nand_inst.get_pin("A") a_pos = a_pin.lc() clk_offset = vector(en_pin.bc().x, a_pos.y) - self.add_segment_center(layer="metal1", + self.add_segment_center(layer="m1", start=clk_offset, end=a_pos) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=clk_offset) # Nand2 out to 2nd inv @@ -167,7 +171,7 @@ class wordline_driver(design.design): # ensure the bend is in the middle mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y) mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y) - self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos]) + self.add_path("m1", [zr_pos, mid1_pos, mid2_pos, al_pos]) # connect the decoder input pin to nand2 B b_pin = nand_inst.get_pin("B") @@ -177,29 +181,29 @@ class wordline_driver(design.design): up_or_down = self.m2_space if row % 2 else -self.m2_space input_offset = vector(0, b_pos.y + up_or_down) base_offset = vector(clk_offset.x, input_offset.y) - contact_offset = vector(0.5 * self.m2_width + self.m2_space + 0.5 * contact.m1m2.width, 0) + contact_offset = vector(0.5 * self.m2_width + self.m2_space + 0.5 * contact.m1_via.width, 0) mid_via_offset = base_offset + contact_offset # must under the clk line in M1 self.add_layout_pin_segment_center(text="in_{0}".format(row), - layer="metal1", + layer="m1", start=input_offset, end=mid_via_offset) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=mid_via_offset, directions=("V", "V")) # now connect to the nand2 B - self.add_path("metal2", [mid_via_offset, b_pos]) - contact_offset = b_pos - vector(0.5 * contact.m1m2.height, 0) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_path("m2", [mid_via_offset, b_pos]) + contact_offset = b_pos - vector(0.5 * contact.m1_via.height, 0) + self.add_via_center(layers=self.m1_stack, offset=contact_offset, directions=("H", "H")) # output each WL on the right wl_offset = inv2_inst.get_pin("Z").rc() self.add_layout_pin_segment_center(text="wl_{0}".format(row), - layer="metal1", + layer="m1", start=wl_offset, end=wl_offset - vector(self.m1_width, 0)) diff --git a/compiler/modules/write_driver.py b/compiler/modules/write_driver.py index 85a58fd5..db08bcf4 100644 --- a/compiler/modules/write_driver.py +++ b/compiler/modules/write_driver.py @@ -8,6 +8,7 @@ import debug import design import utils +from globals import OPTS from tech import GDS,layer class write_driver(design.design): @@ -20,8 +21,12 @@ class write_driver(design.design): pin_names = ["din", "bl", "br", "en", "vdd", "gnd"] type_list = ["INPUT", "OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] - (width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"]) - pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"]) + if not OPTS.netlist_only: + (width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"]) + else: + (width,height) = (0,0) + pin_map = [] def __init__(self, name): design.design.__init__(self, name) @@ -32,6 +37,12 @@ class write_driver(design.design): self.pin_map = write_driver.pin_map self.add_pin_types(self.type_list) + def get_bl_names(self): + return "bl" + + def get_br_names(self): + return "br" + def get_w_en_cin(self): """Get the relative capacitance of a single input""" # This is approximated from SCMOS. It has roughly 5 3x transistor gates. diff --git a/compiler/modules/write_driver_array.py b/compiler/modules/write_driver_array.py index 100ee3a2..dd7430a9 100644 --- a/compiler/modules/write_driver_array.py +++ b/compiler/modules/write_driver_array.py @@ -37,6 +37,19 @@ class write_driver_array(design.design): if not OPTS.netlist_only: self.create_layout() + def get_bl_name(self, port=0): + bl_name = self.driver.get_bl_names() + if len(self.all_ports) == 1: + return bl_name + else: + return bl_name + "{}".format(port) + + def get_br_name(self, port=0): + br_name = self.driver.get_br_names() + if len(self.all_ports) == 1: + return br_name + else: + return br_name + "{}".format(port) def create_netlist(self): self.add_modules() @@ -106,34 +119,43 @@ class write_driver_array(design.design): def place_write_array(self): + from tech import cell_properties if self.bitcell.width > self.driver.width: self.driver_spacing = self.bitcell.width else: self.driver_spacing = self.driver.width for i in range(0,self.columns,self.words_per_row): index = int(i/self.words_per_row) - base = vector(i * self.driver_spacing, 0) - self.driver_insts[index].place(base) + xoffset = i * self.driver_spacing + + if cell_properties.bitcell.mirror.y and i % 2: + mirror = "MY" + xoffset = xoffset + self.driver.width + else: + mirror = "" + + base = vector(xoffset, 0) + self.driver_insts[index].place(offset=base, mirror=mirror) def add_layout_pins(self): for i in range(self.word_size): din_pin = self.driver_insts[i].get_pin("din") self.add_layout_pin(text="data_{0}".format(i), - layer="metal2", + layer="m2", offset=din_pin.ll(), width=din_pin.width(), height=din_pin.height()) bl_pin = self.driver_insts[i].get_pin("bl") self.add_layout_pin(text="bl_{0}".format(i), - layer="metal2", + layer="m2", offset=bl_pin.ll(), width=bl_pin.width(), height=bl_pin.height()) br_pin = self.driver_insts[i].get_pin("br") self.add_layout_pin(text="br_{0}".format(i), - layer="metal2", + layer="m2", offset=br_pin.ll(), width=br_pin.width(), height=br_pin.height()) @@ -141,13 +163,10 @@ 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: - pin_pos = pin.center() - # Add the M2->M3 stack - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=pin_pos) - self.add_layout_pin_rect_center(text=n, - layer="metal3", - offset=pin_pos) + self.add_power_pin(name = n, + loc = pin.center(), + vertical=True, + start_layer = "m2") if self.write_size: for bit in range(self.num_wmasks): en_pin = self.driver_insts[bit*self.write_size].get_pin("en") @@ -165,7 +184,7 @@ class write_driver_array(design.design): height=en_pin.height()) else: self.add_layout_pin(text="en", - layer="metal1", + layer="m1", offset=self.driver_insts[0].get_pin("en").ll().scale(0,1), width=self.width) diff --git a/compiler/modules/write_mask_and_array.py b/compiler/modules/write_mask_and_array.py index 258bbd8d..25acb324 100644 --- a/compiler/modules/write_mask_and_array.py +++ b/compiler/modules/write_mask_and_array.py @@ -104,8 +104,6 @@ class write_mask_and_array(design.design): def add_layout_pins(self): - self.nand2 = factory.create(module_type="pnand2") - supply_pin=self.nand2.get_pin("vdd") # Create the enable pin that connects all write mask AND array's B pins beg_en_pin = self.and2_insts[0].get_pin("B") @@ -114,16 +112,16 @@ class write_mask_and_array(design.design): # Extend metal3 to edge of AND array in multiport en_to_edge = self.and2.width - beg_en_pin.cx() self.add_layout_pin(text="en", - layer="metal3", + layer="m3", offset=beg_en_pin.bc(), width=end_en_pin.cx() - beg_en_pin.cx() + en_to_edge) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=vector(end_en_pin.cx() + en_to_edge, end_en_pin.cy())) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=vector(end_en_pin.cx() + en_to_edge, end_en_pin.cy())) else: self.add_layout_pin(text="en", - layer="metal3", + layer="m3", offset=beg_en_pin.bc(), width=end_en_pin.cx() - beg_en_pin.cx()) @@ -134,20 +132,21 @@ class write_mask_and_array(design.design): # Add via connections to metal3 for AND array's B pin en_pin = self.and2_insts[i].get_pin("B") - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=en_pin.center()) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=en_pin.center()) - self.add_power_pin("gnd", vector(supply_pin.width() + i * self.wmask_en_len, 0)) - self.add_power_pin("vdd", vector(supply_pin.width() + i * self.wmask_en_len, self.height)) - # Route power and ground rails together - if i < self.num_wmasks-1: - for n in ["gnd","vdd"]: - pin = self.and2_insts[i].get_pin(n) - next_pin = self.and2_insts[i+1].get_pin(n) - self.add_path("metal1",[pin.center(),next_pin.center()]) + for supply in ["gnd", "vdd"]: + supply_pin=self.and2_insts[i].get_pin(supply) + self.add_power_pin(supply, supply_pin.center()) + + for supply in ["gnd", "vdd"]: + supply_pin_left = self.and2_insts[0].get_pin(supply) + supply_pin_right = self.and2_insts[self.num_wmasks-1].get_pin(supply) + self.add_path("m1",[supply_pin_left.lc(), supply_pin_right.rc()]) + def get_cin(self): """Get the relative capacitance of all the input connections in the bank""" # The enable is connected to an and2 for every row. diff --git a/compiler/pgates/pand2.py b/compiler/pgates/pand2.py index d410a8c7..23fd5e5b 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -76,14 +76,14 @@ class pand2(pgate.pgate): a2_pin = self.inv_inst.get_pin("A") mid1_point = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy()) mid2_point = vector(mid1_point, a2_pin.cy()) - self.add_path("metal1", + self.add_path("m1", [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) def add_layout_pins(self): # Continous vdd rail along with label. vdd_pin = self.inv_inst.get_pin("vdd") self.add_layout_pin(text="vdd", - layer="metal1", + layer="m1", offset=vdd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) @@ -91,7 +91,7 @@ class pand2(pgate.pgate): # Continous gnd rail along with label. gnd_pin = self.inv_inst.get_pin("gnd") self.add_layout_pin(text="gnd", - layer="metal1", + layer="m1", offset=gnd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) diff --git a/compiler/pgates/pand3.py b/compiler/pgates/pand3.py index b02e18e4..dd1d87f7 100644 --- a/compiler/pgates/pand3.py +++ b/compiler/pgates/pand3.py @@ -76,14 +76,14 @@ class pand3(pgate.pgate): a2_pin = self.inv_inst.get_pin("A") mid1_point = vector(0.5 * (z1_pin.cx()+a2_pin.cx()), z1_pin.cy()) mid2_point = vector(mid1_point, a2_pin.cy()) - self.add_path("metal1", + self.add_path("m1", [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) def add_layout_pins(self): # Continous vdd rail along with label. vdd_pin = self.inv_inst.get_pin("vdd") self.add_layout_pin(text="vdd", - layer="metal1", + layer="m1", offset=vdd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) @@ -91,7 +91,7 @@ class pand3(pgate.pgate): # Continous gnd rail along with label. gnd_pin = self.inv_inst.get_pin("gnd") self.add_layout_pin(text="gnd", - layer="metal1", + layer="m1", offset=gnd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) diff --git a/compiler/pgates/pbuf.py b/compiler/pgates/pbuf.py index 0149f75f..3171324b 100644 --- a/compiler/pgates/pbuf.py +++ b/compiler/pgates/pbuf.py @@ -78,13 +78,13 @@ class pbuf(pgate.pgate): z1_pin = self.inv1_inst.get_pin("Z") a2_pin = self.inv2_inst.get_pin("A") mid_point = vector(z1_pin.cx(), a2_pin.cy()) - self.add_path("metal1", [z1_pin.center(), mid_point, a2_pin.center()]) + self.add_path("m1", [z1_pin.center(), mid_point, a2_pin.center()]) def add_layout_pins(self): # Continous vdd rail along with label. vdd_pin = self.inv1_inst.get_pin("vdd") self.add_layout_pin(text="vdd", - layer="metal1", + layer="m1", offset=vdd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) @@ -92,7 +92,7 @@ class pbuf(pgate.pgate): # Continous gnd rail along with label. gnd_pin = self.inv1_inst.get_pin("gnd") self.add_layout_pin(text="gnd", - layer="metal1", + layer="m1", offset=gnd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) diff --git a/compiler/pgates/pdriver.py b/compiler/pgates/pdriver.py index bb7739b7..5aa5393e 100644 --- a/compiler/pgates/pdriver.py +++ b/compiler/pgates/pdriver.py @@ -140,7 +140,7 @@ class pdriver(pgate.pgate): z_inst_list.append(self.inv_inst_list[x].get_pin("Z")) a_inst_list.append(self.inv_inst_list[x + 1].get_pin("A")) mid_point = vector(z_inst_list[x].cx(), a_inst_list[x].cy()) - self.add_path("metal1", + self.add_path("m1", [z_inst_list[x].center(), mid_point, a_inst_list[x].center()]) @@ -148,7 +148,7 @@ class pdriver(pgate.pgate): # Continous vdd rail along with label. vdd_pin = self.inv_inst_list[0].get_pin("vdd") self.add_layout_pin(text="vdd", - layer="metal1", + layer="m1", offset=vdd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) @@ -156,7 +156,7 @@ class pdriver(pgate.pgate): # Continous gnd rail along with label. gnd_pin = self.inv_inst_list[0].get_pin("gnd") self.add_layout_pin(text="gnd", - layer="metal1", + layer="m1", offset=gnd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 0c1c0b52..6d385d09 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -8,10 +8,9 @@ import contact import design import debug -from tech import layer, drc +from tech import layer from vector import vector from globals import OPTS -from sram_factory import factory class pgate(design.design): @@ -27,9 +26,9 @@ class pgate(design.design): if height: self.height = height elif not height: - b = factory.create(module_type="bitcell") - self.height = b.height - + # By default, we make it 8 M1 pitch tall + self.height = 8*self.m1_pitch + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -59,12 +58,12 @@ class pgate(design.design): debug.error("Invalid supply name.", -1) if abs(height) > 0: - self.add_rect(layer="metal1", + self.add_rect(layer="m1", offset=source_pin.ll(), height=height, width=source_pin.width()) - def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left", rotate=False): + def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left"): """ Route the input gate to the left side of the cell for access. Position specifies to place the contact the left, center, or @@ -75,11 +74,13 @@ class pgate(design.design): pmos_gate_pin = pmos_inst.get_pin("G") # Check if the gates are aligned and give an error if they aren't! + if nmos_gate_pin.ll().x != pmos_gate_pin.ll().x: + self.gds_write("unaliged_gates.gds") debug.check(nmos_gate_pin.ll().x == pmos_gate_pin.ll().x, - "Connecting unaligned gates not supported.") + "Connecting unaligned gates not supported. See unaligned_gates.gds.") - # Pick point on the left of NMOS and connect down to PMOS - nmos_gate_pos = nmos_gate_pin.ll() + vector(0.5 * self.poly_width, 0) + # Pick point on the left of NMOS and up to PMOS + nmos_gate_pos = nmos_gate_pin.ul() + vector(0.5 * self.poly_width, 0) pmos_gate_pos = vector(nmos_gate_pos.x, pmos_gate_pin.bc().y) self.add_path("poly", [nmos_gate_pos, pmos_gate_pos]) @@ -87,23 +88,16 @@ class pgate(design.design): left_gate_offset = vector(nmos_gate_pin.lx(), ypos) # Center is completely symmetric. - if rotate: - contact_width = contact.poly.height - contact_m1_width = contact.poly.second_layer_height - contact_m1_height = contact.poly.second_layer_width - directions = ("H", "V") - else: - contact_width = contact.poly.width - contact_m1_width = contact.poly.second_layer_width - contact_m1_height = contact.poly.second_layer_height - directions = ("V", "H") + contact_width = contact.poly_contact.width + contact_m1_width = contact.poly_contact.second_layer_width + contact_m1_height = contact.poly_contact.second_layer_height if position == "center": contact_offset = left_gate_offset \ + vector(0.5 * self.poly_width, 0) elif position == "farleft": contact_offset = left_gate_offset \ - - vector(0.5 * contact.poly.width, 0) + - vector(0.5 * contact.poly_contact.width, 0) elif position == "left": contact_offset = left_gate_offset \ - vector(0.5 * contact_width - 0.5 * self.poly_width, 0) @@ -113,83 +107,87 @@ class pgate(design.design): else: debug.error("Invalid contact placement option.", -1) - # Non-preferred direction via - - self.add_via_center(layers=("poly", "contact", "metal1"), - offset=contact_offset, - directions=directions) + v=self.add_via_center(layers=self.poly_stack, + offset=contact_offset) self.add_layout_pin_rect_center(text=name, - layer="metal1", + layer="m1", offset=contact_offset, width=contact_m1_width, height=contact_m1_height) - # This is to ensure that the contact is # connected to the gate mid_point = contact_offset.scale(0.5, 1) \ + left_gate_offset.scale(0.5, 0) self.add_rect_center(layer="poly", offset=mid_point, - height=contact.poly.first_layer_width, + height=contact.poly_contact.first_layer_width, width=left_gate_offset.x - contact_offset.x) - def extend_wells(self, middle_position): + def extend_wells(self): """ Extend the n/p wells to cover whole cell """ - # Add a rail width to extend the well to the top of the rail - max_y_offset = self.height + 0.5 * self.m1_width - self.nwell_position = middle_position - nwell_height = max_y_offset - middle_position.y - if layer["nwell"]: + # This should match the cells in the cell library + nwell_y_offset = 0.48 * self.height + full_height = self.height + 0.5*self.m1_width + + # FIXME: float rounding problem + if "nwell" in layer: + # Add a rail width to extend the well to the top of the rail + nwell_max_offset = max(self.find_highest_layer_coords("nwell").y, + full_height) + nwell_position = vector(0, nwell_y_offset) - vector(self.well_extend_active, 0) + nwell_height = nwell_max_offset - nwell_y_offset self.add_rect(layer="nwell", - offset=middle_position, - width=self.well_width, - height=nwell_height) - if layer["vtg"]: - self.add_rect(layer="vtg", - offset=self.nwell_position, + offset=nwell_position, width=self.well_width, height=nwell_height) + if "vtg" in layer: + self.add_rect(layer="vtg", + offset=nwell_position, + width=self.well_width, + height=nwell_height) - pwell_position = vector(0, -0.5 * self.m1_width) - pwell_height = middle_position.y - pwell_position.y - if layer["pwell"]: + # Start this half a rail width below the cell + if "pwell" in layer: + pwell_min_offset = min(self.find_lowest_layer_coords("pwell").y, + -0.5 * self.m1_width) + pwell_position = vector(-self.well_extend_active, pwell_min_offset) + pwell_height = nwell_y_offset - pwell_position.y self.add_rect(layer="pwell", offset=pwell_position, width=self.well_width, height=pwell_height) - if layer["vtg"]: - self.add_rect(layer="vtg", - offset=pwell_position, - width=self.well_width, - height=pwell_height) + if "vtg" in layer: + self.add_rect(layer="vtg", + offset=pwell_position, + width=self.well_width, + height=pwell_height) def add_nwell_contact(self, pmos, pmos_pos): """ Add an nwell contact next to the given pmos device. """ - layer_stack = ("active", "contact", "metal1") + layer_stack = self.active_stack # To the right a spacing away from the pmos right active edge contact_xoffset = pmos_pos.x + pmos.active_width \ - + drc("active_to_body_active") + + self.active_space # Must be at least an well enclosure of active down # from the top of the well # OR align the active with the top of PMOS active. max_y_offset = self.height + 0.5 * self.m1_width contact_yoffset = min(pmos_pos.y + pmos.active_height - pmos.active_contact.first_layer_height, - max_y_offset - pmos.active_contact.first_layer_height / 2 - self.well_enclose_active) + max_y_offset - pmos.active_contact.first_layer_height / 2 - self.nwell_enclose_active) contact_offset = vector(contact_xoffset, contact_yoffset) # Offset by half a contact in x and y contact_offset += vector(0.5 * pmos.active_contact.first_layer_width, 0.5 * pmos.active_contact.first_layer_height) self.nwell_contact = self.add_via_center(layers=layer_stack, offset=contact_offset, - directions=("H", "V"), implant_type="n", well_type="n") - self.add_rect_center(layer="metal1", + self.add_rect_center(layer="m1", offset=contact_offset + vector(0, 0.5 * (self.height-contact_offset.y)), width=self.nwell_contact.mod.second_layer_width, height=self.height - contact_offset.y) @@ -221,17 +219,17 @@ class pgate(design.design): def add_pwell_contact(self, nmos, nmos_pos): """ Add an pwell contact next to the given nmos device. """ - layer_stack = ("active", "contact", "metal1") + layer_stack = self.active_stack pwell_position = vector(0, -0.5 * self.m1_width) # To the right a spacing away from the nmos right active edge contact_xoffset = nmos_pos.x + nmos.active_width \ - + drc("active_to_body_active") + + self.active_space # Must be at least an well enclosure of active up # from the bottom of the well contact_yoffset = max(nmos_pos.y, - self.well_enclose_active \ + self.nwell_enclose_active \ - nmos.active_contact.first_layer_height / 2) contact_offset = vector(contact_xoffset, contact_yoffset) @@ -240,10 +238,9 @@ class pgate(design.design): 0.5 * nmos.active_contact.first_layer_height) self.pwell_contact= self.add_via_center(layers=layer_stack, offset=contact_offset, - directions=("H", "V"), implant_type="p", well_type="p") - self.add_rect_center(layer="metal1", + self.add_rect_center(layer="m1", offset=contact_offset.scale(1,0.5), width=self.pwell_contact.mod.second_layer_width, height=contact_offset.y) diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index 2b8ec7b7..c0f1f955 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -52,10 +52,10 @@ class pinv(pgate.pgate): def create_layout(self): """ Calls all functions related to the generation of the layout """ self.setup_layout_constants() - self.route_supply_rails() self.place_ptx() self.add_well_contacts() - self.extend_wells(self.well_pos) + self.extend_wells() + self.route_supply_rails() self.connect_rails() self.route_input_gate(self.pmos_inst, self.nmos_inst, @@ -89,22 +89,24 @@ class pinv(pgate.pgate): # Assume we need 3 metal 1 pitches (2 power rails, one # between the tx for the drain) # plus the tx height - nmos = factory.create(module_type="ptx", tx_type="nmos") + nmos = factory.create(module_type="ptx", + tx_type="nmos") pmos = factory.create(module_type="ptx", width=drc("minwidth_tx"), tx_type="pmos") tx_height = nmos.poly_height + pmos.poly_height # rotated m1 pitch or poly to active spacing - min_channel = max(contact.poly.width + self.m1_space, - contact.poly.width + 2 * drc("poly_to_active")) + min_channel = max(contact.poly_contact.width + self.m1_space, + contact.poly_contact.width + 2 * self.poly_to_active) # This is the extra space needed to ensure DRC rules # to the active contacts extra_contact_space = max(-nmos.get_pin("D").by(), 0) # This is a poly-to-poly of a flipped cell self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space, - drc("poly_extend_active"), self.poly_space) + self.poly_extend_active + self.poly_space) total_height = tx_height + min_channel + 2 * self.top_bottom_space + debug.check(self.height > total_height, "Cell height {0} too small for simple min height {1}.".format(self.height, total_height)) @@ -115,8 +117,8 @@ class pinv(pgate.pgate): # Divide the height in half. Could divide proportional to beta, # but this makes connecting wells of multiple cells easier. # Subtract the poly space under the rail of the tx - nmos_height_available = 0.5 * tx_height_available - 0.5 * drc("poly_to_poly") - pmos_height_available = 0.5 * tx_height_available - 0.5 * drc("poly_to_poly") + nmos_height_available = 0.5 * tx_height_available - 0.5 * self.poly_space + pmos_height_available = 0.5 * tx_height_available - 0.5 * self.poly_space debug.info(2, "Height avail {0:.4f} PMOS {1:.4f} NMOS {2:.4f}".format(tx_height_available, @@ -147,14 +149,18 @@ class pinv(pgate.pgate): def setup_layout_constants(self): """ - Pre-compute some handy layout parameters. + Compute the width and height """ - # the well width is determined the multi-finger PMOS device width plus - # the well contact width and half well enclosure on both sides - self.well_width = self.pmos.active_width + self.pmos.active_contact.width \ - + drc("active_to_body_active") + 2*drc("well_enclosure_active") - self.width = self.well_width + # the width is determined the multi-finger PMOS device width plus + # the well contact width, spacing between them + # space is for power supply contact to nwell m1 spacing + self.width = self.pmos.active_offset.x + self.pmos.active_width \ + + self.active_space + contact.nwell_contact.width \ + + 0.5 * self.nwell_enclose_active \ + + self.m1_space + # This includes full enclosures on each end + self.well_width = self.width + 2*self.nwell_enclose_active # Height is an input parameter, so it is not recomputed. def add_ptx(self): @@ -178,12 +184,12 @@ class pinv(pgate.pgate): def route_supply_rails(self): """ Add vdd/gnd rails to the top and bottom. """ self.add_layout_pin_rect_center(text="gnd", - layer="metal1", + layer="m1", offset=vector(0.5 * self.width, 0), width=self.width) self.add_layout_pin_rect_center(text="vdd", - layer="metal1", + layer="m1", offset=vector(0.5 * self.width, self.height), width=self.width) @@ -222,9 +228,6 @@ class pinv(pgate.pgate): 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)) - # This will help with the wells - self.well_pos = vector(0, self.nmos_inst.uy()) - def route_outputs(self): """ Route the output (drains) together. @@ -238,7 +241,7 @@ class pinv(pgate.pgate): # Pick point at right most of NMOS and connect down to PMOS nmos_drain_pos = nmos_drain_pin.bc() pmos_drain_pos = vector(nmos_drain_pos.x, pmos_drain_pin.uc().y) - self.add_path("metal1", [nmos_drain_pos, pmos_drain_pos]) + self.add_path("m1", [nmos_drain_pos, pmos_drain_pos]) # Remember the mid for the output mid_drain_offset = vector(nmos_drain_pos.x, self.output_pos.y) @@ -247,13 +250,13 @@ class pinv(pgate.pgate): # This extends the output to the edge of the cell output_offset = mid_drain_offset.scale(0, 1) + vector(self.width, 0) self.add_layout_pin_segment_center(text="Z", - layer="metal1", + layer="m1", start=mid_drain_offset, end=output_offset) else: # This leaves the output as an internal pin (min sized) self.add_layout_pin_rect_center(text="Z", - layer="metal1", + layer="m1", offset=mid_drain_offset \ + vector(0.5 * self.m1_width, 0)) diff --git a/compiler/pgates/pinvbuf.py b/compiler/pgates/pinvbuf.py index 75b7ce6f..b8ecb3bf 100644 --- a/compiler/pgates/pinvbuf.py +++ b/compiler/pgates/pinvbuf.py @@ -114,21 +114,21 @@ class pinvbuf(pgate.pgate): z1_pin = self.inv1_inst.get_pin("Z") a2_pin = self.inv2_inst.get_pin("A") mid_point = vector(z1_pin.cx(), a2_pin.cy()) - self.add_path("metal1", [z1_pin.center(), mid_point, a2_pin.center()]) + self.add_path("m1", [z1_pin.center(), mid_point, a2_pin.center()]) # inv2 Z to inv3 A z2_pin = self.inv2_inst.get_pin("Z") a3_pin = self.inv3_inst.get_pin("A") mid_point = vector(z2_pin.cx(), a3_pin.cy()) - self.add_path("metal1", [z2_pin.center(), mid_point, a3_pin.center()]) + self.add_path("m1", [z2_pin.center(), mid_point, a3_pin.center()]) # inv1 Z to inv4 A (up and over) z1_pin = self.inv1_inst.get_pin("Z") a4_pin = self.inv4_inst.get_pin("A") mid_point = vector(z1_pin.cx(), a4_pin.cy()) - self.add_wire(("metal1", "via1", "metal2"), + self.add_wire(self.m1_stack, [z1_pin.center(), mid_point, a4_pin.center()]) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=z1_pin.center()) def add_layout_pins(self): @@ -136,7 +136,7 @@ class pinvbuf(pgate.pgate): # Continous vdd rail along with label. vdd_pin = self.inv1_inst.get_pin("vdd") self.add_layout_pin(text="vdd", - layer="metal1", + layer="m1", offset=vdd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) @@ -144,7 +144,7 @@ class pinvbuf(pgate.pgate): # Continous vdd rail along with label. gnd_pin = self.inv4_inst.get_pin("gnd") self.add_layout_pin(text="gnd", - layer="metal1", + layer="m1", offset=gnd_pin.ll().scale(0, 1), width=self.width, height=gnd_pin.height()) @@ -152,30 +152,30 @@ class pinvbuf(pgate.pgate): # Continous gnd rail along with label. gnd_pin = self.inv1_inst.get_pin("gnd") self.add_layout_pin(text="gnd", - layer="metal1", + layer="m1", offset=gnd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) z_pin = self.inv4_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Z", - layer="metal2", + layer="m2", offset=z_pin.center()) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=z_pin.center()) zb_pin = self.inv3_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Zb", - layer="metal2", + layer="m2", offset=zb_pin.center()) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=zb_pin.center()) a_pin = self.inv1_inst.get_pin("A") self.add_layout_pin_rect_center(text="A", - layer="metal2", + layer="m2", offset=a_pin.center()) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=a_pin.center()) def determine_clk_buf_stage_efforts(self, external_cout, inp_is_rise=False): diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index c5c69fd4..dd04ccb9 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -53,7 +53,7 @@ class pnand2(pgate.pgate): self.place_ptx() self.connect_rails() self.add_well_contacts() - self.extend_wells(self.well_pos) + self.extend_wells() self.route_inputs() self.route_output() @@ -85,10 +85,10 @@ class pnand2(pgate.pgate): """ Pre-compute some handy layout parameters. """ # metal spacing to allow contacts on any layer - self.input_spacing = max(self.poly_space + contact.poly.first_layer_width, - self.m1_space + contact.m1m2.first_layer_width, - self.m2_space + contact.m2m3.first_layer_width, - self.m3_space + contact.m2m3.second_layer_width) + self.input_spacing = max(self.poly_space + contact.poly_contact.first_layer_width, + self.m1_space + contact.m1_via.first_layer_width, + self.m2_space + contact.m2_via.first_layer_width, + self.m3_space + contact.m2_via.second_layer_width) # Compute the other pmos2 location, @@ -98,11 +98,11 @@ class pnand2(pgate.pgate): # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. - self.well_width = 2 * self.pmos.active_width + contact.active.width \ - + 2 * drc("active_to_body_active") \ - + 2 * drc("well_enclosure_active") + self.width = 2 * self.pmos.active_width + contact.active_contact.width \ + + 2 * self.active_space \ + + 0.5 * self.nwell_enclose_active - self.width = self.well_width + self.well_width = self.width + 2 * self.nwell_enclose_active # Height is an input parameter, so it is not recomputed. # This is the extra space needed to ensure DRC rules @@ -110,17 +110,17 @@ class pnand2(pgate.pgate): extra_contact_space = max(-self.nmos.get_pin("D").by(), 0) # This is a poly-to-poly of a flipped cell self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space, - drc("poly_extend_active"), self.poly_space) + self.poly_extend_active + self.poly_space) def route_supply_rails(self): """ Add vdd/gnd rails to the top and bottom. """ self.add_layout_pin_rect_center(text="gnd", - layer="metal1", + layer="m1", offset=vector(0.5*self.width, 0), width=self.width) self.add_layout_pin_rect_center(text="vdd", - layer="metal1", + layer="m1", offset=vector(0.5 * self.width, self.height), width=self.width) @@ -170,9 +170,6 @@ class pnand2(pgate.pgate): self.output_pos = vector(0, 0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos.active_height)) - # This will help with the wells - self.well_pos = vector(0, self.nmos1_inst.uy()) - def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies @@ -224,27 +221,29 @@ class pnand2(pgate.pgate): # Midpoints of the L routes go horizontal first then vertical mid1_offset = vector(out_offset.x, top_pin_offset.y) mid2_offset = vector(out_offset.x, bottom_pin_offset.y) - - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=pmos_pin.center(), - directions=("V", "H")) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=nmos_pin.center(), - directions=("V", "H")) - self.add_via_center(layers=("metal1", "via1", "metal2"), + + # Non-preferred active contacts + self.add_via_center(layers=self.m1_stack, + directions=("V", "H"), + offset=pmos_pin.center()) + # Non-preferred active contacts + self.add_via_center(layers=self.m1_stack, + directions=("V", "H"), + offset=nmos_pin.center()) + self.add_via_center(layers=self.m1_stack, offset=out_offset) # PMOS1 to mid-drain to NMOS2 drain - self.add_path("metal2", + self.add_path("m2", [top_pin_offset, mid1_offset, out_offset, mid2_offset, bottom_pin_offset]) # This extends the output to the edge of the cell self.add_layout_pin_rect_center(text="Z", - layer="metal1", + layer="m1", offset=out_offset, - width=contact.m1m2.first_layer_height, - height=contact.m1m2.first_layer_width) + width=contact.m1_via.first_layer_height, + height=contact.m1_via.first_layer_width) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 621829f1..9ec9ff60 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -61,7 +61,7 @@ class pnand3(pgate.pgate): self.place_ptx() self.connect_rails() self.add_well_contacts() - self.extend_wells(self.well_pos) + self.extend_wells() self.route_inputs() self.route_output() @@ -91,34 +91,29 @@ class pnand3(pgate.pgate): # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. - self.well_width = 3 * self.pmos.active_width + self.pmos.active_contact.width \ - + 2 * drc("active_to_body_active") + 2 * drc("well_enclosure_active") \ + self.width = 3 * self.pmos.active_width + self.pmos.active_contact.width \ + + 2 * self.active_space + 0.5 * self.nwell_enclose_active \ - self.overlap_offset.x - self.width = self.well_width + self.well_width = self.width + 2 * self.nwell_enclose_active # Height is an input parameter, so it is not recomputed. - # This will help with the wells and the input/output placement - self.output_pos = vector(0, 0.5*self.height) - # This is the extra space needed to ensure DRC rules # to the active contacts nmos = factory.create(module_type="ptx", tx_type="nmos") extra_contact_space = max(-nmos.get_pin("D").by(), 0) # This is a poly-to-poly of a flipped cell - self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space \ - + extra_contact_space, - drc("poly_extend_active"), - self.poly_space) + self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space, + self.poly_extend_active + self.poly_space) def route_supply_rails(self): """ Add vdd/gnd rails to the top and bottom. """ self.add_layout_pin_rect_center(text="gnd", - layer="metal1", + layer="m1", offset=vector(0.5 * self.width, 0), width=self.width) self.add_layout_pin_rect_center(text="vdd", - layer="metal1", + layer="m1", offset=vector(0.5 * self.width, self.height), width=self.width) @@ -178,10 +173,10 @@ class pnand3(pgate.pgate): self.nmos3_pos = nmos2_pos + self.overlap_offset self.nmos3_inst.place(self.nmos3_pos) - - # This should be placed at the top of the NMOS well - self.well_pos = vector(0, self.nmos1_inst.uy()) - + + # This will help with the wells and the input/output placement + self.output_pos = vector(0, 0.5*self.height) + def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ @@ -202,10 +197,10 @@ class pnand3(pgate.pgate): # wire space or wire and one contact space metal_spacing = max(self.m1_space + self.m1_width, self.m2_space + self.m2_width, - self.m1_space + 0.5 *contact.poly.width + 0.5 * self.m1_width) + self.m1_space + 0.5 *contact.poly_contact.width + 0.5 * self.m1_width) active_spacing = max(self.m1_space, - 0.5 * contact.poly.first_layer_width + drc("poly_to_active")) + 0.5 * contact.poly_contact.first_layer_width + self.poly_to_active) inputC_yoffset = self.nmos3_pos.y + self.nmos.active_height + active_spacing self.route_input_gate(self.pmos3_inst, self.nmos3_inst, @@ -237,27 +232,30 @@ class pnand3(pgate.pgate): nmos3_pin = self.nmos3_inst.get_pin("D") # Go up to metal2 for ease on all output pins - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=pmos1_pin.center()) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=pmos3_pin.center()) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=nmos3_pin.center()) + self.add_via_center(layers=self.m1_stack, + offset=pmos1_pin.center(), + directions=("V", "V")) + self.add_via_center(layers=self.m1_stack, + offset=pmos3_pin.center(), + directions=("V", "V")) + self.add_via_center(layers=self.m1_stack, + offset=nmos3_pin.center(), + directions=("V", "V")) # PMOS3 and NMOS3 are drain aligned - self.add_path("metal2", [pmos3_pin.bc(), nmos3_pin.uc()]) + self.add_path("m2", [pmos3_pin.center(), nmos3_pin.uc()]) # Route in the A input track (top track) mid_offset = vector(nmos3_pin.center().x, self.inputA_yoffset) - self.add_path("metal2", [pmos1_pin.bc(), mid_offset, nmos3_pin.uc()]) + self.add_path("m2", [pmos1_pin.center(), mid_offset, nmos3_pin.uc()]) # This extends the output to the edge of the cell - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=mid_offset) self.add_layout_pin_rect_center(text="Z", - layer="metal1", + layer="m1", offset=mid_offset, - width=contact.m1m2.first_layer_width, - height=contact.m1m2.first_layer_height) + width=contact.m1_via.first_layer_width, + height=contact.m1_via.first_layer_height) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index a99253cd..d6605d68 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -52,7 +52,7 @@ class pnor2(pgate.pgate): self.place_ptx() self.connect_rails() self.add_well_contacts() - self.extend_wells(self.well_pos) + self.extend_wells() self.route_inputs() self.route_output() @@ -84,10 +84,10 @@ class pnor2(pgate.pgate): """ Pre-compute some handy layout parameters. """ # metal spacing to allow contacts on any layer - self.input_spacing = max(self.poly_space + contact.poly.first_layer_width, - self.m1_space + contact.m1m2.first_layer_width, - self.m2_space + contact.m2m3.first_layer_width, - self.m3_space + contact.m2m3.second_layer_width) + self.input_spacing = max(self.poly_space + contact.poly_contact.first_layer_width, + self.m1_space + contact.m1_via.first_layer_width, + self.m2_space + contact.m2_via.first_layer_width, + self.m3_space + contact.m2_via.second_layer_width) # Compute the other pmos2 location, but determining # offset to overlap the source and drain pins @@ -95,12 +95,11 @@ class pnor2(pgate.pgate): # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. - self.well_width = 2 * self.pmos.active_width \ + self.width = 2 * self.pmos.active_width \ + self.pmos.active_contact.width \ - + 2 * drc("active_to_body_active") \ - + 2 * drc("well_enclosure_active") - - self.width = self.well_width + + 2 * self.active_space \ + + 0.5 * self.nwell_enclose_active + self.well_width = self.width + 2 * self.nwell_enclose_active # Height is an input parameter, so it is not recomputed. # This is the extra space needed to ensure DRC rules @@ -108,18 +107,18 @@ class pnor2(pgate.pgate): extra_contact_space = max(-self.nmos.get_pin("D").by(), 0) # This is a poly-to-poly of a flipped cell self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space, - drc("poly_extend_active"), + self.poly_extend_active, self.poly_space) def route_supply_rails(self): """ Add vdd/gnd rails to the top and bottom. """ self.add_layout_pin_rect_center(text="gnd", - layer="metal1", + layer="m1", offset=vector(0.5 * self.width, 0), width=self.width) self.add_layout_pin_rect_center(text="vdd", - layer="metal1", + layer="m1", offset=vector(0.5 * self.width, self.height), width=self.width) @@ -137,7 +136,6 @@ class pnor2(pgate.pgate): mod=self.pmos) self.connect_inst(["net1", "B", "Z", "vdd"]) - self.nmos1_inst = self.add_inst(name="pnor2_nmos1", mod=self.nmos) self.connect_inst(["Z", "A", "gnd", "gnd"]) @@ -170,9 +168,6 @@ class pnor2(pgate.pgate): self.output_pos = vector(0, 0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos.active_height)) - # This will help with the wells - self.well_pos = vector(0, self.nmos1_inst.uy()) - def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ @@ -216,9 +211,9 @@ class pnor2(pgate.pgate): nmos2_pin = self.nmos2_inst.get_pin("D") # Go up to metal2 for ease on all output pins - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=pmos_pin.center()) - m1m2_contact = self.add_via_center(layers=("metal1", "via1", "metal2"), + m1m2_contact = self.add_via_center(layers=self.m1_stack, offset=nmos_pin.center()) mid1_offset = vector(pmos_pin.center().x, nmos2_pin.center().y) @@ -226,18 +221,18 @@ class pnor2(pgate.pgate): mid3_offset = mid2_offset + vector(self.m2_width, 0) # PMOS1 to mid-drain to NMOS2 drain - self.add_path("metal2", - [pmos_pin.bc(), mid2_offset, mid3_offset]) - self.add_path("metal2", + self.add_path("m2", + [pmos_pin.center(), mid2_offset, mid3_offset]) + self.add_path("m2", [nmos_pin.rc(), mid1_offset, mid2_offset]) # This extends the output to the edge of the cell - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=mid3_offset) self.add_layout_pin_rect_center(text="Z", - layer="metal1", + layer="m1", offset=mid3_offset, - width=contact.m1m2.first_layer_height, - height=contact.m1m2.first_layer_width) + width=contact.m1_via.first_layer_height, + height=contact.m1_via.first_layer_width) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index bd391ecc..acb2062f 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -8,7 +8,7 @@ import contact import design import debug -from tech import drc, parameter +from tech import parameter from vector import vector from globals import OPTS from sram_factory import factory @@ -38,6 +38,12 @@ class precharge(design.design): self.create_layout() self.DRC_LVS() + def get_bl_names(self): + return "bl" + + def get_br_names(self): + return "br" + def create_netlist(self): self.add_pins() self.add_ptx() @@ -72,7 +78,7 @@ class precharge(design.design): # Adds the rail across the width of the cell vdd_position = vector(0.5 * self.width, self.height) - self.add_rect_center(layer="metal1", + self.add_rect_center(layer="m1", offset=vdd_position, width=self.width, height=self.m1_width) @@ -80,7 +86,7 @@ class precharge(design.design): pmos_pin = self.upper_pmos2_inst.get_pin("S") # center of vdd rail pmos_vdd_pos = vector(pmos_pin.cx(), vdd_position.y) - self.add_path("metal1", [pmos_pin.uc(), pmos_vdd_pos]) + self.add_path("m1", [pmos_pin.uc(), pmos_vdd_pos]) # Add vdd pin above the transistor self.add_power_pin("vdd", pmos_pin.center(), vertical=True) @@ -116,12 +122,12 @@ class precharge(design.design): # adds the lower pmos to layout bl_xoffset = self.bitcell.get_pin(self.bitcell_bl).lx() self.lower_pmos_position = vector(max(bl_xoffset - contact_xdiff, - self.well_enclose_active), + self.nwell_enclose_active), self.pmos.active_offset.y) self.lower_pmos_inst.place(self.lower_pmos_position) # adds the upper pmos(s) to layout - ydiff = self.pmos.height + 2 * self.m1_space + contact.poly.width + ydiff = self.pmos.height + 2 * self.m1_space + contact.poly_contact.width self.upper_pmos1_pos = self.lower_pmos_position + vector(0, ydiff) self.upper_pmos1_inst.place(self.upper_pmos1_pos) @@ -159,12 +165,12 @@ class precharge(design.design): # adds the en contact to connect the gates to the en rail on metal1 offset = self.lower_pmos_inst.get_pin("G").ul() \ + vector(0, 0.5 * self.poly_space) - self.add_via_center(layers=("poly", "contact", "metal1"), + self.add_via_center(layers=self.poly_stack, offset=offset) # adds the en rail on metal1 self.add_layout_pin_segment_center(text="en_bar", - layer="metal1", + layer="m1", start=offset.scale(0, 1), end=offset.scale(0, 1) + vector(self.width, 0)) @@ -175,15 +181,15 @@ class precharge(design.design): # adds the contact from active to metal1 well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1, 0) \ - + vector(0, self.upper_pmos1_inst.uy() + contact.well.height / 2 \ - + drc("well_extend_active")) - self.add_via_center(layers=("active", "contact", "metal1"), + + vector(0, self.upper_pmos1_inst.uy() + contact.active_contact.height / 2 \ + + self.nwell_extend_active) + self.add_via_center(layers=self.active_stack, offset=well_contact_pos, implant_type="n", well_type="n") # leave an extra pitch for the height - self.height = well_contact_pos.y + contact.well.height + self.m1_pitch + self.height = well_contact_pos.y + contact.active_contact.height + self.m1_pitch # nwell should span the whole design since it is pmos only self.add_rect(layer="nwell", @@ -200,18 +206,16 @@ class precharge(design.design): offset = vector(self.bitcell.get_pin(self.bitcell_bl).cx(), 0) \ - vector(0.5 * self.m2_width, 0) self.bl_pin = self.add_layout_pin(text="bl", - layer="metal2", + layer="m2", offset=offset, - width=drc("minwidth_metal2"), height=self.height) # adds the BR on metal 2 offset = vector(self.bitcell.get_pin(self.bitcell_br).cx(), 0) \ - vector(0.5 * self.m2_width, 0) self.br_pin = self.add_layout_pin(text="br", - layer="metal2", + layer="m2", offset=offset, - width=drc("minwidth_metal2"), height=self.height) def connect_to_bitlines(self): @@ -233,23 +237,22 @@ class precharge(design.design): Adds contacts/via from metal1 to metal2 for bit-lines """ - stack = ("metal1", "via1", "metal2") upper_pin = self.upper_pmos1_inst.get_pin("S") lower_pin = self.lower_pmos_inst.get_pin("S") # BL goes up to M2 at the transistor - self.bl_contact =self.add_via_center(layers=stack, + self.bl_contact =self.add_via_center(layers=self.m1_stack, offset=upper_pin.center(), directions=("V", "V")) - self.add_via_center(layers=stack, + self.add_via_center(layers=self.m1_stack, offset=lower_pin.center(), directions=("V", "V")) # BR routes over on M1 first - self.add_via_center(layers=stack, + self.add_via_center(layers=self.m1_stack, offset=vector(self.br_pin.cx(), upper_pin.cy()), directions=("V", "V")) - self.add_via_center(layers=stack, + self.add_via_center(layers=self.m1_stack, offset=vector(self.br_pin.cx(), lower_pin.cy()), directions=("V", "V")) @@ -261,7 +264,7 @@ class precharge(design.design): left_pos = vector(min(pmos_pin.cx(), bit_pin.cx()), pmos_pin.cy()) right_pos = vector(max(pmos_pin.cx(), bit_pin.cx()), pmos_pin.cy()) - self.add_path("metal1", [left_pos, right_pos] ) + self.add_path("m1", [left_pos, right_pos] ) def connect_pmos_m2(self, pmos_pin, bit_pin): """ @@ -271,7 +274,7 @@ class precharge(design.design): left_pos = vector(min(pmos_pin.cx(), bit_pin.cx()), pmos_pin.cy()) right_pos = vector(max(pmos_pin.cx(), bit_pin.cx()), pmos_pin.cy()) - self.add_path("metal2", [left_pos, right_pos], self.bl_contact.height) + self.add_path("m2", [left_pos, right_pos], self.bl_contact.height) def get_en_cin(self): """Get the relative capacitance of the enable in the precharge cell""" diff --git a/compiler/pgates/ptristate_inv.py b/compiler/pgates/ptristate_inv.py index 2a972407..9586e72b 100644 --- a/compiler/pgates/ptristate_inv.py +++ b/compiler/pgates/ptristate_inv.py @@ -44,7 +44,7 @@ class ptristate_inv(pgate.pgate): """ Calls all functions related to the generation of the netlist """ self.add_pins() self.add_ptx() - self.create_ptx() + self.create_ptx() def create_layout(self): """ Calls all functions related to the generation of the layout """ @@ -61,7 +61,6 @@ class ptristate_inv(pgate.pgate): """ Adds pins for spice netlist """ self.add_pin_list(["in", "out", "en", "en_bar", "vdd", "gnd"]) - def setup_layout_constants(self): """ Pre-compute some handy layout parameters. @@ -73,15 +72,14 @@ class ptristate_inv(pgate.pgate): # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. - self.well_width = 2 * self.pmos.active_width + drc("well_enclosure_active") + self.well_width = 2 * self.pmos.active_width + self.nwell_enclose_active # Add an extra space because we route the output on the right of the S/D self.width = self.well_width + 0.5 * self.m1_space # Height is an input parameter, so it is not recomputed. # Make sure we can put a well above and below - self.top_bottom_space = max(contact.well.width, contact.well.height) - + self.top_bottom_space = max(contact.active_contact.width, contact.active_contact.height) def add_ptx(self): """ Create the PMOS and NMOS transistors. """ @@ -95,18 +93,17 @@ class ptristate_inv(pgate.pgate): width=self.pmos_width, mults=1, tx_type="pmos") - self.add_mod(self.pmos) def route_supply_rails(self): """ Add vdd/gnd rails to the top and bottom. """ self.add_layout_pin_rect_center(text="gnd", - layer="metal1", + layer="m1", offset=vector(0.5 * self.width, 0), width=self.width) self.add_layout_pin_rect_center(text="vdd", - layer="metal1", + layer="m1", offset=vector(0.5 * self.width, self.height), width=self.width) @@ -138,8 +135,8 @@ class ptristate_inv(pgate.pgate): """ pmos_yoff = self.height - self.pmos.active_height \ - - self.top_bottom_space - 0.5 * contact.well.height - nmos_yoff = self.top_bottom_space + 0.5 * contact.well.height + - self.top_bottom_space - 0.5 * contact.active_contact.height + nmos_yoff = self.top_bottom_space + 0.5 * contact.active_contact.height # Tristate transistors pmos1_pos = vector(self.pmos.active_offset.x, pmos_yoff) @@ -181,7 +178,7 @@ class ptristate_inv(pgate.pgate): pmos_drain_pos = pmos_drain_pin.ur() self.add_layout_pin(text="out", - layer="metal1", + layer="m1", offset=nmos_drain_pos, height=pmos_drain_pos.y - nmos_drain_pos.y) @@ -191,7 +188,7 @@ class ptristate_inv(pgate.pgate): supplies AFTER the wells are created """ - layer_stack = ("active", "contact", "metal1") + layer_stack = self.active_stack drain_pos = self.nmos1_inst.get_pin("S").center() vdd_pos = self.get_pin("vdd").center() diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index a1606ac3..f331dc28 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -52,18 +52,17 @@ class ptx(design.design): self.connect_poly = connect_poly self.num_contacts = num_contacts - # Do NOT create the netlist and layout (not a pgate) # Since it has variable height, it is not a pgate. self.create_netlist() # We must always create ptx layout for pbitcell # some transistor sizes in other netlist depend on pbitcell self.create_layout() - #ll = self.find_lowest_coords() - #ur = self.find_highest_coords() - #self.add_boundary(ll, ur) + ll = self.find_lowest_coords() + ur = self.find_highest_coords() + self.add_boundary(ll, ur) - # (0,0) will be the corner ofthe active area (not the larger well) + # (0,0) will be the corner of the active area (not the larger well) self.translate_all(self.active_offset) def create_layout(self): @@ -92,15 +91,15 @@ class ptx(design.design): # " ".join(self.pins))) # Just make a guess since these will actually # be decided in the layout later. - area_sd = 2.5 * drc("minwidth_poly") * self.tx_width - perimeter_sd = 2 * drc("minwidth_poly") + 2 * self.tx_width + area_sd = 2.5 * self.poly_width * self.tx_width + perimeter_sd = 2 * self.poly_width + 2 * self.tx_width main_str = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type], self.mults, self.tx_width, drc("minwidth_poly")) area_str = "pd={0:.2f}u ps={0:.2f}u as={1:.2f}p ad={1:.2f}p".format(perimeter_sd, area_sd) - self.spice_device= main_str + area_str + self.spice_device = main_str + area_str self.spice.append("\n* ptx " + self.spice_device) # self.spice.append(".ENDS {0}".format(self.name)) @@ -109,8 +108,8 @@ class ptx(design.design): Pre-compute some handy layout parameters. """ - if self.num_contacts==None: - self.num_contacts=self.calculate_num_contacts() + if not self.num_contacts: + self.num_contacts = self.calculate_num_contacts() # Determine layer types needed if self.tx_type == "nmos": @@ -120,72 +119,83 @@ class ptx(design.design): self.implant_type = "p" self.well_type = "n" else: - self.error("Invalid transitor type.",-1) - + self.error("Invalid transitor type.", -1) # This is not actually instantiated but used for calculations self.active_contact = factory.create(module_type="contact", - layer_stack=("active", "contact", "metal1"), + layer_stack=self.active_stack, + directions=("V", "V"), dimensions=(1, self.num_contacts)) - - # The contacted poly pitch (or uncontacted in an odd technology) - self.poly_pitch = max(2 * self.active_contact_to_gate + self.contact_width + self.poly_width, + # The contacted poly pitch + self.poly_pitch = max(2 * self.contact_to_gate + self.contact_width + self.poly_width, self.poly_space) - # The contacted poly pitch (or uncontacted in an odd technology) - self.contact_pitch = 2 * self.active_contact_to_gate + self.contact_width + self.poly_width - - # The enclosure of an active contact. Not sure about second term. - active_enclose_contact = max(drc("active_enclosure_active_contact"), - (self.active_width - self.contact_width) / 2) - - # This is the distance from the edge of poly to the contacted end of active - self.end_to_poly = active_enclose_contact + self.contact_width + self.active_contact_to_gate - + # The contacted poly pitch + self.contact_spacing = 2 * self.contact_to_gate + \ + self.contact_width + self.poly_width + # This is measured because of asymmetric enclosure rules + active_enclose_contact = 0.5*(self.active_contact.width - self.contact_width) + + # This is the distance from the side of + # poly gate to the contacted end of active + # (i.e. the "outside" contacted diffusion sizes) + self.end_to_poly = self.active_contact.width - active_enclose_contact + \ + self.contact_to_gate + # Active width is determined by enclosure on both ends and contacted pitch, # at least one poly and n-1 poly pitches - self.active_width = 2 * self.end_to_poly + self.poly_width + (self.mults - 1) * self.poly_pitch + self.active_width = 2 * self.end_to_poly + self.poly_width + \ + (self.mults - 1) * self.poly_pitch # Active height is just the transistor width self.active_height = self.tx_width # Poly height must include poly extension over active self.poly_height = self.tx_width + 2 * self.poly_extend_active - + # The active offset is due to the well extension - self.active_offset = vector([self.well_enclose_active] * 2) + if "pwell" in layer: + pwell_enclose_active = drc("pwell_enclose_active") + else: + pwell_enclose_active = 0 + if "nwell" in layer: + nwell_enclose_active = drc("nwell_enclose_active") + else: + nwell_enclose_active = 0 + # Use the max of either so that the poly gates will align properly + well_enclose_active = max(pwell_enclose_active, + nwell_enclose_active) + self.active_offset = vector([well_enclose_active] * 2) # Well enclosure of active, ensure minwidth as well well_name = "{}well".format(self.well_type) - if layer[well_name]: - self.cell_well_width = max(self.active_width + 2 * self.well_enclose_active, - self.well_width) - self.cell_well_height = max(self.tx_width + 2 * self.well_enclose_active, - self.well_width) + if well_name in layer: + well_width_rule = drc("minwidth_" + well_name) + well_enclose_active = drc(well_name + "_enclose_active") + self.well_width = max(self.active_width + 2 * well_enclose_active, + well_width_rule) + self.well_height = max(self.active_height + 2 * well_enclose_active, + well_width_rule) # We are going to shift the 0,0, so include that in the width and height - self.height = self.cell_well_height - self.active_offset.y - self.width = self.cell_well_width - self.active_offset.x + self.height = self.well_height - self.active_offset.y + self.width = self.well_width - self.active_offset.x else: - # If no well, use the boundary of the active and poly + # The well is not included in the height and width self.height = self.poly_height self.width = self.active_width - - # The active offset is due to the well extension - self.active_offset = vector([self.well_enclose_active] * 2) # This is the center of the first active contact offset (centered vertically) - self.contact_offset = self.active_offset + vector(active_enclose_contact + 0.5 * self.contact_width, + self.contact_offset = self.active_offset + vector(0.5 * self.active_contact.width, 0.5 * self.active_height) - # Min area results are just flagged for now. - debug.check(self.active_width * self.active_height >= drc("minarea_active"), + debug.check(self.active_width * self.active_height >= self.minarea_active, "Minimum active area violated.") # We do not want to increase the poly dimensions to fix # an area problem as it would cause an LVS issue. - debug.check(self.poly_width * self.poly_height >= drc("minarea_poly"), + debug.check(self.poly_width * self.poly_height >= self.minarea_poly, "Minimum poly area violated.") def connect_fingered_poly(self, poly_positions): @@ -215,13 +225,13 @@ class ptx(design.design): poly_offset = poly_positions[0] + vector(-0.5 * self.poly_width, distance_above_active) # Remove the old pin and add the new one - self.remove_layout_pin("G") # only keep the main pin + # only keep the main pin + self.remove_layout_pin("G") self.add_layout_pin(text="G", layer="poly", offset=poly_offset, width=poly_width, - height=drc("minwidth_poly")) - + height=self.poly_width) def connect_fingered_active(self, drain_positions, source_positions): """ @@ -230,10 +240,10 @@ class ptx(design.design): # This is the distance that we must route up or down from the center # of the contacts to avoid DRC violations to the other contacts - pin_offset = vector(0, 0.5 * self.active_contact.second_layer_height \ - + self.m1_space + 0.5 * self.m1_width) + pin_offset = vector(0, + 0.5 * self.active_contact.second_layer_height + self.m1_space + 0.5 * self.m1_width) # This is the width of a m1 extend the ends of the pin - end_offset = vector(self.m1_width/2,0) + end_offset = vector(self.m1_width / 2.0, 0) # drains always go to the MIDDLE of the cell, # so top of NMOS, bottom of PMOS @@ -246,16 +256,17 @@ class ptx(design.design): source_dir = -1 if len(source_positions) > 1: - source_offset = pin_offset.scale(source_dir,source_dir) - self.remove_layout_pin("S") # remove the individual connections + source_offset = pin_offset.scale(source_dir, source_dir) + # remove the individual connections + self.remove_layout_pin("S") # Add each vertical segment for a in source_positions: - self.add_path(("metal1"), + self.add_path(("m1"), [a, a + pin_offset.scale(source_dir, source_dir)]) # Add a single horizontal pin self.add_layout_pin_segment_center(text="S", - layer="metal1", + layer="m1", start=source_positions[0] + source_offset - end_offset, end=source_positions[-1] + source_offset + end_offset) @@ -264,10 +275,10 @@ class ptx(design.design): self.remove_layout_pin("D") # remove the individual connections # Add each vertical segment for a in drain_positions: - self.add_path(("metal1"), [a,a+drain_offset]) + self.add_path(("m1"), [a,a+drain_offset]) # Add a single horizontal pin self.add_layout_pin_segment_center(text="D", - layer="metal1", + layer="m1", start=drain_positions[0] + drain_offset - end_offset, end=drain_positions[-1] + drain_offset + end_offset) @@ -314,7 +325,7 @@ class ptx(design.design): height=self.active_height) # If the implant must enclose the active, shift offset # and increase width/height - enclose_width = drc("implant_enclosure_active") + enclose_width = self.implant_enclose_active enclose_offset = [enclose_width] * 2 self.add_rect(layer="{}implant".format(self.implant_type), offset=self.active_offset - enclose_offset, @@ -326,26 +337,31 @@ class ptx(design.design): Add an (optional) well and implant for the type of transistor. """ well_name = "{}well".format(self.well_type) - if layer[well_name]: - self.add_rect(layer=well_name, - offset=(0,0), - width=self.cell_well_width, - height=self.cell_well_height) - if layer["vtg"]: - self.add_rect(layer="vtg", - offset=(0,0), - width=self.cell_well_width, - height=self.cell_well_height) + if not (well_name in layer or "vtg" in layer): + return + center_pos = self.active_offset + vector(0.5*self.active_width, + 0.5*self.active_height) + well_ll = center_pos - vector(0.5*self.well_width, + 0.5*self.well_height) + if well_name in layer: + self.add_rect(layer=well_name, + offset=well_ll, + width=self.well_width, + height=self.well_height) + if "vtg" in layer: + self.add_rect(layer="vtg", + offset=well_ll, + width=self.well_width, + height=self.well_height) def calculate_num_contacts(self): - """ + """ Calculates the possible number of source/drain contacts in a finger. For now, it is hard set as 1. """ return 1 - def get_contact_positions(self): """ Create a list of the centers of drain and source contact positions. @@ -359,10 +375,10 @@ class ptx(design.design): for i in range(self.mults): if i%2: # It's a source... so offset from previous drain. - source_positions.append(drain_positions[-1] + vector(self.contact_pitch, 0)) + source_positions.append(drain_positions[-1] + vector(self.contact_spacing, 0)) else: # It's a drain... so offset from previous source. - drain_positions.append(source_positions[-1] + vector(self.contact_pitch, 0)) + drain_positions.append(source_positions[-1] + vector(self.contact_spacing, 0)) return [source_positions,drain_positions] @@ -374,28 +390,28 @@ class ptx(design.design): [source_positions,drain_positions] = self.get_contact_positions() for pos in source_positions: - contact=self.add_via_center(layers=("active", "contact", "metal1"), + contact=self.add_via_center(layers=self.active_stack, offset=pos, size=(1, self.num_contacts), - directions=("H","V"), + directions=("V","V"), implant_type=self.implant_type, well_type=self.well_type) self.add_layout_pin_rect_center(text="S", - layer="metal1", + layer="m1", offset=pos, width=contact.mod.second_layer_width, height=contact.mod.second_layer_height) for pos in drain_positions: - contact=self.add_via_center(layers=("active", "contact", "metal1"), + contact=self.add_via_center(layers=self.active_stack, offset=pos, size=(1, self.num_contacts), - directions=("H","V"), + directions=("V","V"), implant_type=self.implant_type, well_type=self.well_type) self.add_layout_pin_rect_center(text="D", - layer="metal1", + layer="m1", offset=pos, width=contact.mod.second_layer_width, height=contact.mod.second_layer_height) diff --git a/compiler/pgates/pwrite_driver.py b/compiler/pgates/pwrite_driver.py index a808737b..16830e62 100644 --- a/compiler/pgates/pwrite_driver.py +++ b/compiler/pgates/pwrite_driver.py @@ -6,14 +6,13 @@ #All rights reserved. # import design -from tech import drc, parameter, spice +from tech import parameter import debug -import math -from tech import drc from vector import vector from globals import OPTS from sram_factory import factory + class pwrite_driver(design.design): """ The pwrite_driver is two tristate inverters that drive the bitlines. @@ -40,12 +39,10 @@ class pwrite_driver(design.design): # Creates the netlist and layout # Since it has variable height, it is not a pgate. self.create_netlist() - if not OPTS.netlist_only: + if not OPTS.netlist_only: self.create_layout() self.DRC_LVS() - - def create_netlist(self): self.add_pins() self.add_modules() @@ -55,8 +52,6 @@ class pwrite_driver(design.design): self.place_modules() self.route_wires() self.route_supplies() - - def add_pins(self): self.add_pin("din", "INPUT") @@ -66,17 +61,19 @@ class pwrite_driver(design.design): self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") - def add_modules(self): # Tristate inverter self.tri = factory.create(module_type="ptristate_inv", height="min") self.add_mod(self.tri) - debug.check(self.tri.width nmos_upper/D on metal1 # bl_out -> nmos_upper/S on metal2 - self.add_path("metal1", + self.add_path("m1", [bl_pin.ll(), vector(nmos_upper_d_pin.cx(), bl_pin.by()), nmos_upper_d_pin.center()]) # halfway up, move over @@ -149,12 +155,12 @@ class single_level_column_mux(pgate.pgate): + nmos_upper_s_pin.bc().scale(0, 0.4) mid2 = bl_out_pin.uc().scale(0, 0.4) \ + nmos_upper_s_pin.bc().scale(1, 0.4) - self.add_path("metal2", + self.add_path("m2", [bl_out_pin.uc(), mid1, mid2, nmos_upper_s_pin.bc()]) # br -> nmos_lower/D on metal2 # br_out -> nmos_lower/S on metal1 - self.add_path("metal1", + self.add_path("m1", [br_out_pin.uc(), vector(nmos_lower_s_pin.cx(), br_out_pin.uy()), nmos_lower_s_pin.center()]) @@ -163,7 +169,7 @@ class single_level_column_mux(pgate.pgate): + nmos_lower_d_pin.uc().scale(0,0.5) mid2 = br_pin.bc().scale(0,0.5) \ + nmos_lower_d_pin.uc().scale(1,0.5) - self.add_path("metal2", + self.add_path("m2", [br_pin.bc(), mid1, mid2, nmos_lower_d_pin.uc()]) def add_wells(self): @@ -175,19 +181,15 @@ class single_level_column_mux(pgate.pgate): # Add it to the right, aligned in between the two tx active_pos = vector(self.bitcell.width, self.nmos_upper.by() - 0.5 * self.poly_space) - self.add_via_center(layers=("active", "contact", "metal1"), + self.add_via_center(layers=self.active_stack, offset=active_pos, implant_type="p", well_type="p") - # Add the M1->M2->M3 stack - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=active_pos) - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=active_pos) - self.add_layout_pin_rect_center(text="gnd", - layer="metal3", - offset=active_pos) + # Add the M1->..->power_grid_layer stack + self.add_power_pin(name = "gnd", + loc = active_pos, + start_layer="m1") # Add well enclosure over all the tx and contact self.add_rect(layer="pwell", diff --git a/compiler/router/router_tech.py b/compiler/router/router_tech.py index efae2708..e0f277e9 100644 --- a/compiler/router/router_tech.py +++ b/compiler/router/router_tech.py @@ -5,7 +5,7 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from tech import drc, layer +from tech import drc, layer, preferred_directions from contact import contact from vector import vector import debug @@ -26,6 +26,9 @@ class router_tech: self.rail_track_width = rail_track_width if len(self.layers) == 1: + if preferred_directions[self.layers[0]] != "H": + debug.warning("Using '{}' for horizontal routing, but it " \ + "prefers vertical routing".format(self.layers[0])) self.horiz_layer_name = self.vert_layer_name = self.layers[0] self.horiz_lpp = self.vert_lpp = layer[self.layers[0]] @@ -35,7 +38,17 @@ class router_tech: self.horiz_track_width = self.horiz_layer_minwidth + self.horiz_layer_spacing self.vert_track_width = self.vert_layer_minwidth + self.vert_layer_spacing else: - (self.horiz_layer_name, self.via_layer_name, self.vert_layer_name) = self.layers + (try_horiz_layer, self.via_layer_name, try_vert_layer) = self.layers + + # figure out wich of the two layers prefers horizontal/vertical + # routing + if preferred_directions[try_horiz_layer] == "H" and preferred_directions[try_vert_layer] == "V": + self.horiz_layer_name = try_horiz_layer + self.vert_layer_name = try_vert_layer + else: + raise ValueError("Layer '{}' and '{}' are using the wrong " \ + "preferred_directions '{}' and '{}'. Only "\ + "('H', 'V') are supported") via_connect = contact(self.layers, (1, 1)) max_via_size = max(via_connect.width,via_connect.height) diff --git a/compiler/router/supply_grid_router.py b/compiler/router/supply_grid_router.py index 881e87b2..b28f875e 100644 --- a/compiler/router/supply_grid_router.py +++ b/compiler/router/supply_grid_router.py @@ -31,6 +31,8 @@ class supply_grid_router(router): 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). """ + start_time = datetime.now() + # Power rail width in minimum wire widths self.rail_track_width = 3 @@ -40,8 +42,8 @@ class supply_grid_router(router): self.supply_rails = {} # This is the same as above but as a sigle set for the all the rails self.supply_rail_tracks = {} - + print_time("Init supply router", datetime.now(), start_time, 3) def create_routing_grid(self): diff --git a/compiler/router/vector3d.py b/compiler/router/vector3d.py index 0d183021..066f843f 100644 --- a/compiler/router/vector3d.py +++ b/compiler/router/vector3d.py @@ -27,7 +27,7 @@ class vector3d(): self.x = x self.y = y self.z = z - + self._hash = hash((self.x,self.y,self.z)) def __str__(self): """ override print function output """ @@ -96,7 +96,7 @@ class vector3d(): Note: This assumes that you DON'T CHANGE THE VECTOR or it will break things. """ - return hash((self.x,self.y,self.z)) + return self._hash def __rsub__(self, other): diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 9e578c7c..2f37d927 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -5,21 +5,10 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys -from tech import drc, spice import debug -from math import log,sqrt,ceil -import datetime -import getpass -import numpy as np from vector import vector -from globals import OPTS, print_time - from sram_base import sram_base -from bank import bank -from contact import m2m3 -from dff_buf_array import dff_buf_array -from dff_array import dff_array +from contact import m2_via class sram_1bank(sram_base): @@ -261,18 +250,18 @@ class sram_1bank(sram_base): # This is the steiner point where the net branches out clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y) - self.add_path("metal1", [control_clk_buf_pos, clk_steiner_pos]) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_path("m1", [control_clk_buf_pos, clk_steiner_pos]) + self.add_via_center(layers=self.m1_stack, offset=clk_steiner_pos) # Note, the via to the control logic is taken care of above - self.add_wire(("metal3","via2","metal2"),[row_addr_clk_pos, mid1_pos, clk_steiner_pos]) + self.add_wire(("m3","via2","m2"),[row_addr_clk_pos, mid1_pos, clk_steiner_pos]) if self.col_addr_dff: dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk") dff_clk_pos = dff_clk_pin.center() mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) - self.add_wire(("metal3","via2","metal2"),[dff_clk_pos, mid_pos, clk_steiner_pos]) + self.add_wire(("m3","via2","m2"),[dff_clk_pos, mid_pos, clk_steiner_pos]) if port in self.write_ports: data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") @@ -280,8 +269,8 @@ class sram_1bank(sram_base): mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) # In some designs, the steiner via will be too close to the mid_pos via # so make the wire as wide as the contacts - self.add_path("metal2",[mid_pos, clk_steiner_pos], width=max(m2m3.width,m2m3.height)) - self.add_wire(("metal3","via2","metal2"),[data_dff_clk_pos, mid_pos, clk_steiner_pos]) + self.add_path("m2",[mid_pos, clk_steiner_pos], width=max(m2_via.width,m2_via.height)) + self.add_wire(("m3","via2","m2"),[data_dff_clk_pos, mid_pos, clk_steiner_pos]) if self.write_size: wmask_dff_clk_pin = self.wmask_dff_insts[port].get_pin("clk") @@ -289,8 +278,8 @@ class sram_1bank(sram_base): mid_pos = vector(clk_steiner_pos.x, wmask_dff_clk_pos.y) # In some designs, the steiner via will be too close to the mid_pos via # so make the wire as wide as the contacts - self.add_path("metal2", [mid_pos, clk_steiner_pos], width=max(m2m3.width, m2m3.height)) - self.add_wire(("metal3", "via2", "metal2"), [wmask_dff_clk_pos, mid_pos, clk_steiner_pos]) + self.add_path("m2", [mid_pos, clk_steiner_pos], width=max(m2_via.width, m2_via.height)) + self.add_wire(("m3", "via2", "m2"), [wmask_dff_clk_pos, mid_pos, clk_steiner_pos]) def route_control_logic(self): @@ -323,20 +312,20 @@ class sram_1bank(sram_base): flop_pos = flop_pin.center() bank_pos = bank_pin.center() mid_pos = vector(bank_pos.x,flop_pos.y) - self.add_wire(("metal3","via2","metal2"),[flop_pos, mid_pos,bank_pos]) - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_wire(("m3","via2","m2"),[flop_pos, mid_pos,bank_pos]) + self.add_via_center(layers=self.m2_stack, offset=flop_pos) def route_col_addr_dff(self): - """ Connect the output of the row flops to the bank pins """ + """ Connect the output of the col flops to the bank pins """ for port in self.all_ports: if port%2: - offset = self.col_addr_dff_insts[port].ll() - vector(0, (self.word_size+2)*self.m1_pitch) + offset = self.col_addr_dff_insts[port].ll() - vector(0, (self.col_addr_size+2)*self.m1_pitch) else: offset = self.col_addr_dff_insts[port].ul() + vector(0, 2*self.m1_pitch) bus_names = ["addr_{}".format(x) for x in range(self.col_addr_size)] - col_addr_bus_offsets = self.create_horizontal_bus(layer="metal1", + col_addr_bus_offsets = self.create_horizontal_bus(layer="m1", pitch=self.m1_pitch, offset=offset, names=bus_names, @@ -371,12 +360,12 @@ class sram_1bank(sram_base): if self.write_size: for x in dff_names: pin_offset = self.data_dff_insts[port].get_pin(x).center() - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=pin_offset, directions = ("V", "V")) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=pin_offset) - self.add_via_center(layers=("metal3", "via3", "metal4"), + self.add_via_center(layers=self.m3_stack, offset=pin_offset) bank_names = ["din{0}_{1}".format(port,x) for x in range(self.word_size)] @@ -387,20 +376,21 @@ class sram_1bank(sram_base): pin_offset = self.bank_inst.get_pin(x).uc() else: pin_offset = self.bank_inst.get_pin(x).bc() - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=pin_offset) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=pin_offset) - self.add_via_center(layers=("metal3", "via3", "metal4"), + self.add_via_center(layers=self.m3_stack, offset=pin_offset) route_map = list(zip(bank_pins, dff_pins)) if self.write_size: - self.create_horizontal_channel_route(netlist=route_map, - offset=offset, - layer_stack=("metal3", "via3", "metal4")) + layer_stack = self.m3_stack else: - self.create_horizontal_channel_route(route_map, offset) + layer_stack = self.m1_stack + self.create_horizontal_channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack) def route_wmask_dff(self): """ Connect the output of the wmask flops to the write mask AND array """ @@ -415,7 +405,7 @@ class sram_1bank(sram_base): dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] for x in dff_names: offset_pin = self.wmask_dff_insts[port].get_pin(x).center() - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=offset_pin, directions=("V", "V")) @@ -423,12 +413,14 @@ class sram_1bank(sram_base): bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] for x in bank_names: offset_pin = self.bank_inst.get_pin(x).center() - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=offset_pin) route_map = list(zip(bank_pins, dff_pins)) - self.create_horizontal_channel_route(route_map,offset) + self.create_horizontal_channel_route(netlist=route_map, + offset=offset, + layer_stack=self.m1_stack) def add_lvs_correspondence_points(self): diff --git a/compiler/sram/sram_2bank.py b/compiler/sram/sram_2bank.py index e1224795..dace1ed9 100644 --- a/compiler/sram/sram_2bank.py +++ b/compiler/sram/sram_2bank.py @@ -103,25 +103,25 @@ class sram_2bank(sram_base): for n in self.control_logic_outputs + ["vdd", "gnd"]: pins = self.control_logic_inst.get_pins(n) for pin in pins: - if pin.layer=="metal2": + if pin.layer=="m2": pin_pos = pin.bc() break rail_pos = vector(pin_pos.x,self.horz_control_bus_positions[n].y) - self.add_path("metal2",[pin_pos,rail_pos]) - self.add_via_center(("metal1","via1","metal2"),rail_pos) + self.add_path("m2",[pin_pos,rail_pos]) + self.add_via_center(self.m1_stack,rail_pos) # connect the control logic cross bar for n in self.control_logic_outputs: cross_pos = vector(self.vert_control_bus_positions[n].x,self.horz_control_bus_positions[n].y) - self.add_via_center(("metal1","via1","metal2"),cross_pos) + self.add_via_center(self.m1_stack,cross_pos) # connect the bank select signals to the vertical bus for i in range(self.num_banks): pin = self.bank_inst[i].get_pin("bank_sel") pin_pos = pin.rc() if i==0 else pin.lc() rail_pos = vector(self.vert_control_bus_positions["bank_sel[{}]".format(i)].x,pin_pos.y) - self.add_path("metal3",[pin_pos,rail_pos]) - self.add_via_center(("metal2","via2","metal3"),rail_pos) + self.add_path("m3",[pin_pos,rail_pos]) + self.add_via_center(self.m2_stack,rail_pos) def route_single_msb_address(self): """ Route one MSB address bit for 2-bank SRAM """ @@ -129,37 +129,37 @@ class sram_2bank(sram_base): # connect the bank MSB flop supplies vdd_pins = self.msb_address_inst.get_pins("vdd") for vdd_pin in vdd_pins: - if vdd_pin.layer != "metal1": continue + if vdd_pin.layer != "m1": continue vdd_pos = vdd_pin.bc() down_pos = vdd_pos - vector(0,self.m1_pitch) rail_pos = vector(vdd_pos.x,self.horz_control_bus_positions["vdd"].y) - self.add_path("metal1",[vdd_pos,down_pos]) - self.add_via_center(("metal1","via1","metal2"),down_pos,rotate=90) - self.add_path("metal2",[down_pos,rail_pos]) - self.add_via_center(("metal1","via1","metal2"),rail_pos) + self.add_path("m1",[vdd_pos,down_pos]) + self.add_via_center(self.m1_stack,down_pos,rotate=90) + self.add_path("m2",[down_pos,rail_pos]) + self.add_via_center(self.m1_stack,rail_pos) gnd_pins = self.msb_address_inst.get_pins("gnd") # Only add the ground connection to the lowest metal2 rail in the flop array # FIXME: SCMOS doesn't have a vertical rail in the cell, or we could use those lowest_y = None for gnd_pin in gnd_pins: - if gnd_pin.layer != "metal2": continue + if gnd_pin.layer != "m2": continue if lowest_y==None or gnd_pin.by() 0: - self.col_addr_dff = dff_array(name="col_addr_dff", rows=1, columns=self.col_addr_size) + self.col_addr_dff = factory.create("dff_array", module_name="col_addr_dff", rows=1, columns=self.col_addr_size) self.add_mod(self.col_addr_dff) else: self.col_addr_dff = None - self.data_dff = dff_array(name="data_dff", rows=1, columns=self.word_size) + self.data_dff = factory.create("dff_array", module_name="data_dff", rows=1, columns=self.word_size) self.add_mod(self.data_dff) if self.write_size: - self.wmask_dff = dff_array(name="wmask_dff", rows=1, columns=self.num_wmasks) + self.wmask_dff = factory.create("dff_array", module_name="wmask_dff", rows=1, columns=self.num_wmasks) self.add_mod(self.wmask_dff) # Create the bank module (up to four are instantiated) - from bank import bank - self.bank = bank(self.sram_config, - name="bank") + self.bank = factory.create("bank", sram_config=self.sram_config, module_name="bank") self.add_mod(self.bank) # Create bank decoder @@ -529,12 +533,12 @@ class sram_base(design, verilog, lef): out_pos = dest_pin.uc() # move horizontal first - self.add_wire(("metal3","via2","metal2"),[in_pos, vector(out_pos.x,in_pos.y),out_pos]) - if src_pin.layer=="metal1": - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_wire(("m3","via2","m2"),[in_pos, vector(out_pos.x,in_pos.y),out_pos]) + if src_pin.layer=="m1": + self.add_via_center(layers=self.m1_stack, offset=in_pos) - if src_pin.layer in ["metal1","metal2"]: - self.add_via_center(layers=("metal2","via2","metal3"), + if src_pin.layer in ["m1","m2"]: + self.add_via_center(layers=self.m2_stack, offset=in_pos) diff --git a/compiler/sram/sram_config.py b/compiler/sram/sram_config.py index 265cdba8..cc1b44b5 100644 --- a/compiler/sram/sram_config.py +++ b/compiler/sram/sram_config.py @@ -24,9 +24,9 @@ class sram_config: # This will get over-written when we determine the organization self.words_per_row = words_per_row - self.compute_sizes() + def set_local_config(self, module): """ Copy all of the member variables to the given module for convenience """ diff --git a/compiler/sram_factory.py b/compiler/sram_factory.py index bd602980..110dbe15 100644 --- a/compiler/sram_factory.py +++ b/compiler/sram_factory.py @@ -7,7 +7,6 @@ # from globals import OPTS - class sram_factory: """ This is a factory pattern to create modules for usage in an SRAM. @@ -31,57 +30,108 @@ class sram_factory: Clear the factory instances for testing. """ self.__init__() - - def create(self, module_type, **kwargs): + + def get_techmodule_type(self, module_type): """ - A generic function to create a module with a given module_type. - The args are passed directly to the module constructor. + Try to load the custom tech module type. """ - # if name!="": - # # This is a special case where the name and type don't match - # # Can't be overridden in the config file - # module_name = name + overridden = False + try: + from tech import tech_modules + real_module_type = tech_modules[module_type] + overridden = tech_modules.is_overridden(module_type) + except ImportError: + # If they didn't define these, then don't use the option types. + # Primarily for backward compatibility and simplicity of tech files. + real_module_type = module_type + except KeyError: + # If it wasn't a tech module type, we can ignore that too. + real_module_type = module_type + return (real_module_type, overridden) + + def get_usermodule_type(self, module_type): + """ + Try to load the custom user module type. If the user hasn't specified + anything, we use the default from 'options.py'. If we cannot find anything, we + fall back to the original 'module_type'. + """ + overridden = False if hasattr(OPTS, module_type): # Retrieve the name from OPTS if it exists, # otherwise just use the name module_type = getattr(OPTS, module_type) - + overridden = module_type in OPTS.overridden.keys() + return (module_type, overridden) + + def is_duplicate_name(self, name): + for mods in self.objects.values(): + for insts in mods: + if insts[1].name == name: + return True + return False + + def create(self, module_type, module_name=None, **kwargs): + """ + A generic function to create a module with a given module_type. + The args are passed directly to the module constructor. + """ + tech_module_type, tm_overridden = self.get_techmodule_type(module_type) + user_module_type, um_overridden = self.get_usermodule_type(module_type) + + # overridden user modules have priority + if um_overridden: + real_module_type = user_module_type + # then overridden tech modules + elif tm_overridden: + real_module_type = tech_module_type + # if nothing else works use the name generated by get_usermodule_type() + else: + real_module_type = user_module_type + # Either retrieve the already loaded module or load it try: - mod = self.modules[module_type] + # Load a cached version from previous usage + mod = self.modules[real_module_type] except KeyError: + # Dynamically load the module import importlib - c = importlib.reload(__import__(module_type)) - mod = getattr(c, module_type) - self.modules[module_type] = mod - self.module_indices[module_type] = 0 - self.objects[module_type] = [] + c = importlib.reload(__import__(real_module_type)) + mod = getattr(c, real_module_type) + self.modules[real_module_type] = mod + self.module_indices[real_module_type] = 0 + self.objects[real_module_type] = [] # Either retreive a previous object or create a new one - for obj in self.objects[module_type]: + for obj in self.objects[real_module_type]: (obj_kwargs, obj_item) = obj # Must have the same dictionary exactly (conservative) if obj_kwargs == kwargs: return obj_item - # Use the default name if there are default arguments - # This is especially for library cells so that the - # spice and gds files can be found. - if len(kwargs) > 0: - # Create a unique name and increment the index - module_name = "{0}_{1}".format(module_type, - self.module_indices[module_type]) - self.module_indices[module_type] += 1 + # If no prefered module name is provided, we generate one. + if module_name is None: + # Use the default name if there are default arguments + # This is especially for library cells so that the + # spice and gds files can be found. + if len(kwargs) > 0: + # Create a unique name and increment the index + module_name = "{0}_{1}".format(real_module_type, + self.module_indices[real_module_type]) + self.module_indices[real_module_type] += 1 + else: + module_name = real_module_type else: - module_name = module_type + if self.is_duplicate_name(module_name): + raise ValueError("Modules with duplicate name are not allowed." \ + " '{}'".format(module_name)) - # type_str = "type={}".format(module_type) + # type_str = "type={}".format(real_module_type) # name_str = "name={}".format(module_name) # kwargs_str = "kwargs={}".format(str(kwargs)) # import debug # debug.info(0, "New module:" + type_str + name_str + kwargs_str) obj = mod(name=module_name, **kwargs) - self.objects[module_type].append((kwargs, obj)) + self.objects[real_module_type].append((kwargs, obj)) return obj def get_mods(self, module_type): diff --git a/compiler/tests/01_library_drc_test.py b/compiler/tests/01_library_drc_test.py index ad8671d2..8b254bbc 100755 --- a/compiler/tests/01_library_drc_test.py +++ b/compiler/tests/01_library_drc_test.py @@ -21,16 +21,15 @@ class library_drc_test(openram_test): globals.init_openram(config_file) import verify - (gds_dir, gds_files) = setup_files() + (gds_dir, allnames) = setup_files() drc_errors = 0 - debug.info(1, "\nPerforming DRC on: " + ", ".join(gds_files)) - for f in gds_files: - name = re.sub('\.gds$', '', f) - gds_name = "{0}/{1}".format(gds_dir, f) + debug.info(1, "\nPerforming DRC on: " + ", ".join(allnames)) + for f in allnames: + gds_name = "{0}/{1}.gds".format(gds_dir, f) if not os.path.isfile(gds_name): drc_errors += 1 debug.error("Missing GDS file: {}".format(gds_name)) - drc_errors += verify.run_drc(name, gds_name) + drc_errors += verify.run_drc(f, gds_name) # fails if there are any DRC errors on any cells self.assertEqual(drc_errors, 0) @@ -42,12 +41,23 @@ def setup_files(): files = os.listdir(gds_dir) nametest = re.compile("\.gds$", re.IGNORECASE) gds_files = list(filter(nametest.search, files)) - import tech - if tech.blackbox_bitcell: - # Ignore DRC of all bitcells - nametest = re.compile("cell", re.IGNORECASE) - gds_files = list(filter(lambda v: not nametest.search(v), gds_files)) - return (gds_dir, gds_files) + + tempnames = gds_files + + # remove the .gds and .sp suffixes + for i in range(len(tempnames)): + gds_files[i] = re.sub('\.gds$', '', tempnames[i]) + + try: + from tech import blackbox_cells + nameset = list(set(tempnames) - set(blackbox_cells)) + except ImportError: + # remove duplicate base names + nameset = set(tempnames) + + allnames = list(nameset) + + return (gds_dir, allnames) # run the test from the command line diff --git a/compiler/tests/02_library_lvs_test.py b/compiler/tests/02_library_lvs_test.py index 5f53753b..35ac5d76 100755 --- a/compiler/tests/02_library_lvs_test.py +++ b/compiler/tests/02_library_lvs_test.py @@ -62,8 +62,13 @@ def setup_files(): tempnames[i] = re.sub('\.gds$', '', tempnames[i]) tempnames[i] = re.sub('\.sp$', '', tempnames[i]) - # remove duplicate base names - nameset = set(tempnames) + try: + from tech import blackbox_cells + nameset = list(set(tempnames) - set(blackbox_cells)) + except ImportError: + # remove duplicate base names + nameset = set(tempnames) + allnames = list(nameset) return (gds_dir, sp_dir, allnames) diff --git a/compiler/tests/03_contact_test.py b/compiler/tests/03_contact_test.py index 2e3f4f0f..d2f0c7f6 100755 --- a/compiler/tests/03_contact_test.py +++ b/compiler/tests/03_contact_test.py @@ -21,12 +21,16 @@ class contact_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - for layer_stack in [("metal1", "via1", "metal2"), ("poly", "contact", "metal1")]: + from tech import active_stack, poly_stack, beol_stacks + + # Don't do active because of nwell contact rules + # Don't do metal3 because of min area rules + for layer_stack in [poly_stack] + [beol_stacks[0]]: stack_name = ":".join(map(str, layer_stack)) # Check single 1 x 1 contact" debug.info(2, "1 x 1 {} test".format(stack_name)) - c = factory.create(module_type="contact", layer_stack=layer_stack, dimensions=(1, 1)) + c = factory.create(module_type="contact", layer_stack=layer_stack, dimensions=(1, 1), directions=("H", "H")) self.local_drc_check(c) # Check single 1 x 1 contact" @@ -43,6 +47,10 @@ class contact_test(openram_test): debug.info(2, "1 x 1 {} test".format(stack_name)) c = factory.create(module_type="contact", layer_stack=layer_stack, dimensions=(1, 1), directions=("V","V")) self.local_drc_check(c) + + # Only do multiple contacts for BEOL + for layer_stack in beol_stacks: + stack_name = ":".join(map(str, layer_stack)) # check vertical array with one in the middle and two ends debug.info(2, "1 x 3 {} test".format(stack_name)) @@ -59,6 +67,25 @@ class contact_test(openram_test): c = factory.create(module_type="contact", layer_stack=layer_stack, dimensions=(3, 3)) self.local_drc_check(c) + # Test the well taps + # check vertical array with one in the middle and two ends + layer_stack = active_stack + stack_name = ":".join(map(str, layer_stack)) + + debug.info(2, "1 x 1 {} nwell".format(stack_name)) + c = factory.create(module_type="contact", + layer_stack=layer_stack, + implant_type="n", + well_type="n") + self.local_drc_check(c) + + debug.info(2, "1 x 1 {} pwell".format(stack_name)) + c = factory.create(module_type="contact", + layer_stack=layer_stack, + implant_type="p", + well_type="p") + self.local_drc_check(c) + globals.end_openram() diff --git a/compiler/tests/03_path_test.py b/compiler/tests/03_path_test.py index be210c8f..94e7821a 100755 --- a/compiler/tests/03_path_test.py +++ b/compiler/tests/03_path_test.py @@ -23,8 +23,8 @@ class path_test(openram_test): import tech import design - min_space = 2 * tech.drc["minwidth_metal1"] - layer_stack = ("metal1") + min_space = 2 * tech.drc["minwidth_m1"] + layer_stack = ("m1") # checks if we can retrace a path position_list = [[0,0], [0, 3 * min_space ], @@ -37,8 +37,8 @@ class path_test(openram_test): self.local_drc_check(w) - min_space = 2 * tech.drc["minwidth_metal1"] - layer_stack = ("metal1") + min_space = 2 * tech.drc["minwidth_m1"] + layer_stack = ("m1") old_position_list = [[0, 0], [0, 3 * min_space], [1 * min_space, 3 * min_space], @@ -53,8 +53,8 @@ class path_test(openram_test): wire_path.wire_path(w,layer_stack, position_list) self.local_drc_check(w) - min_space = 2 * tech.drc["minwidth_metal2"] - layer_stack = ("metal2") + min_space = 2 * tech.drc["minwidth_m2"] + layer_stack = ("m2") old_position_list = [[0, 0], [0, 3 * min_space], [1 * min_space, 3 * min_space], @@ -69,8 +69,8 @@ class path_test(openram_test): wire_path.wire_path(w, layer_stack, position_list) self.local_drc_check(w) - min_space = 2 * tech.drc["minwidth_metal3"] - layer_stack = ("metal3") + min_space = 2 * tech.drc["minwidth_m3"] + layer_stack = ("m3") position_list = [[0, 0], [0, 3 * min_space], [1 * min_space, 3 * min_space], diff --git a/compiler/tests/03_wire_test.py b/compiler/tests/03_wire_test.py index 74d5ae1e..61e21985 100755 --- a/compiler/tests/03_wire_test.py +++ b/compiler/tests/03_wire_test.py @@ -8,11 +8,11 @@ # import unittest from testutils import * -import sys,os +import sys +import os sys.path.append(os.getenv("OPENRAM_HOME")) import globals -from globals import OPTS -import debug + class wire_test(openram_test): @@ -22,107 +22,33 @@ class wire_test(openram_test): import wire import tech import design - - min_space = 2 * (tech.drc["minwidth_poly"] + - tech.drc["minwidth_metal1"]) - layer_stack = ("poly", "contact", "metal1") - old_position_list = [[0, 0], - [0, 3 * min_space], - [1 * min_space, 3 * min_space], - [4 * min_space, 3 * min_space], - [4 * min_space, 0], - [7 * min_space, 0], - [7 * min_space, 4 * min_space], - [-1 * min_space, 4 * min_space], - [-1 * min_space, 0]] - position_list = [[x-min_space, y-min_space] for x,y in old_position_list] - w = design.design("wire_test1") - wire.wire(w, layer_stack, position_list) - self.local_drc_check(w) - min_space = 2 * (tech.drc["minwidth_poly"] + - tech.drc["minwidth_metal1"]) - layer_stack = ("poly", "contact", "metal1") - old_position_list = [[0, 0], - [0, 3 * min_space], - [1 * min_space, 3 * min_space], - [4 * min_space, 3 * min_space], - [4 * min_space, 0], - [7 * min_space, 0], - [7 * min_space, 4 * min_space], - [-1 * min_space, 4 * min_space], - [-1 * min_space, 0]] - position_list = [[x+min_space, y+min_space] for x,y in old_position_list] - w = design.design("wire_test2") - wire.wire(w, layer_stack, position_list) - self.local_drc_check(w) + layer_stacks = [tech.poly_stack] + tech.beol_stacks - min_space = 2 * (tech.drc["minwidth_metal2"] + - tech.drc["minwidth_metal1"]) - layer_stack = ("metal1", "via1", "metal2") - position_list = [[0, 0], - [0, 3 * min_space], - [1 * min_space, 3 * min_space], - [4 * min_space, 3 * min_space], - [4 * min_space, 0], - [7 * min_space, 0], - [7 * min_space, 4 * min_space], - [-1 * min_space, 4 * min_space], - [-1 * min_space, 0]] - w = design.design("wire_test3") - wire.wire(w, layer_stack, position_list) - self.local_drc_check(w) + for reverse in [False, True]: + for stack in layer_stacks: + if reverse: + layer_stack = stack[::-1] + else: + layer_stack = stack + # Just make a conservative spacing. Make it wire pitch instead? + min_space = 2 * (tech.drc["minwidth_{}".format(layer_stack[0])] + + tech.drc["minwidth_{}".format(layer_stack[2])]) - min_space = 2 * (tech.drc["minwidth_metal2"] + - tech.drc["minwidth_metal1"]) - layer_stack = ("metal2", "via1", "metal1") - position_list = [[0, 0], - [0, 3 * min_space], - [1 * min_space, 3 * min_space], - [4 * min_space, 3 * min_space], - [4 * min_space, 0], - [7 * min_space, 0], - [7 * min_space, 4 * min_space], - [-1 * min_space, 4 * min_space], - [-1 * min_space, 0]] - w = design.design("wire_test4") - wire.wire(w, layer_stack, position_list) - self.local_drc_check(w) - - min_space = 2 * (tech.drc["minwidth_metal2"] + - tech.drc["minwidth_metal3"]) - layer_stack = ("metal2", "via2", "metal3") - position_list = [[0, 0], - [0, 3 * min_space], - [1 * min_space, 3 * min_space], - [4 * min_space, 3 * min_space], - [4 * min_space, 0], - [7 * min_space, 0], - [7 * min_space, 4 * min_space], - [-1 * min_space, 4 * min_space], - [-1 * min_space, 0]] - position_list.reverse() - w = design.design("wire_test5") - wire.wire(w, layer_stack, position_list) - self.local_drc_check(w) - - min_space = 2 * (tech.drc["minwidth_metal2"] + - tech.drc["minwidth_metal3"]) - layer_stack = ("metal3", "via2", "metal2") - position_list = [[0, 0], - [0, 3 * min_space], - [1 * min_space, 3 * min_space], - [4 * min_space, 3 * min_space], - [4 * min_space, 0], - [7 * min_space, 0], - [7 * min_space, 4 * min_space], - [-1 * min_space, 4 * min_space], - [-1 * min_space, 0]] - position_list.reverse() - w = design.design("wire_test6") - wire.wire(w, layer_stack, position_list) - self.local_drc_check(w) + position_list = [[0, 0], + [0, 3 * min_space], + [1 * min_space, 3 * min_space], + [4 * min_space, 3 * min_space], + [4 * min_space, 0], + [7 * min_space, 0], + [7 * min_space, 4 * min_space], + [-1 * min_space, 4 * min_space], + [-1 * min_space, 0]] + position_list = [[x - min_space, y - min_space] for x, y in position_list] + w = design.design("wire_test_{}".format("_".join(layer_stack))) + wire.wire(w, layer_stack, position_list) + self.local_drc_check(w) globals.end_openram() diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index a8b9d263..c2bf135f 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -7,12 +7,37 @@ # import os from design_rules import * +from module_type import * +from custom_cell_properties import cell_properties """ File containing the process technology parameters for FreePDK 45nm. """ -#GDS file info +################################################### +# Custom modules +################################################### + +# This uses the default classes to instantiate module from +# '$OPENRAM_HOME/compiler/modules'. +# Using tech_modules['cellname'] you can override each class by providing a custom +# implementation in '$OPENRAM_TECHDIR/modules/' +# For example: tech_modules['contact'] = 'contact_freepdk45' +tech_modules = module_type() + + +################################################### +# Custom cell properties +################################################### +cell_properties = cell_properties() +cell_properties.bitcell.mirror.x = True +cell_properties.bitcell.mirror.y = False + + +################################################### +# GDS file info +################################################### + GDS = {} # gds units # From http://www.cnf.cornell.edu/cnf_spie9.html: "The first @@ -29,7 +54,40 @@ GDS["unit"] = (0.0005,1e-9) GDS["zoom"] = 0.05 ################################################### -##GDS Layer Map +# Interconnect stacks +################################################### + +poly_stack = ("poly", "contact", "m1") +active_stack = ("active", "contact", "m1") +m1_stack = ("m1", "via1", "m2") +m2_stack = ("m2", "via2", "m3") +m3_stack = ("m3", "via3", "m4") + +# The FEOL stacks get us up to m1 +feol_stacks = [poly_stack, + active_stack] + +# The BEOL stacks are m1 and up +beol_stacks = [m1_stack, + m2_stack, + m3_stack] + +layer_stacks = feol_stacks + beol_stacks + +preferred_directions = {"poly": "V", + "active": "V", + "m1": "H", + "m2": "V", + "m3": "H", + "m4": "V"} +################################################### +# Power grid +################################################### +# Use M3/M4 +power_grid = m3_stack + +################################################### +# GDS Layer Map ################################################### # create the GDS layer map @@ -44,36 +102,31 @@ layer["vtg"] = (6, 0) layer["vth"] = (7, 0) layer["thkox"] = (8, 0) layer["poly"] = (9, 0) -layer["active_contact"] = (10, 0) -layer["poly_contact"] = (10, 0) -layer["metal1"] = (11, 0) +layer["contact"] = (10, 0) +layer["m1"] = (11, 0) layer["via1"] = (12, 0) -layer["metal2"] = (13, 0) +layer["m2"] = (13, 0) layer["via2"] = (14, 0) -layer["metal3"] = (15, 0) +layer["m3"] = (15, 0) layer["via3"] = (16, 0) -layer["metal4"] = (17, 0) +layer["m4"] = (17, 0) layer["via4"] = (18, 0) -layer["metal5"] = (19, 0) +layer["m5"] = (19, 0) layer["via5"] = (20, 0) -layer["metal6"] = (21, 0) +layer["m6"] = (21, 0) layer["via6"] = (22, 0) -layer["metal7"] = (23, 0) +layer["m7"] = (23, 0) layer["via7"] = (24, 0) -layer["metal8"] = (25, 0) +layer["m8"] = (25, 0) layer["via8"] = (26, 0) -layer["metal9"] = (27, 0) +layer["m9"] = (27, 0) layer["via9"] = (28, 0) -layer["metal10"] = (29, 0) +layer["m10"] = (29, 0) layer["text"] = (239, 0) layer["boundary"]= (239, 0) ################################################### -##END GDS Layer Map -################################################### - -################################################### -##DRC/LVS Rules Setup +# DRC/LVS Rules Setup ################################################### #technology parameter @@ -105,20 +158,26 @@ drc["minlength_channel"] = 0.05 # WELL.2 Minimum spacing of nwell/pwell at different potential drc["pwell_to_nwell"] = 0.225 # WELL.3 Minimum spacing of nwell/pwell at the same potential -drc["well_to_well"] = 0.135 # WELL.4 Minimum width of nwell/pwell -drc["minwidth_well"] = 0.2 +drc.add_layer("nwell", + width = 0.2, + spacing = 0.135) +drc.add_layer("pwell", + width = 0.2, + spacing = 0.135) # POLY.1 Minimum width of poly -drc["minwidth_poly"] = 0.05 # POLY.2 Minimum spacing of poly AND active -drc["poly_to_poly"] = 0.14 +drc.add_layer("poly", + width = 0.05, + spacing = 0.14) + # POLY.3 Minimum poly extension beyond active drc["poly_extend_active"] = 0.055 # Not a rule -drc["poly_to_poly_contact"] = 0.075 +drc["poly_to_contact"] = 0.075 # POLY.4 Minimum enclosure of active around gate -drc["active_enclosure_gate"] = 0.07 +drc["active_enclose_gate"] = 0.07 # POLY.5 Minimum spacing of field poly to active drc["poly_to_active"] = 0.05 # POLY.6 Minimum Minimum spacing of field poly @@ -126,165 +185,169 @@ drc["poly_to_field_poly"] = 0.075 # Not a rule drc["minarea_poly"] = 0.0 -# ACTIVE.2 Minimum spacing of active -drc["active_to_body_active"] = 0.08 # ACTIVE.1 Minimum width of active -drc["minwidth_active"] = 0.09 -# Not a rule -drc["active_to_active"] = 0 +# ACTIVE.2 Minimum spacing of active +drc.add_layer("active", + width = 0.09, + spacing = 0.08) # ACTIVE.3 Minimum enclosure/spacing of nwell/pwell to active -drc["well_enclosure_active"] = 0.055 -# Reserved for asymmetric enclosures -drc["well_extend_active"] = 0.055 -# Not a rule -drc["minarea_active"] = 0 +drc.add_enclosure("nwell", + layer = "active", + enclosure = 0.055) +drc.add_enclosure("pwell", + layer = "active", + enclosure = 0.055) # IMPLANT.1 Minimum spacing of nimplant/ pimplant to channel drc["implant_to_channel"] = 0.07 # Not a rule -drc["implant_enclosure_active"] = 0 +drc.add_enclosure("implant", + layer = "active", + enclosure = 0) # Not a rule -drc["implant_enclosure_contact"] = 0 +drc.add_enclosure("implant", + layer = "contact", + enclosure = 0) # IMPLANT.2 Minimum spacing of nimplant/ pimplant to contact drc["implant_to_contact"] = 0.025 # IMPLANT.3 Minimum width/ spacing of nimplant/ pimplant -drc["implant_to_implant"] = 0.045 # IMPLANT.4 Minimum width/ spacing of nimplant/ pimplant -drc["minwidth_implant"] = 0.045 +drc.add_layer("implant", + width = 0.045, + spacing = 0.045) # CONTACT.1 Minimum width of contact -drc["minwidth_active_contact"] = 0.065 # CONTACT.2 Minimum spacing of contact -drc["active_contact_to_active_contact"] = 0.075 +drc.add_layer("contact", + width = 0.065, + spacing = 0.075) # CONTACT.4 Minimum enclosure of active around contact -drc["active_enclosure_active_contact"] = 0.005 -# Reserved for asymmetric enclosures -drc["active_extend_active_contact"] = 0.005 +drc.add_enclosure("active", + layer = "contact", + enclosure = 0.005) + # CONTACT.6 Minimum spacing of contact and gate -drc["active_contact_to_gate"] = 0.0375 #changed from 0.035 +drc["contact_to_gate"] = 0.0375 #changed from 0.035 # CONTACT.7 Minimum spacing of contact and poly -drc["active_contact_to_poly"] = 0.090 +drc["contact_to_poly"] = 0.090 # CONTACT.1 Minimum width of contact -drc["minwidth_poly_contact"] = 0.065 # CONTACT.2 Minimum spacing of contact -drc["poly_contact_to_poly_contact"] = 0.075 -# Reserved for asymmetric enclosures -drc["poly_extend_contact"] = 0.005 +drc.add_layer("contact", + width = 0.065, + spacing = 0.075) # CONTACT.5 Minimum enclosure of poly around contact -drc["poly_enclosure_poly_contact"] = 0.005 -# Reserved for asymmetric enclosures -drc["poly_extend_poly_contact"] = 0.005 +drc.add_enclosure("poly", + layer = "contact", + enclosure = 0.005) # CONTACT.6 Minimum spacing of contact and gate -drc["poly_contact_to_gate"] = 0.0375 #changed from 0.035 +drc["contact_to_gate"] = 0.0375 #changed from 0.035 # CONTACT.7 Minimum spacing of contact and poly -drc["poly_contact_to_poly"] = 0.090 +drc["contact_to_poly"] = 0.090 # METAL1.1 Minimum width of metal1 -drc["minwidth_metal1"] = 0.065 # METAL1.2 Minimum spacing of metal1 -drc["metal1_to_metal1"] = 0.065 +drc.add_layer("m1", + width = 0.065, + spacing = 0.065) + # METAL1.3 Minimum enclosure around contact on two opposite sides -drc["metal1_enclosure_active_contact"] = 0 -# Reserved for asymmetric enclosures -drc["metal1_extend_active_contact"] = 0.035 -# METAL1.3 Minimum enclosure around contact on two opposite sides -drc["metal1_enclosure_poly_contact"] = 0 -# Reserved for asymmetric enclosures -drc["metal1_extend_poly_contact"] = 0.035 +drc.add_enclosure("m1", + layer = "contact", + enclosure = 0, + extension = 0.035) # METAL1.4 inimum enclosure around via1 on two opposite sides -drc["metal1_extend_via1"] = 0.035 -# Reserved for asymmetric enclosures -drc["metal1_enclosure_via1"] = 0 -# Not a rule -drc["minarea_metal1"] = 0 +drc.add_enclosure("m1", + layer = "via1", + enclosure = 0, + extension = 0.035) # VIA1.1 Minimum width of via1 -drc["minwidth_via1"] = 0.065 # VIA1.2 Minimum spacing of via1 -drc["via1_to_via1"] = 0.075 +drc.add_layer("via1", + width = 0.065, + spacing = 0.075) + # METALINT.1 Minimum width of intermediate metal -drc["minwidth_metal2"] = 0.07 # METALINT.2 Minimum spacing of intermediate metal -drc["metal2_to_metal2"] = 0.07 +drc.add_layer("m2", + width = 0.07, + spacing = 0.07) + # METALINT.3 Minimum enclosure around via1 on two opposite sides -drc["metal2_extend_via1"] = 0.035 -# Reserved for asymmetric enclosures -drc["metal2_enclosure_via1"] = 0 +drc.add_enclosure("m2", + layer = "via1", + enclosure = 0, + extension = 0.035) + # METALINT.4 Minimum enclosure around via[2-3] on two opposite sides -drc["metal2_extend_via2"] = 0.035 -# Reserved for asymmetric enclosures -drc["metal2_enclosure_via2"] = 0 -# Not a rule -drc["minarea_metal2"] = 0 +drc.add_enclosure("m2", + layer = "via2", + enclosure = 0, + extension = 0.035) # VIA2-3.1 Minimum width of Via[2-3] -drc["minwidth_via2"] = 0.065 # VIA2-3.2 Minimum spacing of Via[2-3] -drc["via2_to_via2"] = 0.075 +drc.add_layer("via2", + width = 0.065, + spacing = 0.075) # METALINT.1 Minimum width of intermediate metal -drc["minwidth_metal3"] = 0.07 # METALINT.2 Minimum spacing of intermediate metal -#drc["metal3_to_metal3"] = 0.07 -# Minimum spacing of metal3 wider than 0.09 & longer than 0.3 = 0.09 -# Minimum spacing of metal3 wider than 0.27 & longer than 0.9 = 0.27 -# Minimum spacing of metal3 wider than 0.5 & longer than 1.8 = 0.5 -# Minimum spacing of metal3 wider than 0.9 & longer than 2.7 = 0.9 -# Minimum spacing of metal3 wider than 1.5 & longer than 4.0 = 1.5 -drc["metal3_to_metal3"] = drc_lut({(0.00, 0.0) : 0.07, - (0.09, 0.3) : 0.09, - (0.27, 0.9) : 0.27, - (0.50, 1.8) : 0.5, - (0.90, 2.7) : 0.9, - (1.50, 4.0) : 1.5}) +# Minimum spacing of m3 wider than 0.09 & longer than 0.3 = 0.09 +# Minimum spacing of m3 wider than 0.27 & longer than 0.9 = 0.27 +# Minimum spacing of m3 wider than 0.5 & longer than 1.8 = 0.5 +# Minimum spacing of m3 wider than 0.9 & longer than 2.7 = 0.9 +# Minimum spacing of m3 wider than 1.5 & longer than 4.0 = 1.5 +drc.add_layer("m3", + width = 0.07, + spacing = drc_lut({(0.00, 0.0) : 0.07, + (0.09, 0.3) : 0.09, + (0.27, 0.9) : 0.27, + (0.50, 1.8) : 0.5, + (0.90, 2.7) : 0.9, + (1.50, 4.0) : 1.5})) # METALINT.3 Minimum enclosure around via1 on two opposite sides -drc["metal3_extend_via2"] = 0.035 -# Reserved for asymmetric enclosures -drc["metal3_enclosure_via2"] = 0 +drc.add_enclosure("m3", + layer = "via2", + enclosure = 0, + extension = 0.035) + # METALINT.4 Minimum enclosure around via[2-3] on two opposite sides -drc["metal3_extend_via3"]=0.035 -# Reserved for asymmetric enclosures -drc["metal3_enclosure_via3"] = 0 -# Not a rule -drc["minarea_metal3"] = 0 +drc.add_enclosure("m3", + layer = "via3", + enclosure = 0, + extension = 0.035) # VIA2-3.1 Minimum width of Via[2-3] -drc["minwidth_via3"] = 0.07 # VIA2-3.2 Minimum spacing of Via[2-3] -drc["via3_to_via3"] = 0.085 +drc.add_layer("via3", + width = 0.07, + spacing = 0.085) # METALSMG.1 Minimum width of semi-global metal -drc["minwidth_metal4"] = 0.14 # METALSMG.2 Minimum spacing of semi-global metal -#drc["metal4_to_metal4"] = 0.14 -# Minimum spacing of metal4 wider than 0.27 & longer than 0.9 = 0.27 -# Minimum spacing of metal4 wider than 0.5 & longer than 1.8 = 0.5 -# Minimum spacing of metal4 wider than 0.9 & longer than 2.7 = 0.9 -# Minimum spacing of metal4 wider than 1.5 & longer than 4.0 = 1.5 -drc["metal4_to_metal4"] = drc_lut({(0.00, 0.0) : 0.14, - (0.27, 0.9) : 0.27, - (0.50, 1.8) : 0.5, - (0.90, 2.7) : 0.9, - (1.50, 4.0) : 1.5}) +# Minimum spacing of m4 wider than 0.27 & longer than 0.9 = 0.27 +# Minimum spacing of m4 wider than 0.5 & longer than 1.8 = 0.5 +# Minimum spacing of m4 wider than 0.9 & longer than 2.7 = 0.9 +# Minimum spacing of m4 wider than 1.5 & longer than 4.0 = 1.5 +drc.add_layer("m4", + width = 0.14, + spacing = drc_lut({(0.00, 0.0) : 0.14, + (0.27, 0.9) : 0.27, + (0.50, 1.8) : 0.5, + (0.90, 2.7) : 0.9, + (1.50, 4.0) : 1.5})) # METALSMG.3 Minimum enclosure around via[3-6] on two opposite sides -drc["metal4_extend_via3"] = 0.0025 -# Reserved for asymmetric enclosure -drc["metal4_enclosure_via3"] = 0.0025 -# Not a rule -drc["minarea_metal4"] = 0 +drc.add_enclosure("m4", + layer = "via3", + enclosure = 0.0025) # Metal 5-10 are ommitted - - ################################################### -##END DRC/LVS Rules -################################################### - -################################################### -##Spice Simulation Parameters +# Spice Simulation Parameters ################################################### #spice info @@ -347,11 +410,7 @@ parameter["sa_inv_nmos_size"] = 0.27 #micro-meters parameter["bitcell_drain_cap"] = 0.1 #In Femto-Farad, approximation of drain capacitance ################################################### -##END Spice Simulation Parameters -################################################### - -################################################### -##BEGIN Technology Tool Preferences +# Technology Tool Preferences ################################################### drc_name = "calibre" @@ -359,7 +418,3 @@ lvs_name = "calibre" pex_name = "calibre" blackbox_bitcell = False - -################################################### -##END Technology Tool Preferences -################################################### diff --git a/technology/scn3me_subm/tech/tech.py b/technology/scn3me_subm/tech/tech.py index 6fc68686..9e6f370b 100755 --- a/technology/scn3me_subm/tech/tech.py +++ b/technology/scn3me_subm/tech/tech.py @@ -1,9 +1,24 @@ import os from design_rules import * +from module_type import * +from custom_cell_properties import CellProperties """ File containing the process technology parameters for SCMOS 3me, subm, 180nm. """ +# This uses the default classes to instantiate module from +# '$OPENRAM_HOME/compiler/modules'. +# Using tech_modules['cellname'] you can override each class by providing a custom +# implementation in '$OPENRAM_TECHDIR/modules/' +# For example: tech_modules['contact'] = 'contact_scn3me' +tech_modules = ModuleType() + +################################################### +# Custom cell properties +################################################### +cell_properties = CellProperties() +cell_properties.bitcell.mirror.x = True +cell_properties.bitcell.mirror.y = False #GDS file info GDS={} diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py index 8e189efd..7c360d38 100644 --- a/technology/scn4m_subm/tech/tech.py +++ b/technology/scn4m_subm/tech/tech.py @@ -7,12 +7,34 @@ # import os from design_rules import * +from module_type import * +from custom_cell_properties import cell_properties """ File containing the process technology parameters for SCMOS 4m, 0.35um """ -#GDS file info +################################################### +# Custom modules +################################################### + +# This uses the default classes to instantiate module from +# '$OPENRAM_HOME/compiler/modules'. +# Using tech_modules['cellname'] you can override each class by providing a custom +# implementation in '$OPENRAM_TECHDIR/modules/' +# For example: tech_modules['contact'] = 'contact_scn4m' +tech_modules = module_type() + +################################################### +# Custom cell properties +################################################### +cell_properties = cell_properties() +cell_properties.bitcell.mirror.x = True +cell_properties.bitcell.mirror.y = False + +################################################### +# GDS file info +################################################### GDS={} # gds units # From http://www.cnf.cornell.edu/cnf_spie9.html: "The first @@ -28,6 +50,39 @@ GDS["unit"]=(0.001,1e-6) # default label zoom GDS["zoom"] = 0.5 +################################################### +# Interconnect stacks +################################################### + +poly_stack = ("poly", "poly_contact", "m1") +active_stack = ("active", "active_contact", "m1") +m1_stack = ("m1", "via1", "m2") +m2_stack = ("m2", "via2", "m3") +m3_stack = ("m3", "via3", "m4") + +# The FEOL stacks get us up to m1 +feol_stacks = [poly_stack, + active_stack] + +# The BEOL stacks are m1 and up +beol_stacks = [m1_stack, + m2_stack, + m3_stack] + +layer_stacks = feol_stacks + beol_stacks + +preferred_directions = {"poly": "V", + "active": "V", + "m1": "H", + "m2": "V", + "m3": "H", + "m4": "V"} + +################################################### +# Power grid +################################################### +# Use M3/M4 +power_grid = m3_stack ################################################### ##GDS Layer Map @@ -35,32 +90,26 @@ GDS["zoom"] = 0.5 # create the GDS layer map layer={} -layer["vtg"] = None -layer["vth"] = None layer["pwell"] = (41, 0) layer["nwell"] = (42, 0) layer["active"] = (43, 0) layer["pimplant"] = (44, 0) layer["nimplant"] = (45, 0) layer["poly"] = (46, 0) -layer["active_contact"] = (48, 0) layer["poly_contact"] = (47, 0) -layer["metal1"] = (49, 0) +layer["active_contact"] = (48, 0) +layer["m1"] = (49, 0) layer["via1"] = (50, 0) -layer["metal2"] = (51, 0) +layer["m2"] = (51, 0) layer["via2"] = (61, 0) -layer["metal3"] = (62, 0) +layer["m3"] = (62, 0) layer["via3"] = (30, 0) -layer["metal4"] = (31, 0) +layer["m4"] = (31, 0) layer["text"] = (63, 0) layer["boundary"] = (63, 0) ################################################### -##END GDS Layer Map -################################################### - -################################################### -##DRC/LVS Rules Setup +# DRC/LVS Rules Setup ################################################### _lambda_ = 0.2 @@ -90,163 +139,173 @@ drc["layer_map"]=os.environ.get("OPENRAM_TECH")+"/scn3me_subm/layers.map" drc["minwidth_tx"] = 4*_lambda_ drc["minlength_channel"] = 2*_lambda_ -# 1.3 Minimum spacing between wells of same type (if both are drawn) -drc["well_to_well"] = 6*_lambda_ -# 1.4 Minimum spacing between wells of different type (if both are drawn) +# 1.4 Minimum spacing between wells of different type (if both are drawn) drc["pwell_to_nwell"] = 0 -# 1.1 Minimum width -drc["minwidth_well"] = 12*_lambda_ +# 1.3 Minimum spacing between wells of same type (if both are drawn) +# 1.1 Minimum width +drc.add_layer("nwell", + width = 12*_lambda_, + spacing = 6*_lambda_) +drc.add_layer("pwell", + width = 12*_lambda_, + spacing = 6*_lambda_) # 3.1 Minimum width -drc["minwidth_poly"] = 2*_lambda_ # 3.2 Minimum spacing over active -drc["poly_to_poly"] = 3*_lambda_ +drc.add_layer("poly", + width = 2*_lambda_, + spacing = 3*_lambda_) # 3.3 Minimum gate extension of active drc["poly_extend_active"] = 2*_lambda_ # 5.5.b Minimum spacing between poly contact and other poly (alternative rules) -drc["poly_to_poly_contact"] = 4*_lambda_ +drc["poly_to_contact"] = 4*_lambda_ # ?? -drc["active_enclosure_gate"] = 0.0 +drc["active_enclose_gate"] = 0.0 # 3.5 Minimum field poly to active drc["poly_to_active"] = _lambda_ # 3.2.a Minimum spacing over field poly drc["poly_to_field_poly"] = 3*_lambda_ -# Not a rule -drc["minarea_poly"] = 0.0 -# ?? -drc["active_to_body_active"] = 4*_lambda_ # Fix me # 2.1 Minimum width -drc["minwidth_active"] = 3*_lambda_ # 2.2 Minimum spacing -drc["active_to_active"] = 3*_lambda_ +drc.add_layer("active", + width = 3*_lambda_, + spacing = 4*_lambda_) + # 2.3 Source/drain active to well edge -drc["well_enclosure_active"] = 6*_lambda_ -# Reserved for asymmetric enclosures -drc["well_extend_active"] = 6*_lambda_ -# Not a rule -drc["minarea_active"] = 0.0 +drc.add_enclosure("nwell", + layer = "active", + enclosure = 6*_lambda_) +drc.add_enclosure("pwell", + layer = "active", + enclosure = 6*_lambda_) # 4.1 Minimum select spacing to channel of transistor to ensure adequate source/drain width drc["implant_to_channel"] = 3*_lambda_ # 4.2 Minimum select overlap of active -drc["implant_enclosure_active"] = 2*_lambda_ +drc.add_enclosure("implant", + layer = "active", + enclosure = 2*_lambda_) # 4.3 Minimum select overlap of contact -drc["implant_enclosure_contact"] = _lambda_ +drc.add_enclosure("implant", + layer = "contact", + enclosure = _lambda_) # Not a rule drc["implant_to_contact"] = 0 # Not a rule -drc["implant_to_implant"] = 0 -# Not a rule -drc["minwidth_implant"] = 0 +drc.add_layer("implant", + width = 0, + spacing = 0) # 6.1 Exact contact size -drc["minwidth_active_contact"] = 2*_lambda_ # 5.3 Minimum contact spacing -drc["active_contact_to_active_contact"] = 3*_lambda_ -# 6.2.b Minimum active overlap -drc["active_enclosure_active_contact"] = _lambda_ -# Reserved for asymmetric enclosure -drc["active_extend_active_contact"] = _lambda_ +drc.add_layer("active_contact", + width = 2*_lambda_, + spacing = 3*_lambda_) +# 6.2.b Minimum active overlap +drc.add_enclosure("active", + layer = "active_contact", + enclosure = _lambda_) +drc.add_enclosure("active", + layer = "contact", + enclosure = _lambda_) # Reserved for other technologies -drc["active_contact_to_gate"] = 2*_lambda_ +drc["contact_to_gate"] = 2*_lambda_ # 5.4 Minimum spacing to gate of transistor -drc["active_contact_to_poly"] = 2*_lambda_ +drc["contact_to_poly"] = 2*_lambda_ # 6.1 Exact contact size -drc["minwidth_poly_contact"] = 2*_lambda_ # 5.3 Minimum contact spacing -drc["poly_contact_to_poly_contact"] = 3*_lambda_ +drc.add_layer("poly_contact", + width = 2*_lambda_, + spacing = 3*_lambda_) # 5.2.b Minimum poly overlap -drc["poly_enclosure_poly_contact"] = _lambda_ -# Reserved for asymmetric enclosures -drc["poly_extend_poly_contact"] = _lambda_ +drc.add_enclosure("poly", + layer = "poly_contact", + enclosure = _lambda_) # Reserved for other technologies drc["poly_contact_to_gate"] = 2*_lambda_ # 5.4 Minimum spacing to gate of transistor drc["poly_contact_to_poly"] = 2*_lambda_ # 7.1 Minimum width -drc["minwidth_metal1"] = 3*_lambda_ # 7.2 Minimum spacing -drc["metal1_to_metal1"] = 3*_lambda_ +drc.add_layer("m1", + width = 3*_lambda_, + spacing = 3*_lambda_) # 7.3 Minimum overlap of any contact -drc["metal1_enclosure_active_contact"] = _lambda_ -# Reserved for asymmetric enclosure -drc["metal1_extend_active_contact"] = _lambda_ -# 7.3 Minimum overlap of any contact -drc["metal1_enclosure_poly_contact"] = _lambda_ -# Reserved for asymmetric enclosure -drc["metal1_extend_poly_contact"] = _lambda_ -# 8.3 Minimum overlap by metal1 -drc["metal1_enclosure_via1"] = _lambda_ -# Reserve for asymmetric enclosures -drc["metal1_extend_via1"] = _lambda_ -# Not a rule -drc["minarea_metal1"] = 0 +drc.add_enclosure("m1", + layer = "poly_contact", + enclosure = _lambda_) +drc.add_enclosure("m1", + layer = "active_contact", + enclosure = _lambda_) +# 8.3 Minimum overlap by m1 +drc.add_enclosure("m1", + layer = "via1", + enclosure = _lambda_) # 8.1 Exact size -drc["minwidth_via1"] = 2*_lambda_ # 8.2 Minimum via1 spacing -drc["via1_to_via1"] = 3*_lambda_ +drc.add_layer("via1", + width = 2*_lambda_, + spacing = 3*_lambda_) # 9.1 Minimum width -drc["minwidth_metal2"] = 3*_lambda_ # 9.2 Minimum spacing -drc["metal2_to_metal2"] = 3*_lambda_ +drc.add_layer("m2", + width = 3*_lambda_, + spacing = 3*_lambda_) # 9.3 Minimum overlap of via1 -drc["metal2_extend_via1"] = _lambda_ -# Reserved for asymmetric enclosures -drc["metal2_enclosure_via1"] = _lambda_ -# 14.3 Minimum overlap by metal2 -drc["metal2_extend_via2"] = _lambda_ -# Reserved for asymmetric enclosures -drc["metal2_enclosure_via2"] = _lambda_ -# Not a rule -drc["minarea_metal2"] = 0 +drc.add_enclosure("m2", + layer = "via1", + enclosure = _lambda_) +# 14.3 Minimum overlap by m2 +drc.add_enclosure("m2", + layer = "via2", + enclosure = _lambda_) # 14.1 Exact size -drc["minwidth_via2"] = 2*_lambda_ # 14.2 Minimum spacing -drc["via2_to_via2"] = 3*_lambda_ +drc.add_layer("via2", + width = 2*_lambda_, + spacing = 3*_lambda_) # 15.1 Minimum width -drc["minwidth_metal3"] = 3*_lambda_ -# 15.2 Minimum spacing to metal3 -drc["metal3_to_metal3"] = 3*_lambda_ +# 15.2 Minimum spacing to m3 +drc.add_layer("m3", + width = 3*_lambda_, + spacing = 3*_lambda_) + # 15.3 Minimum overlap of via 2 -drc["metal3_extend_via2"] = _lambda_ -# Reserved for asymmetric enclosures -drc["metal3_enclosure_via2"] = _lambda_ -# 21.3 Minimum overlap by metal3 -drc["metal3_extend_via3"] = _lambda_ -# Reserved for asymmetric enclosures -drc["metal3_enclosure_via3"] = _lambda_ -# Not a rule -drc["minarea_metal3"] = 0 +drc.add_enclosure("m3", + layer = "via2", + enclosure = _lambda_) + +# 21.3 Minimum overlap by m3 +drc.add_enclosure("m3", + layer = "via3", + enclosure = _lambda_) # 21.1 Exact size -drc["minwidth_via3"] = 2*_lambda_ # 21.2 Minimum spacing -drc["via3_to_via3"] = 3*_lambda_ +drc.add_layer("via3", + width = 2*_lambda_, + spacing = 3*_lambda_) # 22.1 Minimum width -drc["minwidth_metal4"] = 6*_lambda_ -# 22.2 Minimum spacing to metal4 -drc["metal4_to_metal4"] = 6*_lambda_ +# 22.2 Minimum spacing to m4 +drc.add_layer("m4", + width = 6*_lambda_, + spacing = 6*_lambda_) + # 22.3 Minimum overlap of via 3 -drc["metal4_extend_via3"] = 2*_lambda_ -# Reserved for asymmetric enclosures -drc["metal4_enclosure_via3"] = 2*_lambda_ -# Not a rule -drc["minarea_metal4"] = 0 +drc.add_enclosure("m4", + layer = "via3", + enclosure = 2*_lambda_) ################################################### -##END DRC/LVS Rules -################################################### - -################################################### -##Spice Simulation Parameters +# Spice Simulation Parameters ################################################### # spice model info @@ -311,11 +370,7 @@ parameter["sa_inv_nmos_size"] = 9*_lambda_ parameter["bitcell_drain_cap"] = 0.2 #In Femto-Farad, approximation of drain capacitance ################################################### -##END Spice Simulation Parameters -################################################### - -################################################### -##BEGIN Technology Tool Preferences +# Technology Tool Preferences ################################################### drc_name = "magic" @@ -323,7 +378,3 @@ lvs_name = "netgen" pex_name = "magic" blackbox_bitcell = False - -################################################### -##END Technology Tool Preferences -###################################################