diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 27c341aa..27431cb2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ before_script: - . /home/gitlab-runner/setup-paths.sh - export OPENRAM_HOME="`pwd`/compiler" - - export OPENRAM_TECH="`pwd`/technology" + - export OPENRAM_TECH="`pwd`/technology:/home/PDKs/skywater-tech" stages: - test @@ -25,6 +25,15 @@ scn4m_subm: - .coverage.* expire_in: 1 week +# s8: +# stage: test +# script: +# - coverage run -p $OPENRAM_HOME/tests/regress.py -t s8 +# artifacts: +# paths: +# - .coverage.* +# expire_in: 1 week + coverage: stage: coverage script: diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py new file mode 100644 index 00000000..cd4bbdc1 --- /dev/null +++ b/compiler/base/channel_route.py @@ -0,0 +1,309 @@ +# 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 collections +import debug +from tech import drc +from vector import vector +import design + + +class channel_route(design.design): + + unique_id = 0 + + def __init__(self, + netlist, + offset, + layer_stack, + directions=None, + vertical=False): + """ + The net list is a list of the nets with each net being a list of pins + to be connected. The offset is the lower-left of where the + routing channel will start. This does NOT try to minimize the + number of tracks -- instead, it picks an order to avoid the + vertical conflicts between pins. The track size must be the number of + nets times the *nonpreferred* routing of the non-track layer pitch. + + """ + name = "cr_{0}".format(channel_route.unique_id) + channel_route.unique_id += 1 + design.design.__init__(self, name) + + self.netlist = netlist + self.offset = offset + self.layer_stack = layer_stack + self.directions = directions + self.vertical = vertical + + if not directions or directions == "pref": + # Use the preferred layer directions + 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] + elif directions == "nonpref": + # Use the preferred layer directions + if self.get_preferred_direction(layer_stack[0]) == "V": + self.vertical_layer = layer_stack[2] + self.horizontal_layer = layer_stack[0] + else: + self.vertical_layer = layer_stack[0] + self.horizontal_layer = layer_stack[2] + else: + # Use the layer directions specified to the router rather than + # the preferred directions + debug.check(directions[0] != directions[1], "Must have unique layer directions.") + if directions[0] == "V": + self.vertical_layer = layer_stack[0] + self.horizontal_layer = layer_stack[2] + else: + self.horizontal_layer = layer_stack[0] + self.vertical_layer = layer_stack[2] + + layer_stuff = self.get_layer_pitch(self.vertical_layer) + (self.vertical_nonpref_pitch, self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff + + layer_stuff = self.get_layer_pitch(self.horizontal_layer) + (self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff + + self.route() + + def remove_net_from_graph(self, pin, g): + """ + Remove the pin from the graph and all conflicts + """ + g.pop(pin, None) + + # Remove the pin from all conflicts + # FIXME: This is O(n^2), so maybe optimize it. + for other_pin, conflicts in g.items(): + if pin in conflicts: + conflicts.remove(pin) + g[other_pin]=conflicts + return g + + def vcg_nets_overlap(self, net1, net2): + """ + Check all the pin pairs on two nets and return a pin + overlap if any pin overlaps. + """ + + if self.vertical: + pitch = self.horizontal_nonpref_pitch + else: + pitch = self.vertical_nonpref_pitch + + for pin1 in net1: + for pin2 in net2: + if self.vcg_pin_overlap(pin1, pin2, pitch): + return True + + return False + + def route(self): + # FIXME: Must extend this to a horizontal conflict graph + # too if we want to minimize the + # number of tracks! + # hcg = {} + + # 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) + for pin_list in self.netlist: + net_name = "n{}".format(index) + index += 1 + nets[net_name] = pin_list + + # print("Nets:") + # for net_name in nets: + # print(net_name, [x.name for x in nets[net_name]]) + + # 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] = [] + for net_name2 in nets: + if net_name2 not in vcg.keys(): + vcg[net_name2] = [] + # Skip yourself + if net_name1 == net_name2: + continue + if self.vcg_nets_overlap(nets[net_name1], + nets[net_name2]): + vcg[net_name2].append(net_name1) + + current_offset = self.offset + + # list of routes to do + while 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 = self.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) + + # 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]) + + # Remove the net from other constriants in the VCG + vcg = self.remove_net_from_graph(net_name, vcg) + + # Add the trunk routes from the bottom up for + # horizontal or the left to right for vertical + if self.vertical: + self.add_vertical_trunk_route(pin_list, + current_offset, + self.vertical_nonpref_pitch) + # This accounts for the via-to-via spacings + current_offset += vector(self.horizontal_nonpref_pitch, 0) + else: + self.add_horizontal_trunk_route(pin_list, + current_offset, + self.horizontal_nonpref_pitch) + # This accounts for the via-to-via spacings + current_offset += vector(0, self.vertical_nonpref_pitch) + + # Return the size of the channel + if self.vertical: + self.width = 0 + self.height = current_offset.y + return current_offset.y + self.vertical_nonpref_pitch - self.offset.y + else: + self.width = current_offset.x + self.height = 0 + return current_offset.x + self.horizontal_nonpref_pitch - self.offset.x + + def get_layer_pitch(self, layer): + """ Return the track pitch on a given layer """ + try: + # FIXME: Using non-pref pitch here due to overlap bug in VCG constraints. + # It should just result in inefficient channel width but will work. + pitch = getattr(self, "{}_pitch".format(layer)) + nonpref_pitch = getattr(self, "{}_nonpref_pitch".format(layer)) + space = getattr(self, "{}_space".format(layer)) + except AttributeError: + debug.error("Cannot find layer pitch.", -1) + return (nonpref_pitch, pitch, pitch - space, space) + + def add_horizontal_trunk_route(self, + pins, + trunk_offset, + pitch): + """ + 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 + non_preferred_route = max_x - min_x <= pitch + + if non_preferred_route: + 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)]) + else: + # Add the horizontal trunk + 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: + # Find the correct side of the pin + if pin.cy() < trunk_offset.y: + pin_pos = pin.uc() + else: + pin_pos = pin.bc() + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + if not non_preferred_route: + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin_pos) + + def add_vertical_trunk_route(self, + pins, + trunk_offset, + pitch): + """ + 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]) + + # if we are less than a pitch, just create a non-preferred layer jog + non_preferred_route = max_y - min_y <= pitch + + if non_preferred_route: + 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)]) + else: + # Add the vertical trunk + 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: + # Find the correct side of the pin + if pin.cx() < trunk_offset.x: + pin_pos = pin.rc() + else: + pin_pos = pin.lc() + mid = vector(trunk_offset.x, pin_pos.y) + self.add_path(self.horizontal_layer, [pin_pos, mid]) + if not non_preferred_route: + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.horizontal_layer, + offset=pin_pos) + + def vcg_pin_overlap(self, pin1, pin2, 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 extra comparison *shouldn't* matter. + + # Pin 1 must be in the "BOTTOM" set + x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch + + # Pin 1 must be in the "LEFT" set + y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch + overlaps = (not self.vertical and x_overlap) or (self.vertical and y_overlap) + return overlaps + diff --git a/compiler/base/contact.py b/compiler/base/contact.py index f4fda552..cc1ca27a 100644 --- a/compiler/base/contact.py +++ b/compiler/base/contact.py @@ -7,7 +7,7 @@ # import hierarchy_design import debug -from tech import drc +from tech import drc, layer import tech from vector import vector from sram_factory import factory @@ -44,7 +44,8 @@ class contact(hierarchy_design.hierarchy_design): self.add_comment("well_type: {}\n".format(well_type)) self.is_well_contact = implant_type == well_type - + + # If we have a special tap layer, use it self.layer_stack = layer_stack self.dimensions = dimensions @@ -53,6 +54,10 @@ class contact(hierarchy_design.hierarchy_design): first_dir = "H" if self.get_preferred_direction(layer_stack[0])=="V" else "V" second_dir = "H" if self.get_preferred_direction(layer_stack[2])=="V" else "V" self.directions = (first_dir, second_dir) + # Preferred directions + elif directions == "pref": + self.directions = (tech.preferred_directions[layer_stack[0]], + tech.preferred_directions[layer_stack[2]]) # User directions elif directions: self.directions = directions @@ -149,7 +154,7 @@ class contact(hierarchy_design.hierarchy_design): 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) + debug.error("Invalid first layer direction: ".format(self.directions[0]), -1) # In some technologies, the minimum width may be larger # than the overlap requirement around the via, so @@ -165,7 +170,7 @@ class contact(hierarchy_design.hierarchy_design): 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) + debug.error("Invalid secon layer direction: ".format(self.directions[1]), -1) def create_contact_array(self): """ Create the contact array at the origin""" @@ -192,17 +197,19 @@ class contact(hierarchy_design.hierarchy_design): if "npc" not in tech.layer: return + npc_enclose_poly = drc("npc_enclose_poly") + npc_enclose_offset = vector(npc_enclose_poly, npc_enclose_poly) # 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) + offset=self.first_layer_position - npc_enclose_offset, + width=self.first_layer_width + 2 * npc_enclose_poly, + height=self.first_layer_height + 2 * npc_enclose_poly) 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) + offset=self.second_layer_position - npc_enclose_offset, + width=self.second_layer_width + 2 * npc_enclose_poly, + height=self.second_layer_height + 2 * npc_enclose_poly) def create_first_layer_enclosure(self): # this is if the first and second layers are different @@ -214,7 +221,11 @@ class contact(hierarchy_design.hierarchy_design): 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, + if self.is_well_contact and self.first_layer_name == "active" and "tap" in layer: + first_layer_name = "tap" + else: + first_layer_name = self.first_layer_name + self.add_rect(layer=first_layer_name, offset=self.first_layer_position, width=self.first_layer_width, height=self.first_layer_height) diff --git a/compiler/base/design.py b/compiler/base/design.py index 7a570b28..2b2d7711 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -172,6 +172,20 @@ class design(hierarchy_design): 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) + + # The active offset is due to the well extension + if "pwell" in layer: + self.pwell_enclose_active = drc("pwell_enclose_active") + else: + self.pwell_enclose_active = 0 + if "nwell" in layer: + self.nwell_enclose_active = drc("nwell_enclose_active") + else: + self.nwell_enclose_active = 0 + # Use the max of either so that the poly gates will align properly + self.well_enclose_active = max(self.pwell_enclose_active, + self.nwell_enclose_active, + self.active_space) # These are for debugging previous manual rules if False: @@ -191,7 +205,8 @@ class design(hierarchy_design): 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("active_contact_to_gate", self.active_contact_to_gate) + print("poly_contact_to_gate", self.poly_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) diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index cfcfa73a..32af7ee9 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -66,14 +66,17 @@ class geometry: self.compute_boundary(self.offset, self.mirror, self.rotate) def compute_boundary(self, offset=vector(0, 0), mirror="", rotate=0): - """ 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. """ + """ + 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)] + self.boundary = [vector(0, 0), vector(0, 0)] return (ll, ur) = [vector(0, 0), vector(self.width, self.height)] + # Mirroring is performed before rotation if mirror == "MX": ll = ll.scale(1, -1) ur = ur.scale(1, -1) @@ -83,8 +86,14 @@ class geometry: elif mirror == "XY": ll = ll.scale(-1, -1) ur = ur.scale(-1, -1) + elif mirror == "" or mirror == "R0": + pass + else: + debug.error("Invalid mirroring: {}".format(mirror), -1) - if rotate == 90: + if rotate == 0: + pass + elif rotate == 90: ll = ll.rotate_scale(-1, 1) ur = ur.rotate_scale(-1, 1) elif rotate == 180: @@ -93,6 +102,8 @@ class geometry: elif rotate == 270: ll = ll.rotate_scale(1, -1) ur = ur.rotate_scale(1, -1) + else: + debug.error("Invalid rotation: {}".format(rotate), -1) self.boundary = [offset + ll, offset + ur] self.normalize() @@ -136,6 +147,10 @@ class geometry: def cy(self): """ Return the center y """ return 0.5 * (self.boundary[0].y + self.boundary[1].y) + + def center(self): + """ Return the center coordinate """ + return vector(self.cx(), self.cy()) class instance(geometry): @@ -195,14 +210,13 @@ class instance(geometry): blockages = [] blockages = self.mod.gds.getBlockages(lpp) for b in blockages: - new_blockages.append(self.transform_coords(b,self.offset, mirr, angle)) + new_blockages.append(self.transform_coords(b, self.offset, mirr, angle)) else: blockages = self.mod.get_blockages(lpp) for b in blockages: - new_blockages.append(self.transform_coords(b,self.offset, mirr, angle)) + new_blockages.append(self.transform_coords(b, self.offset, mirr, angle)) return new_blockages - def gds_write_file(self, new_layout): """Recursively writes all the sub-modules in this instance""" debug.info(4, "writing instance: " + self.name) @@ -225,26 +239,25 @@ class instance(geometry): self.update_boundary() debug.info(3, "placing instance {}".format(self)) - - def get_pin(self,name,index=-1): + def get_pin(self, name, index=-1): """ Return an absolute pin that is offset and transformed based on this instance location. Index will return one of several pins.""" import copy if index == -1: pin = copy.deepcopy(self.mod.get_pin(name)) - pin.transform(self.offset,self.mirror,self.rotate) + pin.transform(self.offset, self.mirror, self.rotate) return pin else: pins = copy.deepcopy(self.mod.get_pin(name)) - pin.transform(self.offset,self.mirror,self.rotate) + pins.transform(self.offset, self.mirror, self.rotate) return pin[index] def get_num_pins(self, name): """ Return the number of pins of a given name """ return len(self.mod.get_pins(name)) - def get_pins(self,name): + def get_pins(self, name): """ Return an absolute pin that is offset and transformed based on this instance location. """ @@ -253,7 +266,7 @@ class instance(geometry): new_pins = [] for p in pin: - p.transform(self.offset,self.mirror,self.rotate) + p.transform(self.offset, self.mirror, self.rotate) new_pins.append(p) return new_pins @@ -265,6 +278,7 @@ class instance(geometry): """ override print function output """ return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")" + class path(geometry): """Represents a Path""" @@ -322,7 +336,7 @@ class label(geometry): self.size = 0 - debug.info(4,"creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset)) + debug.info(4, "creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset)) def gds_write_file(self, new_layout): """Writes the text label to GDS""" @@ -340,7 +354,7 @@ class label(geometry): def __str__(self): """ override print function output """ - return "label: " + self.text + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) + return "label: " + self.text + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) def __repr__(self): """ override print function output """ diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index bb57ae3e..b0370179 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -7,11 +7,10 @@ # import hierarchy_layout import hierarchy_spice -import verify import debug import os from globals import OPTS - +import tech class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): """ @@ -26,7 +25,12 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): # If we have a separate lvs directory, then all the lvs files # should be in there (all or nothing!) - lvs_dir = OPTS.openram_tech + "lvs_lib/" + try: + lvs_subdir = tech.lvs_lib + except AttributeError: + lvs_subdir = "lvs_lib" + lvs_dir = OPTS.openram_tech + lvs_subdir + "/" + if os.path.exists(lvs_dir): self.lvs_file = lvs_dir + name + ".sp" else: @@ -44,12 +48,13 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): if i.name == inst.name: break else: - debug.error("Couldn't find instance {0}".format(inst_name), -1) + debug.error("Couldn't find instance {0}".format(inst.name), -1) inst_map = inst.mod.pin_map return inst_map def DRC_LVS(self, final_verification=False, force_check=False): """Checks both DRC and LVS for a module""" + import verify # No layout to check if OPTS.netlist_only: @@ -89,6 +94,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): def DRC(self, final_verification=False): """Checks DRC for a module""" + import verify + # Unit tests will check themselves. # Do not run if disabled in options. @@ -112,6 +119,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): def LVS(self, final_verification=False): """Checks LVS for a module""" + import verify + # Unit tests will check themselves. # Do not run if disabled in options. @@ -180,7 +189,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): """Given a list of nets, will compare the internal alias of a mod to determine if the nets have a connection to this mod's net (but not inst). """ - if exclusion_set == None: + if not exclusion_set: exclusion_set = set() try: self.name_dict diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index df7095df..8995488a 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -5,7 +5,6 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import collections import geometry import gdsMill import debug @@ -14,6 +13,7 @@ from tech import drc, GDS from tech import layer as techlayer from tech import layer_indices from tech import layer_stacks +from tech import preferred_directions import os from globals import OPTS from vector import vector @@ -42,27 +42,30 @@ class layout(): 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 ############################################################ def offset_all_coordinates(self): - """ This function is called after everything is placed to - shift the origin in the lowest left corner """ + """ + This function is called after everything is placed to + shift the origin in the lowest left corner + """ offset = self.find_lowest_coords() self.translate_all(offset) return offset def get_gate_offset(self, x_offset, height, inv_num): - """Gets the base offset and y orientation of stacked rows of gates + """ + Gets the base offset and y orientation of stacked rows of gates assuming a minwidth metal1 vdd/gnd rail. Input is which gate in the stack from 0..n """ @@ -120,6 +123,7 @@ class layout(): highesty2 = max(inst.uy() for inst in self.insts) else: highestx2 = highesty2 = None + if highestx1 == None and highestx2 == None: return None elif highestx1 == None: @@ -188,7 +192,7 @@ class layout(): inst.offset = vector(inst.offset - offset) # The instances have a precomputed boundary that we need to update. if inst.__class__.__name__ == "instance": - inst.compute_boundary(inst.offset) + inst.compute_boundary(inst.offset, inst.mirror, inst.rotate) for pin_name in self.pin_map.keys(): # All the pins are absolute coordinates that need to be updated. pin_list = self.pin_map[pin_name] @@ -219,8 +223,6 @@ class layout(): if not height: height = drc["minwidth_{}".format(layer)] lpp = techlayer[layer] - if abs(offset[0]-5.16250)<0.01 and abs(offset[1]-8.70750)<0.01: - import pdb; pdb.set_trace() self.objs.append(geometry.rectangle(lpp, offset, width, @@ -244,27 +246,46 @@ class layout(): height)) return self.objs[-1] - def add_segment_center(self, layer, start, end): + def add_segment_center(self, layer, start, end, width=None): """ Add a min-width rectanglular segment using center line on the start to end point """ - minwidth_layer = drc["minwidth_{}".format(layer)] + if not width: + width = 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) + offset = vector(0, 0.5 * width) return self.add_rect(layer, start - offset, end.x - start.x, - minwidth_layer) + width) else: - offset = vector(0.5 * minwidth_layer, 0) + offset = vector(0.5 * width, 0) return self.add_rect(layer, start - offset, - minwidth_layer, + width, end.y - start.y) + def get_tx_insts(self, tx_type=None): + """ + Return a list of the instances of given tx type. + """ + tx_list = [] + for i in self.insts: + try: + if tx_type and i.mod.tx_type == tx_type: + tx_list.append(i) + elif not tx_type: + if i.mod.tx_type == "nmos" or i.mod.tx_type == "pmos": + tx_list.append(i) + except AttributeError: + pass + + return tx_list + def get_pin(self, text): """ Return the pin or list of pins @@ -322,7 +343,7 @@ class layout(): 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): + def add_layout_pin_segment_center(self, text, layer, start, end, width=None): """ Creates a path like pin with center-line convention """ @@ -331,27 +352,27 @@ class layout(): self.gds_write(file_name) debug.error("Cannot have a non-manhatten layout pin: {}".format(file_name), -1) - minwidth_layer = drc["minwidth_{}".format(layer)] + if not width: + layer_width = drc["minwidth_{}".format(layer)] + else: + layer_width = width # 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) + bbox_width = max(start.x, end.x) - min(start.x, end.x) + bbox_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) - # This makes sure it is long enough, but also it is not 0 width! - height = max(minwidth_layer, height) - width = max(minwidth_layer, width) + if bbox_height == 0: + ll_offset -= vector(0, 0.5 * layer_width) + if bbox_width == 0: + ll_offset -= vector(0.5 * layer_width, 0) - return self.add_layout_pin(text, - layer, - ll_offset, - width, - height) + return self.add_layout_pin(text=text, + layer=layer, + offset=ll_offset, + width=bbox_width, + height=bbox_height) def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None): """ Creates a path like pin with center-line convention """ @@ -448,24 +469,31 @@ class layout(): path=coordinates, layer_widths=layer_widths) - def add_zjog(self, layer, start, end, first_direction="H"): + def add_zjog(self, layer, start, end, first_direction="H", var_offset=0.5, fixed_offset=None): """ Add a simple jog at the halfway point. If layer is a single value, it is a path. If layer is a tuple, it is a wire with preferred directions. """ + neg_offset = 1.0 - var_offset # vertical first if first_direction == "V": - mid1 = vector(start.x, 0.5 * start.y + 0.5 * end.y) + if fixed_offset: + mid1 = vector(start.x, fixed_offset) + else: + mid1 = vector(start.x, neg_offset * start.y + var_offset * end.y) mid2 = vector(end.x, mid1.y) # horizontal first elif first_direction == "H": - mid1 = vector(0.5 * start.x + 0.5 * end.x, start.y) + if fixed_offset: + mid1 = vector(fixed_offset, start.y) + else: + mid1 = vector(neg_offset * start.x + var_offset * end.x, start.y) mid2 = vector(mid1, end.y) else: debug.error("Invalid direction for jog -- must be H or V.") - + if layer in layer_stacks: self.add_wire(layer, [start, mid1, mid2, end]) elif layer in techlayer: @@ -480,7 +508,7 @@ class layout(): mid1 = vector(0.5 * start.x + 0.5 * end.x, start.y) mid2 = vector(mid1, end.y) self.add_path(layer, [start, mid1, mid2, end]) - + def add_wire(self, layers, coordinates, widen_short_wires=True): """Connects a routing path on given layer,coordinates,width. The layers are the (horizontal, via, vertical). """ @@ -494,7 +522,6 @@ class layout(): def get_preferred_direction(self, layer): """ Return the preferred routing directions """ - 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): @@ -540,24 +567,6 @@ class layout(): self.connect_inst([]) return inst - def add_via_stack(self, offset, from_layer, to_layer, - directions=None, - size=[1, 1], - implant_type=None, - well_type=None): - """ - Punch a stack of vias from a start layer to a target layer. - """ - return self.__add_via_stack_internal(offset=offset, - directions=directions, - from_layer=from_layer, - to_layer=to_layer, - via_func=self.add_via, - last_via=None, - size=size, - implant_type=implant_type, - well_type=well_type) - def add_via_stack_center(self, offset, from_layer, @@ -567,60 +576,74 @@ class layout(): implant_type=None, well_type=None): """ - 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, - directions=directions, - from_layer=from_layer, - to_layer=to_layer, - via_func=self.add_via_center, - last_via=None, - size=size, - implant_type=implant_type, - well_type=well_type) - - def __add_via_stack_internal(self, offset, directions, from_layer, to_layer, - via_func, last_via, size, implant_type=None, well_type=None): - """ - 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. + Punch a stack of vias from a start layer to a target layer by the center. """ if from_layer == to_layer: - return last_via + # In the case where we have no vias added, make sure that there is at least + # a metal enclosure. This helps with center-line path routing. + self.add_rect_center(layer=from_layer, + offset=offset) + return None - from_id = layer_indices[from_layer] - to_id = layer_indices[to_layer] + via = None + cur_layer = from_layer + while cur_layer != to_layer: + from_id = layer_indices[cur_layer] + to_id = layer_indices[to_layer] - if from_id < to_id: # grow the stack up - search_id = 0 - next_id = 2 - else: # grow the stack down - search_id = 2 - next_id = 0 + 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)) + curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, layer_stacks), None) + + via = self.add_via_center(layers=curr_stack, + size=size, + offset=offset, + directions=directions, + implant_type=implant_type, + well_type=well_type) + + if cur_layer != from_layer: + self.add_min_area_rect_center(cur_layer, + offset, + via.mod.first_layer_width, + via.mod.first_layer_height) + + cur_layer = curr_stack[next_id] - via = via_func(layers=curr_stack, - size=size, - offset=offset, - directions=directions, - implant_type=implant_type, - well_type=well_type) - - via = self.__add_via_stack_internal(offset=offset, - directions=directions, - from_layer=curr_stack[next_id], - to_layer=to_layer, - via_func=via_func, - last_via=via, - size=size) return via - + + def add_min_area_rect_center(self, + layer, + offset, + width=None, + height=None): + """ + Add a minimum area retcangle at the given point. + Either width or height should be fixed. + """ + + min_area = drc("minarea_{}".format(layer)) + if min_area == 0: + return + + min_width = drc("minwidth_{}".format(layer)) + + if preferred_directions[layer] == "V": + height = max(min_area / width, min_width) + else: + width = max(min_area / height, min_width) + + self.add_rect_center(layer=layer, + offset=offset, + width=width, + height=height) + def add_ptx(self, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"): """Adds a ptx module to the design.""" import ptx @@ -688,12 +711,19 @@ class layout(): # we should add a boundary just for DRC in some technologies 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. + boundary_layers = [] + if "boundary" in techlayer.keys(): + boundary_layers.append("boundary") if "stdc" in techlayer.keys(): - boundary_layer = "stdc" - boundary = [self.find_lowest_coords(), - self.find_highest_coords()] - height = boundary[1][1] - boundary[0][1] - width = boundary[1][0] - boundary[0][0] + boundary_layers.append("stdc") + boundary = [self.find_lowest_coords(), + self.find_highest_coords()] + debug.check(boundary[0] and boundary[1], "No shapes to make a boundary.") + + height = boundary[1][1] - boundary[0][1] + width = boundary[1][0] - boundary[0][0] + + for boundary_layer in boundary_layers: (layer_number, layer_purpose) = techlayer[boundary_layer] gds_layout.addBox(layerNumber=layer_number, purposeNumber=layer_purpose, @@ -701,7 +731,7 @@ class layout(): width=width, height=height, center=False) - debug.info(2, "Adding {0} boundary {1}".format(self.name, boundary)) + debug.info(4, "Adding {0} boundary {1}".format(self.name, boundary)) self.visited.append(self.name) @@ -827,51 +857,57 @@ class layout(): if not pitch: pitch = getattr(self, "{}_pitch".format(layer)) - line_positions = {} + pins = {} if vertical: for i in range(len(names)): line_offset = offset + vector(i * pitch, 0) if make_pins: - self.add_layout_pin(text=names[i], - layer=layer, - offset=line_offset, - height=length) + new_pin = self.add_layout_pin(text=names[i], + layer=layer, + offset=line_offset, + height=length) else: - self.add_rect(layer=layer, - offset=line_offset, - height=length) - line_positions[names[i]] = line_offset + vector(half_minwidth, 0) + rect = self.add_rect(layer=layer, + offset=line_offset, + height=length) + new_pin = pin_layout(names[i], + [rect.ll(), rect.ur()], + layer) + + pins[names[i]] = new_pin else: for i in range(len(names)): line_offset = offset + vector(0, i * pitch + half_minwidth) if make_pins: - self.add_layout_pin(text=names[i], - layer=layer, - offset=line_offset, - width=length) + new_pin = self.add_layout_pin(text=names[i], + layer=layer, + offset=line_offset, + width=length) else: - self.add_rect(layer=layer, - offset=line_offset, - width=length) - # Make this the center of the rail - line_positions[names[i]] = line_offset + vector(0.5 * length, - half_minwidth) + rect = self.add_rect(layer=layer, + offset=line_offset, + width=length) + new_pin = pin_layout(names[i], + [rect.ll(), rect.ur()], + layer) + + pins[names[i]] = new_pin - return line_positions + return pins - def connect_horizontal_bus(self, mapping, inst, bus_offsets, + def connect_horizontal_bus(self, mapping, inst, bus_pins, layer_stack=("m1", "via1", "m2")): """ Horizontal version of connect_bus. """ - self.connect_bus(mapping, inst, bus_offsets, layer_stack, True) + self.connect_bus(mapping, inst, bus_pins, layer_stack, True) - def connect_vertical_bus(self, mapping, inst, bus_offsets, + def connect_vertical_bus(self, mapping, inst, bus_pins, layer_stack=("m1", "via1", "m2")): """ Vertical version of connect_bus. """ - self.connect_bus(mapping, inst, bus_offsets, layer_stack, False) + self.connect_bus(mapping, inst, bus_pins, layer_stack, False) - def connect_bus(self, mapping, inst, bus_offsets, layer_stack, horizontal): + def connect_bus(self, mapping, inst, bus_pins, layer_stack, horizontal): """ Connect a mapping of pin -> name for a bus. This could be replaced with a channel router in the future. @@ -881,13 +917,15 @@ class layout(): (horizontal_layer, via_layer, vertical_layer) = layer_stack if horizontal: route_layer = vertical_layer + bys_layer = horizontal_layer else: route_layer = horizontal_layer + bus_layer = vertical_layer for (pin_name, bus_name) in mapping: pin = inst.get_pin(pin_name) pin_pos = pin.center() - bus_pos = bus_offsets[bus_name] + bus_pos = bus_pins[bus_name].center() if horizontal: # up/down then left/right @@ -904,17 +942,18 @@ class layout(): # 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) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=route_layer, + offset=pin_pos) # 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) + self.add_via_stack_center(from_layer=route_layer, + to_layer=bus_layer, + offset=bus_pos) def connect_vbus(self, src_pin, dest_pin, hlayer="m3", vlayer="m2"): """ @@ -973,313 +1012,91 @@ class layout(): self.add_via_stack_center(from_layer=vlayer, to_layer=dest_pin.layer, offset=out_pos) - - def get_layer_pitch(self, layer): - """ Return the track pitch on a given layer """ - try: - # FIXME: Using non-pref pitch here due to overlap bug in VCG constraints. - # It should just result in inefficient channel width but will work. - pitch = getattr(self, "{}_pitch".format(layer)) - nonpref_pitch = getattr(self, "{}_nonpref_pitch".format(layer)) - space = getattr(self, "{}_space".format(layer)) - except AttributeError: - debug.error("Cannot find layer pitch.", -1) - return (nonpref_pitch, pitch, pitch - space, space) - - def add_horizontal_trunk_route(self, - pins, - trunk_offset, - layer_stack, - pitch): - """ - 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)] - - # 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)]) - - # Route each pin to the trunk - for pin in pins: - # No bend needed here - mid = vector(pin.center().x, trunk_offset.y) - 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)]) - - # Route each pin to the trunk - for pin in pins: - mid = vector(pin.center().x, trunk_offset.y) - self.add_path(self.vertical_layer, [pin.center(), mid]) - self.add_via_center(layers=layer_stack, - offset=mid) - - def add_vertical_trunk_route(self, - pins, - trunk_offset, - layer_stack, - pitch): - """ - 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]) - - # 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)] - - # 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)]) - - # Route each pin to the trunk - for pin in pins: - # No bend needed here - mid = vector(trunk_offset.x, pin.center().y) - 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)]) - - # Route each pin to the trunk - for pin in pins: - mid = vector(trunk_offset.x, pin.center().y) - 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, - directions=None, - vertical=False): - """ - The net list is a list of the nets with each net being a list of pins - to be connected. The offset is the lower-left of where the - routing channel will start. This does NOT try to minimize the - number of tracks -- instead, it picks an order to avoid the - vertical conflicts between pins. The track size must be the number of - nets times the *nonpreferred* routing of the non-track layer pitch. - - """ - def remove_net_from_graph(pin, g): - """ - Remove the pin from the graph and all conflicts - """ - g.pop(pin, None) - - # Remove the pin from all conflicts - # FIXME: This is O(n^2), so maybe optimize it. - for other_pin, conflicts in g.items(): - if pin in conflicts: - conflicts.remove(pin) - g[other_pin]=conflicts - return g - - def vcg_nets_overlap(net1, net2, vertical): - """ - Check all the pin pairs on two nets and return a pin - overlap if any pin overlaps. - """ - - if vertical: - pitch = self.horizontal_nonpref_pitch - else: - pitch = self.vertical_nonpref_pitch - - for pin1 in net1: - for pin2 in net2: - if vcg_pin_overlap(pin1, pin2, vertical, pitch): - return True - - return False - - 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 extra comparison *shouldn't* matter. - - # Pin 1 must be in the "BOTTOM" set - x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch - - # Pin 1 must be in the "LEFT" set - y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch - overlaps = (not vertical and x_overlap) or (vertical and y_overlap) - return overlaps - - if not directions: - # Use the preferred layer directions - 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] - else: - # Use the layer directions specified to the router rather than - # the preferred directions - debug.check(directions[0] != directions[1], "Must have unique layer directions.") - if directions[0] == "V": - self.vertical_layer = layer_stack[0] - self.horizontal_layer = layer_stack[2] - else: - self.horizontal_layer = layer_stack[0] - self.vertical_layer = layer_stack[2] - - layer_stuff = self.get_layer_pitch(self.vertical_layer) - (self.vertical_nonpref_pitch, self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff - - layer_stuff = self.get_layer_pitch(self.horizontal_layer) - (self.horizontal_nonpref_pitch, 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 - # number of tracks! - # hcg = {} - - # 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) - for pin_list in netlist: - net_name = "n{}".format(index) - index += 1 - nets[net_name] = pin_list - - # print("Nets:") - # for net_name in nets: - # print(net_name, [x.name for x in nets[net_name]]) - - # 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] = [] - for net_name2 in nets: - if net_name2 not in vcg.keys(): - vcg[net_name2] = [] - # Skip yourself - if net_name1 == net_name2: - continue - if vcg_nets_overlap(nets[net_name1], - nets[net_name2], - vertical): - vcg[net_name2].append(net_name1) - - # list of routes to do - while 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) - break - else: - # FIXME: We don't support cyclic VCGs right now. - 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]) - - # Remove the net from other constriants in the 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 - if vertical: - self.add_vertical_trunk_route(pin_list, - offset, - layer_stack, - self.vertical_nonpref_pitch) - # This accounts for the via-to-via spacings - offset += vector(self.horizontal_nonpref_pitch, 0) - else: - self.add_horizontal_trunk_route(pin_list, - offset, - layer_stack, - self.horizontal_nonpref_pitch) - # This accounts for the via-to-via spacings - offset += vector(0, self.vertical_nonpref_pitch) def create_vertical_channel_route(self, netlist, offset, layer_stack, directions=None): """ Wrapper to create a vertical channel route """ - self.create_channel_route(netlist, offset, layer_stack, directions, vertical=True) + import channel_route + cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=True) + self.add_inst("vc", cr) + self.connect_inst([]) def create_horizontal_channel_route(self, netlist, offset, layer_stack, directions=None): """ Wrapper to create a horizontal channel route """ - self.create_channel_route(netlist, offset, layer_stack, directions, vertical=False) - + import channel_route + cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=False) + self.add_inst("hc", cr) + self.connect_inst([]) + def add_boundary(self, ll=vector(0, 0), ur=None): """ Add boundary for debugging dimensions """ if OPTS.netlist_only: return + boundary_layers = [] if "stdc" in techlayer.keys(): - boundary_layer = "stdc" - else: - boundary_layer = "boundary" - if not ur: - self.bounding_box = self.add_rect(layer=boundary_layer, - offset=ll, - height=self.height, - width=self.width) - else: - self.bounding_box = self.add_rect(layer=boundary_layer, - offset=ll, - height=ur.y - ll.y, - width=ur.x - ll.x) + boundary_layers.append("stdc") + if "boundary" in techlayer.keys(): + boundary_layers.append("boundary") + # Save the last one as self.bounding_box + for boundary_layer in boundary_layers: + if not ur: + self.bounding_box = self.add_rect(layer=boundary_layer, + offset=ll, + height=self.height, + width=self.width) + else: + 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 + def add_enclosure(self, insts, layer="nwell", extend=0, leftx=None, rightx=None, topy=None, boty=None): + """ + Add a layer that surrounds the given instances. Useful for creating wells, for example. Doesn't check for minimum widths or - spacings.""" + spacings. Extra arg can force a dimension to one side left/right top/bot. + """ - 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()) - xmax = max(xmax, inst.rx()) - ymax = max(ymax, inst.uy()) + if leftx != None: + xmin = leftx + else: + xmin = insts[0].lx() + for inst in insts: + xmin = min(xmin, inst.lx()) + xmin = xmin - extend + if boty != None: + ymin = boty + else: + ymin = insts[0].by() + for inst in insts: + ymin = min(ymin, inst.by()) + ymin = ymin - extend + if rightx != None: + xmax = rightx + else: + xmax = insts[0].rx() + for inst in insts: + xmax = max(xmax, inst.rx()) + xmax = xmax + extend + if topy != None: + ymax = topy + else: + ymax = insts[0].uy() + for inst in insts: + ymax = max(ymax, inst.uy()) + ymax = ymax + extend - self.add_rect(layer=layer, - offset=vector(xmin, ymin), - width=xmax - xmin, - height=ymax - ymin) - - def copy_power_pins(self, inst, name): + rect = self.add_rect(layer=layer, + offset=vector(xmin, ymin), + width=xmax - xmin, + height=ymax - ymin) + return rect + + def copy_power_pins(self, inst, name, add_vias=True): """ 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. @@ -1292,12 +1109,9 @@ class layout(): 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 {2} or metal1 for "\ - "supply router." - .format(name, inst.name, self.pwr_grid_layer)) + + elif add_vias: + self.add_power_pin(name, pin.center(), start_layer=pin.layer) def add_power_pin(self, name, loc, size=[1, 1], directions=None, start_layer="m1"): """ @@ -1306,20 +1120,21 @@ class layout(): which vias are needed. """ - via = self.add_via_stack_center(from_layer=start_layer, - to_layer=self.pwr_grid_layer, - size=size, - offset=loc, - directions=directions) if start_layer == self.pwr_grid_layer: self.add_layout_pin_rect_center(text=name, layer=self.pwr_grid_layer, offset=loc) else: + via = self.add_via_stack_center(from_layer=start_layer, + to_layer=self.pwr_grid_layer, + size=size, + offset=loc, + directions=directions) + # Hack for min area - if OPTS.tech_name == "s8": + if OPTS.tech_name == "sky130": width = round_to_grid(sqrt(drc["minarea_m3"])) - height = round_to_grid(drc["minarea_m3"]/width) + height = round_to_grid(drc["minarea_m3"] / width) else: width = via.width height = via.height @@ -1329,6 +1144,42 @@ class layout(): width=width, height=height) + def add_perimeter_pin(self, name, pin, side, bbox): + """ + Add a pin along the perimeter side specified by the bbox with + the given name and layer from the pin starting location. + """ + (ll, ur) = bbox + left = ll.x + bottom = ll.y + right = ur.x + top = ur.y + + pin_loc = pin.center() + if side == "left": + peri_pin_loc = vector(left, pin_loc.y) + layer = "m3" + elif side == "right": + layer = "m3" + peri_pin_loc = vector(right, pin_loc.y) + elif side == "top": + layer = "m4" + peri_pin_loc = vector(pin_loc.x, top) + elif side == "bottom": + layer = "m4" + peri_pin_loc = vector(pin_loc.x, bottom) + + self.add_via_stack_center(from_layer=pin.layer, + to_layer=layer, + offset=pin_loc) + + self.add_path(layer, + [pin_loc, peri_pin_loc]) + + return self.add_layout_pin_rect_center(text=name, + layer=layer, + offset=peri_pin_loc) + def add_power_ring(self, bbox): """ Create vdd and gnd power rings around an area of the bounding box diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 8091af63..5a15fce5 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -10,9 +10,9 @@ import re import os import math import tech -from delay_data import * -from wire_spice_model import * -from power_data import * +from delay_data import delay_data +from wire_spice_model import wire_spice_model +from power_data import power_data import logical_effort @@ -40,6 +40,8 @@ class spice(): # THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the # Spice format) self.conns = [] + # If this is set, it will out output subckt or isntances of this (for row/col caps etc.) + self.no_instances = False # Keep track of any comments to add the the spice try: self.commments @@ -208,11 +210,12 @@ class spice(): # parses line into ports and remove subckt self.pins = subckt_line.split(" ")[2:] else: + debug.info(4, "no spfile {0}".format(self.sp_file)) self.spice = [] # We don't define self.lvs and will use self.spice if dynamically created # or they are the same file - if self.lvs_file!=self.sp_file and os.path.isfile(self.lvs_file): + if self.lvs_file != self.sp_file and os.path.isfile(self.lvs_file): debug.info(3, "opening {0}".format(self.lvs_file)) f = open(self.lvs_file) self.lvs = f.readlines() @@ -262,7 +265,12 @@ class spice(): Recursive spice subcircuit write; Writes the spice subcircuit from the library or the dynamically generated one """ - if not self.spice: + + if self.no_instances: + return + elif not self.spice: + # If spice isn't defined, we dynamically generate one. + # recursively write the modules for i in self.mods: if self.contains(i, usedMODS): @@ -299,6 +307,9 @@ class spice(): # these are wires and paths if self.conns[i] == []: continue + # Instance with no devices in it needs no subckt/instance + if self.insts[i].mod.no_instances: + continue if lvs_netlist and hasattr(self.insts[i].mod, "lvs_device"): sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name, " ".join(self.conns[i]))) @@ -315,7 +326,7 @@ class spice(): sp.write(".ENDS {0}\n".format(self.name)) else: - # write the subcircuit itself + # If spice is a hard module, output the spice file contents. # Including the file path makes the unit test fail for other users. # if os.path.isfile(self.sp_file): # sp.write("\n* {0}\n".format(self.sp_file)) @@ -355,7 +366,7 @@ class spice(): stage_effort = self.get_stage_effort(relative_cap) # If it fails, then keep running with a valid object. - if stage_effort == None: + if not stage_effort: return delay_data(0.0, 0.0) abs_delay = stage_effort.get_absolute_delay() diff --git a/compiler/base/pin_layout.py b/compiler/base/pin_layout.py index f18956a8..dac4e525 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -8,7 +8,7 @@ import debug from tech import GDS, drc from vector import vector -from tech import layer +from tech import layer, layer_indices import math @@ -31,12 +31,15 @@ class pin_layout: debug.check(self.width() > 0, "Zero width pin.") debug.check(self.height() > 0, "Zero height pin.") + # These are the valid pin layers + valid_layers = { x: layer[x] for x in layer_indices.keys()} + # if it's a string, use the name if type(layer_name_pp) == str: self._layer = layer_name_pp # else it is required to be a lpp else: - for (layer_name, lpp) in layer.items(): + for (layer_name, lpp) in valid_layers.items(): if not lpp: continue if self.same_lpp(layer_name_pp, lpp): @@ -367,8 +370,17 @@ class pin_layout: + str(self.width()) + "x" + str(self.height()) + " @ " + str(self.ll())) (layer_num, purpose) = layer[self.layer] + try: + from tech import pin_purpose + except ImportError: + pin_purpose = purpose + try: + from tech import label_purpose + except ImportError: + label_purpose = purpose + newLayout.addBox(layerNumber=layer_num, - purposeNumber=purpose, + purposeNumber=pin_purpose, offsetInMicrons=self.ll(), width=self.width(), height=self.height(), @@ -378,7 +390,7 @@ class pin_layout: # imported into Magic. newLayout.addText(text=self.name, layerNumber=layer_num, - purposeNumber=purpose, + purposeNumber=label_purpose, offsetInMicrons=self.center(), magnification=GDS["zoom"], rotate=None) diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index b0e79208..e91d8c2f 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -7,10 +7,10 @@ # import debug import utils -from tech import GDS, layer, parameter +from tech import GDS, layer from tech import cell_properties as props import bitcell_base - +from globals import OPTS class bitcell(bitcell_base.bitcell_base): """ @@ -50,6 +50,8 @@ 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) + + debug.check(OPTS.tech_name != "sky130", "sky130 does not yet support single port cells") def get_all_wl_names(self): """ Creates a list of all wordline pin names """ diff --git a/compiler/bitcells/col_cap_bitcell_1rw_1r.py b/compiler/bitcells/col_cap_bitcell_1rw_1r.py new file mode 100644 index 00000000..01818a12 --- /dev/null +++ b/compiler/bitcells/col_cap_bitcell_1rw_1r.py @@ -0,0 +1,44 @@ +# 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 utils +from tech import GDS, layer +from tech import cell_properties as props +import bitcell_base + + +class col_cap_bitcell_1rw_1r(bitcell_base.bitcell_base): + """ + todo""" + + 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.vdd] + + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", + "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("col_cap_cell_1rw_1r", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, + "col_cap_cell_1rw_1r", + GDS["unit"]) + + def __init__(self, name=""): + # Ignore the name argument + bitcell_base.bitcell_base.__init__(self, "col_cap_cell_1rw_1r") + debug.info(2, "Create col_cap bitcell 1rw+1r object") + + self.width = col_cap_bitcell_1rw_1r.width + self.height = col_cap_bitcell_1rw_1r.height + self.pin_map = col_cap_bitcell_1rw_1r.pin_map + self.add_pin_types(self.type_list) + self.no_instances = True diff --git a/compiler/bitcells/row_cap_bitcell_1rw_1r.py b/compiler/bitcells/row_cap_bitcell_1rw_1r.py new file mode 100644 index 00000000..f7a3a687 --- /dev/null +++ b/compiler/bitcells/row_cap_bitcell_1rw_1r.py @@ -0,0 +1,44 @@ +# 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 utils +from tech import GDS, layer +from tech import cell_properties as props +import bitcell_base + + +class row_cap_bitcell_1rw_1r(bitcell_base.bitcell_base): + """ + A single bit cell which is forced to store a 0. + This module implements the single memory cell used in the design. It + is a hand-made cell, so the layout and netlist should be available in + the technology library. """ + + pin_names = [props.bitcell.cell_1rw1r.pin.wl0, + props.bitcell.cell_1rw1r.pin.wl1, + props.bitcell.cell_1rw1r.pin.gnd] + + type_list = ["INPUT", "INPUT", "GROUND"] + + (width, height) = utils.get_libcell_size("row_cap_cell_1rw_1r", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, + "row_cap_cell_1rw_1r", + GDS["unit"]) + + def __init__(self, name=""): + # Ignore the name argument + bitcell_base.bitcell_base.__init__(self, "row_cap_cell_1rw_1r") + debug.info(2, "Create row_cap bitcell 1rw+1r object") + + self.width = row_cap_bitcell_1rw_1r.width + self.height = row_cap_bitcell_1rw_1r.height + self.pin_map = row_cap_bitcell_1rw_1r.pin_map + self.add_pin_types(self.type_list) + self.no_instances = True diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 62367d72..b5214198 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -1077,7 +1077,8 @@ class delay(simulation): self.trimsp.set_configuration(self.num_banks, self.num_rows, self.num_cols, - self.word_size) + self.word_size, + self.num_spare_rows) self.trimsp.trim(self.probe_address,self.probe_data) else: # The non-reduced netlist file when it is disabled diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 6d827aa3..2c391e38 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -5,23 +5,18 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys,re,shutil -import copy import collections -from design import design import debug -import math -import tech import random from .stimuli import * from .charutils import * -import utils from globals import OPTS from .simulation import simulation -from .delay import delay +# from .delay import delay import graph_util from sram_factory import factory + class functional(simulation): """ Functions to write random data values to a random address then read them back and check @@ -40,6 +35,9 @@ class functional(simulation): else: self.num_wmasks = 0 + if not self.num_spare_cols: + self.num_spare_cols = 0 + self.set_corner(corner) self.set_spice_constants() self.set_stimulus_variables() @@ -57,7 +55,6 @@ class functional(simulation): self.read_check = [] self.read_results = [] - def run(self, feasible_period=None): if feasible_period: #period defaults to tech.py feasible period otherwise. self.period = feasible_period @@ -82,10 +79,11 @@ class functional(simulation): for port in self.all_ports: checks = [] if port in self.read_ports: - checks.append((self.addr_value[port],"addr")) + checks.append((self.addr_value[port], "addr")) if port in self.write_ports: - checks.append((self.data_value[port],"data")) - checks.append((self.wmask_value[port],"wmask")) + checks.append((self.data_value[port], "data")) + checks.append((self.wmask_value[port], "wmask")) + checks.append((self.spare_wen_value[port], "spare_wen")) for (val, name) in checks: debug.check(len(self.cycle_times)==len(val), @@ -104,15 +102,15 @@ class functional(simulation): r_ops = ["noop", "read"] # First cycle idle is always an idle cycle - comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, "0"*self.num_wmasks, 0, self.t_current) + comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current) self.add_noop_all_ports(comment) # 1. Write all the write ports first to seed a bunch of locations. for port in self.write_ports: addr = self.gen_addr() word = self.gen_data() - comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current) - self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port) + comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current) + self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port) self.stored_words[addr] = word # All other read-only ports are noops. @@ -131,7 +129,7 @@ class functional(simulation): if port in self.write_ports: self.add_noop_one_port(port) else: - comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current) + comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current) self.add_read_one_port(comment, addr, port) self.add_read_check(word, port) self.cycle_times.append(self.t_current) @@ -160,13 +158,13 @@ class functional(simulation): self.add_noop_one_port(port) else: word = self.gen_data() - comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current) - self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port) + comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current) + self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port) self.stored_words[addr] = word w_addrs.append(addr) elif op == "partial_write": # write only to a word that's been written to - (addr,old_word) = self.get_data() + (addr, old_word) = self.get_data() # two ports cannot write to the same address if addr in w_addrs: self.add_noop_one_port(port) @@ -179,7 +177,7 @@ class functional(simulation): self.stored_words[addr] = new_word w_addrs.append(addr) else: - (addr,word) = random.choice(list(self.stored_words.items())) + (addr, word) = random.choice(list(self.stored_words.items())) # The write driver is not sized sufficiently to drive through the two # bitcell access transistors to the read port. So, for now, we do not allow # a simultaneous write and read to the same address on different ports. This @@ -187,7 +185,7 @@ class functional(simulation): if addr in w_addrs: self.add_noop_one_port(port) else: - comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current) + comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current) self.add_read_one_port(comment, addr, port) self.add_read_check(word, port) @@ -195,7 +193,7 @@ class functional(simulation): self.t_current += self.period # Last cycle idle needed to correctly measure the value on the second to last clock edge - comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, "0"*self.num_wmasks, 0, self.t_current) + comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current) self.add_noop_all_ports(comment) def gen_masked_data(self, old_word, word, wmask): @@ -209,7 +207,7 @@ class functional(simulation): if wmask[bit] == "0": lower = bit * self.write_size upper = lower + self.write_size - 1 - new_word = new_word[:lower] + old_word[lower:upper+1] + new_word[upper + 1:] + new_word = new_word[:lower] + old_word[lower:upper + 1] + new_word[upper + 1:] return new_word @@ -219,15 +217,15 @@ class functional(simulation): self.check except: self.check = 0 - self.read_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, self.check]) + self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check]) self.check += 1 def read_stim_results(self): # Extract dout values from spice timing.lis for (word, dout_port, eo_period, check) in self.read_check: sp_read_value = "" - for bit in range(self.word_size): - value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(),bit,check)) + for bit in range(self.word_size + self.num_spare_cols): + value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(), bit, check)) if value > self.v_high: sp_read_value = "1" + sp_read_value elif value < self.v_low: @@ -278,17 +276,24 @@ class functional(simulation): # wmask must be reversed since a python list goes right to left and sram bits go left to right. return wmask[::-1] - def gen_data(self): """ Generates a random word to write. """ - random_value = random.randint(0,(2**self.word_size)-1) - data_bits = self.convert_to_bin(random_value,False) + if not self.num_spare_cols: + random_value = random.randint(0, (2 ** self.word_size) - 1) + else: + random_value1 = random.randint(0, (2 ** self.word_size) - 1) + random_value2 = random.randint(0, (2 ** self.num_spare_cols) - 1) + random_value = random_value1 + random_value2 + data_bits = self.convert_to_bin(random_value, False) return data_bits def gen_addr(self): """ Generates a random address value to write to. """ - random_value = random.randint(0,(2**self.addr_size)-1) - addr_bits = self.convert_to_bin(random_value,True) + if self.num_spare_rows==0: + random_value = random.randint(0, (2 ** self.addr_size) - 1) + else: + random_value = random.randint(0, ((2 ** (self.addr_size - 1) - 1)) + (self.num_spare_rows * self.words_per_row)) + addr_bits = self.convert_to_bin(random_value, True) return addr_bits def get_data(self): @@ -296,37 +301,36 @@ class functional(simulation): # Used for write masks since they should be writing to previously written addresses addr = random.choice(list(self.stored_words.keys())) word = self.stored_words[addr] - return (addr,word) + return (addr, word) - def convert_to_bin(self,value,is_addr): + def convert_to_bin(self, value, is_addr): """ Converts addr & word to usable binary values. """ - new_value = str.replace(bin(value),"0b","") + new_value = str.replace(bin(value), "0b", "") if(is_addr): expected_value = self.addr_size else: - - expected_value = self.word_size - for i in range (expected_value - len(new_value)): + expected_value = self.word_size + self.num_spare_cols + for i in range(expected_value - len(new_value)): new_value = "0" + new_value - #print("Binary Conversion: {} to {}".format(value, new_value)) - return new_value + # print("Binary Conversion: {} to {}".format(value, new_value)) + return new_value def write_functional_stimulus(self): """ Writes SPICE stimulus. """ temp_stim = "{0}/stim.sp".format(OPTS.openram_temp) - self.sf = open(temp_stim,"w") + self.sf = open(temp_stim, "w") self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period)) - self.stim = stimuli(self.sf,self.corner) + self.stim = stimuli(self.sf, self.corner) - #Write include statements + # Write include statements self.stim.write_include(self.sp_file) - #Write Vdd/Gnd statements + # Write Vdd/Gnd statements self.sf.write("\n* Global Power Supplies\n") self.stim.write_supply() - #Instantiate the SRAM + # Instantiate the SRAM self.sf.write("\n* Instantiation of the SRAM\n") self.stim.inst_model(pins=self.pins, model_name=self.sram.name) @@ -334,7 +338,7 @@ class functional(simulation): # Add load capacitance to each of the read ports self.sf.write("\n* SRAM output loads\n") for port in self.read_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): sig_name="{0}{1}_{2} ".format(self.dout_name, port, bit) self.sf.write("CD{0}{1} {2} 0 {3}f\n".format(port, bit, sig_name, self.load)) @@ -351,10 +355,10 @@ class functional(simulation): for comment in self.fn_cycle_comments: self.sf.write("*{}\n".format(comment)) - # Generate data input bits + # Generate data input bits self.sf.write("\n* Generation of data and address signals\n") for port in self.write_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): sig_name="{0}{1}_{2} ".format(self.din_name, port, bit) self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[port][bit], self.period, self.slew, 0.05) @@ -367,10 +371,10 @@ class functional(simulation): # Generate control signals self.sf.write("\n * Generation of control signals\n") for port in self.all_ports: - self.stim.gen_pwl("CSB{}".format(port), self.cycle_times , self.csb_values[port], self.period, self.slew, 0.05) + self.stim.gen_pwl("CSB{}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05) for port in self.readwrite_ports: - self.stim.gen_pwl("WEB{}".format(port), self.cycle_times , self.web_values[port], self.period, self.slew, 0.05) + self.stim.gen_pwl("WEB{}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05) # Generate wmask bits for port in self.write_ports: @@ -383,6 +387,15 @@ class functional(simulation): self.stim.gen_pwl(sig_name, self.cycle_times, self.wmask_values[port][bit], self.period, self.slew, 0.05) + # Generate spare enable bits (for spare cols) + for port in self.write_ports: + if self.num_spare_cols: + self.sf.write("\n* Generation of spare enable signals\n") + for bit in range(self.num_spare_cols): + sig_name = "SPARE_WEN{0}_{1} ".format(port, bit) + self.stim.gen_pwl(sig_name, self.cycle_times, self.spare_wen_values[port][bit], self.period, + self.slew, 0.05) + # Generate CLK signals for port in self.all_ports: self.stim.gen_pulse(sig_name="{0}{1}".format("clk", port), @@ -396,11 +409,11 @@ class functional(simulation): # Generate dout value measurements self.sf.write("\n * Generation of dout measurements\n") for (word, dout_port, eo_period, check) in self.read_check: - t_intital = eo_period - 0.01*self.period - t_final = eo_period + 0.01*self.period - for bit in range(self.word_size): - self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port,bit,check), - dout="{0}_{1}".format(dout_port,bit), + t_intital = eo_period - 0.01 * self.period + t_final = eo_period + 0.01 * self.period + for bit in range(self.word_size + self.num_spare_cols): + self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port, bit, check), + dout="{0}_{1}".format(dout_port, bit), t_intital=t_intital, t_final=t_final) @@ -430,7 +443,7 @@ class functional(simulation): # Generate new graph every analysis as edges might change depending on test bit self.graph = graph_util.timing_graph() self.sram_spc_name = "X{}".format(self.sram.name) - self.sram.build_graph(self.graph,self.sram_spc_name,self.pins) + self.sram.build_graph(self.graph, self.sram_spc_name, self.pins) # FIXME: refactor to share with delay.py def set_internal_spice_names(self): @@ -438,17 +451,17 @@ class functional(simulation): # For now, only testing these using first read port. port = self.read_ports[0] - self.graph.get_all_paths('{}{}'.format("clk", port), + self.graph.get_all_paths('{}{}'.format("clk", port), '{}{}_{}'.format(self.dout_name, port, 0).lower()) - self.sen_name = self.get_sen_name(self.graph.all_paths) - debug.info(2,"s_en name = {}".format(self.sen_name)) + self.sen_name = self.get_sen_name(self.graph.all_paths) + debug.info(2, "s_en name = {}".format(self.sen_name)) - self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths, port) - debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name)) + self.bl_name, self.br_name = self.get_bl_name(self.graph.all_paths, port) + debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name)) - self.q_name,self.qbar_name = self.get_bit_name() - debug.info(2,"q name={}\nqbar name={}".format(self.q_name,self.qbar_name)) + self.q_name, self.qbar_name = self.get_bit_name() + debug.info(2, "q name={}\nqbar name={}".format(self.q_name, self.qbar_name)) def get_bit_name(self): """ Get a bit cell name """ @@ -456,10 +469,10 @@ class functional(simulation): storage_names = cell_inst.mod.get_storage_net_names() debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" "supported for characterization. Storage nets={}").format(storage_names)) - q_name = cell_name+'.'+str(storage_names[0]) - qbar_name = cell_name+'.'+str(storage_names[1]) + q_name = cell_name + '.' + str(storage_names[0]) + qbar_name = cell_name + '.' + str(storage_names[1]) - return (q_name,qbar_name) + return (q_name, qbar_name) # FIXME: refactor to share with delay.py def get_sen_name(self, paths): @@ -469,29 +482,28 @@ class functional(simulation): """ sa_mods = factory.get_mods(OPTS.sense_amp) - # Any sense amp instantiated should be identical, any change to that + # Any sense amp instantiated should be identical, any change to that # will require some identification to determine the mod desired. debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.") enable_name = sa_mods[0].get_enable_name() sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0]) - return sen_name + return sen_name # FIXME: refactor to share with delay.py def get_bl_name(self, paths, port): """Gets the signal name associated with the bitlines in the bank.""" - cell_mod = factory.create(module_type=OPTS.bitcell) + cell_mod = factory.create(module_type=OPTS.bitcell) cell_bl = cell_mod.get_bl_name(port) cell_br = cell_mod.get_br_name(port) - bl_found = False # Only a single path should contain a single s_en name. Anything else is an error. bl_names = [] exclude_set = self.get_bl_name_search_exclusions() for int_net in [cell_bl, cell_br]: bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set)) - return bl_names[0], bl_names[1] + return bl_names[0], bl_names[1] def get_bl_name_search_exclusions(self): """Gets the mods as a set which should be excluded while searching for name.""" @@ -500,9 +512,9 @@ class functional(simulation): # so it makes the search awkward return set(factory.get_mods(OPTS.replica_bitline)) - def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None): + def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None): """ - Finds a single alias for the int_net in given paths. + Finds a single alias for the int_net in given paths. More or less hits cause an error """ @@ -510,14 +522,14 @@ class functional(simulation): for path in paths: aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set) if net_found and len(aliases) >= 1: - debug.error('Found multiple paths with {} net.'.format(int_net),1) + debug.error('Found multiple paths with {} net.'.format(int_net), 1) elif len(aliases) > 1: - debug.error('Found multiple {} nets in single path.'.format(int_net),1) + debug.error('Found multiple {} nets in single path.'.format(int_net), 1) elif not net_found and len(aliases) == 1: path_net_name = aliases[0] net_found = True if not net_found: - debug.error("Could not find {} net in timing paths.".format(int_net),1) + debug.error("Could not find {} net in timing paths.".format(int_net), 1) - return path_net_name + return path_net_name diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index e6befc2b..92882db7 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -592,7 +592,10 @@ class lib: char_results = self.d.analytical_delay(self.slews,self.loads) self.char_sram_results, self.char_port_results = char_results else: - probe_address = "1" * self.sram.addr_size + if (self.sram.num_spare_rows == 0): + probe_address = "1" * self.sram.addr_size + else: + probe_address = "0" + "1" * (self.sram.addr_size - 1) probe_data = self.sram.word_size - 1 char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads) self.char_sram_results, self.char_port_results = char_results @@ -654,8 +657,12 @@ class lib: )) # information of checks - (drc_errors, lvs_errors) = self.sram.DRC_LVS(final_verification=True) - datasheet.write("{0},{1},".format(drc_errors, lvs_errors)) + # run it only the first time + try: + datasheet.write("{0},{1},".format(self.drc_errors, self.lvs_errors)) + except AttributeError: + (self.drc_errors, self.lvs_errors) = self.sram.DRC_LVS(final_verification=True) + datasheet.write("{0},{1},".format(self.drc_errors, self.lvs_errors)) # write area datasheet.write(str(self.sram.width * self.sram.height) + ',') diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index 0d5cfb25..064eb2c5 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -25,18 +25,23 @@ class simulation(): self.word_size = self.sram.word_size self.addr_size = self.sram.addr_size self.write_size = self.sram.write_size + self.num_spare_rows = self.sram.num_spare_rows + if not self.sram.num_spare_cols: + self.num_spare_cols = 0 + else: + self.num_spare_cols = self.sram.num_spare_cols self.sp_file = spfile self.all_ports = self.sram.all_ports self.readwrite_ports = self.sram.readwrite_ports self.read_ports = self.sram.read_ports self.write_ports = self.sram.write_ports + self.words_per_row = self.sram.words_per_row if self.write_size: self.num_wmasks = int(self.word_size/self.write_size) else: self.num_wmasks = 0 - def set_corner(self,corner): """ Set the corner values """ self.corner = corner @@ -59,10 +64,10 @@ class simulation(): self.pins = self.gen_pin_names(port_signal_names=(self.addr_name,self.din_name,self.dout_name), port_info=(len(self.all_ports),self.write_ports,self.read_ports), abits=self.addr_size, - dbits=self.word_size) + dbits=self.word_size + self.num_spare_cols) debug.check(len(self.sram.pins) == len(self.pins), "Number of pins generated for characterization \ - do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins, + do not match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins, self.pins)) #This is TODO once multiport control has been finalized. #self.control_name = "CSB" @@ -80,11 +85,13 @@ class simulation(): self.addr_value = {port:[] for port in self.all_ports} self.data_value = {port:[] for port in self.write_ports} self.wmask_value = {port:[] for port in self.write_ports} + self.spare_wen_value = {port:[] for port in self.write_ports} # Three dimensional list to handle each addr and data bits for each port over the number of checks self.addr_values = {port:[[] for bit in range(self.addr_size)] for port in self.all_ports} - self.data_values = {port:[[] for bit in range(self.word_size)] for port in self.write_ports} + self.data_values = {port:[[] for bit in range(self.word_size + self.num_spare_cols)] for port in self.write_ports} self.wmask_values = {port:[[] for bit in range(self.num_wmasks)] for port in self.write_ports} + self.spare_wen_values = {port:[[] for bit in range(self.num_spare_cols)] for port in self.write_ports} # For generating comments in SPICE stimulus self.cycle_comments = [] @@ -111,10 +118,10 @@ class simulation(): def add_data(self, data, port): """ Add the array of data values """ - debug.check(len(data)==self.word_size, "Invalid data word size.") + debug.check(len(data)==(self.word_size + self.num_spare_cols), "Invalid data word size.") self.data_value[port].append(data) - bit = self.word_size - 1 + bit = self.word_size + self.num_spare_cols - 1 for c in data: if c=="0": self.data_values[port][bit].append(0) @@ -124,7 +131,6 @@ class simulation(): debug.error("Non-binary data string",1) bit -= 1 - def add_address(self, address, port): """ Add the array of address values """ debug.check(len(address)==self.addr_size, "Invalid address size.") @@ -135,7 +141,7 @@ class simulation(): if c=="0": self.addr_values[port][bit].append(0) elif c=="1": - self.addr_values[port][bit].append(1) + self.addr_values[port][bit].append(1) else: debug.error("Non-binary address string",1) bit -= 1 @@ -156,7 +162,21 @@ class simulation(): debug.error("Non-binary wmask string", 1) bit -= 1 - + def add_spare_wen(self, spare_wen, port): + """ Add the array of spare write enable values (for spare cols) """ + debug.check(len(spare_wen) == self.num_spare_cols, "Invalid spare enable size.") + + self.spare_wen_value[port].append(spare_wen) + bit = self.num_spare_cols - 1 + for c in spare_wen: + if c == "0": + self.spare_wen_values[port][bit].append(0) + elif c == "1": + self.spare_wen_values[port][bit].append(1) + else: + debug.error("Non-binary spare enable signal string", 1) + bit -= 1 + def add_write(self, comment, address, data, wmask, port): """ Add the control values for a write cycle. """ debug.check(port in self.write_ports, @@ -172,7 +192,8 @@ class simulation(): self.add_control_one_port(port, "write") self.add_data(data,port) self.add_address(address,port) - self.add_wmask(wmask,port) + self.add_wmask(wmask,port) + self.add_spare_wen("1" * self.num_spare_cols, port) #Add noops to all other ports. for unselected_port in self.all_ports: @@ -191,19 +212,20 @@ class simulation(): self.cycle_times.append(self.t_current) self.t_current += self.period self.add_control_one_port(port, "read") - self.add_address(address, port) - + self.add_address(address, port) + # If the port is also a readwrite then add # the same value as previous cycle if port in self.write_ports: try: self.add_data(self.data_value[port][-1], port) except: - self.add_data("0"*self.word_size, port) + self.add_data("0"*(self.word_size + self.num_spare_cols), port) try: self.add_wmask(self.wmask_value[port][-1], port) except: self.add_wmask("0"*self.num_wmasks, port) + self.add_spare_wen("0" * self.num_spare_cols, port) #Add noops to all other ports. for unselected_port in self.all_ports: @@ -234,6 +256,7 @@ class simulation(): self.add_data(data, port) self.add_address(address, port) self.add_wmask(wmask, port) + self.add_spare_wen("1" * self.num_spare_cols, port) def add_read_one_port(self, comment, address, port): """ Add the control values for a read cycle. Does not increment the period. """ @@ -245,23 +268,24 @@ class simulation(): self.add_control_one_port(port, "read") self.add_address(address, port) + # If the port is also a readwrite then add # the same value as previous cycle if port in self.write_ports: try: self.add_data(self.data_value[port][-1], port) except: - self.add_data("0"*self.word_size, port) + self.add_data("0"*(self.word_size + self.num_spare_cols), port) try: self.add_wmask(self.wmask_value[port][-1], port) except: - self.add_wmask("0"*self.num_wmasks, port) - + self.add_wmask("0"*self.num_wmasks, port) + self.add_spare_wen("0" * self.num_spare_cols, port) def add_noop_one_port(self, port): """ Add the control values for a noop to a single port. Does not increment the period. """ self.add_control_one_port(port, "noop") - + try: self.add_address(self.addr_value[port][-1], port) except: @@ -273,12 +297,13 @@ class simulation(): try: self.add_data(self.data_value[port][-1], port) except: - self.add_data("0"*self.word_size, port) + self.add_data("0"*(self.word_size + self.num_spare_cols), port) try: self.add_wmask(self.wmask_value[port][-1], port) except: self.add_wmask("0"*self.num_wmasks, port) - + self.add_spare_wen("0" * self.num_spare_cols, port) + def add_noop_clock_one_port(self, port): """ Add the control values for a noop to a single port. Increments the period. """ debug.info(2, 'Clock only on port {}'.format(port)) @@ -369,6 +394,11 @@ class simulation(): for port in write_index: for bit in range(self.num_wmasks): pin_names.append("WMASK{0}_{1}".format(port,bit)) + + if self.num_spare_cols: + for port in write_index: + for bit in range(self.num_spare_cols): + pin_names.append("SPARE_WEN{0}_{1}".format(port,bit)) for read_output in read_index: for i in range(dbits): diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index b5a143cf..11dc449a 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -254,7 +254,7 @@ class stimuli(): includes = self.device_models + [circuit] self.sf.write("* {} process corner\n".format(self.process)) - if OPTS.tech_name == "s8": + if OPTS.tech_name == "sky130": libraries = self.device_libraries for item in list(libraries): if os.path.isfile(item[0]): @@ -302,7 +302,9 @@ class stimuli(): OPTS.openram_temp) valid_retcode=0 else: - # ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit + # ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit + # Measurements can't be made with a raw file set in ngspice + # -r {2}timing.raw cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe, temp_stim, OPTS.openram_temp) diff --git a/compiler/characterizer/trim_spice.py b/compiler/characterizer/trim_spice.py index ffc45a9c..d20dfe42 100644 --- a/compiler/characterizer/trim_spice.py +++ b/compiler/characterizer/trim_spice.py @@ -6,7 +6,7 @@ # All rights reserved. # import debug -from math import log +from math import log,ceil import re class trim_spice(): @@ -42,7 +42,7 @@ class trim_spice(): self.word_size = word_size self.words_per_row = self.num_columns / self.word_size - self.row_addr_size = int(log(self.num_rows, 2)) + self.row_addr_size = ceil(log(self.num_rows, 2)) self.col_addr_size = int(log(self.words_per_row, 2)) self.bank_addr_size = self.col_addr_size + self.row_addr_size self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2)) diff --git a/compiler/custom/and2_dec.py b/compiler/custom/and2_dec.py new file mode 100644 index 00000000..e6f314c4 --- /dev/null +++ b/compiler/custom/and2_dec.py @@ -0,0 +1,147 @@ +# 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 +from vector import vector +import design +from sram_factory import factory +from globals import OPTS +from tech import layer + + +class and2_dec(design.design): + """ + This is an AND with configurable drive strength. + """ + def __init__(self, name, size=1, height=None, add_wells=True): + + design.design.__init__(self, name) + + debug.info(1, "Creating and2_dec {}".format(name)) + self.add_comment("size: {}".format(size)) + self.size = size + self.height = height + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.add_pins() + self.create_modules() + self.create_insts() + + def create_modules(self): + self.nand = factory.create(module_type="nand2_dec", + height=self.height) + + self.inv = factory.create(module_type="inv_dec", + height=self.height, + size=self.size) + + self.add_mod(self.nand) + self.add_mod(self.inv) + + def create_layout(self): + + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + self.width = self.nand.width + self.inv.width + self.height = self.nand.height + + self.place_insts() + self.add_wires() + self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + self.add_pin("A", "INPUT") + self.add_pin("B", "INPUT") + self.add_pin("Z", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def create_insts(self): + self.nand_inst = self.add_inst(name="pand2_dec_nand", + mod=self.nand) + self.connect_inst(["A", "B", "zb_int", "vdd", "gnd"]) + + self.inv_inst = self.add_inst(name="pand2_dec_inv", + mod=self.inv) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + + def place_insts(self): + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) + + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + if OPTS.tech_name == "sky130": + for name in ["vdd", "gnd"]: + for inst in [self.nand_inst, self.inv_inst]: + self.copy_layout_pin(inst, name) + else: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + + def add_wires(self): + # nand Z to inv A + z1_pin = self.nand_inst.get_pin("Z") + a2_pin = self.inv_inst.get_pin("A") + if OPTS.tech_name == "sky130": + mid1_point = vector(a2_pin.cx(), z1_pin.cy()) + else: + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(self.route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + + def add_layout_pins(self): + pin = self.inv_inst.get_pin("Z") + self.add_layout_pin_rect_center(text="Z", + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + for pin_name in ["A", "B"]: + pin = self.nand_inst.get_pin(pin_name) + self.add_layout_pin_rect_center(text=pin_name, + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + def get_stage_efforts(self, external_cout, inp_is_rise=False): + """Get the stage efforts of the A or B -> Z path""" + stage_effort_list = [] + stage1_cout = self.inv.get_cin() + stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) + stage_effort_list.append(stage1) + last_stage_is_rise = stage1.is_rise + + stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise) + stage_effort_list.append(stage2) + + return stage_effort_list + + def get_cin(self): + """Return the relative input capacitance of a single input""" + return self.nand.get_cin() + diff --git a/compiler/custom/and3_dec.py b/compiler/custom/and3_dec.py new file mode 100644 index 00000000..207d545b --- /dev/null +++ b/compiler/custom/and3_dec.py @@ -0,0 +1,156 @@ +# 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 +from vector import vector +import design +from sram_factory import factory +from globals import OPTS +from tech import layer + + +class and3_dec(design.design): + """ + This is an AND with configurable drive strength. + """ + def __init__(self, name, size=1, height=None, add_wells=True): + design.design.__init__(self, name) + debug.info(1, "Creating and3_dec {}".format(name)) + self.add_comment("size: {}".format(size)) + self.size = size + self.height = height + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.add_pins() + self.create_modules() + self.create_insts() + + def create_modules(self): + self.nand = factory.create(module_type="nand3_dec", + height=self.height) + + self.inv = factory.create(module_type="inv_dec", + height=self.height, + size=self.size) + + self.add_mod(self.nand) + self.add_mod(self.inv) + + def create_layout(self): + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + + self.width = self.nand.width + self.inv.width + self.height = self.nand.height + + self.place_insts() + self.add_wires() + self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + self.add_pin("A", "INPUT") + self.add_pin("B", "INPUT") + self.add_pin("C", "INPUT") + self.add_pin("Z", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def create_insts(self): + self.nand_inst = self.add_inst(name="pand3_dec_nand", + mod=self.nand) + self.connect_inst(["A", "B", "C", "zb_int", "vdd", "gnd"]) + + self.inv_inst = self.add_inst(name="pand3_dec_inv", + mod=self.inv) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + + def place_insts(self): + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) + + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + if OPTS.tech_name == "sky130": + for name in ["vdd", "gnd"]: + for inst in [self.nand_inst, self.inv_inst]: + self.copy_layout_pin(inst, name) + else: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + + def add_wires(self): + # nand Z to inv A + z1_pin = self.nand_inst.get_pin("Z") + a2_pin = self.inv_inst.get_pin("A") + if OPTS.tech_name == "sky130": + mid1_point = vector(a2_pin.cx(), z1_pin.cy()) + else: + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(self.route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + + def add_layout_pins(self): + pin = self.inv_inst.get_pin("Z") + self.add_layout_pin_rect_center(text="Z", + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + for pin_name in ["A", "B", "C"]: + pin = self.nand_inst.get_pin(pin_name) + self.add_layout_pin_rect_center(text=pin_name, + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + def analytical_delay(self, corner, slew, load=0.0): + """ Calculate the analytical delay of DFF-> INV -> INV """ + nand_delay = self.nand.analytical_delay(corner, + slew=slew, + load=self.inv.input_load()) + inv_delay = self.inv.analytical_delay(corner, + slew=nand_delay.slew, + load=load) + return nand_delay + inv_delay + + def get_stage_efforts(self, external_cout, inp_is_rise=False): + """Get the stage efforts of the A or B -> Z path""" + stage_effort_list = [] + stage1_cout = self.inv.get_cin() + stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) + stage_effort_list.append(stage1) + last_stage_is_rise = stage1.is_rise + + stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise) + stage_effort_list.append(stage2) + + return stage_effort_list + + def get_cin(self): + """Return the relative input capacitance of a single input""" + return self.nand.get_cin() + diff --git a/compiler/custom/and4_dec.py b/compiler/custom/and4_dec.py new file mode 100644 index 00000000..9c68f78b --- /dev/null +++ b/compiler/custom/and4_dec.py @@ -0,0 +1,159 @@ +# 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 +from vector import vector +import design +from sram_factory import factory +from globals import OPTS +from tech import layer + + +class and4_dec(design.design): + """ + This is an AND with configurable drive strength. + """ + def __init__(self, name, size=1, height=None, add_wells=True): + + design.design.__init__(self, name) + + debug.info(1, "Creating and4_dec {}".format(name)) + self.add_comment("size: {}".format(size)) + self.size = size + self.height = height + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.add_pins() + self.create_modules() + self.create_insts() + + def create_modules(self): + self.nand = factory.create(module_type="nand4_dec", + height=self.height) + + self.inv = factory.create(module_type="inv_dec", + height=self.height, + size=self.size) + + self.add_mod(self.nand) + self.add_mod(self.inv) + + def create_layout(self): + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + + self.width = self.nand.width + self.inv.width + self.height = self.nand.height + + self.place_insts() + self.add_wires() + self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + self.add_pin("A", "INPUT") + self.add_pin("B", "INPUT") + self.add_pin("C", "INPUT") + self.add_pin("D", "INPUT") + self.add_pin("Z", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def create_insts(self): + self.nand_inst = self.add_inst(name="pand4_dec_nand", + mod=self.nand) + self.connect_inst(["A", "B", "C", "D", "zb_int", "vdd", "gnd"]) + + self.inv_inst = self.add_inst(name="pand4_dec_inv", + mod=self.inv) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + + def place_insts(self): + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) + + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + if OPTS.tech_name == "sky130": + for name in ["vdd", "gnd"]: + for inst in [self.nand_inst, self.inv_inst]: + self.copy_layout_pin(inst, name) + else: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + + def add_wires(self): + # nand Z to inv A + z1_pin = self.nand_inst.get_pin("Z") + a2_pin = self.inv_inst.get_pin("A") + if OPTS.tech_name == "sky130": + mid1_point = vector(a2_pin.cx(), z1_pin.cy()) + else: + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(self.route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + + def add_layout_pins(self): + pin = self.inv_inst.get_pin("Z") + self.add_layout_pin_rect_center(text="Z", + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + for pin_name in ["A", "B", "C"]: + pin = self.nand_inst.get_pin(pin_name) + self.add_layout_pin_rect_center(text=pin_name, + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + def analytical_delay(self, corner, slew, load=0.0): + """ Calculate the analytical delay of DFF-> INV -> INV """ + nand_delay = self.nand.analytical_delay(corner, + slew=slew, + load=self.inv.input_load()) + inv_delay = self.inv.analytical_delay(corner, + slew=nand_delay.slew, + load=load) + return nand_delay + inv_delay + + def get_stage_efforts(self, external_cout, inp_is_rise=False): + """Get the stage efforts of the A or B -> Z path""" + stage_effort_list = [] + stage1_cout = self.inv.get_cin() + stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) + stage_effort_list.append(stage1) + last_stage_is_rise = stage1.is_rise + + stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise) + stage_effort_list.append(stage2) + + return stage_effort_list + + def get_cin(self): + """Return the relative input capacitance of a single input""" + return self.nand.get_cin() + diff --git a/compiler/modules/dff.py b/compiler/custom/dff.py similarity index 95% rename from compiler/modules/dff.py rename to compiler/custom/dff.py index e319459e..c8fdb4b0 100644 --- a/compiler/modules/dff.py +++ b/compiler/custom/dff.py @@ -61,7 +61,7 @@ class dff(design.design): #Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width. return parameter["dff_clk_cin"] - def build_graph(self, graph, inst_name, port_nets): + 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) + self.add_graph_edges(graph, port_nets) diff --git a/compiler/custom/inv_dec.py b/compiler/custom/inv_dec.py new file mode 100644 index 00000000..80fdb74e --- /dev/null +++ b/compiler/custom/inv_dec.py @@ -0,0 +1,80 @@ +# 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 design +from tech import GDS, layer, spice, parameter +import logical_effort +import utils +import debug + + +class inv_dec(design.design): + """ + INV for address decoders. + """ + + pin_names = ["A", "Z", "vdd", "gnd"] + type_list = ["INPUT", "OUTPUT", "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("inv_dec", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "inv_dec", GDS["unit"]) + + def __init__(self, name="inv_dec", height=None): + design.design.__init__(self, name) + + self.width = inv_dec.width + self.height = inv_dec.height + self.pin_map = inv_dec.pin_map + self.add_pin_types(self.type_list) + + def analytical_power(self, corner, load): + """Returns dynamic and leakage power. Results in nW""" + c_eff = self.calculate_effective_capacitance(load) + freq = spice["default_event_frequency"] + power_dyn = self.calc_dynamic_power(corner, c_eff, freq) + power_leak = spice["inv_leakage"] + + total_power = self.return_power(power_dyn, power_leak) + return total_power + + def calculate_effective_capacitance(self, load): + """Computes effective capacitance. Results in fF""" + c_load = load + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) + + return transition_prob * (c_load + c_para) + + def input_load(self): + """ + Return the capacitance of the gate connection in generic capacitive + units relative to the minimum width of a transistor + """ + return self.nmos_size + self.pmos_size + + def get_stage_effort(self, cout, inp_is_rise=True): + """ + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 1 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) + + 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) diff --git a/compiler/custom/nand2_dec.py b/compiler/custom/nand2_dec.py new file mode 100644 index 00000000..c806bf5a --- /dev/null +++ b/compiler/custom/nand2_dec.py @@ -0,0 +1,85 @@ +# 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 design +from tech import GDS, layer, spice, parameter, drc +import logical_effort +import utils + + +class nand2_dec(design.design): + """ + 2-input NAND decoder for address decoders. + """ + + pin_names = ["A", "B", "Z", "vdd", "gnd"] + type_list = ["INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("nand2_dec", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "nand2_dec", GDS["unit"]) + + def __init__(self, name="nand2_dec", height=None): + design.design.__init__(self, name) + + self.width = nand2_dec.width + self.height = nand2_dec.height + self.pin_map = nand2_dec.pin_map + self.add_pin_types(self.type_list) + + # FIXME: For now... + size = 1 + self.size = size + self.nmos_size = 2 * size + self.pmos_size = parameter["beta"] * size + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + + def analytical_power(self, corner, load): + """Returns dynamic and leakage power. Results in nW""" + c_eff = self.calculate_effective_capacitance(load) + freq = spice["default_event_frequency"] + power_dyn = self.calc_dynamic_power(corner, c_eff, freq) + power_leak = spice["nand2_leakage"] + + total_power = self.return_power(power_dyn, power_leak) + return total_power + + def calculate_effective_capacitance(self, load): + """Computes effective capacitance. Results in fF""" + c_load = load + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) + transition_prob = 0.1875 + return transition_prob * (c_load + c_para) + + def input_load(self): + """Return the relative input capacitance of a single input""" + return self.nmos_size + self.pmos_size + + def get_stage_effort(self, cout, inp_is_rise=True): + """ + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 2 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) + + 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) + diff --git a/compiler/custom/nand3_dec.py b/compiler/custom/nand3_dec.py new file mode 100644 index 00000000..5eea68de --- /dev/null +++ b/compiler/custom/nand3_dec.py @@ -0,0 +1,85 @@ +# 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 design +from tech import GDS, layer, spice, parameter, drc +import logical_effort +import utils + + +class nand3_dec(design.design): + """ + 3-input NAND decoder for address decoders. + """ + + pin_names = ["A", "B", "C", "Z", "vdd", "gnd"] + type_list = ["INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("nand3_dec", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "nand3_dec", GDS["unit"]) + + def __init__(self, name="nand3_dec", height=None): + design.design.__init__(self, name) + + self.width = nand3_dec.width + self.height = nand3_dec.height + self.pin_map = nand3_dec.pin_map + self.add_pin_types(self.type_list) + + # FIXME: For now... + size = 1 + self.size = size + self.nmos_size = 2 * size + self.pmos_size = parameter["beta"] * size + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + + def analytical_power(self, corner, load): + """Returns dynamic and leakage power. Results in nW""" + c_eff = self.calculate_effective_capacitance(load) + freq = spice["default_event_frequency"] + power_dyn = self.calc_dynamic_power(corner, c_eff, freq) + power_leak = spice["nand3_leakage"] + + total_power = self.return_power(power_dyn, power_leak) + return total_power + + def calculate_effective_capacitance(self, load): + """Computes effective capacitance. Results in fF""" + c_load = load + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) + transition_prob = 0.1875 + return transition_prob * (c_load + c_para) + + def input_load(self): + """Return the relative input capacitance of a single input""" + return self.nmos_size + self.pmos_size + + def get_stage_effort(self, cout, inp_is_rise=True): + """ + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 2 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) + + 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) + diff --git a/compiler/custom/nand4_dec.py b/compiler/custom/nand4_dec.py new file mode 100644 index 00000000..df3eee14 --- /dev/null +++ b/compiler/custom/nand4_dec.py @@ -0,0 +1,85 @@ +# 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 design +from tech import GDS, layer, spice, parameter, drc +import logical_effort +import utils + + +class nand4_dec(design.design): + """ + 2-input NAND decoder for address decoders. + """ + + pin_names = ["A", "B", "C", "D", "Z", "vdd", "gnd"] + type_list = ["INPUT", "INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("nand4_dec", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "nand4_dec", GDS["unit"]) + + def __init__(self, name="nand4_dec", height=None): + design.design.__init__(self, name) + + self.width = nand4_dec.width + self.height = nand4_dec.height + self.pin_map = nand4_dec.pin_map + self.add_pin_types(self.type_list) + + # FIXME: For now... + size = 1 + self.size = size + self.nmos_size = 2 * size + self.pmos_size = parameter["beta"] * size + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + + def analytical_power(self, corner, load): + """Returns dynamic and leakage power. Results in nW""" + c_eff = self.calculate_effective_capacitance(load) + freq = spice["default_event_frequency"] + power_dyn = self.calc_dynamic_power(corner, c_eff, freq) + power_leak = spice["nand4_leakage"] + + total_power = self.return_power(power_dyn, power_leak) + return total_power + + def calculate_effective_capacitance(self, load): + """Computes effective capacitance. Results in fF""" + c_load = load + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) + transition_prob = 0.1875 + return transition_prob * (c_load + c_para) + + def input_load(self): + """Return the relative input capacitance of a single input""" + return self.nmos_size + self.pmos_size + + def get_stage_effort(self, cout, inp_is_rise=True): + """ + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 2 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) + + 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) + diff --git a/compiler/modules/tri_gate.py b/compiler/custom/tri_gate.py similarity index 100% rename from compiler/modules/tri_gate.py rename to compiler/custom/tri_gate.py diff --git a/compiler/modules/write_driver.py b/compiler/custom/write_driver.py similarity index 100% rename from compiler/modules/write_driver.py rename to compiler/custom/write_driver.py diff --git a/compiler/debug.py b/compiler/debug.py index a902bca0..f07471cc 100644 --- a/compiler/debug.py +++ b/compiler/debug.py @@ -40,7 +40,7 @@ def error(str, return_value=0): log("ERROR: file {0}: line {1}: {2}\n".format( os.path.basename(filename), line_number, str)) - if globals.OPTS.debug_level > 0: + if globals.OPTS.debug_level > 0 and return_value != 0: import pdb pdb.set_trace() assert return_value == 0 diff --git a/compiler/gen_stimulus.py b/compiler/gen_stimulus.py index 0c5d988c..cfea17d4 100755 --- a/compiler/gen_stimulus.py +++ b/compiler/gen_stimulus.py @@ -52,11 +52,12 @@ import sram class fake_sram(sram.sram): """ This is an SRAM that doesn't actually create itself, just computes the sizes. """ - def __init__(self, word_size, num_words, num_banks, name): + def __init__(self, word_size, num_words, num_banks, name, num_spare_rows): self.name = name self.word_size = word_size self.num_words = num_words self.num_banks = num_banks + self.num_spare_rows = num_spare_rows c = reload(__import__(OPTS.bitcell)) self.mod_bitcell = getattr(c, OPTS.bitcell) self.bitcell = self.mod_bitcell() @@ -75,7 +76,10 @@ d.period = period # Set the load of outputs and slew of inputs d.set_load_slew(load,slew) # Set the probe address/bit -probe_address = "1" * sram.addr_size +if (self.num_spare_rows == 0): + probe_address = "1" * sram.addr_size +else: + probe_address = "0" + ("1" * sram.addr_size - 1) probe_data = sram.word_size - 1 d.set_probe(probe_address, probe_data) diff --git a/compiler/globals.py b/compiler/globals.py index 92780c1a..a192ebc9 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -99,6 +99,9 @@ def parse_args(): # Alias SCMOS to 180nm if OPTS.tech_name == "scmos": OPTS.tech_name = "scn4m_subm" + # Alias s8 to sky130 + if OPTS.tech_name == "s8": + OPTS.tech_name = "sky130" return (options, args) @@ -214,25 +217,18 @@ def setup_bitcell(): if OPTS.num_r_ports > 0: ports += "{}r".format(OPTS.num_r_ports) - OPTS.bitcell = "bitcell_"+ports - OPTS.replica_bitcell = "replica_bitcell_"+ports - OPTS.dummy_bitcell = "dummy_bitcell_"+ports - else: - OPTS.replica_bitcell = "replica_" + OPTS.bitcell - OPTS.replica_bitcell = "dummy_" + OPTS.bitcell + if ports != "": + OPTS.bitcell_suffix = "_" + ports + OPTS.bitcell = "bitcell" + OPTS.bitcell_suffix # See if bitcell exists try: __import__(OPTS.bitcell) - __import__(OPTS.replica_bitcell) - __import__(OPTS.dummy_bitcell) except ImportError: # Use the pbitcell if we couldn't find a custom bitcell # or its custom replica bitcell # Use the pbitcell (and give a warning if not in unit test mode) OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell = "replica_pbitcell" - OPTS.replica_bitcell = "dummy_pbitcell" if not OPTS.is_unit_test: debug.warning("Using the parameterized bitcell which may have suboptimal density.") debug.info(1, "Using bitcell: {}".format(OPTS.bitcell)) @@ -539,7 +535,7 @@ def report_status(): debug.error("{0} is not an integer in config file.".format(OPTS.sram_size)) if type(OPTS.write_size) is not int and OPTS.write_size is not None: debug.error("{0} is not an integer in config file.".format(OPTS.write_size)) - + # If a write mask is specified by the user, the mask write size should be the same as # the word size so that an entire word is written at once. if OPTS.write_size is not None: diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 393631e5..158ac37b 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -8,8 +8,8 @@ import debug import design from sram_factory import factory -from math import log -from tech import drc +from math import log, ceil +from tech import drc, layer from vector import vector from globals import OPTS @@ -31,6 +31,9 @@ class bank(design.design): else: self.num_wmasks = 0 + if not self.num_spare_cols: + self.num_spare_cols = 0 + if name == "": name = "bank_{0}_{1}".format(self.word_size, self.num_words) design.design.__init__(self, name) @@ -77,12 +80,12 @@ class bank(design.design): def add_pins(self): """ Adding pins for Bank module""" for port in self.read_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): self.add_pin("dout{0}_{1}".format(port, bit), "OUTPUT") for port in self.all_ports: self.add_pin(self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]), "OUTPUT") for port in self.write_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): self.add_pin("din{0}_{1}".format(port, bit), "INPUT") for port in self.all_ports: for bit in range(self.addr_size): @@ -101,6 +104,8 @@ class bank(design.design): self.add_pin("w_en{0}".format(port), "INPUT") for bit in range(self.num_wmasks): self.add_pin("bank_wmask{0}_{1}".format(port, bit), "INPUT") + for bit in range(self.num_spare_cols): + self.add_pin("bank_spare_wen{0}_{1}".format(port, bit), "INPUT") for port in self.all_ports: self.add_pin("wl_en{0}".format(port), "INPUT") self.add_pin("vdd", "POWER") @@ -123,23 +128,25 @@ class bank(design.design): def route_rbl(self, port): """ Route the rbl_bl and rbl_wl """ - - bl_pin_name = self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]) - bl_pin = self.bitcell_array_inst.get_pin(bl_pin_name) - # This will ensure the pin is only on the top or bottom edge + + # Connect the rbl to the port data pin + bl_pin = self.port_data_inst[port].get_pin("rbl_bl") if port % 2: - via_offset = bl_pin.uc() + vector(0, 1.5 * self.m2_pitch) - left_right_offset = vector(self.max_x_offset, via_offset.y) + pin_pos = bl_pin.uc() + pin_offset = pin_pos + vector(0, self.m3_pitch) + left_right_offset = vector(self.max_x_offset, pin_offset.y) else: - via_offset = bl_pin.bc() - vector(0, 1.5 * self.m2_pitch) - left_right_offset = vector(self.min_x_offset, via_offset.y) + pin_pos = bl_pin.bc() + pin_offset = pin_pos - vector(0, self.m3_pitch) + left_right_offset = vector(self.min_x_offset, pin_offset.y) self.add_via_stack_center(from_layer=bl_pin.layer, to_layer="m3", - offset=via_offset) + offset=pin_offset) + self.add_path(bl_pin.layer, [pin_offset, pin_pos]) self.add_layout_pin_segment_center(text="rbl_bl{0}".format(port), layer="m3", start=left_right_offset, - end=via_offset) + end=pin_offset) def route_bitlines(self, port): """ Route the bitlines depending on the port type rw, w, or r. """ @@ -213,11 +220,12 @@ class bank(design.design): # Place the col decoder left aligned with wordline driver # This is also placed so that it's supply rails do not align with the SRAM-level # control logic to allow control signals to easily pass over in M3 - # by placing 1/2 a cell pitch down + # by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs + # may be routed in M3 or M4 x_offset = self.central_bus_width[port] + self.port_address.wordline_driver.width if self.col_addr_size > 0: x_offset += self.column_decoder.width + self.col_addr_bus_width - y_offset = 0.5 * self.dff.height + self.column_decoder.height + y_offset = 1.25 * self.dff.height + self.column_decoder.height else: y_offset = 0 self.column_decoder_offsets[port] = vector(-x_offset, -y_offset) @@ -254,10 +262,14 @@ class bank(design.design): # UPPER RIGHT QUADRANT # Place the col decoder right aligned with wordline driver # Above the bitcell array with a well spacing + # This is also placed so that it's supply rails do not align with the SRAM-level + # control logic to allow control signals to easily pass over in M3 + # by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs + # may be routed in M3 or M4 x_offset = self.bitcell_array_right + self.central_bus_width[port] + self.port_address.wordline_driver.width if self.col_addr_size > 0: x_offset += self.column_decoder.width + self.col_addr_bus_width - y_offset = self.bitcell_array_top + 0.5 * self.dff.height + self.column_decoder.height + y_offset = self.bitcell_array_top + 1.25 * self.dff.height + self.column_decoder.height else: y_offset = self.bitcell_array_top self.column_decoder_offsets[port] = vector(x_offset, y_offset) @@ -288,13 +300,14 @@ class bank(design.design): """ Computes the required sizes to create the bank """ self.num_cols = int(self.words_per_row * self.word_size) - self.num_rows = int(self.num_words / self.words_per_row) + self.num_rows_temp = int(self.num_words / self.words_per_row) + self.num_rows = self.num_rows_temp + self.num_spare_rows - self.row_addr_size = int(log(self.num_rows, 2)) + self.row_addr_size = ceil(log(self.num_rows, 2)) self.col_addr_size = int(log(self.words_per_row, 2)) self.addr_size = self.col_addr_size + self.row_addr_size - debug.check(self.num_rows * self.num_cols==self.word_size * self.num_words, + debug.check(self.num_rows_temp * self.num_cols==self.word_size * self.num_words, "Invalid bank sizes.") debug.check(self.addr_size==self.col_addr_size + self.row_addr_size, "Invalid address break down.") @@ -303,7 +316,7 @@ class bank(design.design): self.input_control_signals = [] port_num = 0 for port in range(OPTS.num_rw_ports): - self.input_control_signals.append(["w_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) + self.input_control_signals.append(["s_en{}".format(port_num), "w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) port_num += 1 for port in range(OPTS.num_w_ports): self.input_control_signals.append(["w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) @@ -316,7 +329,7 @@ class bank(design.design): self.num_control_lines = [len(x) for x in self.input_control_signals] # The width of this bus is needed to place other modules (e.g. decoder) for each port - self.central_bus_width = [self.m2_pitch * x + self.m2_width for x in self.num_control_lines] + self.central_bus_width = [self.m3_pitch * x + self.m3_width for x in self.num_control_lines] # These will be outputs of the gaters if this is multibank, if not, normal signals. self.control_signals = [] @@ -325,6 +338,7 @@ class bank(design.design): self.control_signals.append(["gated_" + str for str in self.input_control_signals[port]]) else: self.control_signals.append(self.input_control_signals[port]) + # The central bus is the column address (one hot) and row address (binary) if self.col_addr_size>0: @@ -356,7 +370,7 @@ class bank(design.design): self.add_mod(self.port_data[port]) self.port_address = factory.create(module_type="port_address", - cols=self.num_cols, + cols=self.num_cols + self.num_spare_cols, rows=self.num_rows) self.add_mod(self.port_address) @@ -364,7 +378,7 @@ class bank(design.design): self.num_rbl = len(self.all_ports) self.bitcell_array = factory.create(module_type="replica_bitcell_array", - cols=self.num_cols, + cols=self.num_cols + self.num_spare_cols, rows=self.num_rows, left_rbl=1, right_rbl=1 if len(self.all_ports)>1 else 0, @@ -382,7 +396,7 @@ class bank(design.design): mod=self.bitcell_array) temp = [] - for col in range(self.num_cols): + for col in range(self.num_cols + self.num_spare_cols): for bitline in self.bitline_names: temp.append("{0}_{1}".format(bitline, col)) for rbl in range(self.num_rbl): @@ -416,14 +430,14 @@ class bank(design.design): rbl_br_name=self.bitcell_array.get_rbl_br_name(self.port_rbl_map[port]) temp.append(rbl_bl_name) temp.append(rbl_br_name) - for col in range(self.num_cols): + for col in range(self.num_cols + self.num_spare_cols): temp.append("{0}_{1}".format(self.bl_names[port], col)) temp.append("{0}_{1}".format(self.br_names[port], col)) if port in self.read_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): temp.append("dout{0}_{1}".format(port, bit)) if port in self.write_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): temp.append("din{0}_{1}".format(port, bit)) # Will be empty if no col addr lines sel_names = ["sel{0}_{1}".format(port, x) for x in range(self.num_col_addr_lines)] @@ -435,6 +449,8 @@ class bank(design.design): temp.append("w_en{0}".format(port)) for bit in range(self.num_wmasks): temp.append("bank_wmask{0}_{1}".format(port, bit)) + for bit in range(self.num_spare_cols): + temp.append("bank_spare_wen{0}_{1}".format(port, bit)) temp.extend(["vdd", "gnd"]) self.connect_inst(temp) @@ -490,18 +506,20 @@ 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") + self.dff =factory.create(module_type="dff") if self.col_addr_size == 0: return elif self.col_addr_size == 1: - self.column_decoder = factory.create(module_type="pinvbuf", height=self.dff.height) + self.column_decoder = factory.create(module_type="pinvbuf", + height=self.dff.height) elif self.col_addr_size == 2: - self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", height=self.dff.height) + self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", + height=self.dff.height) + elif self.col_addr_size == 3: - self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", height=self.dff.height) + self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", + height=self.dff.height) else: # No error checking before? debug.error("Invalid column decoder?", -1) @@ -569,9 +587,23 @@ class bank(design.design): def route_supplies(self): """ Propagate all vdd/gnd pins up to this level for all modules """ + # Copy only the power pins already on the power layer + # (this won't add vias to internal bitcell pins, for example) for inst in self.insts: - self.copy_power_pins(inst, "vdd") - self.copy_power_pins(inst, "gnd") + self.copy_power_pins(inst, "vdd", add_vias=False) + self.copy_power_pins(inst, "gnd", add_vias=False) + + # If we use the pinvbuf as the decoder, we need to add power pins. + # Other decoders already have them. + if self.col_addr_size == 1: + for port in self.all_ports: + inst = self.column_decoder_inst[port] + for pin_name in ["vdd", "gnd"]: + pin_list = inst.get_pins(pin_name) + for pin in pin_list: + self.add_power_pin(pin_name, + pin.center(), + start_layer=pin.layer) def route_bank_select(self, port): """ Route the bank select logic. """ @@ -594,7 +626,7 @@ class bank(design.design): # Connect the inverter output to the central bus 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) + bus_pos = vector(self.bus_pins[port][name].cx(), out_pos.y) self.add_path("m3", [out_pos, bus_pos]) self.add_via_center(layers=self.m2_stack, offset=bus_pos) @@ -629,19 +661,20 @@ class bank(design.design): # Overall central bus width. It includes all the column mux lines, # and control lines. - self.bus_xoffset = [None] * len(self.all_ports) + self.bus_pins = [None] * len(self.all_ports) # Port 0 # The bank is at (0,0), so this is to the left of the y-axis. # 2 pitches on the right for vias/jogs to access the inputs control_bus_offset = vector(-self.m3_pitch * self.num_control_lines[0] - self.m3_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="m2", - offset=control_bus_offset, - names=self.control_signals[0], - length=control_bus_length, - vertical=True, - make_pins=(self.num_banks==1)) + self.bus_pins[0] = self.create_bus(layer="m2", + offset=control_bus_offset, + names=self.control_signals[0], + length=control_bus_length, + vertical=True, + make_pins=(self.num_banks==1), + pitch=self.m3_pitch) # Port 1 if len(self.all_ports)==2: @@ -650,12 +683,13 @@ class bank(design.design): control_bus_offset = vector(self.bitcell_array_right + self.m3_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="m2", - offset=control_bus_offset, - names=list(reversed(self.control_signals[1])), - length=control_bus_length, - vertical=True, - make_pins=(self.num_banks==1)) + self.bus_pins[1] = self.create_bus(layer="m2", + offset=control_bus_offset, + names=list(reversed(self.control_signals[1])), + length=control_bus_length, + vertical=True, + make_pins=(self.num_banks==1), + pitch=self.m3_pitch) def route_port_data_to_bitcell_array(self, port): """ Routing of BL and BR between port data and bitcell array """ @@ -676,6 +710,11 @@ class bank(design.design): inst1_br_name=inst1_br_name, inst2_bl_name=inst2_bl_name, inst2_br_name=inst2_br_name) + + # connect spare bitlines + for i in range(self.num_spare_cols): + self.connect_bitline(inst1, inst2, inst1_bl_name.format(self.num_cols+i), "spare" + inst2_bl_name.format(i)) + self.connect_bitline(inst1, inst2, inst1_br_name.format(self.num_cols+i), "spare" + inst2_br_name.format(i)) # Connect the replica bitlines rbl_bl_name=self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]) @@ -686,7 +725,7 @@ class bank(design.design): def route_port_data_out(self, port): """ Add pins for the port data out """ - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): data_pin = self.port_data_inst[port].get_pin("dout_{0}".format(bit)) self.add_layout_pin_rect_center(text="dout{0}_{1}".format(port, bit), layer=data_pin.layer, @@ -707,16 +746,21 @@ class bank(design.design): def route_port_data_in(self, port): """ Connecting port data in """ - for row in range(self.word_size): + for row in range(self.word_size + self.num_spare_cols): data_name = "din_{}".format(row) din_name = "din{0}_{1}".format(port, row) self.copy_layout_pin(self.port_data_inst[port], data_name, din_name) - if self.word_size: + if self.write_size: for row in range(self.num_wmasks): wmask_name = "bank_wmask_{}".format(row) bank_wmask_name = "bank_wmask{0}_{1}".format(port, row) self.copy_layout_pin(self.port_data_inst[port], wmask_name, bank_wmask_name) + + for col in range(self.num_spare_cols): + sparecol_name = "bank_spare_wen{}".format(col) + bank_sparecol_name = "bank_spare_wen{0}_{1}".format(port, col) + self.copy_layout_pin(self.port_data_inst[port], sparecol_name, bank_sparecol_name) def channel_route_bitlines(self, inst1, inst2, num_bits, inst1_bl_name="bl_{}", inst1_br_name="br_{}", @@ -799,28 +843,47 @@ class bank(design.design): for row in range(self.num_rows): # The mid guarantees we exit the input cell to the right. - driver_wl_pos = self.port_address_inst[port].get_pin("wl_{}".format(row)).rc() - bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row)).lc() + driver_wl_pin = self.port_address_inst[port].get_pin("wl_{}".format(row)) + driver_wl_pos = driver_wl_pin.rc() + bitcell_wl_pin = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row)) + bitcell_wl_pos = bitcell_wl_pin.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("m1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + self.add_path(driver_wl_pin.layer, [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + self.add_via_stack_center(from_layer=driver_wl_pin.layer, + to_layer=bitcell_wl_pin.layer, + offset=bitcell_wl_pos, + directions=("H", "H")) def route_port_address_right(self, port): """ Connecting Wordline driver output to Bitcell WL connection """ for row in range(self.num_rows): # The mid guarantees we exit the input cell to the right. - driver_wl_pos = self.port_address_inst[port].get_pin("wl_{}".format(row)).lc() - bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row)).rc() + driver_wl_pin = self.port_address_inst[port].get_pin("wl_{}".format(row)) + driver_wl_pos = driver_wl_pin.lc() + bitcell_wl_pin = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row)) + bitcell_wl_pos = bitcell_wl_pin.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("m1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + self.add_path(driver_wl_pin.layer, [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + self.add_via_stack_center(from_layer=driver_wl_pin.layer, + to_layer=bitcell_wl_pin.layer, + offset=bitcell_wl_pos, + directions=("H", "H")) def route_column_address_lines(self, port): """ Connecting the select lines of column mux to the address bus """ if not self.col_addr_size>0: return + if OPTS.tech_name == "sky130": + stack = self.m2_stack + pitch = self.m3_pitch + else: + stack = self.m1_stack + pitch = self.m2_pitch + if self.col_addr_size == 1: # Connect to sel[0] and sel[1] @@ -840,9 +903,9 @@ class bank(design.design): self.copy_layout_pin(self.column_decoder_inst[port], decoder_name, addr_name) if port % 2: - offset = self.column_decoder_inst[port].ll() - vector(self.num_col_addr_lines * self.m2_nonpref_pitch, 0) + offset = self.column_decoder_inst[port].ll() - vector((self.num_col_addr_lines + 1) * pitch, 0) else: - offset = self.column_decoder_inst[port].lr() + vector(self.m2_nonpref_pitch, 0) + offset = self.column_decoder_inst[port].lr() + vector(pitch, 0) decode_pins = [self.column_decoder_inst[port].get_pin(x) for x in decode_names] @@ -852,7 +915,7 @@ class bank(design.design): route_map = list(zip(decode_pins, column_mux_pins)) self.create_vertical_channel_route(route_map, offset, - self.m1_stack) + stack) def add_lvs_correspondence_points(self): """ @@ -908,39 +971,32 @@ class bank(design.design): # pre-decoder and this connection is in metal3 connection = [] connection.append((self.prefix + "p_en_bar{}".format(port), - self.port_data_inst[port].get_pin("p_en_bar").lc(), - self.port_data_inst[port].get_pin("p_en_bar").layer)) + self.port_data_inst[port].get_pin("p_en_bar"))) 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(), - self.bitcell_array_inst.get_pin(rbl_wl_name).layer)) + self.bitcell_array_inst.get_pin(rbl_wl_name))) 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(), - self.port_data_inst[port].get_pin("w_en").layer)) - else: - connection.append((self.prefix + "w_en{}".format(port), - self.port_data_inst[port].get_pin("w_en").lc(), - self.port_data_inst[port].get_pin("w_en").layer)) + connection.append((self.prefix + "w_en{}".format(port), + self.port_data_inst[port].get_pin("w_en"))) if port in self.read_ports: connection.append((self.prefix + "s_en{}".format(port), - self.port_data_inst[port].get_pin("s_en").lc(), - self.port_data_inst[port].get_pin("s_en").layer)) + self.port_data_inst[port].get_pin("s_en"))) - for (control_signal, pin_pos, pin_layer) in connection: - if port==0: - y_offset = self.min_y_offset - else: - y_offset = self.max_y_offset - control_pos = vector(self.bus_xoffset[port][control_signal].x, y_offset) - if pin_layer == "m1": - self.add_wire(self.m1_stack, [control_pos, pin_pos]) - elif pin_layer == "m3": - self.add_wire(self.m2_stack[::-1], [control_pos, pin_pos]) + for (control_signal, pin) in connection: + control_pin = self.bus_pins[port][control_signal] + control_pos = vector(control_pin.cx(), pin.cy()) + # If the y doesn't overlap the bus, add a segment + if pin.cy() < control_pin.by(): + self.add_path("m2", [control_pos, control_pin.bc()]) + elif pin.cy() > control_pin.uy(): + self.add_path("m2", [control_pos, control_pin.uc()]) + self.add_path(pin.layer, [control_pos, pin.center()]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer="m2", + offset=control_pos) # clk to wordline_driver control_signal = self.prefix + "wl_en{}".format(port) @@ -950,7 +1006,7 @@ class bank(design.design): else: pin_pos = self.port_address_inst[port].get_pin("wl_en").bc() 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_x_offset = self.bus_pins[port][control_signal].cx() control_pos = vector(control_x_offset, mid_pos.y) self.add_wire(self.m1_stack, [pin_pos, mid_pos, control_pos]) self.add_via_center(layers=self.m1_stack, diff --git a/compiler/modules/bitcell_base_array.py b/compiler/modules/bitcell_base_array.py index a8829fc3..7d241b4d 100644 --- a/compiler/modules/bitcell_base_array.py +++ b/compiler/modules/bitcell_base_array.py @@ -9,6 +9,7 @@ import debug import design from tech import cell_properties + class bitcell_base_array(design.design): """ Abstract base class for bitcell-arrays -- bitcell, dummy @@ -68,10 +69,10 @@ class bitcell_base_array(design.design): pin_names = self.cell.get_all_bitline_names() for pin in pin_names: - bitcell_pins.append(pin+"_{0}".format(col)) + 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(pin + "_{0}".format(row)) bitcell_pins.append("vdd") bitcell_pins.append("gnd") @@ -85,39 +86,28 @@ class bitcell_base_array(design.design): 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), + 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), + 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), + 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), + offset=wl_pin.ll().scale(0, 1), width=self.width, height=wl_pin.height()) - # For non-square via stacks, vertical/horizontal direction refers to the stack orientation in 2d space - # Default uses prefered directions for each layer; this cell property is only currently used by s8 tech (03/20) - try: - bitcell_power_pin_directions = cell_properties.bitcell_power_pin_directions - except AttributeError: - bitcell_power_pin_directions = None - - # Add vdd/gnd via stacks + # Copy a vdd/gnd layout pin from every cell for row in range(self.row_size): for col in range(self.column_size): - inst = self.cell_inst[row,col] + 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(), - directions=bitcell_power_pin_directions, - start_layer=pin.layer) + self.copy_layout_pin(inst, pin_name) def _adjust_x_offset(self, xoffset, col, col_offset): tempx = xoffset @@ -137,11 +127,10 @@ class bitcell_base_array(design.design): 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 + 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): @@ -149,7 +138,6 @@ class bitcell_base_array(design.design): 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: @@ -161,7 +149,7 @@ class bitcell_base_array(design.design): else: dir_key = "" - self.cell_inst[row,col].place(offset=[tempx, tempy], - mirror=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/col_cap_array.py b/compiler/modules/col_cap_array.py new file mode 100644 index 00000000..ee9302d8 --- /dev/null +++ b/compiler/modules/col_cap_array.py @@ -0,0 +1,93 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +from bitcell_base_array import bitcell_base_array +from sram_factory import factory +from globals import OPTS +from tech import cell_properties + + +class col_cap_array(bitcell_base_array): + """ + Generate a dummy row/column for the replica array. + """ + def __init__(self, cols, rows, column_offset=0, mirror=0, name=""): + super().__init__(cols, rows, name, column_offset) + self.mirror = mirror + + self.no_instances = True + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + """ Create and connect the netlist """ + self.add_modules() + self.add_pins() + self.create_instances() + + def create_layout(self): + + self.place_array("dummy_r{0}_c{1}", self.mirror) + self.add_layout_pins() + self.add_boundary() + self.DRC_LVS() + + def add_modules(self): + """ Add the modules used in this design """ + self.dummy_cell = factory.create(module_type="col_cap_{}".format(OPTS.bitcell)) + self.add_mod(self.dummy_cell) + + self.cell = factory.create(module_type="bitcell") + + def create_instances(self): + """ Create the module instances used in this design """ + self.cell_inst = {} + for col in range(self.column_size): + for row in range(self.row_size): + name = "bit_r{0}_c{1}".format(row, col) + self.cell_inst[row, col]=self.add_inst(name=name, + mod=self.dummy_cell) + self.connect_inst(self.get_bitcell_pins(col, row)) + + 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 + """ + + pin_name = cell_properties.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), + "vdd"] + + return bitcell_pins + + def add_layout_pins(self): + """ Add the layout pins """ + + 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) + + # Add vdd/gnd via stacks + 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(), + start_layer=pin.layer) + diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 078dc3ae..a4e26a6f 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -20,7 +20,7 @@ class control_logic(design.design): Dynamically generated Control logic for the total SRAM circuit. """ - def __init__(self, num_rows, words_per_row, word_size, sram=None, port_type="rw", name=""): + def __init__(self, num_rows, words_per_row, word_size, spare_columns=None, sram=None, port_type="rw", name=""): """ Constructor """ name = "control_logic_" + port_type design.design.__init__(self, name) @@ -35,7 +35,12 @@ class control_logic(design.design): self.word_size = word_size self.port_type = port_type - self.num_cols = word_size * words_per_row + if not spare_columns: + self.num_spare_cols = 0 + else: + self.num_spare_cols = spare_columns + + self.num_cols = word_size * words_per_row + self.num_spare_cols self.num_words = num_rows * words_per_row self.enable_delay_chain_resizing = False @@ -102,7 +107,7 @@ class control_logic(design.design): # clk_buf drives a flop for every address addr_flops = math.log(self.num_words, 2) + math.log(self.words_per_row, 2) # plus data flops and control flops - num_flops = addr_flops + self.word_size + self.num_control_signals + num_flops = addr_flops + self.word_size + self.num_spare_cols + self.num_control_signals # each flop internally has a FO 5 approximately # plus about 5 fanouts for the control logic clock_fanout = 5 * num_flops + 5 @@ -130,7 +135,7 @@ class control_logic(design.design): # s_en drives every sense amp self.sen_and3 = factory.create(module_type="pand3", - size=self.word_size, + size=self.word_size + self.num_spare_cols, height=dff_height) self.add_mod(self.sen_and3) @@ -358,7 +363,7 @@ class control_logic(design.design): # list of output control signals (for making a vertical bus) if self.port_type == "rw": - self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "clk_buf", "we_bar", "cs"] + self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "we_bar", "clk_buf", "cs"] elif self.port_type == "r": self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs_bar", "cs"] else: @@ -384,10 +389,10 @@ 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("m2", - offset, - self.internal_bus_list, - height) + self.input_bus = self.create_vertical_bus("m2", + offset, + self.internal_bus_list, + height) def create_instances(self): """ Create all the instances """ @@ -493,7 +498,7 @@ class control_logic(design.design): # Connect to the rail level with the vdd rail # Use pen since it is in every type of control logic vdd_ypos = self.p_en_bar_nand_inst.get_pin("vdd").by() - in_pos = vector(self.rail_offsets["rbl_bl_delay"].x, vdd_ypos) + in_pos = vector(self.input_bus["rbl_bl_delay"].cx(), vdd_ypos) mid1 = vector(out_pos.x, in_pos.y) self.add_wire(self.m1_stack, [out_pos, mid1, in_pos]) self.add_via_center(layers=self.m1_stack, @@ -518,12 +523,12 @@ class control_logic(design.design): def route_clk_buf(self): clk_pin = self.clk_buf_inst.get_pin("A") clk_pos = clk_pin.center() - self.add_layout_pin_segment_center(text="clk", - layer="m2", - start=clk_pos, - end=clk_pos.scale(1, 0)) - self.add_via_center(layers=self.m1_stack, - offset=clk_pos) + self.add_layout_pin_rect_center(text="clk", + layer="m2", + offset=clk_pos) + self.add_via_stack_center(from_layer=clk_pin.layer, + to_layer="m2", + offset=clk_pos) self.route_output_to_bus_jogged(self.clk_buf_inst, "clk_buf") @@ -548,17 +553,23 @@ class control_logic(design.design): def route_gated_clk_bar(self): clkbuf_map = zip(["A"], ["clk_buf"]) - self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.rail_offsets) + self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.input_bus) - out_pos = self.clk_bar_inst.get_pin("Z").center() - in_pos = self.gated_clk_bar_inst.get_pin("A").center() - self.add_zjog("m1", out_pos, in_pos) + out_pin = self.clk_bar_inst.get_pin("Z") + out_pos = out_pin.center() + in_pin = self.gated_clk_bar_inst.get_pin("A") + in_pos = in_pin.center() + self.add_zjog(out_pin.layer, out_pos, in_pos) + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer=in_pin.layer, + offset=in_pos) + # This is the second gate over, so it needs to be on M3 clkbuf_map = zip(["B"], ["cs"]) self.connect_vertical_bus(clkbuf_map, self.gated_clk_bar_inst, - self.rail_offsets, + self.input_bus, self.m2_stack[::-1]) # The pin is on M1, so we need another via as well b_pin = self.gated_clk_bar_inst.get_pin("B") @@ -586,12 +597,12 @@ class control_logic(design.design): clkbuf_map = zip(["A", "B"], ["clk_buf", "cs"]) self.connect_vertical_bus(clkbuf_map, self.gated_clk_buf_inst, - self.rail_offsets) + self.input_bus) clkbuf_map = zip(["Z"], ["gated_clk_buf"]) self.connect_vertical_bus(clkbuf_map, self.gated_clk_buf_inst, - self.rail_offsets, + self.input_bus, self.m2_stack[::-1]) # The pin is on M1, so we need another via as well z_pin = self.gated_clk_buf_inst.get_pin("Z") @@ -614,7 +625,7 @@ class control_logic(design.design): def route_wlen(self): wlen_map = zip(["A"], ["gated_clk_bar"]) - self.connect_vertical_bus(wlen_map, self.wl_en_inst, self.rail_offsets) + self.connect_vertical_bus(wlen_map, self.wl_en_inst, self.input_bus) self.connect_output(self.wl_en_inst, "Z", "wl_en") @@ -639,7 +650,7 @@ class control_logic(design.design): def route_pen(self): in_map = zip(["A", "B"], ["gated_clk_buf", "rbl_bl_delay"]) - self.connect_vertical_bus(in_map, self.p_en_bar_nand_inst, self.rail_offsets) + self.connect_vertical_bus(in_map, self.p_en_bar_nand_inst, self.input_bus) out_pin = self.p_en_bar_nand_inst.get_pin("Z") out_pos = out_pin.center() @@ -682,7 +693,7 @@ class control_logic(design.design): 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) + self.connect_vertical_bus(sen_map, self.s_en_gate_inst, self.input_bus) self.connect_output(self.s_en_gate_inst, "Z", "s_en") @@ -706,7 +717,7 @@ class control_logic(design.design): self.route_output_to_bus_jogged(self.rbl_bl_delay_inv_inst, "rbl_bl_delay_bar") rbl_map = zip(["A"], ["rbl_bl_delay"]) - self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets) + self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.input_bus) def create_wen_row(self): @@ -738,7 +749,7 @@ class control_logic(design.design): input_name = "cs" wen_map = zip(["A", "B", "C"], [input_name, "rbl_bl_delay_bar", "gated_clk_bar"]) - self.connect_vertical_bus(wen_map, self.w_en_gate_inst, self.rail_offsets) + self.connect_vertical_bus(wen_map, self.w_en_gate_inst, self.input_bus) self.connect_output(self.w_en_gate_inst, "Z", "w_en") @@ -761,13 +772,13 @@ 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, self.m2_stack[::-1]) + self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.input_bus, self.m2_stack[::-1]) # Connect the clock rail to the other clock rail # by routing in the supply rail track to avoid channel conflicts in_pos = self.ctrl_dff_inst.get_pin("clk").uc() mid_pos = in_pos + vector(0, self.and2.height) - rail_pos = vector(self.rail_offsets["clk_buf"].x, mid_pos.y) + rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y) self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos]) self.add_via_center(layers=self.m1_stack, offset=rail_pos) @@ -791,32 +802,41 @@ class control_logic(design.design): """ Create an output pin on the right side from the pin of a given instance. """ out_pin = inst.get_pin(pin_name) - right_pos = out_pin.center() + vector(self.width - out_pin.cx(), 0) + out_pos = out_pin.center() + right_pos = out_pos + vector(self.width - out_pin.cx(), 0) + + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer="m2", + offset=out_pos) self.add_layout_pin_segment_center(text=out_name, - layer="m1", - start=out_pin.center(), + layer="m2", + start=out_pos, end=right_pos) def route_supply(self): """ Add vdd and gnd to the instance cells """ + if OPTS.tech_name == "sky130": + supply_layer = "li" + else: + supply_layer = "m1" max_row_x_loc = max([inst.rx() for inst in self.row_end_inst]) for inst in self.row_end_inst: pins = inst.get_pins("vdd") for pin in pins: - if pin.layer == "m1": + if pin.layer == supply_layer: row_loc = pin.rc() pin_loc = vector(max_row_x_loc, pin.rc().y) - self.add_power_pin("vdd", pin_loc) - self.add_path("m1", [row_loc, pin_loc]) + self.add_power_pin("vdd", pin_loc, start_layer=pin.layer) + self.add_path(supply_layer, [row_loc, pin_loc]) pins = inst.get_pins("gnd") for pin in pins: - if pin.layer == "m1": + if pin.layer == supply_layer: row_loc = pin.rc() pin_loc = vector(max_row_x_loc, pin.rc().y) - self.add_power_pin("gnd", pin_loc) - self.add_path("m1", [row_loc, pin_loc]) + self.add_power_pin("gnd", pin_loc, start_layer=pin.layer) + self.add_path(supply_layer, [row_loc, pin_loc]) self.copy_layout_pin(self.delay_inst, "gnd") self.copy_layout_pin(self.delay_inst, "vdd") @@ -999,12 +1019,13 @@ class control_logic(design.design): def route_output_to_bus_jogged(self, inst, name): # Connect this at the bottom of the buffer - out_pos = inst.get_pin("Z").center() - mid1 = vector(out_pos.x, out_pos.y - 0.25 * inst.mod.height) - mid2 = vector(self.rail_offsets[name].x, mid1.y) - bus_pos = self.rail_offsets[name] + out_pin = inst.get_pin("Z") + out_pos = out_pin.center() + mid1 = vector(out_pos.x, out_pos.y - 0.4 * inst.mod.height) + mid2 = vector(self.input_bus[name].cx(), mid1.y) + bus_pos = self.input_bus[name].center() self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos]) - # The pin is on M1, so we need another via as well - self.add_via_center(layers=self.m1_stack, - offset=out_pos) + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer="m2", + offset=out_pos) diff --git a/compiler/modules/delay_chain.py b/compiler/modules/delay_chain.py index ad9bc1cc..c261138a 100644 --- a/compiler/modules/delay_chain.py +++ b/compiler/modules/delay_chain.py @@ -140,21 +140,20 @@ 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=self.m1_stack, - offset=a_pin.center()) - self.add_via_center(layers=self.m2_stack, - offset=a_pin.center()) + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m3", + 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.load_inst_map[inv][-1].get_pin("A") - self.add_via_center(layers=self.m1_stack, - offset=a_pin.center()) - self.add_via_center(layers=self.m1_stack, - offset=z_pin.center()) - self.add_via_center(layers=self.m2_stack, - offset=z_pin.center()) + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m2", + offset=a_pin.center()) + self.add_via_stack_center(from_layer=z_pin.layer, + to_layer="m3", + offset=z_pin.center()) self.add_path("m3", [z_pin.center(), a_max.center()]) # Route Z to the A of the next stage @@ -178,17 +177,22 @@ class delay_chain(design.design): load_list = self.load_inst_map[inst] for pin_name in ["vdd", "gnd"]: pin = load_list[0].get_pin(pin_name) - self.add_power_pin(pin_name, pin.rc() - vector(self.m1_pitch, 0)) + self.add_power_pin(pin_name, + pin.rc() - vector(self.m1_pitch, 0), + start_layer=pin.layer) - pin = load_list[-1].get_pin(pin_name) - self.add_power_pin(pin_name, pin.rc() - vector(0.5 * self.m1_pitch, 0)) + pin = load_list[-2].get_pin(pin_name) + self.add_power_pin(pin_name, + pin.rc() - vector(self.m1_pitch, 0), + start_layer=pin.layer) def add_layout_pins(self): # input is A pin of first inverter a_pin = self.driver_inst_list[0].get_pin("A") - self.add_via_center(layers=self.m1_stack, - offset=a_pin.center()) + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m2", + offset=a_pin.center()) self.add_layout_pin(text="in", layer="m2", offset=a_pin.ll().scale(1, 0), @@ -197,8 +201,9 @@ class delay_chain(design.design): # output is A pin of last load inverter last_driver_inst = self.driver_inst_list[-1] a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A") - self.add_via_center(layers=self.m1_stack, - offset=a_pin.center()) + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m2", + offset=a_pin.center()) mid_point = vector(a_pin.cx() + 3 * self.m2_width, a_pin.cy()) self.add_path("m2", [a_pin.center(), mid_point, mid_point.scale(1, 0)]) self.add_layout_pin_segment_center(text="out", diff --git a/compiler/modules/dff_array.py b/compiler/modules/dff_array.py index 62464834..d3f9b68e 100644 --- a/compiler/modules/dff_array.py +++ b/compiler/modules/dff_array.py @@ -7,12 +7,11 @@ # import debug import design -from tech import drc -from math import log from vector import vector from sram_factory import factory from globals import OPTS + class dff_array(design.design): """ This is a simple row (or multiple rows) of flops. @@ -52,42 +51,41 @@ class dff_array(design.design): self.add_mod(self.dff) def add_pins(self): - for row in range(self.rows): + for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_din_name(row,col), "INPUT") - for row in range(self.rows): + self.add_pin(self.get_din_name(row, col), "INPUT") + for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_dout_name(row,col), "OUTPUT") + self.add_pin(self.get_dout_name(row, col), "OUTPUT") self.add_pin("clk", "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") def create_dff_array(self): self.dff_insts={} - for row in range(self.rows): + for row in range(self.rows): for col in range(self.columns): - name = "dff_r{0}_c{1}".format(row,col) - self.dff_insts[row,col]=self.add_inst(name=name, - mod=self.dff) - instance_ports = [self.get_din_name(row,col), - self.get_dout_name(row,col)] + name = "dff_r{0}_c{1}".format(row, col) + self.dff_insts[row, col]=self.add_inst(name=name, + mod=self.dff) + 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): + for row in range(self.rows): for col in range(self.columns): - name = "dff_r{0}_c{1}".format(row,col) if (row % 2 == 0): - base = vector(col*self.dff.width,row*self.dff.height) + base = vector(col * self.dff.width, row * self.dff.height) mirror = "R0" else: - base = vector(col*self.dff.width,(row+1)*self.dff.height) + base = vector(col * self.dff.width, (row + 1) * self.dff.height) mirror = "MX" - self.dff_insts[row,col].place(offset=base, - mirror=mirror) + self.dff_insts[row, col].place(offset=base, + mirror=mirror) def get_din_name(self, row, col): if self.columns == 1: @@ -95,7 +93,7 @@ class dff_array(design.design): elif self.rows == 1: din_name = "din_{0}".format(col) else: - din_name = "din_{0}_{1}".format(row,col) + din_name = "din_{0}_{1}".format(row, col) return din_name @@ -105,61 +103,58 @@ class dff_array(design.design): elif self.rows == 1: dout_name = "dout_{0}".format(col) else: - dout_name = "dout_{0}_{1}".format(row,col) + dout_name = "dout_{0}_{1}".format(row, col) return dout_name - def add_layout_pins(self): for row in range(self.rows): - for col in range(self.columns): + for col in range(self.columns): # Continous vdd rail along with label. - vdd_pin=self.dff_insts[row,col].get_pin("vdd") - self.add_power_pin("vdd", vdd_pin.center()) + vdd_pin=self.dff_insts[row, col].get_pin("vdd") + self.add_power_pin("vdd", vdd_pin.center(), start_layer=vdd_pin.layer) # Continous gnd rail along with label. - gnd_pin=self.dff_insts[row,col].get_pin("gnd") - self.add_power_pin("gnd", gnd_pin.center()) + gnd_pin=self.dff_insts[row, col].get_pin("gnd") + self.add_power_pin("gnd", gnd_pin.center(), start_layer=gnd_pin.layer) - - 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=="m2","DFF D pin not on metal2") - self.add_layout_pin(text=self.get_din_name(row,col), + 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 == "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(), width=din_pin.width(), height=din_pin.height()) - dout_pin = self.dff_insts[row,col].get_pin("Q") - debug.check(dout_pin.layer=="m2","DFF Q pin not on metal2") - self.add_layout_pin(text=self.get_dout_name(row,col), + dout_pin = self.dff_insts[row, col].get_pin("Q") + 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(), width=dout_pin.width(), height=dout_pin.height()) - - # Create vertical spines to a single horizontal rail - 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=="m2","DFF clk pin not on metal2") + 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 == "m2", "DFF clk pin not on metal2") self.add_layout_pin_segment_center(text="clk", layer="m3", - start=vector(0,clk_ypos), - end=vector(self.width,clk_ypos)) + 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(self.dff.clk_pin) + clk_pin = self.dff_insts[0, col].get_pin(self.dff.clk_pin) # Make a vertical strip for each column self.add_rect(layer="m2", - offset=clk_pin.ll().scale(1,0), + 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=self.m2_stack, - offset=vector(clk_pin.cx(),clk_ypos)) + self.add_via_stack_center(from_layer=clk_pin.layer, + to_layer="m3", + offset=vector(clk_pin.cx(), clk_ypos)) def get_clk_cin(self): """Return the total capacitance (in relative units) that the clock is loaded by in the dff array""" diff --git a/compiler/modules/dff_buf.py b/compiler/modules/dff_buf.py index 0366b10b..a1e54a4d 100644 --- a/compiler/modules/dff_buf.py +++ b/compiler/modules/dff_buf.py @@ -7,7 +7,7 @@ # import debug import design -from tech import parameter +from tech import parameter, layer from tech import cell_properties as props from vector import vector from globals import OPTS @@ -52,7 +52,6 @@ class dff_buf(design.design): def create_layout(self): self.place_instances() self.width = self.inv2_inst.rx() - self.height = self.dff.height self.route_wires() self.add_layout_pins() @@ -120,39 +119,37 @@ class dff_buf(design.design): except AttributeError: pass 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)) def route_wires(self): + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + # Route dff q to inv1 a q_pin = self.dff_inst.get_pin("Q") a1_pin = self.inv1_inst.get_pin("A") - 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("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=self.m2_stack, - offset=a1_pin.center()) - self.add_via_center(layers=self.m1_stack, - offset=a1_pin.center()) + mid1 = vector(a1_pin.cx(), q_pin.cy()) + self.add_path(q_pin.layer, [q_pin.center(), mid1, a1_pin.center()], width=q_pin.height()) + self.add_via_stack_center(from_layer=a1_pin.layer, + to_layer=q_pin.layer, + offset=a1_pin.center()) # Route inv1 z to inv2 a z1_pin = self.inv1_inst.get_pin("Z") a2_pin = self.inv2_inst.get_pin("A") - 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("m1", [z1_pin.center(), self.mid_qb_pos, mid2, a2_pin.center()]) + self.mid_qb_pos = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy()) + self.add_zjog(z1_pin.layer, z1_pin.center(), 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="m1", + layer=vdd_pin.layer, offset=vdd_pin.ll(), width=self.width, height=vdd_pin.height()) @@ -160,7 +157,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="m1", + layer=gnd_pin.layer, offset=gnd_pin.ll(), width=self.width, height=vdd_pin.height()) @@ -180,22 +177,25 @@ class dff_buf(design.design): height=din_pin.height()) dout_pin = self.inv2_inst.get_pin("Z") - mid_pos = dout_pin.center() + vector(self.m1_nonpref_pitch, 0) - q_pos = mid_pos - vector(0, self.m2_pitch) + mid_pos = dout_pin.center() + vector(self.m2_nonpref_pitch, 0) + q_pos = mid_pos - vector(0, 2 * self.m2_nonpref_pitch) self.add_layout_pin_rect_center(text="Q", layer="m2", offset=q_pos) - self.add_path("m1", [dout_pin.center(), mid_pos, q_pos]) - self.add_via_center(layers=self.m1_stack, - offset=q_pos) + self.add_path(self.route_layer, [dout_pin.center(), mid_pos, q_pos]) + self.add_via_stack_center(from_layer=dout_pin.layer, + to_layer="m2", + offset=q_pos) - qb_pos = self.mid_qb_pos + vector(0, self.m2_pitch) + qb_pos = self.mid_qb_pos + vector(0, 2 * self.m2_nonpref_pitch) self.add_layout_pin_rect_center(text="Qb", layer="m2", offset=qb_pos) - self.add_path("m1", [self.mid_qb_pos, qb_pos]) - self.add_via_center(layers=self.m1_stack, - offset=qb_pos) + self.add_path(self.route_layer, [self.mid_qb_pos, qb_pos]) + a2_pin = self.inv2_inst.get_pin("A") + self.add_via_stack_center(from_layer=a2_pin.layer, + to_layer="m2", + offset=qb_pos) def get_clk_cin(self): """Return the total capacitance (in relative units) that the clock is loaded by in the dff""" diff --git a/compiler/modules/dff_buf_array.py b/compiler/modules/dff_buf_array.py index b608cdeb..1cbd9284 100644 --- a/compiler/modules/dff_buf_array.py +++ b/compiler/modules/dff_buf_array.py @@ -167,11 +167,11 @@ class dff_buf_array(design.design): for col in range(self.columns): # Continous vdd rail along with label. vdd_pin=self.dff_insts[row, col].get_pin("vdd") - self.add_power_pin("vdd", vdd_pin.lc()) + self.add_power_pin("vdd", vdd_pin.lc(), start_layer=vdd_pin.layer) # Continous gnd rail along with label. gnd_pin=self.dff_insts[row, col].get_pin("gnd") - self.add_power_pin("gnd", gnd_pin.lc()) + self.add_power_pin("gnd", gnd_pin.lc(), start_layer=gnd_pin.layer) def add_layout_pins(self): diff --git a/compiler/modules/dummy_array.py b/compiler/modules/dummy_array.py index de15d0ce..f4b240da 100644 --- a/compiler/modules/dummy_array.py +++ b/compiler/modules/dummy_array.py @@ -38,20 +38,19 @@ class dummy_array(bitcell_base_array): def add_modules(self): """ Add the modules used in this design """ - self.dummy_cell = factory.create(module_type="dummy_bitcell") + self.dummy_cell = factory.create(module_type="dummy_{}".format(OPTS.bitcell)) self.add_mod(self.dummy_cell) self.cell = factory.create(module_type="bitcell") - def create_instances(self): """ Create the module instances used in this design """ self.cell_inst = {} for col in range(self.column_size): for row in range(self.row_size): name = "bit_r{0}_c{1}".format(row, col) - self.cell_inst[row,col]=self.add_inst(name=name, - mod=self.dummy_cell) + self.cell_inst[row, col]=self.add_inst(name=name, + mod=self.dummy_cell) self.connect_inst(self.get_bitcell_pins(col, row)) def input_load(self): @@ -60,7 +59,7 @@ class dummy_array(bitcell_base_array): def get_wordline_cin(self): """Get the relative input capacitance from the wordline connections in all the bitcell""" - #A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns + # A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns bitcell_wl_cin = self.cell.get_wl_cin() total_cin = bitcell_wl_cin * self.column_size return total_cin diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index c1593932..3233bdc8 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -11,8 +11,6 @@ import math from sram_factory import factory from vector import vector from globals import OPTS -from errors import drc_error -from tech import cell_properties, layer class hierarchical_decoder(design.design): @@ -28,12 +26,8 @@ class hierarchical_decoder(design.design): self.pre3x8_inst = [] b = factory.create(module_type="bitcell") - try: - self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple - except AttributeError: - self.cell_multiple = 1 - self.cell_height = self.cell_multiple * b.height - + self.cell_height = b.height + self.num_outputs = num_outputs self.num_inputs = math.ceil(math.log(self.num_outputs, 2)) (self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs) @@ -41,41 +35,6 @@ class hierarchical_decoder(design.design): self.create_netlist() if not OPTS.netlist_only: self.create_layout() - - def find_decoder_height(self): - """ - Dead code. This would dynamically determine the bitcell multiple, - but I just decided to hard code it in the tech file if it is not 1 - because a DRC tool would be required even to run in front-end mode. - """ - b = factory.create(module_type="bitcell") - - # Old behavior - if OPTS.netlist_only: - return (b.height, 1) - - # Search for the smallest multiple that works - cell_multiple = 1 - while cell_multiple < 5: - cell_height = cell_multiple * b.height - # debug.info(2,"Trying mult = {0} height={1}".format(cell_multiple, cell_height)) - try: - and3 = factory.create(module_type="pand3", - height=cell_height) - except drc_error: - # debug.info(1, "Incrementing decoder height by 1 bitcell height {}".format(b.height)) - pass - else: - (drc_errors, lvs_errors) = and3.DRC_LVS(force_check=True) - total_errors = drc_errors + lvs_errors - if total_errors == 0: - debug.info(1, "Decoder height is multiple of {} bitcells.".format(cell_multiple)) - return (cell_height, cell_multiple) - - cell_multiple += 1 - - else: - debug.error("Couldn't find a valid decoder height multiple.", -1) def create_netlist(self): self.add_modules() @@ -88,24 +47,32 @@ class hierarchical_decoder(design.design): self.setup_layout_constants() self.place_pre_decoder() self.place_row_decoder() + + self.height = max(self.predecoder_height, self.row_decoder_height) + self.bus_space + self.route_inputs() self.route_outputs() self.route_decoder_bus() self.route_vdd_gnd() + self.offset_all_coordinates() + + self.width = self.and_inst[0].rx() + self.m1_space + self.add_boundary() self.DRC_LVS() def add_modules(self): - self.inv = factory.create(module_type="pinv", - height=self.cell_height) - self.add_mod(self.inv) - self.and2 = factory.create(module_type="pand2", + self.and2 = factory.create(module_type="and2_dec", height=self.cell_height) self.add_mod(self.and2) - self.and3 = factory.create(module_type="pand3", + + self.and3 = factory.create(module_type="and3_dec", height=self.cell_height) self.add_mod(self.and3) + # TBD + # self.and4 = factory.create(module_type="and4_dec") + # self.add_mod(self.and4) self.add_decoders() @@ -176,56 +143,49 @@ class hierarchical_decoder(design.design): -1) # Calculates height and width of pre-decoder, - if self.no_of_pre3x8 > 0: + # FIXME: Update with 4x16 + if self.no_of_pre3x8 > 0 and self.no_of_pre2x4 > 0: + self.predecoder_width = max(self.pre3_8.width, self.pre2_4.width) + elif self.no_of_pre3x8 > 0: self.predecoder_width = self.pre3_8.width else: self.predecoder_width = self.pre2_4.width - - self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 - # We may have more than one bitcell per decoder row - self.num_rows = math.ceil(self.num_outputs / self.cell_multiple) - # We will place this many final decoders per row - self.decoders_per_row = math.ceil(self.num_outputs / self.num_rows) - # We will need to use M2 and M3 in the vertical bus if we have multiple decoders per row - if self.decoders_per_row == 1: - self.decoder_bus_pitch = self.m2_pitch - elif self.decoders_per_row == 2: - self.decoder_bus_pitch = self.m3_pitch + # How much space between each predecoder + self.predecoder_spacing = 2 * self.and2.height + self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 \ + + (self.no_of_pre2x4 + self.no_of_pre3x8 - 1) * self.predecoder_spacing + + # Inputs to cells are on input layer + # Outputs from cells are on output layer + if OPTS.tech_name == "sky130": + self.bus_layer = "m1" + self.bus_directions = "nonpref" + self.bus_pitch = self.m1_pitch + self.bus_space = self.m2_space + self.input_layer = "m2" + self.output_layer = "li" + self.output_layer_pitch = self.li_pitch else: - debug.error("Insufficient layers for multi-bit height decoder.", -1) + self.bus_layer = "m2" + self.bus_directions = "pref" + self.bus_pitch = self.m2_pitch + self.bus_space = self.m2_space + # These two layers being the same requires a special jog + # to ensure to conflicts with the output layers + self.input_layer = "m1" + self.output_layer = "m3" + self.output_layer_pitch = self.m3_pitch - # Calculates height and width of row-decoder - if (self.num_inputs == 4 or self.num_inputs == 5): - nand_width = self.and2.width - nand_inputs = 2 - else: - nand_width = self.and3.width - nand_inputs = 3 - self.internal_routing_width = self.decoder_bus_pitch * (self.total_number_of_predecoder_outputs + 1) - self.row_decoder_height = self.inv.height * self.num_rows - - decoder_input_wire_height = self.decoders_per_row * nand_inputs * self.m2_pitch - # print(self.decoders_per_row, nand_inputs) - # print(decoder_input_wire_height, self.cell_height) - if decoder_input_wire_height > self.cell_height: - debug.warning("Cannot fit multi-bit decoder routes per row.") - # debug.check(decoder_input_wire_height < self.cell_height, "Cannot fit multi-bit decoder routes per row.") + # Two extra pitches between modules on left and right + self.internal_routing_width = self.total_number_of_predecoder_outputs * self.bus_pitch + self.bus_pitch + self.row_decoder_height = self.and2.height * self.num_outputs - self.input_routing_width = (self.num_inputs + 1) * self.m2_pitch + # Extra bus space for supply contacts + self.input_routing_width = self.num_inputs * self.bus_pitch + self.bus_space - # Calculates height and width of hierarchical decoder - # Add extra pitch for good measure - self.height = max(self.predecoder_height, self.row_decoder_height) + self.m2_pitch - self.width = self.input_routing_width + self.predecoder_width \ - + self.internal_routing_width \ - + self.decoders_per_row * nand_width + self.inv.width - def route_inputs(self): """ Create input bus for the predecoders """ - # inputs should be as high as the decoders - input_height = self.no_of_pre2x4 * self.pre2_4.height + self.no_of_pre3x8 * self.pre3_8.height - # Find the left-most predecoder min_x = 0 if self.no_of_pre2x4 > 0: @@ -235,10 +195,10 @@ 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_bus = self.create_vertical_pin_bus(layer="m2", + self.input_bus = self.create_vertical_pin_bus(layer=self.bus_layer, offset=input_offset, names=input_bus_names, - length=input_height) + length=self.predecoder_height) self.route_input_to_predecodes() @@ -248,14 +208,12 @@ class hierarchical_decoder(design.design): for i in range(2): index = pre_num * 2 + i - input_pos = self.input_bus["addr_{}".format(index)] + input_pos = self.input_bus["addr_{}".format(index)].center() in_name = "in_{}".format(i) decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name) - # To prevent conflicts, we will offset each input connect so - # that it aligns with the vdd/gnd rails - decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch)) + decoder_offset = decoder_pin.center() input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) self.route_input_bus(decoder_offset, input_offset) @@ -264,14 +222,12 @@ class hierarchical_decoder(design.design): for i in range(3): index = pre_num * 3 + i + self.no_of_pre2x4 * 2 - input_pos = self.input_bus["addr_{}".format(index)] + input_pos = self.input_bus["addr_{}".format(index)].center() in_name = "in_{}".format(i) decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name) - # To prevent conflicts, we will offset each input connect so - # that it aligns with the vdd/gnd rails - decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch)) + decoder_offset = decoder_pin.center() input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) self.route_input_bus(decoder_offset, input_offset) @@ -282,13 +238,14 @@ class hierarchical_decoder(design.design): vertical M2 coordinate to the predecode inputs """ - self.add_via_stack_center(from_layer="m2", - to_layer="m3", + self.add_via_stack_center(from_layer=self.bus_layer, + to_layer=self.input_layer, offset=input_offset) - self.add_via_stack_center(from_layer="m2", - to_layer="m3", - offset=output_offset) - self.add_path("m3", [input_offset, output_offset]) + self.add_via_stack_center(from_layer=self.bus_layer, + to_layer=self.input_layer, + offset=output_offset, + directions=self.bus_directions) + self.add_path(self.input_layer, [input_offset, output_offset]) def add_pins(self): """ Add the module pins """ @@ -363,19 +320,19 @@ class hierarchical_decoder(design.design): if (self.num_inputs == 2): base = vector(-self.pre2_4.width, 0) else: - base= vector(-self.pre2_4.width, num * self.pre2_4.height) + base= vector(-self.pre2_4.width, num * (self.pre2_4.height + self.predecoder_spacing)) - self.pre2x4_inst[num].place(base - vector(2 * self.m2_pitch, 0)) + 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 """ if (self.num_inputs == 3): offset = vector(-self.pre_3_8.width, 0) else: - height = self.no_of_pre2x4 * self.pre2_4.height + num * self.pre3_8.height + height = self.no_of_pre2x4 * (self.pre2_4.height + self.predecoder_spacing) + num * (self.pre3_8.height + self.predecoder_spacing) offset = vector(-self.pre3_8.width, height) - self.pre3x8_inst[num].place(offset - vector(2 * self.m2_pitch, 0)) + self.pre3x8_inst[num].place(offset) def create_row_decoder(self): """ Create the row-decoder by placing AND2/AND3 and Inverters @@ -431,7 +388,6 @@ class hierarchical_decoder(design.design): if (self.num_inputs >= 4): self.place_decoder_and_array() - def place_decoder_and_array(self): """ Add a column of AND gates for final decode. @@ -452,9 +408,7 @@ class hierarchical_decoder(design.design): Add a column of AND gates for the decoder above the predecoders. """ - for inst_index in range(self.num_outputs): - row = math.floor(inst_index / self.decoders_per_row) - dec = inst_index % self.decoders_per_row + for row in range(self.num_outputs): if ((row % 2) == 0): y_off = and_mod.height * row mirror = "R0" @@ -462,32 +416,16 @@ class hierarchical_decoder(design.design): y_off = and_mod.height * (row + 1) mirror = "MX" - x_off = self.internal_routing_width + dec * and_mod.width - self.and_inst[inst_index].place(offset=vector(x_off, y_off), - mirror=mirror) + x_off = self.internal_routing_width + self.and_inst[row].place(offset=vector(x_off, y_off), + mirror=mirror) def route_outputs(self): """ Add the pins. """ - max_xoffset = max(x.rx() for x in self.and_inst) - - for output_index in range(self.num_outputs): - row_remainder = (output_index % self.decoders_per_row) - - and_inst = self.and_inst[output_index] - z_pin = and_inst.get_pin("Z") - if row_remainder == 0 and self.decoders_per_row > 1: - layer = "m3" - self.add_via_stack_center(from_layer=z_pin.layer, - to_layer="m3", - offset=z_pin.center()) - else: - layer = z_pin.layer - - self.add_layout_pin_segment_center(text="decode_{0}".format(output_index), - layer=layer, - start=z_pin.center(), - end=vector(max_xoffset, z_pin.cy())) + for row in range(self.num_outputs): + and_inst = self.and_inst[row] + self.copy_layout_pin(and_inst, "Z", "decode_{0}".format(row)) def route_decoder_bus(self): """ @@ -498,9 +436,9 @@ class hierarchical_decoder(design.design): if (self.num_inputs >= 4): # This leaves an offset for the predecoder output jogs input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)] - self.predecode_bus = self.create_vertical_pin_bus(layer="m2", - pitch=self.decoder_bus_pitch, - offset=vector(0, 0), + self.predecode_bus = self.create_vertical_pin_bus(layer=self.bus_layer, + pitch=self.bus_pitch, + offset=vector(self.bus_pitch, 0), names=input_bus_names, length=self.height) @@ -518,8 +456,9 @@ class hierarchical_decoder(design.design): predecode_name = "predecode_{}".format(pre_num * 4 + i) out_name = "out_{}".format(i) pin = self.pre2x4_inst[pre_num].get_pin(out_name) - x_offset = self.pre2x4_inst[pre_num].rx() + self.m2_pitch - self.route_predecode_bus_inputs(predecode_name, pin, x_offset) + x_offset = self.pre2x4_inst[pre_num].rx() + self.output_layer_pitch + y_offset = self.pre2x4_inst[pre_num].by() + i * self.cell_height + self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset) # FIXME: convert to connect_bus for pre_num in range(self.no_of_pre3x8): @@ -527,8 +466,9 @@ class hierarchical_decoder(design.design): predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4) out_name = "out_{}".format(i) pin = self.pre3x8_inst[pre_num].get_pin(out_name) - x_offset = self.pre3x8_inst[pre_num].rx() + self.m2_pitch - self.route_predecode_bus_inputs(predecode_name, pin, x_offset) + x_offset = self.pre3x8_inst[pre_num].rx() + self.output_layer_pitch + y_offset = self.pre3x8_inst[pre_num].by() + i * self.cell_height + self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset) def route_bus_to_decoder(self): """ @@ -542,12 +482,6 @@ class hierarchical_decoder(design.design): and the 128th AND3 is connected to [3,7,15] """ output_index = 0 - - if "li" in layer: - self.decoder_layers = [self.m1_stack, self.m2_stack[::-1]] - else: - self.decoder_layers = [self.m2_stack[::-1]] - debug.check(self.decoders_per_row <= len(self.decoder_layers), "Must have more layers for multi-height decoder.") if (self.num_inputs == 4 or self.num_inputs == 5): for index_B in self.predec_groups[1]: @@ -557,13 +491,11 @@ class hierarchical_decoder(design.design): predecode_name = "predecode_{}".format(index_A) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("A"), - output_index, - 0) + output_index) predecode_name = "predecode_{}".format(index_B) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("B"), - output_index, - 1) + output_index) output_index = output_index + 1 elif (self.num_inputs > 5): @@ -575,18 +507,15 @@ class hierarchical_decoder(design.design): predecode_name = "predecode_{}".format(index_A) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("A"), - output_index, - 0) + output_index) predecode_name = "predecode_{}".format(index_B) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("B"), - output_index, - 1) + output_index) predecode_name = "predecode_{}".format(index_C) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("C"), - output_index, - 2) + output_index) output_index = output_index + 1 def route_vdd_gnd(self): @@ -594,90 +523,90 @@ class hierarchical_decoder(design.design): Add a pin for each row of vdd/gnd which are must-connects next level up. """ + + if OPTS.tech_name == "sky130": + for n in ["vdd", "gnd"]: + pins = self.and_inst[0].get_pins(n) + for pin in pins: + self.add_rect(layer=pin.layer, + offset=pin.ll() + vector(0, self.bus_space), + width=pin.width(), + height=self.height - 2 * self.bus_space) - # The vias will be placed at the right of the cells. - xoffset = max(x.rx() for x in self.and_inst) - for num in range(0, self.num_outputs): - # Only add the power pin for the 1st in each row - if num % self.decoders_per_row: - continue - - for pin_name in ["vdd", "gnd"]: - # The nand and inv are the same height rows... - supply_pin = self.and_inst[num].get_pin(pin_name) - pin_pos = vector(xoffset, supply_pin.cy()) - self.add_path(supply_pin.layer, - [supply_pin.lc(), vector(xoffset, supply_pin.cy())]) - self.add_power_pin(name=pin_name, - loc=pin_pos, - start_layer=supply_pin.layer) - - # Copy the pins from the predecoders - for pre in self.pre2x4_inst + self.pre3x8_inst: - self.copy_layout_pin(pre, "vdd") - self.copy_layout_pin(pre, "gnd") + # This adds power vias at the top of each cell + # (except the last to keep them inside the boundary) + for i in self.and_inst[:-1]: + pins = i.get_pins(n) + for pin in pins: + self.add_power_pin(name=n, + loc=pin.uc(), + start_layer=pin.layer) + self.add_power_pin(name=n, + loc=pin.uc(), + start_layer=pin.layer) + + for i in self.pre2x4_inst + self.pre3x8_inst: + self.copy_layout_pin(i, n) + else: + # The vias will be placed at the right of the cells. + xoffset = max(x.rx() for x in self.and_inst) + 0.5 * self.m1_space + for row in range(0, self.num_outputs): + for pin_name in ["vdd", "gnd"]: + # The nand and inv are the same height rows... + supply_pin = self.and_inst[row].get_pin(pin_name) + pin_pos = vector(xoffset, supply_pin.cy()) + self.add_power_pin(name=pin_name, + loc=pin_pos, + start_layer=supply_pin.layer) + + # Copy the pins from the predecoders + for pre in self.pre2x4_inst + self.pre3x8_inst: + for pin_name in ["vdd", "gnd"]: + self.copy_layout_pin(pre, pin_name) - def route_predecode_bus_outputs(self, rail_name, pin, output_index, pin_index): + def route_predecode_bus_outputs(self, rail_name, pin, row): """ Connect the routing rail to the given metal1 pin using a routing track at the given y_offset """ - row_index = math.floor(output_index / self.decoders_per_row) - row_remainder = (output_index % self.decoders_per_row) - row_offset = row_index * self.and_inst[0].height - pin_pos = pin.center() - - # y_offset is the same for both the M2 and M4 routes so that the rail - # contacts align and don't cause problems - if pin_index == 0: - # Bottom pitch - y_offset = row_offset - elif pin_index == 1: - # One pitch from top - y_offset = row_offset + self.and_inst[0].height - self.m3_pitch - elif pin_index == 2: - # One pitch from bottom - y_offset = row_offset + self.m3_pitch - else: - debug.error("Invalid decoder pitch.") - - rail_pos = vector(self.predecode_bus[rail_name].x, y_offset) - mid_pos = vector(pin_pos.x, rail_pos.y) - self.add_wire(self.decoder_layers[row_remainder], [rail_pos, mid_pos, pin_pos]) + rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y) + self.add_path(self.input_layer, [rail_pos, pin_pos]) - self.add_via_stack_center(from_layer="m2", - to_layer=self.decoder_layers[row_remainder][0], - offset=rail_pos) + self.add_via_stack_center(from_layer=self.bus_layer, + to_layer=self.input_layer, + offset=rail_pos, + directions=self.bus_directions) self.add_via_stack_center(from_layer=pin.layer, - to_layer=self.decoder_layers[row_remainder][2], + to_layer=self.input_layer, offset=pin_pos, directions=("H", "H")) - def route_predecode_bus_inputs(self, rail_name, pin, x_offset): + def route_predecode_bus_inputs(self, rail_name, pin, x_offset, y_offset): """ Connect the routing rail to the given metal1 pin using a jog to the right of the cell at the given x_offset. """ # This routes the pin up to the rail, basically, to avoid conflicts. # It would be fixed with a channel router. - # pin_pos = pin.center() - # mid_point1 = vector(x_offset, pin_pos.y) - # mid_point2 = vector(x_offset, pin_pos.y + self.inv.height / 2) - # rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y) - # self.add_path("m1", [pin_pos, mid_point1, mid_point2, rail_pos]) + pin_pos = pin.rc() + mid_point1 = vector(x_offset, pin_pos.y) + mid_point2 = vector(x_offset, y_offset) + rail_pos = vector(self.predecode_bus[rail_name].cx(), mid_point2.y) + self.add_path(self.output_layer, [pin_pos, mid_point1, mid_point2, rail_pos]) - pin_pos = pin.center() - rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y) - self.add_path("m1", [pin_pos, rail_pos]) + # pin_pos = pin.center() + # rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y) + # self.add_path(self.output_layer, [pin_pos, rail_pos]) self.add_via_stack_center(from_layer=pin.layer, - to_layer="m1", + to_layer=self.output_layer, offset=pin_pos) - self.add_via_stack_center(from_layer="m1", - to_layer="m2", - offset=rail_pos) + self.add_via_stack_center(from_layer=self.bus_layer, + to_layer=self.output_layer, + offset=rail_pos, + directions=self.bus_directions) def input_load(self): if self.determine_predecodes(self.num_inputs)[1]==0: diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index 9a658546..a89f5fa6 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -8,29 +8,28 @@ import debug import design import math -import contact from vector import vector from sram_factory import factory -from tech import cell_properties +from globals import OPTS class hierarchical_predecode(design.design): """ - Pre 2x4 and 3x8 decoder shared code. + Pre 2x4 and 3x8 and TBD 4x16 decoder shared code. """ def __init__(self, name, input_number, height=None): self.number_of_inputs = input_number + b = factory.create(module_type="bitcell") if not height: - b = factory.create(module_type="bitcell") - try: - self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple - except AttributeError: - self.cell_multiple = 1 - self.cell_height = self.cell_multiple * b.height + self.cell_height = b.height + self.column_decoder = False else: self.cell_height = height - + # If we are pitch matched to the bitcell, it's a predecoder + # otherwise it's a column decoder (out of pgates) + self.column_decoder = (height != b.height) + self.number_of_outputs = int(math.pow(2, self.number_of_inputs)) design.design.__init__(self, name) @@ -44,34 +43,71 @@ class hierarchical_predecode(design.design): def add_modules(self): """ Add the INV and AND gate modules """ - - self.inv = factory.create(module_type="pinv", - height=self.cell_height) - self.add_mod(self.inv) - - self.add_and(self.number_of_inputs) + + debug.check(self.number_of_inputs < 4, + "Invalid number of predecode inputs: {}".format(self.number_of_inputs)) + + if self.column_decoder: + and_type = "pand{}".format(self.number_of_inputs) + inv_type = "pinv" + else: + and_type = "and{}_dec".format(self.number_of_inputs) + inv_type = "inv_dec" + self.and_mod = factory.create(module_type=and_type, + height=self.cell_height) self.add_mod(self.and_mod) - def add_and(self, inputs): - """ Create the NAND for the predecode input stage """ - if inputs==2: - self.and_mod = factory.create(module_type="pand2", - height=self.cell_height) - elif inputs==3: - self.and_mod = factory.create(module_type="pand3", - height=self.cell_height) - else: - debug.error("Invalid number of predecode inputs: {}".format(inputs), -1) - + # This uses the pinv_dec parameterized cell + self.inv = factory.create(module_type=inv_type, + height=self.cell_height, + size=1) + self.add_mod(self.inv) + + def create_layout(self): + """ The general organization is from left to right: + 1) a set of M2 rails for input signals + 2) a set of inverters to invert input signals + 3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs + 4) a set of AND gates for inversion + """ + self.setup_layout_constraints() + self.route_rails() + self.place_input_inverters() + self.place_and_array() + self.route() + self.add_boundary() + self.DRC_LVS() + def setup_layout_constraints(self): + # Inputs to cells are on input layer + # Outputs from cells are on output layer + if OPTS.tech_name == "sky130": + self.bus_layer = "m1" + self.bus_directions = "nonpref" + self.bus_pitch = self.m1_pitch + self.bus_space = 1.5 * self.m1_space + self.input_layer = "m2" + self.output_layer = "li" + self.output_layer_pitch = self.li_pitch + else: + self.bus_layer = "m2" + self.bus_directions = "pref" + self.bus_pitch = self.m2_pitch + self.bus_space = self.m2_space + # This requires a special jog to ensure to conflicts with the output layers + self.input_layer = "m1" + self.output_layer = "m1" + self.output_layer_pitch = self.m1_pitch + self.height = self.number_of_outputs * self.and_mod.height # x offset for input inverters - self.x_off_inv_1 = self.number_of_inputs * self.m2_pitch + self.m2_space + # +1 input for spacing for supply rail contacts + self.x_off_inv_1 = (self.number_of_inputs + 1) * self.bus_pitch + self.bus_pitch - # x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra m2 pitches - self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.m2_pitch + # x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra bus pitches + self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.bus_pitch # x offset to output inverters self.width = self.x_off_and + self.and_mod.width @@ -79,28 +115,30 @@ class hierarchical_predecode(design.design): def route_rails(self): """ 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, self.m3_pitch) - self.input_rails = self.create_vertical_pin_bus(layer="m2", - offset=offset, - names=input_names, - length=self.height - 2 * self.m1_pitch) + # Offsets for the perimeter spacing to other modules + # This uses m3 pitch to leave space for power routes + offset = vector(self.bus_pitch, self.bus_pitch) + self.input_rails = self.create_vertical_bus(layer=self.bus_layer, + offset=offset, + names=input_names, + length=self.height - 2 * self.bus_pitch) invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)] 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, self.m3_pitch) - self.decode_rails = self.create_vertical_bus(layer="m2", + offset = vector(self.x_off_inv_1 + self.inv.width + self.bus_pitch, self.bus_pitch) + self.decode_rails = self.create_vertical_bus(layer=self.bus_layer, offset=offset, names=decode_names, - length=self.height - 2 * self.m1_pitch) + length=self.height - 2 * self.bus_pitch) def create_input_inverters(self): """ Create the input inverters to invert input signals for the decode stage. """ - self.in_inst = [] + self.inv_inst = [] for inv_num in range(self.number_of_inputs): name = "pre_inv_{0}".format(inv_num) - self.in_inst.append(self.add_inst(name=name, - mod=self.inv)) + self.inv_inst.append(self.add_inst(name=name, + mod=self.inv)) self.connect_inst(["in_{0}".format(inv_num), "inbar_{0}".format(inv_num), "vdd", "gnd"]) @@ -108,6 +146,7 @@ class hierarchical_predecode(design.design): def place_input_inverters(self): """ Place the input inverters to invert input signals for the decode stage. """ for inv_num in range(self.number_of_inputs): + if (inv_num % 2 == 0): y_off = inv_num * (self.inv.height) mirror = "R0" @@ -115,8 +154,8 @@ class hierarchical_predecode(design.design): y_off = (inv_num + 1) * (self.inv.height) mirror="MX" offset = vector(self.x_off_inv_1, y_off) - self.in_inst[inv_num].place(offset=offset, - mirror=mirror) + self.inv_inst[inv_num].place(offset=offset, + mirror=mirror) def create_and_array(self, connections): """ Create the AND stage for the decodes """ @@ -151,22 +190,31 @@ class hierarchical_predecode(design.design): def route_inputs_to_rails(self): """ Route the uninverted inputs to the second set of rails """ + + top_and_gate = self.and_inst[-1] for num in range(self.number_of_inputs): - # 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 and gates. - y_offset = (num + self.number_of_inputs) * self.inv.height + contact.m2_via.width + self.m2_space + if num == 0: + pin = top_and_gate.get_pin("A") + elif num == 1: + pin = top_and_gate.get_pin("B") + elif num == 2: + pin = top_and_gate.get_pin("C") + elif num == 3: + pin = top_and_gate.get_pin("D") + else: + debug.error("Too many inputs for predecoder.", -1) + y_offset = pin.cy() 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("m1", [in_pos, a_pos]) - self.add_via_stack_center(from_layer="m1", - to_layer="m2", - offset=[self.input_rails[in_pin].x, y_offset]) - self.add_via_stack_center(from_layer="m1", - to_layer="m2", - offset=[self.decode_rails[a_pin].x, y_offset]) + in_pos = vector(self.input_rails[in_pin].cx(), y_offset) + a_pos = vector(self.decode_rails[a_pin].cx(), y_offset) + self.add_path(self.input_layer, [in_pos, a_pos]) + self.add_via_stack_center(from_layer=self.input_layer, + to_layer=self.bus_layer, + offset=[self.input_rails[in_pin].cx(), y_offset]) + self.add_via_stack_center(from_layer=self.input_layer, + to_layer=self.bus_layer, + offset=[self.decode_rails[a_pin].cx(), y_offset]) def route_output_and(self): """ @@ -188,31 +236,43 @@ class hierarchical_predecode(design.design): for inv_num in range(self.number_of_inputs): out_pin = "Abar_{}".format(inv_num) in_pin = "in_{}".format(inv_num) + + inv_out_pin = self.inv_inst[inv_num].get_pin("Z") # add output so that it is just below the vdd or gnd rail # since this is where the p/n devices are and there are no # pins in the and gates. - y_offset = (inv_num + 1) * self.inv.height - 3 * self.m1_space - inv_out_pin = self.in_inst[inv_num].get_pin("Z") inv_out_pos = inv_out_pin.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(inv_out_pin.layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos]) + y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch + right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0) + rail_pos = vector(self.decode_rails[out_pin].cx(), y_offset) + self.add_path(self.output_layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos]) + self.add_via_stack_center(from_layer=inv_out_pin.layer, - to_layer="m2", - offset=rail_pos) + to_layer=self.output_layer, + offset=inv_out_pos) + self.add_via_stack_center(from_layer=self.output_layer, + to_layer=self.bus_layer, + offset=rail_pos, + directions=self.bus_directions) # route input - pin = self.in_inst[inv_num].get_pin("A") - inv_in_pos = pin.lc() - in_pos = vector(self.input_rails[in_pin].x, inv_in_pos.y) - self.add_path("m1", [in_pos, inv_in_pos]) + pin = self.inv_inst[inv_num].get_pin("A") + inv_in_pos = pin.center() + in_pos = vector(self.input_rails[in_pin].cx(), inv_in_pos.y) + self.add_path(self.input_layer, [in_pos, inv_in_pos]) self.add_via_stack_center(from_layer=pin.layer, - to_layer="m1", + to_layer=self.input_layer, offset=inv_in_pos) - self.add_via_stack_center(from_layer="m1", - to_layer="m2", - offset=in_pos) + via=self.add_via_stack_center(from_layer=self.input_layer, + to_layer=self.bus_layer, + offset=in_pos) + # Create the input pin at this location on the rail + self.add_layout_pin_rect_center(text=in_pin, + layer=self.bus_layer, + offset=in_pos, + height=via.mod.second_layer_height, + width=via.mod.second_layer_width) def route_and_to_rails(self): # This 2D array defines the connection mapping @@ -230,44 +290,68 @@ class hierarchical_predecode(design.design): for rail_pin, gate_pin in zip(index_lst, gate_lst): pin = self.and_inst[k].get_pin(gate_pin) pin_pos = pin.center() - rail_pos = vector(self.decode_rails[rail_pin].x, pin_pos.y) - self.add_path("m1", [rail_pos, pin_pos]) - self.add_via_stack_center(from_layer="m1", - to_layer="m2", - offset=rail_pos) + rail_pos = vector(self.decode_rails[rail_pin].cx(), pin_pos.y) + self.add_path(self.input_layer, [rail_pos, pin_pos]) + self.add_via_stack_center(from_layer=self.input_layer, + to_layer=self.bus_layer, + offset=rail_pos, + directions=self.bus_directions) if gate_pin == "A": direction = None else: direction = ("H", "H") self.add_via_stack_center(from_layer=pin.layer, - to_layer="m1", + to_layer=self.input_layer, offset=pin_pos, directions=direction) def route_vdd_gnd(self): """ 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() + self.m1_space - # out_xoffset = self.and_inst[0].cx() + self.m1_space - for num in range(0, self.number_of_outputs): - # this will result in duplicate polygons for rails, but who cares - - # Route both supplies + # In sky130, we use hand-made decoder cells with vertical power + if OPTS.tech_name == "sky130" and not self.column_decoder: for n in ["vdd", "gnd"]: - and_pin = self.and_inst[num].get_pin(n) - supply_offset = and_pin.ll().scale(0, 1) - self.add_rect(layer=and_pin.layer, - offset=supply_offset, - width=self.and_inst[num].rx()) + # This makes a wire from top to bottom for both inv and and gates + for i in [self.inv_inst, self.and_inst]: + bot_pins = i[0].get_pins(n) + top_pins = i[-1].get_pins(n) + for (bot_pin, top_pin) in zip(bot_pins, top_pins): + self.add_rect(layer=bot_pin.layer, + offset=vector(bot_pin.lx(), self.bus_pitch), + width=bot_pin.width(), + height=top_pin.uy() - self.bus_pitch) + # This adds power vias at the top of each cell + # (except the last to keep them inside the boundary) + for i in self.inv_inst[:-1:2] + self.and_inst[:-1:2]: + pins = i.get_pins(n) + for pin in pins: + self.add_power_pin(name=n, + loc=pin.uc(), + start_layer=pin.layer) + self.add_power_pin(name=n, + loc=pin.uc(), + start_layer=pin.layer) + + # In other techs, we are using standard cell decoder cells with horizontal power + else: + for num in range(0, self.number_of_outputs): - # Add pins in two locations - for xoffset in [in_xoffset]: - pin_pos = vector(xoffset, and_pin.cy()) - self.add_power_pin(name=n, - loc=pin_pos, - start_layer=and_pin.layer) + # Route both supplies + for n in ["vdd", "gnd"]: + and_pins = self.and_inst[num].get_pins(n) + for and_pin in and_pins: + self.add_segment_center(layer=and_pin.layer, + start=vector(0, and_pin.cy()), + end=vector(self.width, and_pin.cy())) + + # Add pins in two locations + for xoffset in [self.inv_inst[0].lx() - self.bus_space, + self.and_inst[0].lx() - self.bus_space]: + pin_pos = vector(xoffset, and_pin.cy()) + self.add_power_pin(name=n, + loc=pin_pos, + start_layer=and_pin.layer) diff --git a/compiler/modules/hierarchical_predecode2x4.py b/compiler/modules/hierarchical_predecode2x4.py index b1aacc5a..9c7ddfa3 100644 --- a/compiler/modules/hierarchical_predecode2x4.py +++ b/compiler/modules/hierarchical_predecode2x4.py @@ -5,13 +5,10 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from tech import drc -import debug -import design -from vector import vector from hierarchical_predecode import hierarchical_predecode from globals import OPTS + class hierarchical_predecode2x4(hierarchical_predecode): """ Pre 2x4 decoder used in hierarchical_decoder. @@ -33,21 +30,6 @@ class hierarchical_predecode2x4(hierarchical_predecode): ["in_0", "in_1", "out_3", "vdd", "gnd"]] self.create_and_array(connections) - def create_layout(self): - """ The general organization is from left to right: - 1) a set of M2 rails for input signals - 2) a set of inverters to invert input signals - 3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs - 4) a set of AND gates for inversion - """ - self.setup_layout_constraints() - self.route_rails() - self.place_input_inverters() - self.place_and_array() - self.route() - self.add_boundary() - self.DRC_LVS() - def get_and_input_line_combination(self): """ These are the decoder connections of the AND gates to the A,B pins """ combination = [["Abar_0", "Abar_1"], diff --git a/compiler/modules/hierarchical_predecode3x8.py b/compiler/modules/hierarchical_predecode3x8.py index 4f2294f1..e8c44e48 100644 --- a/compiler/modules/hierarchical_predecode3x8.py +++ b/compiler/modules/hierarchical_predecode3x8.py @@ -5,13 +5,10 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from tech import drc -import debug -import design -from vector import vector from hierarchical_predecode import hierarchical_predecode from globals import OPTS + class hierarchical_predecode3x8(hierarchical_predecode): """ Pre 3x8 decoder used in hierarchical_decoder. @@ -37,22 +34,6 @@ class hierarchical_predecode3x8(hierarchical_predecode): ["in_0", "in_1", "in_2", "out_7", "vdd", "gnd"]] self.create_and_array(connections) - def create_layout(self): - """ - The general organization is from left to right: - 1) a set of M2 rails for input signals - 2) a set of inverters to invert input signals - 3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs - 4) a set of NAND gates for inversion - """ - self.setup_layout_constraints() - self.route_rails() - self.place_input_inverters() - self.place_and_array() - self.route() - self.add_boundary() - self.DRC_LVS() - def get_and_input_line_combination(self): """ These are the decoder connections of the NAND gates to the A,B,C pins """ combination = [["Abar_0", "Abar_1", "Abar_2"], diff --git a/compiler/modules/hierarchical_predecode4x16.py b/compiler/modules/hierarchical_predecode4x16.py new file mode 100644 index 00000000..4a258bfb --- /dev/null +++ b/compiler/modules/hierarchical_predecode4x16.py @@ -0,0 +1,64 @@ +# 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. +# +from hierarchical_predecode import hierarchical_predecode +from globals import OPTS + + +class hierarchical_predecode4x16(hierarchical_predecode): + """ + Pre 4x16 decoder used in hierarchical_decoder. + """ + def __init__(self, name, height=None): + hierarchical_predecode.__init__(self, name, 4, height) + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.add_pins() + self.add_modules() + self.create_input_inverters() + connections=[["inbar_0", "inbar_1", "inbar_2", "inbar_3", "out_0", "vdd", "gnd"], + ["in_0", "inbar_1", "inbar_2", "inbar_3", "out_1", "vdd", "gnd"], + ["inbar_0", "in_1", "inbar_2", "inbar_3", "out_2", "vdd", "gnd"], + ["in_0", "in_1", "inbar_2", "inbar_3", "out_3", "vdd", "gnd"], + ["inbar_0", "inbar_1", "in_2", "inbar_3", "out_4", "vdd", "gnd"], + ["in_0", "inbar_1", "in_2", "inbar_3", "out_5", "vdd", "gnd"], + ["inbar_0", "in_1", "in_2", "inbar_3", "out_6", "vdd", "gnd"], + ["in_0", "in_1", "in_2", "inbar_3", "out_7", "vdd", "gnd"], + ["inbar_0", "inbar_1", "inbar_2", "in_3", "out_0", "vdd", "gnd"], + ["in_0", "inbar_1", "inbar_2", "in_3", "out_1", "vdd", "gnd"], + ["inbar_0", "in_1", "inbar_2", "in_3", "out_2", "vdd", "gnd"], + ["in_0", "in_1", "inbar_2", "in_3", "out_3", "vdd", "gnd"], + ["inbar_0", "inbar_1", "in_2", "in_3", "out_4", "vdd", "gnd"], + ["in_0", "inbar_1", "in_2", "in_3", "out_5", "vdd", "gnd"], + ["inbar_0", "in_1", "in_2", "in_3", "out_6", "vdd", "gnd"], + ["in_0", "in_1", "in_2", "in_3", "out_7", "vdd", "gnd"] ] + + self.create_and_array(connections) + + def get_and_input_line_combination(self): + """ These are the decoder connections of the AND gates to the A,B pins """ + combination = [["Abar_0", "Abar_1", "Abar_2", "Abar_3"], + ["A_0", "Abar_1", "Abar_2", "Abar_3"], + ["Abar_0", "A_1", "Abar_2", "Abar_3"], + ["A_0", "A_1", "Abar_2", "Abar_3"], + ["Abar_0", "Abar_1", "A_2" , "Abar_3"], + ["A_0", "Abar_1", "A_2" , "Abar_3"], + ["Abar_0", "A_1", "A_2" , "Abar_3"], + ["A_0", "A_1", "A_2" , "Abar_3"], + ["Abar_0", "Abar_1", "Abar_2", "A_3"], + ["A_0", "Abar_1", "Abar_2", "A_3"], + ["Abar_0", "A_1", "Abar_2", "A_3"], + ["A_0", "A_1", "Abar_2", "A_3"], + ["Abar_0", "Abar_1", "A_2", "A_3"], + ["A_0", "Abar_1", "A_2", "A_3"], + ["Abar_0", "A_1", "A_2", "A_3"], + ["A_0", "A_1", "A_2", "A_3"]] + return combination diff --git a/compiler/modules/port_address.py b/compiler/modules/port_address.py index a27894ae..a73e536c 100644 --- a/compiler/modules/port_address.py +++ b/compiler/modules/port_address.py @@ -3,12 +3,12 @@ # Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # -from math import log +from math import log, ceil import debug import design from sram_factory import factory from vector import vector - +from tech import layer from globals import OPTS @@ -21,7 +21,7 @@ class port_address(design.design): self.num_cols = cols self.num_rows = rows - self.addr_size = int(log(self.num_rows, 2)) + self.addr_size = ceil(log(self.num_rows, 2)) if name == "": name = "port_address_{0}_{1}".format(cols, rows) @@ -41,6 +41,10 @@ class port_address(design.design): self.create_wordline_driver() def create_layout(self): + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" self.place_instances() self.route_layout() self.DRC_LVS() @@ -85,11 +89,19 @@ class port_address(design.design): def route_internal(self): for row in range(self.num_rows): # The pre/post is to access the pin from "outside" the cell to avoid DRCs - decoder_out_pos = self.row_decoder_inst.get_pin("decode_{}".format(row)).rc() - 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("m1", [decoder_out_pos, mid1, mid2, driver_in_pos]) + decoder_out_pin = self.row_decoder_inst.get_pin("decode_{}".format(row)) + decoder_out_pos = decoder_out_pin.rc() + driver_in_pin = self.wordline_driver_inst.get_pin("in_{}".format(row)) + driver_in_pos = driver_in_pin.lc() + self.add_zjog(self.route_layer, decoder_out_pos, driver_in_pos, var_offset=0.3) + + self.add_via_stack_center(from_layer=decoder_out_pin.layer, + to_layer=self.route_layer, + offset=decoder_out_pos) + + self.add_via_stack_center(from_layer=driver_in_pin.layer, + to_layer=self.route_layer, + offset=driver_in_pos) def add_modules(self): @@ -97,7 +109,7 @@ class port_address(design.design): num_outputs=self.num_rows) self.add_mod(self.row_decoder) - self.wordline_driver = factory.create(module_type="wordline_driver", + self.wordline_driver = factory.create(module_type="wordline_driver_array", rows=self.num_rows, cols=self.num_cols) self.add_mod(self.wordline_driver) @@ -139,7 +151,6 @@ class port_address(design.design): row_decoder_offset = vector(0, 0) wordline_driver_offset = vector(self.row_decoder.width, 0) - self.wordline_driver_inst.place(wordline_driver_offset) self.row_decoder_inst.place(row_decoder_offset) diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index 91e20a45..bd6b39e2 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -19,7 +19,7 @@ class port_data(design.design): """ def __init__(self, sram_config, port, name=""): - + sram_config.set_local_config(self) self.port = port if self.write_size is not None: @@ -27,6 +27,9 @@ class port_data(design.design): else: self.num_wmasks = 0 + if self.num_spare_cols is None: + self.num_spare_cols = 0 + if name == "": name = "port_data_{0}".format(self.port) design.design.__init__(self, name) @@ -102,7 +105,7 @@ class port_data(design.design): self.DRC_LVS() def add_pins(self): - """ Adding pins for port address module""" + """ Adding pins for port data module""" self.add_pin("rbl_bl", "INOUT") self.add_pin("rbl_br", "INOUT") @@ -111,11 +114,17 @@ class port_data(design.design): br_name = self.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") + for bit in range(self.num_spare_cols): + bl_name = self.get_bl_name(self.port) + br_name = self.get_br_name(self.port) + self.add_pin("spare{0}_{1}".format(bl_name, bit), "INOUT") + self.add_pin("spare{0}_{1}".format(br_name, bit), "INOUT") + if self.port in self.read_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): self.add_pin("dout_{}".format(bit), "OUTPUT") if self.port in self.write_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): self.add_pin("din_{}".format(bit), "INPUT") # Will be empty if no col addr lines sel_names = ["sel_{}".format(x) for x in range(self.num_col_addr_lines)] @@ -128,6 +137,8 @@ class port_data(design.design): self.add_pin("w_en", "INPUT") for bit in range(self.num_wmasks): self.add_pin("bank_wmask_{}".format(bit), "INPUT") + for bit in range(self.num_spare_cols): + self.add_pin("bank_spare_wen{}".format(bit), "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") @@ -178,21 +189,27 @@ class port_data(design.design): # Extra column +1 is for RBL # Precharge will be shifted left if needed + # Column offset is set to port so extra column can be on left or right + # and mirroring happens correctly self.precharge_array = factory.create(module_type="precharge_array", - columns=self.num_cols + 1, + columns=self.num_cols + self.num_spare_cols + 1, bitcell_bl=self.bl_names[self.port], - bitcell_br=self.br_names[self.port]) + bitcell_br=self.br_names[self.port], + column_offset=self.port - 1) self.add_mod(self.precharge_array) if self.port in self.read_ports: + # RBLs don't get a sense amp self.sense_amp_array = factory.create(module_type="sense_amp_array", word_size=self.word_size, - words_per_row=self.words_per_row) + words_per_row=self.words_per_row, + num_spare_cols=self.num_spare_cols) self.add_mod(self.sense_amp_array) else: self.sense_amp_array = None if self.col_addr_size > 0: + # RBLs dont get a col mux self.column_mux_array = factory.create(module_type="column_mux_array", columns=self.num_cols, word_size=self.word_size, @@ -203,17 +220,19 @@ class port_data(design.design): self.column_mux_array = None if self.port in self.write_ports: + # RBLs dont get a write driver self.write_driver_array = factory.create(module_type="write_driver_array", columns=self.num_cols, word_size=self.word_size, - write_size=self.write_size) + write_size=self.write_size, + num_spare_cols=self.num_spare_cols) self.add_mod(self.write_driver_array) if self.write_size is not None: + # RBLs don't get a write mask self.write_mask_and_array = factory.create(module_type="write_mask_and_array", columns=self.num_cols, word_size=self.word_size, - write_size=self.write_size, - port = self.port) + write_size=self.write_size) self.add_mod(self.write_mask_and_array) else: self.write_mask_and_array = None @@ -246,12 +265,6 @@ class port_data(design.design): 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 """ @@ -272,6 +285,10 @@ class port_data(design.design): for bit in range(self.num_cols): temp.append("{0}_{1}".format(bl_name, bit)) temp.append("{0}_{1}".format(br_name, bit)) + + for bit in range(self.num_spare_cols): + temp.append("spare{0}_{1}".format(bl_name, bit)) + temp.append("spare{0}_{1}".format(br_name, bit)) # Use right BLs for RBL if self.port==1: @@ -297,7 +314,6 @@ class port_data(design.design): for col in range(self.num_cols): 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): @@ -330,8 +346,14 @@ class port_data(design.design): else: temp.append("{0}_out_{1}".format(bl_name, bit)) temp.append("{0}_out_{1}".format(br_name, bit)) - - temp.extend(["s_en", "vdd", "gnd"]) + + for bit in range(self.num_spare_cols): + temp.append("dout_{}".format(self.word_size + bit)) + temp.append("spare{0}_{1}".format(bl_name, bit)) + temp.append("spare{0}_{1}".format(br_name, bit)) + + temp.append("s_en") + temp.extend(["vdd", "gnd"]) self.connect_inst(temp) def place_sense_amp_array(self, offset): @@ -346,7 +368,7 @@ class port_data(design.design): br_name = self.get_br_name(self.port) temp = [] - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): temp.append("din_{}".format(bit)) for bit in range(self.word_size): @@ -357,9 +379,20 @@ class port_data(design.design): temp.append("{0}_out_{1}".format(bl_name, bit)) temp.append("{0}_out_{1}".format(br_name, bit)) + for bit in range(self.num_spare_cols): + temp.append("spare{0}_{1}".format(bl_name, bit)) + temp.append("spare{0}_{1}".format(br_name, bit)) + if self.write_size is not None: for i in range(self.num_wmasks): temp.append("wdriver_sel_{}".format(i)) + for i in range(self.num_spare_cols): + temp.append("bank_spare_wen{}".format(i)) + + elif self.num_spare_cols and not self.write_size: + temp.append("w_en") + for i in range(self.num_spare_cols): + temp.append("bank_spare_wen{}".format(i)) else: temp.append("w_en") temp.extend(["vdd", "gnd"]) @@ -444,8 +477,7 @@ class port_data(design.design): def route_sense_amp_out(self, port): """ Add pins for the sense amp output """ - - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): data_pin = self.sense_amp_array_inst.get_pin("data_{}".format(bit)) self.add_layout_pin_rect_center(text="dout_{0}".format(bit), layer=data_pin.layer, @@ -456,7 +488,7 @@ class port_data(design.design): def route_write_driver_in(self, port): """ Connecting write driver """ - for row in range(self.word_size): + for row in range(self.word_size + self.num_spare_cols): data_name = "data_{}".format(row) din_name = "din_{}".format(row) self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name) @@ -469,45 +501,38 @@ class port_data(design.design): bank_wmask_name = "bank_wmask_{}".format(bit) self.copy_layout_pin(self.write_mask_and_array_inst, wmask_in_name, bank_wmask_name) - def route_write_mask_and_array_to_write_driver(self,port): - """ Routing of wdriver_sel_{} between write mask AND array and write driver array. Adds layout pin for write - mask AND array output and via for write driver enable """ + def route_write_mask_and_array_to_write_driver(self, port): + """ + Routing of wdriver_sel_{} between write mask AND array and + write driver array. Adds layout pin for write + mask AND array output and via for write driver enable + """ - inst1 = self.write_mask_and_array_inst - inst2 = self.write_driver_array_inst + wmask_inst = self.write_mask_and_array_inst + wdriver_inst = self.write_driver_array_inst - loc = 0 for bit in range(self.num_wmasks): # Bring write mask AND array output pin to port data level - self.copy_layout_pin(inst1, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit)) + self.copy_layout_pin(wmask_inst, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit)) - wmask_out_pin = inst1.get_pin("wmask_out_{0}".format(bit)) - wdriver_en_pin = inst2.get_pin("en_{0}".format(bit)) + wmask_out_pin = wmask_inst.get_pin("wmask_out_{0}".format(bit)) + wdriver_en_pin = wdriver_inst.get_pin("en_{0}".format(bit)) - # The metal2 wdriver_sel_{} wire must hit the en_{} pin after the closest bitline pin that's right of the - # the wdriver_sel_{} pin in the write driver AND array. - if bit == 0: - # When the write mask output pin is right of the bitline, the target is found - while (wmask_out_pin.lx() + self.m2_pitch > inst2.get_pin("data_{0}".format(loc)).rx()): - loc += 1 - length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch - debug.check(loc<=self.num_wmasks, - "Couldn't route the write mask select.") - else: - # Stride by the write size rather than finding the next pin to the right - loc += self.write_size - length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch - - beg_pos = wmask_out_pin.center() - middle_pos = vector(length, wmask_out_pin.cy()) - end_pos = vector(length, wdriver_en_pin.cy()) + wmask_pos = wmask_out_pin.center() + wdriver_pos = wdriver_en_pin.rc() - vector(self.m2_pitch, 0) + mid_pos = vector(wdriver_pos.x, wmask_pos.y) + # Add driver on mask output + self.add_via_stack_center(from_layer=wmask_out_pin.layer, + to_layer="m1", + offset=wmask_pos) # Add via for the write driver array's enable input - self.add_via_center(layers=self.m1_stack, - offset=end_pos) + self.add_via_stack_center(from_layer=wdriver_en_pin.layer, + to_layer="m2", + offset=wdriver_pos) # Route between write mask AND array and write driver array - self.add_wire(self.m1_stack, [beg_pos, middle_pos, end_pos]) + self.add_wire(self.m1_stack, [wmask_pos, mid_pos, wdriver_pos]) def route_column_mux_to_precharge_array(self, port): """ Routing of BL and BR between col mux and precharge array """ @@ -516,15 +541,12 @@ class port_data(design.design): if self.col_addr_size==0: return - inst1 = self.column_mux_array_inst - inst2 = self.precharge_array_inst + start_bit = 1 if self.port == 0 else 0 - insn2_start_bit = 1 if self.port == 0 else 0 - - self.connect_bitlines(inst1=inst1, - inst2=inst2, + self.connect_bitlines(inst1=self.column_mux_array_inst, + inst2=self.precharge_array_inst, num_bits=self.num_cols, - inst2_start_bit=insn2_start_bit) + inst2_start_bit=start_bit) def route_sense_amp_to_column_mux_or_precharge_array(self, port): """ Routing of BL and BR between sense_amp and column mux or precharge array """ @@ -545,12 +567,41 @@ class port_data(design.design): else: start_bit=0 - self.channel_route_bitlines(inst1=inst1, - inst1_bls_template=inst1_bls_templ, - inst2=inst2, - num_bits=self.word_size, - inst1_start_bit=start_bit) - + # spare cols connected to precharge array since they are read independently + if self.num_spare_cols and self.col_addr_size>0: + if self.port==0: + off = 1 + else: + off = 0 + + self.channel_route_bitlines(inst1=self.column_mux_array_inst, + inst1_bls_template="{inst}_out_{bit}", + inst2=inst2, + num_bits=self.word_size, + inst1_start_bit=start_bit) + + self.channel_route_bitlines(inst1=self.precharge_array_inst, + inst1_bls_template="{inst}_{bit}", + inst2=inst2, + num_bits=self.num_spare_cols, + inst1_start_bit=self.num_cols + off, + inst2_start_bit=self.word_size) + + # This could be a channel route, but in some techs the bitlines + # are too close together. + elif OPTS.tech_name == "sky130": + self.connect_bitlines(inst1=inst1, + inst1_bls_template=inst1_bls_templ, + inst2=inst2, + num_bits=self.word_size, + inst1_start_bit=start_bit) + else: + self.channel_route_bitlines(inst1=inst1, + inst1_bls_template=inst1_bls_templ, + inst2=inst2, + num_bits=self.word_size + self.num_spare_cols, + inst1_start_bit=start_bit) + def route_write_driver_to_column_mux_or_precharge_array(self, port): """ Routing of BL and BR between sense_amp and column mux or precharge array """ inst2 = self.write_driver_array_inst @@ -569,10 +620,44 @@ class port_data(design.design): else: start_bit=0 - self.channel_route_bitlines(inst1=inst1, inst2=inst2, - num_bits=self.word_size, - inst1_bls_template=inst1_bls_templ, - inst1_start_bit=start_bit) + if self.port==0: + off = 1 + else: + off = 0 + + # Channel route spare columns' bitlines + if self.num_spare_cols and self.col_addr_size>0: + if self.port==0: + off = 1 + else: + off = 0 + + self.channel_route_bitlines(inst1=self.column_mux_array_inst, + inst1_bls_template="{inst}_out_{bit}", + inst2=inst2, + num_bits=self.word_size, + inst1_start_bit=start_bit) + + self.channel_route_bitlines(inst1=self.precharge_array_inst, + inst1_bls_template="{inst}_{bit}", + inst2=inst2, + num_bits=self.num_spare_cols, + inst1_start_bit=self.num_cols + off, + inst2_start_bit=self.word_size) + + # This could be a channel route, but in some techs the bitlines + # are too close together. + elif OPTS.tech_name == "sky130": + self.connect_bitlines(inst1=inst1, inst2=inst2, + num_bits=self.word_size, + inst1_bls_template=inst1_bls_templ, + inst1_start_bit=start_bit) + else: + self.channel_route_bitlines(inst1=inst1, inst2=inst2, + num_bits=self.word_size+self.num_spare_cols, + inst1_bls_template=inst1_bls_templ, + inst1_start_bit=start_bit) + def route_write_driver_to_sense_amp(self, port): """ Routing of BL and BR between write driver and sense amp """ @@ -580,11 +665,11 @@ class port_data(design.design): inst1 = self.write_driver_array_inst inst2 = self.sense_amp_array_inst - # These should be pitch matched in the cell library, - # but just in case, do a channel route. - self.channel_route_bitlines(inst1=inst1, - inst2=inst2, - num_bits=self.word_size) + # This could be a channel route, but in some techs the bitlines + # are too close together. + self.connect_bitlines(inst1=inst1, + inst2=inst2, + num_bits=self.word_size + self.num_spare_cols) def route_bitline_pins(self): """ Add the bitline pins for the given port """ @@ -595,8 +680,8 @@ class port_data(design.design): self.copy_layout_pin(self.precharge_array_inst, "br_0", "rbl_br") bit_offset=1 elif self.port==1: - self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(self.num_cols), "rbl_bl") - self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(self.num_cols), "rbl_br") + self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(self.num_cols + self.num_spare_cols), "rbl_bl") + self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(self.num_cols + self.num_spare_cols), "rbl_br") bit_offset=0 else: bit_offset=0 @@ -611,6 +696,19 @@ class port_data(design.design): "br_{}".format(bit)) else: debug.error("Didn't find precharge array.") + + # Copy bitlines of spare columns + for bit in range(self.num_spare_cols): + if self.precharge_array_inst: + self.copy_layout_pin(self.precharge_array_inst, + "bl_{}".format(self.num_cols + bit + bit_offset), + "sparebl_{}".format(bit)) + self.copy_layout_pin(self.precharge_array_inst, + "br_{}".format(self.num_cols + bit + bit_offset), + "sparebr_{}".format(bit)) + else: + debug.error("Didn't find precharge array.") + def route_control_pins(self): """ Add the control pins: s_en, p_en_bar, w_en """ @@ -621,12 +719,19 @@ class port_data(design.design): for pin_name in sel_names: self.copy_layout_pin(self.column_mux_array_inst, pin_name) if self.sense_amp_array_inst: - self.copy_layout_pin(self.sense_amp_array_inst, "en", "s_en") + self.copy_layout_pin(self.sense_amp_array_inst, "en", "s_en") if self.write_driver_array_inst: if self.write_mask_and_array_inst: for bit in range(self.num_wmasks): # Add write driver's en_{} pins self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit), "wdriver_sel_{}".format(bit)) + for bit in range(self.num_spare_cols): + # Add spare columns' en_{} pins + self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit + self.num_wmasks), "bank_spare_wen{}".format(bit)) + elif self.num_spare_cols and not self.write_mask_and_array_inst: + self.copy_layout_pin(self.write_driver_array_inst, "en_0", "w_en") + for bit in range(self.num_spare_cols): + self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit + 1), "bank_spare_wen{}".format(bit)) else: self.copy_layout_pin(self.write_driver_array_inst, "en", "w_en") if self.write_mask_and_array_inst: @@ -687,10 +792,9 @@ class port_data(design.design): Route the bl and br of two modules using the channel router. """ - bot_inst_group, top_inst_group = self._group_bitline_instances( - inst1, inst2, num_bits, - inst1_bls_template, inst1_start_bit, - inst2_bls_template, inst2_start_bit) + bot_inst_group, top_inst_group = self._group_bitline_instances(inst1, inst2, num_bits, + inst1_bls_template, inst1_start_bit, + inst2_bls_template, inst2_start_bit) # Channel route each mux separately since we don't minimize the number # of tracks in teh channel router yet. If we did, we could route all the bits at once! @@ -699,13 +803,8 @@ class port_data(design.design): bottom_names = self._get_bitline_pins(bot_inst_group, bit) top_names = self._get_bitline_pins(top_inst_group, bit) - if bottom_names[0].layer == "m2": - bitline_dirs = ("H", "V") - elif bottom_names[0].layer == "m1": - bitline_dirs = ("V", "H") - route_map = list(zip(bottom_names, top_names)) - self.create_horizontal_channel_route(route_map, offset, self.m1_stack, bitline_dirs) + self.create_horizontal_channel_route(route_map, offset, self.m1_stack) def connect_bitlines(self, inst1, inst2, num_bits, inst1_bls_template="{inst}_{bit}", @@ -717,11 +816,10 @@ class port_data(design.design): This assumes that they have sufficient space to create a jog in the middle between the two modules (if needed). """ - - bot_inst_group, top_inst_group = self._group_bitline_instances( - inst1, inst2, num_bits, - inst1_bls_template, inst1_start_bit, - inst2_bls_template, inst2_start_bit) + + bot_inst_group, top_inst_group = self._group_bitline_instances(inst1, inst2, num_bits, + inst1_bls_template, inst1_start_bit, + inst2_bls_template, inst2_start_bit) for col in range(num_bits): bot_bl_pin, bot_br_pin = self._get_bitline_pins(bot_inst_group, col) @@ -729,18 +827,11 @@ class port_data(design.design): bot_bl, bot_br = bot_bl_pin.uc(), bot_br_pin.uc() top_bl, top_br = top_bl_pin.bc(), top_br_pin.bc() - yoffset = 0.5 * (top_bl.y + bot_bl.y) - self.add_path("m2", [bot_bl, - vector(bot_bl.x, yoffset), - vector(top_bl.x, yoffset), - top_bl]) - self.add_path("m2", [bot_br, - vector(bot_br.x, yoffset), - vector(top_br.x, yoffset), - top_br]) - + layer_pitch = getattr(self, "{}_pitch".format(top_bl_pin.layer)) + self.add_zjog(bot_bl_pin.layer, bot_bl, top_bl, "V", fixed_offset=top_bl_pin.by() - layer_pitch) + self.add_zjog(bot_br_pin.layer, bot_br, top_br, "V", fixed_offset=top_bl_pin.by() - 2 * layer_pitch) + def graph_exclude_precharge(self): """Precharge adds a loop between bitlines, can be excluded to reduce complexity""" if self.precharge_array_inst: self.graph_inst_exclude.add(self.precharge_array_inst) - diff --git a/compiler/modules/precharge_array.py b/compiler/modules/precharge_array.py index af612af4..d37de64f 100644 --- a/compiler/modules/precharge_array.py +++ b/compiler/modules/precharge_array.py @@ -7,10 +7,10 @@ # import design import debug -from tech import drc from vector import vector from sram_factory import factory from globals import OPTS +from tech import layer class precharge_array(design.design): @@ -19,7 +19,7 @@ class precharge_array(design.design): of bit line columns, height is the height of the bit-cell array. """ - def __init__(self, name, columns, size=1, bitcell_bl="bl", bitcell_br="br"): + def __init__(self, name, columns, size=1, bitcell_bl="bl", bitcell_br="br", column_offset=0): design.design.__init__(self, name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("cols: {0} size: {1} bl: {2} br: {3}".format(columns, size, bitcell_bl, bitcell_br)) @@ -28,7 +28,13 @@ class precharge_array(design.design): self.size = size self.bitcell_bl = bitcell_bl self.bitcell_br = bitcell_br + self.column_offset = column_offset + if OPTS.tech_name == "sky130": + self.en_bar_layer = "m3" + else: + self.en_bar_layer = "m1" + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -73,14 +79,18 @@ class precharge_array(design.design): def add_layout_pins(self): - en_bar_pin = self.pc_cell.get_pin("en_bar") - self.add_layout_pin(text="en_bar", - layer=en_bar_pin.layer, - offset=en_bar_pin.ll(), - width=self.width, - height=en_bar_pin.height()) + en_pin = self.pc_cell.get_pin("en_bar") + start_offset = en_pin.lc().scale(0, 1) + end_offset = start_offset + vector(self.width, 0) + self.add_layout_pin_segment_center(text="en_bar", + layer=self.en_bar_layer, + start=start_offset, + end=end_offset) for inst in self.local_insts: + self.add_via_stack_center(from_layer=en_pin.layer, + to_layer=self.en_bar_layer, + offset=inst.get_pin("en_bar").center()) self.copy_layout_pin(inst, "vdd") for i in range(len(self.local_insts)): @@ -106,7 +116,7 @@ class precharge_array(design.design): xoffset = 0 for i in range(self.columns): tempx = xoffset - if cell_properties.bitcell.mirror.y and (i + 1) % 2: + if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: mirror = "MY" tempx = tempx + self.pc_cell.width else: diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index c88dbe6d..0c7e412e 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -1,12 +1,12 @@ # See LICENSE for licensing information. # -# Copyright (c) 2016-2019 Regents of the University of California +# Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # import debug import design -from tech import drc, spice +from tech import drc, spice, cell_properties from vector import vector from globals import OPTS from sram_factory import factory @@ -32,22 +32,23 @@ class replica_bitcell_array(design.design): self.right_rbl = right_rbl self.bitcell_ports = bitcell_ports - debug.check(left_rbl+right_rbl==len(self.all_ports),"Invalid number of RBLs for port configuration.") - debug.check(left_rbl+right_rbl==len(self.bitcell_ports),"Bitcell ports must match total RBLs.") - + debug.check(left_rbl + right_rbl == len(self.all_ports), + "Invalid number of RBLs for port configuration.") + debug.check(left_rbl + right_rbl == len(self.bitcell_ports), + "Bitcell ports must match total RBLs.") + # Two dummy rows/cols plus replica for each port self.extra_rows = 2 + left_rbl + right_rbl self.extra_cols = 2 + left_rbl + right_rbl - + self.create_netlist() if not OPTS.netlist_only: self.create_layout() # 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): """ Create and connect the netlist """ self.add_modules() @@ -55,15 +56,15 @@ class replica_bitcell_array(design.design): self.create_instances() def add_modules(self): - """ Array and dummy/replica columns + """ Array and dummy/replica columns d or D = dummy cell (caps to distinguish grouping) r or R = replica cell (caps to distinguish grouping) - b or B = bitcell - replica columns 1 + b or B = bitcell + replica columns 1 v v - bdDDDDDDDDDDDDDDdb <- Dummy row - bdDDDDDDDDDDDDDDrb <- Dummy row + bdDDDDDDDDDDDDDDdb <- Dummy row + bdDDDDDDDDDDDDDDrb <- Dummy row br--------------rb br| Array |rb br| row x col |rb @@ -90,15 +91,17 @@ class replica_bitcell_array(design.design): # Replica bitlines self.replica_columns = {} - for bit in range(self.left_rbl+self.right_rbl): + for bit in range(self.left_rbl + self.right_rbl): + # Creating left_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.cell.get_all_wl_names()))] + 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) # Regular WLs self.replica_col_wl_names.extend(self.bitcell_array_wl_names) # 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): + 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.cell.get_all_wl_names()))] + 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) @@ -175,14 +195,13 @@ class replica_bitcell_array(design.design): # Left/right dummy columns are connected identically to the replica column self.dummy_col_wl_names = self.replica_col_wl_names - # Per port bitline names self.replica_bl_names = {} self.replica_wl_names = {} # Array of all port bitline names - for port in range(self.left_rbl+self.right_rbl): - left_names=["rbl_{0}_{1}".format(self.cell.get_bl_name(x),port) for x in range(len(self.all_ports))] - right_names=["rbl_{0}_{1}".format(self.cell.get_br_name(x),port) for x in range(len(self.all_ports))] + for port in range(self.left_rbl + self.right_rbl): + left_names=["rbl_{0}_{1}".format(self.cell.get_bl_name(x), port) for x in range(len(self.all_ports))] + right_names=["rbl_{0}_{1}".format(self.cell.get_br_name(x), port) for x in range(len(self.all_ports))] # Keep track of the left pins that are the RBL self.rbl_bl_names[port]=left_names[self.bitcell_ports[port]] self.rbl_br_names[port]=right_names[self.bitcell_ports[port]] @@ -190,36 +209,33 @@ class replica_bitcell_array(design.design): bl_names = [x for t in zip(left_names, right_names) for x in t] self.replica_bl_names[port] = bl_names - wl_names = ["rbl_{0}_{1}".format(x,port) for x in self.cell.get_all_wl_names()] - #wl_names[port] = "rbl_wl{}".format(port) + wl_names = ["rbl_{0}_{1}".format(x, port) for x in self.cell.get_all_wl_names()] self.replica_wl_names[port] = wl_names - # External pins self.add_pin_list(self.bitcell_array_bl_names, "INOUT") # Need to sort by port order since dictionary values may not be in order bl_names = [self.rbl_bl_names[x] for x in sorted(self.rbl_bl_names.keys())] br_names = [self.rbl_br_names[x] for x in sorted(self.rbl_br_names.keys())] - for (bl_name,br_name) in zip(bl_names,br_names): - self.add_pin(bl_name,"OUTPUT") - self.add_pin(br_name,"OUTPUT") + for (bl_name, br_name) in zip(bl_names, br_names): + self.add_pin(bl_name, "OUTPUT") + self.add_pin(br_name, "OUTPUT") self.add_pin_list(self.bitcell_array_wl_names, "INPUT") - # Need to sort by port order since dictionary values may not be in order + # Need to sort by port order since dictionary values may not be in order wl_names = [self.rbl_wl_names[x] for x in sorted(self.rbl_wl_names.keys())] for pin_name in wl_names: - self.add_pin(pin_name,"INPUT") + self.add_pin(pin_name, "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") - def create_instances(self): """ Create the module instances used in this design """ supplies = ["vdd", "gnd"] - + # Used for names/dimensions only self.cell = factory.create(module_type="bitcell") - + # Main array self.bitcell_array_inst=self.add_inst(name="bitcell_array", mod=self.bitcell_array) @@ -227,88 +243,82 @@ class replica_bitcell_array(design.design): # Replica columns self.replica_col_inst = {} - for port in range(self.left_rbl+self.right_rbl): + for port in range(self.left_rbl + self.right_rbl): self.replica_col_inst[port]=self.add_inst(name="replica_col_{}".format(port), - mod=self.replica_columns[port]) + mod=self.replica_columns[port]) self.connect_inst(self.replica_bl_names[port] + self.replica_col_wl_names + supplies) - - + # Dummy rows under the bitcell array (connected with with the replica cell wl) self.dummy_row_replica_inst = {} - for port in range(self.left_rbl+self.right_rbl): + for port in range(self.left_rbl + self.right_rbl): self.dummy_row_replica_inst[port]=self.add_inst(name="dummy_row_{}".format(port), mod=self.dummy_row) self.connect_inst(self.dummy_row_bl_names + self.replica_wl_names[port] + supplies) - - - # Top/bottom dummy rows - self.dummy_row_bot_inst=self.add_inst(name="dummy_row_bot", - mod=self.dummy_row) - self.connect_inst(self.dummy_row_bl_names + [x+"_bot" for x in self.dummy_cell_wl_names] + supplies) - self.dummy_row_top_inst=self.add_inst(name="dummy_row_top", - mod=self.dummy_row) - self.connect_inst(self.dummy_row_bl_names + [x+"_top" for x in self.dummy_cell_wl_names] + supplies) + # Top/bottom dummy rows or col caps + self.dummy_row_bot_inst=self.add_inst(name="dummy_row_bot", + mod=self.edge_row) + self.connect_inst(self.dummy_row_bl_names + [x + "_bot" for x in self.dummy_cell_wl_names] + supplies) + self.dummy_row_top_inst=self.add_inst(name="dummy_row_top", + mod=self.edge_row) + self.connect_inst(self.dummy_row_bl_names + [x + "_top" for x in self.dummy_cell_wl_names] + supplies) # Left/right Dummy columns self.dummy_col_left_inst=self.add_inst(name="dummy_col_left", - mod=self.dummy_col_left) - self.connect_inst([x+"_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) + mod=self.edge_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_right) - self.connect_inst([x+"_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) - + mod=self.edge_col_right) + self.connect_inst([x + "_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) - def create_layout(self): - self.height = (self.row_size+self.extra_rows)*self.dummy_row.height - self.width = (self.column_size+self.extra_cols)*self.cell.width + self.height = (self.row_size + self.extra_rows) * self.dummy_row.height + self.width = (self.column_size + self.extra_cols) * self.cell.width # This is a bitcell x bitcell offset to scale offset = vector(self.cell.width, self.cell.height) - - self.bitcell_array_inst.place(offset=[0,0]) + + self.bitcell_array_inst.place(offset=[0, 0]) # To the left of the bitcell array for bit in range(self.left_rbl): - self.replica_col_inst[bit].place(offset=offset.scale(-bit-1,-self.left_rbl-1)) + self.replica_col_inst[bit].place(offset=offset.scale(-bit - 1, -self.left_rbl - 1)) # To the right of the bitcell array for bit in range(self.right_rbl): - self.replica_col_inst[self.left_rbl+bit].place(offset=offset.scale(bit,-self.left_rbl-1)+self.bitcell_array_inst.lr()) - + self.replica_col_inst[self.left_rbl + bit].place(offset=offset.scale(bit, -self.left_rbl - 1) + self.bitcell_array_inst.lr()) + # FIXME: These depend on the array size itself # Far top dummy row (first row above array is NOT flipped) - flip_dummy = self.right_rbl%2 - self.dummy_row_top_inst.place(offset=offset.scale(0,self.right_rbl+flip_dummy)+self.bitcell_array_inst.ul(), + flip_dummy = self.right_rbl % 2 + self.dummy_row_top_inst.place(offset=offset.scale(0, self.right_rbl + flip_dummy) + self.bitcell_array_inst.ul(), mirror="MX" if flip_dummy else "R0") + # FIXME: These depend on the array size itself # Far bottom dummy row (first row below array IS flipped) - flip_dummy = (self.left_rbl+1)%2 - self.dummy_row_bot_inst.place(offset=offset.scale(0,-self.left_rbl-1+flip_dummy), + flip_dummy = (self.left_rbl + 1) % 2 + self.dummy_row_bot_inst.place(offset=offset.scale(0, -self.left_rbl - 1 + flip_dummy), mirror="MX" if flip_dummy else "R0") # Far left dummy col - self.dummy_col_left_inst.place(offset=offset.scale(-self.left_rbl-1,-self.left_rbl-1)) + self.dummy_col_left_inst.place(offset=offset.scale(-self.left_rbl - 1, -self.left_rbl - 1)) # Far right dummy col - self.dummy_col_right_inst.place(offset=offset.scale(self.right_rbl,-self.left_rbl-1)+self.bitcell_array_inst.lr()) + self.dummy_col_right_inst.place(offset=offset.scale(self.right_rbl, -self.left_rbl - 1) + self.bitcell_array_inst.lr()) # Replica dummy rows for bit in range(self.left_rbl): - self.dummy_row_replica_inst[bit].place(offset=offset.scale(0,-bit-bit%2), - mirror="R0" if bit%2 else "MX") + self.dummy_row_replica_inst[bit].place(offset=offset.scale(0, -bit - bit % 2), + mirror="R0" if bit % 2 else "MX") for bit in range(self.right_rbl): - self.dummy_row_replica_inst[self.left_rbl+bit].place(offset=offset.scale(0,bit+bit%2)+self.bitcell_array_inst.ul(), - mirror="MX" if bit%2 else "R0") - + self.dummy_row_replica_inst[self.left_rbl + bit].place(offset=offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul(), + mirror="MX" if bit % 2 else "R0") + + self.translate_all(offset.scale(-1 - self.left_rbl, -1 - self.left_rbl)) - self.translate_all(offset.scale(-1-self.left_rbl,-1-self.left_rbl)) - self.add_layout_pins() - + self.add_boundary() - + self.DRC_LVS() - def add_layout_pins(self): """ Add the layout pins """ @@ -321,7 +331,7 @@ class replica_bitcell_array(design.design): for pin in pin_list: self.add_layout_pin(text=pin_name, layer=pin.layer, - offset=pin.ll().scale(0,1), + offset=pin.ll().scale(0, 1), width=self.width, height=pin.height()) for bitline in self.bitcell_array_bl_names: @@ -330,27 +340,26 @@ class replica_bitcell_array(design.design): for pin in pin_list: self.add_layout_pin(text=pin_name, layer=pin.layer, - offset=pin.ll().scale(1,0), + offset=pin.ll().scale(1, 0), width=pin.width(), height=self.height) - # Replica wordlines - for port in range(self.left_rbl+self.right_rbl): + for port in range(self.left_rbl + self.right_rbl): inst = self.replica_col_inst[port] - for (pin_name,wl_name) in zip(self.cell.get_all_wl_names(),self.replica_wl_names[port]): + for (pin_name, wl_name) in zip(self.cell.get_all_wl_names(), self.replica_wl_names[port]): # +1 for dummy row - pin_bit = port+1 - # +row_size if above the array + pin_bit = port + 1 + # +row_size if above the array if port>=self.left_rbl: pin_bit += self.row_size - + pin_name += "_{}".format(pin_bit) pin = inst.get_pin(pin_name) if wl_name in self.rbl_wl_names.values(): self.add_layout_pin(text=wl_name, layer=pin.layer, - offset=pin.ll().scale(0,1), + offset=pin.ll().scale(0, 1), width=self.width, height=pin.height()) @@ -368,20 +377,28 @@ class replica_bitcell_array(design.design): offset=pin.ll().scale(1, 0), width=pin.width(), height=self.height) - + + # vdd/gnd are only connected in the perimeter cells + # replica column should only have a vdd/gnd in the dummy cell on top/bottom + supply_insts = [self.dummy_col_left_inst, self.dummy_col_right_inst, + self.dummy_row_top_inst, self.dummy_row_bot_inst] for pin_name in ["vdd", "gnd"]: - for inst in self.insts: + for inst in supply_insts: pin_list = inst.get_pins(pin_name) for pin in pin_list: self.add_power_pin(name=pin_name, loc=pin.center(), directions=("V", "V"), start_layer=pin.layer) + + for inst in list(self.replica_col_inst.values()): + self.copy_layout_pin(inst, pin_name) + self.copy_layout_pin(inst, pin_name) def get_rbl_wl_name(self, port): """ Return the WL for the given RBL port """ return self.rbl_wl_names[port] - + def get_rbl_bl_name(self, port): """ Return the BL for the given RBL port """ return self.rbl_bl_names[port] @@ -392,19 +409,17 @@ class replica_bitcell_array(design.design): def analytical_power(self, corner, load): """Power of Bitcell array and bitline in nW.""" - from tech import drc, parameter - # Dynamic Power from Bitline bl_wire = self.gen_bl_wire() cell_load = 2 * bl_wire.return_input_cap() bl_swing = OPTS.rbl_delay_percentage 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 @@ -415,13 +430,13 @@ 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_m1")) + 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 def get_wordline_cin(self): """Get the relative input capacitance from the wordline connections in all the bitcell""" - #A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns + # A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns bitcell_wl_cin = self.cell.get_wl_cin() total_cin = bitcell_wl_cin * self.column_size return total_cin @@ -429,13 +444,13 @@ class replica_bitcell_array(design.design): def graph_exclude_bits(self, targ_row, targ_col): """Excludes bits in column from being added to graph except target""" self.bitcell_array.graph_exclude_bits(targ_row, targ_col) - + def graph_exclude_replica_col_bits(self): """Exclude all replica/dummy cells in the replica columns except the replica bit.""" - - for port in range(self.left_rbl+self.right_rbl): + + for port in range(self.left_rbl + self.right_rbl): self.replica_columns[port].exclude_all_but_replica() def get_cell_name(self, inst_name, row, col): """Gets the spice name of the target bitcell.""" - return self.bitcell_array.get_cell_name(inst_name+'.x'+self.bitcell_array_inst.name, row, col) + return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col) diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index d874ba8c..9613e6fa 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -1,22 +1,22 @@ # See LICENSE for licensing information. # -# Copyright (c) 2016-2019 Regents of the University of California +# Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # import debug import design -from tech import drc -import contact +from tech import cell_properties from sram_factory import factory from vector import vector from globals import OPTS + class replica_column(design.design): """ Generate a replica bitline column for the replica array. Rows is the total number of rows i the main array. Left_rbl and right_rbl are the number of left and right replica bitlines. - Replica bit specifies which replica column this is (to determine where to put the + Replica bit specifies which replica column this is (to determine where to put the replica cell. """ @@ -29,25 +29,30 @@ class replica_column(design.design): self.right_rbl = right_rbl 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.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, - "Replica bit cannot be in the regular array.") + + 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, + "Replica bit cannot be in the regular array.") + if OPTS.tech_name == "sky130": + debug.check(rows % 2 == 0 and (left_rbl + 1) % 2 == 0, + "sky130 currently requires rows to be even and to start with X mirroring" + + " (left_rbl must be odd) for LVS.") self.create_netlist() if not OPTS.netlist_only: self.create_layout() - + def create_netlist(self): self.add_modules() self.add_pins() self.create_instances() def create_layout(self): - self.height = self.total_size*self.cell.height - self.width = self.cell.width + self.height = self.total_size * self.cell.height + self.width = self.cell.width self.place_instances() self.add_layout_pins() @@ -55,49 +60,70 @@ class replica_column(design.design): self.DRC_LVS() def add_pins(self): - + for bl_name in self.cell.get_all_bitline_names(): # In the replica column, these are only outputs! - self.add_pin("{0}_{1}".format(bl_name,0), "OUTPUT") + self.add_pin("{0}_{1}".format(bl_name, 0), "OUTPUT") for row in range(self.total_size): for wl_name in self.cell.get_all_wl_names(): - self.add_pin("{0}_{1}".format(wl_name,row), "INPUT") - + self.add_pin("{0}_{1}".format(wl_name, row), "INPUT") + self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") def add_modules(self): - self.replica_cell = factory.create(module_type="replica_bitcell") + self.replica_cell = factory.create(module_type="replica_{}".format(OPTS.bitcell)) self.add_mod(self.replica_cell) - self.dummy_cell = factory.create(module_type="dummy_bitcell") + self.dummy_cell = factory.create(module_type="dummy_{}".format(OPTS.bitcell)) self.add_mod(self.dummy_cell) + try: + edge_module_type = ("col_cap" if cell_properties.bitcell.end_caps else "dummy") + except AttributeError: + edge_module_type = "dummy" + self.edge_cell = factory.create(module_type=edge_module_type + "_" + OPTS.bitcell) + self.add_mod(self.edge_cell) # Used for pin names only self.cell = factory.create(module_type="bitcell") - + def create_instances(self): + + try: + end_caps_enabled = cell_properties.bitcell.end_caps + except AttributeError: + end_caps_enabled = False + self.cell_inst = {} for row in range(self.total_size): name="rbc_{0}".format(row) # Top/bottom cell are always dummy cells. # Regular array cells are replica cells (>left_rbl and self.left_rbl and row self.left_rbl and row < self.total_size - self.right_rbl - 1): self.cell_inst[row]=self.add_inst(name=name, mod=self.replica_cell) + self.connect_inst(self.get_bitcell_pins(0, row)) elif row==self.replica_bit: self.cell_inst[row]=self.add_inst(name=name, mod=self.replica_cell) + self.connect_inst(self.get_bitcell_pins(0, row)) + elif (row == 0 or row == self.total_size - 1): + self.cell_inst[row]=self.add_inst(name=name, + mod=self.edge_cell) + if end_caps_enabled: + self.connect_inst(self.get_bitcell_pins_col_cap(0, row)) + else: + self.connect_inst(self.get_bitcell_pins(0, row)) else: self.cell_inst[row]=self.add_inst(name=name, mod=self.dummy_cell) - self.connect_inst(self.get_bitcell_pins(0, row)) - + 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 + 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. @@ -108,12 +134,10 @@ class replica_column(design.design): xoffset = self.replica_cell.width for row in range(self.total_size): - dir_x = False - name = "bit_r{0}_{1}".format(row,"rbl") - if cell_properties.bitcell.mirror.x and (row+rbl_offset)%2: - dir_x = True + # name = "bit_r{0}_{1}".format(row, "rbl") + dir_x = cell_properties.bitcell.mirror.x and (row + rbl_offset) % 2 - offset = vector(xoffset,self.cell.height*(row+(row+rbl_offset)%2)) + offset = vector(xoffset, self.cell.height * (row + (row + rbl_offset) % 2)) if dir_x and dir_y: dir_key = "XY" @@ -127,54 +151,79 @@ class replica_column(design.design): self.cell_inst[row].place(offset=offset, mirror=dir_key) - - def add_layout_pins(self): """ Add the layout pins """ - + 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="m2", - offset=bl_pin.ll(), + layer=bl_pin.layer, + offset=bl_pin.ll().scale(1, 0), width=bl_pin.width(), height=self.height) - for row in range(self.total_size): + try: + end_caps_enabled = cell_properties.bitcell.end_caps + except AttributeError: + end_caps_enabled = False + + if end_caps_enabled: + row_range_max = self.total_size - 1 + row_range_min = 1 + else: + row_range_max = self.total_size + row_range_min = 0 + + for row in range(row_range_min, row_range_max): 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="m1", - offset=wl_pin.ll().scale(0,1), + self.add_layout_pin(text="{0}_{1}".format(wl_name, 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.total_size): - inst = self.cell_inst[row] + # Supplies are only connected in the ends + for (index, inst) in self.cell_inst.items(): for pin_name in ["vdd", "gnd"]: - self.copy_layout_pin(inst, pin_name) + if inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]: + self.copy_power_pins(inst, pin_name) + else: + self.copy_layout_pin(inst, pin_name) def get_bitcell_pins(self, col, row): - """ Creates a list of connections in the bitcell, + """ 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)) + 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(pin + "_{0}".format(row)) bitcell_pins.append("vdd") bitcell_pins.append("gnd") - + return bitcell_pins - + + def get_bitcell_pins_col_cap(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)) + bitcell_pins.append("vdd") + + return bitcell_pins + def exclude_all_but_replica(self): """Excludes all bits except the replica cell (self.replica_bit).""" - + for row, cell in self.cell_inst.items(): if row != self.replica_bit: self.graph_inst_exclude.add(cell) diff --git a/compiler/modules/row_cap_array.py b/compiler/modules/row_cap_array.py new file mode 100644 index 00000000..f108d86e --- /dev/null +++ b/compiler/modules/row_cap_array.py @@ -0,0 +1,117 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +from bitcell_base_array import bitcell_base_array +from sram_factory import factory +from globals import OPTS +from tech import cell_properties + + +class row_cap_array(bitcell_base_array): + """ + Generate a dummy row/column for the replica array. + """ + def __init__(self, cols, rows, column_offset=0, mirror=0, name=""): + super().__init__(cols, rows, name, column_offset) + self.mirror = mirror + self.no_instances = True + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + """ Create and connect the netlist """ + self.add_modules() + self.add_pins() + self.create_instances() + + def create_layout(self): + + self.place_array("dummy_r{0}_c{1}", self.mirror) + self.add_layout_pins() + self.add_boundary() + self.DRC_LVS() + + def add_modules(self): + """ Add the modules used in this design """ + self.dummy_cell = factory.create(module_type="row_cap_{}".format(OPTS.bitcell)) + self.add_mod(self.dummy_cell) + + self.cell = factory.create(module_type="bitcell") + + def create_instances(self): + """ Create the module instances used in this design """ + self.cell_inst = {} + for col in range(self.column_size): + for row in range(1, self.row_size - 1): + name = "bit_r{0}_c{1}".format(row, col) + self.cell_inst[row, col]=self.add_inst(name=name, + mod=self.dummy_cell) + self.connect_inst(self.get_bitcell_pins(col, row)) + + 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 + """ + + pin_name = cell_properties.bitcell.cell_1rw1r.pin + bitcell_pins = ["{0}_{1}".format(pin_name.wl0, row), + "{0}_{1}".format(pin_name.wl1, row), + "gnd"] + + return bitcell_pins + + 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 = self.cell.height + tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset) + + for row in range(1, self.row_size - 1): + 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 + + def add_layout_pins(self): + """ Add the layout pins """ + + row_list = self.cell.get_all_wl_names() + + for row in range(1, self.row_size - 1): + 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()) + + # Add vdd/gnd via stacks + for row in range(1, self.row_size - 1): + 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(), + start_layer=pin.layer) + diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py index ff5638ba..35fbdf42 100644 --- a/compiler/modules/sense_amp.py +++ b/compiler/modules/sense_amp.py @@ -8,11 +8,12 @@ import design import debug import utils -from tech import GDS,layer, parameter,drc +from tech import GDS, layer, parameter, drc from tech import cell_properties as props from globals import OPTS import logical_effort + class sense_amp(design.design): """ This module implements the single sense amp cell used in the design. It @@ -28,10 +29,10 @@ class sense_amp(design.design): props.sense_amp.pin.gnd] type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] if not OPTS.netlist_only: - (width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"]) + (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) + (width, height) = (0, 0) pin_map = [] def get_bl_names(self): @@ -61,41 +62,41 @@ class sense_amp(design.design): # FIXME: This input load will be applied to both the s_en timing and bitline timing. - #Input load for the bitlines which are connected to the source/drain of a TX. Not the selects. - from tech import spice, parameter + # Input load for the bitlines which are connected to the source/drain of a TX. Not the selects. + from tech import spice # Default is 8x. Per Samira and Hodges-Jackson book: # "Column-mux transistors driven by the decoder must be sized for optimal speed" - bitline_pmos_size = 8 #FIXME: This should be set somewhere and referenced. Probably in tech file. - return spice["min_tx_drain_c"]*(bitline_pmos_size)#ff + bitline_pmos_size = 8 # FIXME: This should be set somewhere and referenced. Probably in tech file. + return spice["min_tx_drain_c"] * bitline_pmos_size # ff def get_stage_effort(self, load): - #Delay of the sense amp will depend on the size of the amp and the output load. + # Delay of the sense amp will depend on the size of the amp and the output load. parasitic_delay = 1 - cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"])/drc("minwidth_tx") - sa_size = parameter["sa_inv_nmos_size"]/drc("minwidth_tx") + cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"]) / drc("minwidth_tx") + sa_size = parameter["sa_inv_nmos_size"] / drc("minwidth_tx") cc_inv_cin = cin - return logical_effort.logical_effort('column_mux', sa_size, cin, load+cc_inv_cin, parasitic_delay, False) + return logical_effort.logical_effort('column_mux', sa_size, cin, load + cc_inv_cin, parasitic_delay, False) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" - #Power in this module currently not defined. Returns 0 nW (leakage and dynamic). + # Power in this module currently not defined. Returns 0 nW (leakage and dynamic). total_power = self.return_power() return total_power def get_en_cin(self): """Get the relative capacitance of sense amp enable gate cin""" - pmos_cin = parameter["sa_en_pmos_size"]/drc("minwidth_tx") - nmos_cin = parameter["sa_en_nmos_size"]/drc("minwidth_tx") - #sen is connected to 2 pmos isolation TX and 1 nmos per sense amp. - return 2*pmos_cin + nmos_cin + pmos_cin = parameter["sa_en_pmos_size"] / drc("minwidth_tx") + nmos_cin = parameter["sa_en_nmos_size"] / drc("minwidth_tx") + # sen is connected to 2 pmos isolation TX and 1 nmos per sense amp. + return 2 * pmos_cin + nmos_cin def get_enable_name(self): """Returns name used for enable net""" - #FIXME: A better programmatic solution to designate pins + # FIXME: A better programmatic solution to designate pins enable_name = self.en_name debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(enable_name)) return enable_name - def build_graph(self, graph, inst_name, port_nets): + 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) + self.add_graph_edges(graph, port_nets) diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index cb413c45..98cbee66 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -20,7 +20,8 @@ class sense_amp_array(design.design): Dynamically generated sense amp array for all bitlines. """ - def __init__(self, name, word_size, words_per_row): + def __init__(self, name, word_size, words_per_row, num_spare_cols=None, column_offset=0): + design.design.__init__(self, name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("word_size {0}".format(word_size)) @@ -28,8 +29,19 @@ class sense_amp_array(design.design): self.word_size = word_size self.words_per_row = words_per_row + if not num_spare_cols: + self.num_spare_cols = 0 + else: + self.num_spare_cols = num_spare_cols + + self.column_offset = column_offset self.row_size = self.word_size * self.words_per_row + if OPTS.tech_name == "sky130": + self.en_layer = "m3" + else: + self.en_layer = "m1" + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -59,9 +71,9 @@ class sense_amp_array(design.design): self.height = self.amp.height if self.bitcell.width > self.amp.width: - self.width = self.bitcell.width * self.word_size * self.words_per_row + self.width = self.bitcell.width * (self.word_size * self.words_per_row + self.num_spare_cols) else: - self.width = self.amp.width * self.word_size * self.words_per_row + self.width = self.amp.width * (self.word_size * self.words_per_row + self.num_spare_cols) self.place_sense_amp_array() self.add_layout_pins() @@ -70,7 +82,7 @@ class sense_amp_array(design.design): self.DRC_LVS() def add_pins(self): - for i in range(0, self.word_size): + for i in range(0, self.word_size + self.num_spare_cols): self.add_pin(self.data_name + "_{0}".format(i), "OUTPUT") self.add_pin(self.get_bl_name() + "_{0}".format(i), "INPUT") self.add_pin(self.get_br_name() + "_{0}".format(i), "INPUT") @@ -89,8 +101,7 @@ class sense_amp_array(design.design): def create_sense_amp_array(self): self.local_insts = [] - for i in range(0, self.word_size): - + for i in range(0, self.word_size + self.num_spare_cols): name = "sa_d{0}".format(i) self.local_insts.append(self.add_inst(name=name, mod=self.amp)) @@ -101,42 +112,49 @@ class sense_amp_array(design.design): 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): - 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: + for i in range(0, self.row_size, self.words_per_row): + index = int(i / self.words_per_row) + xoffset = i * self.bitcell.width + + if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 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) + self.local_insts[index].place(offset=amp_position, mirror=mirror) + + # place spare sense amps (will share the same enable as regular sense amps) + for i in range(0, self.num_spare_cols): + index = self.word_size + i + xoffset = ((self.word_size * self.words_per_row) + i) * self.bitcell.width + + if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: + mirror = "MY" + xoffset = xoffset + self.amp.width + else: + mirror = "" + + amp_position = vector(xoffset, 0) + self.local_insts[index].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_pin = inst.get_pin("gnd") - self.add_power_pin(name="gnd", - loc=gnd_pin.center(), - start_layer=gnd_pin.layer, - directions=("V", "V")) - - vdd_pin = inst.get_pin("vdd") - self.add_power_pin(name="vdd", - loc=vdd_pin.center(), - start_layer=vdd_pin.layer, - directions=("V", "V")) + for gnd_pin in inst.get_pins("gnd"): + self.add_power_pin(name="gnd", + loc=gnd_pin.center(), + start_layer=gnd_pin.layer, + directions=("V", "V")) + + for vdd_pin in inst.get_pins("vdd"): + self.add_power_pin(name="vdd", + loc=vdd_pin.center(), + start_layer=vdd_pin.layer, + directions=("V", "V")) bl_pin = inst.get_pin(inst.mod.get_bl_names()) br_pin = inst.get_pin(inst.mod.get_br_names()) @@ -160,14 +178,18 @@ class sense_amp_array(design.design): height=dout_pin.height()) def route_rails(self): - # add sclk rail across entire array - sclk = self.amp.get_pin(self.amp.en_name) - sclk_offset = self.amp.get_pin(self.amp.en_name).ll().scale(0, 1) - self.add_layout_pin(text=self.en_name, - layer=sclk.layer, - offset=sclk_offset, - width=self.width, - height=drc("minwidth_" + sclk.layer)) + # Add enable across the array + en_pin = self.amp.get_pin(self.amp.en_name) + start_offset = en_pin.lc().scale(0, 1) + end_offset = start_offset + vector(self.width, 0) + self.add_layout_pin_segment_center(text=self.en_name, + layer=self.en_layer, + start=start_offset, + end=end_offset) + for inst in self.local_insts: + self.add_via_stack_center(from_layer=en_pin.layer, + to_layer=self.en_layer, + offset=inst.get_pin(self.amp.en_name).center()) 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 5dc9cbf8..8b01d111 100644 --- a/compiler/modules/single_level_column_mux_array.py +++ b/compiler/modules/single_level_column_mux_array.py @@ -7,7 +7,7 @@ # import design import debug -from tech import layer +from tech import layer, preferred_directions from vector import vector from sram_factory import factory from globals import OPTS @@ -20,7 +20,7 @@ class single_level_column_mux_array(design.design): Array of column mux to read the bitlines through the 6T. """ - def __init__(self, name, columns, word_size, bitcell_bl="bl", bitcell_br="br"): + def __init__(self, name, columns, word_size, bitcell_bl="bl", bitcell_br="br", column_offset=0): design.design.__init__(self, name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("cols: {0} word_size: {1} bl: {2} br: {3}".format(columns, word_size, bitcell_bl, bitcell_br)) @@ -30,13 +30,21 @@ class single_level_column_mux_array(design.design): self.words_per_row = int(self.columns / self.word_size) self.bitcell_bl = bitcell_bl self.bitcell_br = bitcell_br + self.column_offset = column_offset - if "li" in layer: - self.col_mux_stack = self.li_stack - self.col_mux_stack_pitch = self.li_pitch + if OPTS.tech_name == "sky130": + self.sel_layer = "m3" + self.sel_pitch = self.m3_pitch + self.bitline_layer = "m1" else: - self.col_mux_stack = self.m1_stack - self.col_mux_stack_pitch = self.m1_pitch + self.sel_layer = "m1" + self.sel_pitch = self.m2_pitch + self.bitline_layer = "m2" + + if preferred_directions[self.sel_layer] == "V": + self.via_directions = ("H", "H") + else: + self.via_directions = "pref" self.create_netlist() if not OPTS.netlist_only: @@ -90,7 +98,7 @@ class single_level_column_mux_array(design.design): self.width = self.columns * self.mux.width # one set of metal1 routes for select signals and a pair to interconnect the mux outputs bl/br # one extra route pitch is to space from the sense amp - self.route_height = (self.words_per_row + 3) * self.col_mux_stack_pitch + self.route_height = (self.words_per_row + 3) * self.sel_pitch def create_array(self): self.mux_inst = [] @@ -112,7 +120,7 @@ class single_level_column_mux_array(design.design): # 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: + if cell_properties.bitcell.mirror.y and (col_num + self.column_offset) % 2: mirror = "MY" xoffset = xoffset + self.mux.width else: @@ -149,11 +157,11 @@ class single_level_column_mux_array(design.design): self.route_bitlines() def add_horizontal_input_rail(self): - """ Create address input rails on M1 below the mux transistors """ + """ Create address input rails below the mux transistors """ for j in range(self.words_per_row): - offset = vector(0, self.route_height + (j - self.words_per_row) * self.col_mux_stack_pitch) + offset = vector(0, self.route_height + (j - self.words_per_row) * self.sel_pitch) self.add_layout_pin(text="sel_{}".format(j), - layer=self.col_mux_stack[0], + layer=self.sel_layer, offset=offset, width=self.mux.width * self.columns) @@ -171,75 +179,57 @@ 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=self.poly_stack, - offset=offset) + self.add_via_stack_center(from_layer="poly", + to_layer=self.sel_layer, + offset=offset, + directions=self.via_directions) 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() - bl_out_offset = bl_offset - vector(0, (self.words_per_row + 1) * self.col_mux_stack_pitch) - br_out_offset = br_offset - vector(0, (self.words_per_row + 2) * self.col_mux_stack_pitch) + bl_offset_begin = self.mux_inst[j].get_pin("bl_out").bc() + br_offset_begin = self.mux_inst[j].get_pin("br_out").bc() - bl_out_offset_end = bl_out_offset + vector(0, self.route_height) - br_out_offset_end = br_out_offset + vector(0, self.route_height) + bl_out_offset_begin = bl_offset_begin - vector(0, (self.words_per_row + 1) * self.sel_pitch) + br_out_offset_begin = br_offset_begin - vector(0, (self.words_per_row + 2) * self.sel_pitch) - 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) - - 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(self.col_mux_stack[0], [bl_out_offset, bl_out_offset + vector(width + dist, 0)]) - self.add_path(self.col_mux_stack[0], [br_out_offset, br_out_offset + vector(width - dist, 0)]) + # Add the horizontal wires for the first bit + if j % self.words_per_row == 0: + bl_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("bl_out").bc() + br_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("br_out").bc() + bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.sel_pitch) + br_out_offset_end = br_offset_end - vector(0, (self.words_per_row + 2) * self.sel_pitch) + + self.add_path(self.sel_layer, [bl_out_offset_begin, bl_out_offset_end]) + self.add_path(self.sel_layer, [br_out_offset_begin, br_out_offset_end]) # 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=self.col_mux_stack[2], - start=bl_out_offset, - end=tmp_bl_out_end) + layer=self.bitline_layer, + start=bl_offset_begin, + end=bl_out_offset_begin) self.add_layout_pin_segment_center(text="br_out_{}".format(int(j / self.words_per_row)), - layer=self.col_mux_stack[2], - start=br_out_offset, - end=tmp_br_out_end) - - # This via is on the right of the wire - self.add_via_center(layers=self.col_mux_stack, - offset=bl_out_offset) - - # This via is on the left of the wire - self.add_via_center(layers=self.col_mux_stack, - offset=br_out_offset) + layer=self.bitline_layer, + start=br_offset_begin, + end=br_out_offset_begin) else: - self.add_path(self.col_mux_stack[2], [bl_out_offset, bl_offset]) - self.add_path(self.col_mux_stack[2], [br_out_offset, br_offset]) + self.add_path(self.bitline_layer, [bl_out_offset_begin, bl_offset_begin]) + self.add_path(self.bitline_layer, [br_out_offset_begin, br_offset_begin]) - # This via is on the right of the wire - self.add_via_center(layers=self.col_mux_stack, - offset=bl_out_offset) - # This via is on the left of the wire - self.add_via_center(layers=self.col_mux_stack, - offset=br_out_offset) + # This via is on the right of the wire + self.add_via_stack_center(from_layer=self.bitline_layer, + to_layer=self.sel_layer, + offset=bl_out_offset_begin, + directions=self.via_directions) + + # This via is on the left of the wire + self.add_via_stack_center(from_layer=self.bitline_layer, + to_layer=self.sel_layer, + offset=br_out_offset_begin, + directions=self.via_directions) def get_drain_cin(self): """Get the relative capacitance of the drain of the NMOS pass TX""" diff --git a/compiler/modules/wordline_driver.py b/compiler/modules/wordline_driver_array.py similarity index 50% rename from compiler/modules/wordline_driver.py rename to compiler/modules/wordline_driver_array.py index 55f5e707..e8a3c110 100644 --- a/compiler/modules/wordline_driver.py +++ b/compiler/modules/wordline_driver_array.py @@ -7,15 +7,12 @@ # import debug import design -import math -from tech import drc +from tech import drc, layer from vector import vector from sram_factory import factory from globals import OPTS -from tech import cell_properties - -class wordline_driver(design.design): +class wordline_driver_array(design.design): """ Creates a Wordline Driver Generates the wordline-driver to drive the bitcell @@ -26,21 +23,9 @@ class wordline_driver(design.design): debug.info(1, "Creating {0}".format(self.name)) self.add_comment("rows: {0} cols: {1}".format(rows, cols)) - self.bitcell_rows = rows - self.bitcell_cols = cols + self.rows = rows + self.cols = cols - b = factory.create(module_type="bitcell") - try: - self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple - except AttributeError: - self.cell_multiple = 1 - self.cell_height = self.cell_multiple * b.height - - # We may have more than one bitcell per decoder row - self.num_rows = math.ceil(self.bitcell_rows / self.cell_multiple) - # We will place this many final decoders per row - self.decoders_per_row = math.ceil(self.bitcell_rows / self.num_rows) - self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -51,7 +36,10 @@ class wordline_driver(design.design): self.create_drivers() def create_layout(self): - self.setup_layout_constants() + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" self.place_drivers() self.route_layout() self.route_vdd_gnd() @@ -61,104 +49,99 @@ class wordline_driver(design.design): def add_pins(self): # inputs to wordline_driver. - for i in range(self.bitcell_rows): + for i in range(self.rows): self.add_pin("in_{0}".format(i), "INPUT") # Outputs from wordline_driver. - for i in range(self.bitcell_rows): + for i in range(self.rows): self.add_pin("wl_{0}".format(i), "OUTPUT") self.add_pin("en", "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") def add_modules(self): - self.and2 = factory.create(module_type="pand2", - height=self.cell_height, - size=self.bitcell_cols) - self.add_mod(self.and2) + self.wl_driver = factory.create(module_type="wordline_driver", + size=self.cols) + self.add_mod(self.wl_driver) def route_vdd_gnd(self): """ 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 - xoffset_list = [self.and_inst[0].lx()] - for num in range(self.bitcell_rows): - # this will result in duplicate polygons for rails, but who cares - - # use the inverter offset even though it will be the and's too - (gate_offset, y_dir) = self.get_gate_offset(0, - self.and2.height, - num) - # Route both supplies + if OPTS.tech_name == "sky130": for name in ["vdd", "gnd"]: - supply_pin = self.and_inst[num].get_pin(name) - - # Add pins in two locations - for xoffset in xoffset_list: - pin_pos = vector(xoffset, supply_pin.cy()) - self.add_power_pin(name, pin_pos) + supply_pins = self.wld_inst[0].get_pins(name) + for pin in supply_pins: + self.add_layout_pin_segment_center(text=name, + layer=pin.layer, + start=pin.bc(), + end=vector(pin.cx(), self.height)) + else: + # Find the x offsets for where the vias/pins should be placed + xoffset_list = [self.wld_inst[0].rx()] + for num in range(self.rows): + # this will result in duplicate polygons for rails, but who cares + + # use the inverter offset even though it will be the and's too + (gate_offset, y_dir) = self.get_gate_offset(0, + self.wl_driver.height, + num) + # Route both supplies + for name in ["vdd", "gnd"]: + supply_pin = self.wld_inst[num].get_pin(name) + + # Add pins in two locations + for xoffset in xoffset_list: + pin_pos = vector(xoffset, supply_pin.cy()) + self.add_power_pin(name, pin_pos) def create_drivers(self): - self.and_inst = [] - for row in range(self.bitcell_rows): + self.wld_inst = [] + for row in range(self.rows): name_and = "wl_driver_and{}".format(row) # add and2 - self.and_inst.append(self.add_inst(name=name_and, - mod=self.and2)) + self.wld_inst.append(self.add_inst(name=name_and, + mod=self.wl_driver)) self.connect_inst(["in_{0}".format(row), "en", "wl_{0}".format(row), "vdd", "gnd"]) - def setup_layout_constants(self): - # We may have more than one bitcell per decoder row - self.driver_rows = math.ceil(self.bitcell_rows / self.cell_multiple) - # We will place this many final decoders per row - self.decoders_per_row = math.ceil(self.bitcell_rows / self.driver_rows) - def place_drivers(self): + for row in range(self.rows): + if (row % 2): + y_offset = self.wl_driver.height * (row + 1) + inst_mirror = "MX" + else: + y_offset = self.wl_driver.height * row + inst_mirror = "R0" + + and2_offset = [self.wl_driver.width, y_offset] + + # add and2 + self.wld_inst[row].place(offset=and2_offset, + mirror=inst_mirror) + # Leave a well gap to separate the bitcell array well from this well well_gap = 2 * drc("pwell_to_nwell") + drc("nwell_enclose_active") - - self.width = self.decoders_per_row * self.and2.width + well_gap - self.height = self.and2.height * self.driver_rows - - for inst_index in range(self.bitcell_rows): - row = math.floor(inst_index / self.decoders_per_row) - dec = inst_index % self.decoders_per_row - - if (row % 2): - y_offset = self.and2.height * (row + 1) - inst_mirror = "MX" - else: - y_offset = self.and2.height * row - inst_mirror = "R0" - - x_offset = dec * self.and2.width - and2_offset = [x_offset, y_offset] - - # add and2 - self.and_inst[inst_index].place(offset=and2_offset, - mirror=inst_mirror) + self.width = self.wl_driver.width + well_gap + self.height = self.wl_driver.height * self.rows def route_layout(self): """ Route all of the signals """ # Wordline enable connection - en_pin = self.and_inst[0].get_pin("B") + en_pin = self.wld_inst[0].get_pin("B") en_bottom_pos = vector(en_pin.lx(), 0) en_pin = self.add_layout_pin(text="en", layer="m2", offset=en_bottom_pos, height=self.height) - for inst_index in range(self.bitcell_rows): - and_inst = self.and_inst[inst_index] - row = math.floor(inst_index / self.decoders_per_row) + for row in range(self.rows): + and_inst = self.wld_inst[row] # Drop a via b_pin = and_inst.get_pin("B") @@ -167,18 +150,12 @@ class wordline_driver(design.design): offset=b_pin.center()) # connect the decoder input pin to and2 A - a_pin = and_inst.get_pin("A") - a_pos = a_pin.center() - # must under the clk line in M1 - self.add_layout_pin_segment_center(text="in_{0}".format(row), - layer="m1", - start=vector(0, a_pos.y), - end=a_pos) + self.copy_layout_pin(and_inst, "A", "in_{0}".format(row)) # output each WL on the right wl_offset = and_inst.get_pin("Z").rc() self.add_layout_pin_segment_center(text="wl_{0}".format(row), - layer="m1", + layer=self.route_layer, start=wl_offset, end=wl_offset - vector(self.m1_width, 0)) @@ -189,7 +166,7 @@ class wordline_driver(design.design): """ stage_effort_list = [] - stage1 = self.and2.get_stage_effort(external_cout, inp_is_rise) + stage1 = self.wl_driver.get_stage_effort(external_cout, inp_is_rise) stage_effort_list.append(stage1) return stage_effort_list @@ -200,5 +177,5 @@ class wordline_driver(design.design): the enable connections in the bank """ # The enable is connected to a and2 for every row. - total_cin = self.and2.get_cin() * self.rows + total_cin = self.wl_driver.get_cin() * self.rows return total_cin diff --git a/compiler/modules/write_driver_array.py b/compiler/modules/write_driver_array.py index 49e830e2..a6eb1384 100644 --- a/compiler/modules/write_driver_array.py +++ b/compiler/modules/write_driver_array.py @@ -7,6 +7,7 @@ # import design import debug +from tech import drc from sram_factory import factory from vector import vector from globals import OPTS @@ -18,7 +19,8 @@ class write_driver_array(design.design): Dynamically generated write driver array of all bitlines. """ - def __init__(self, name, columns, word_size, write_size=None): + def __init__(self, name, columns, word_size, num_spare_cols=None, write_size=None, column_offset=0): + design.design.__init__(self, name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("columns: {0}".format(columns)) @@ -27,7 +29,12 @@ class write_driver_array(design.design): self.columns = columns self.word_size = word_size self.write_size = write_size + self.column_offset = column_offset self.words_per_row = int(columns / word_size) + if not num_spare_cols: + self.num_spare_cols = 0 + else: + self.num_spare_cols = num_spare_cols if self.write_size: self.num_wmasks = int(self.word_size / self.write_size) @@ -60,9 +67,13 @@ class write_driver_array(design.design): def create_layout(self): if self.bitcell.width > self.driver.width: - self.width = self.columns * self.bitcell.width + self.width = (self.columns + self.num_spare_cols) * self.bitcell.width + self.width_regular_cols = self.columns * self.bitcell.width + self.single_col_width = self.bitcell.width else: - self.width = self.columns * self.driver.width + self.width = (self.columns + self.num_spare_cols) * self.driver.width + self.width_regular_cols = self.columns * self.driver.width + self.single_col_width = self.driver.width self.height = self.driver.height self.place_write_array() @@ -71,13 +82,16 @@ class write_driver_array(design.design): self.DRC_LVS() def add_pins(self): - for i in range(self.word_size): + for i in range(self.word_size + self.num_spare_cols): self.add_pin(self.data_name + "_{0}".format(i), "INPUT") - for i in range(self.word_size): + for i in range(self.word_size + self.num_spare_cols): self.add_pin(self.get_bl_name() + "_{0}".format(i), "OUTPUT") self.add_pin(self.get_br_name() + "_{0}".format(i), "OUTPUT") if self.write_size: - for i in range(self.num_wmasks): + for i in range(self.num_wmasks + self.num_spare_cols): + self.add_pin(self.en_name + "_{0}".format(i), "INPUT") + elif self.num_spare_cols and not self.write_size: + for i in range(self.num_spare_cols + 1): self.add_pin(self.en_name + "_{0}".format(i), "INPUT") else: self.add_pin(self.en_name, "INPUT") @@ -112,12 +126,34 @@ class write_driver_array(design.design): if w == self.write_size: w = 0 windex+=1 + + elif self.num_spare_cols and not self.write_size: + self.connect_inst([self.data_name + "_{0}".format(index), + self.get_bl_name() + "_{0}".format(index), + self.get_br_name() + "_{0}".format(index), + self.en_name + "_{0}".format(0), "vdd", "gnd"]) + else: self.connect_inst([self.data_name + "_{0}".format(index), self.get_bl_name() + "_{0}".format(index), self.get_br_name() + "_{0}".format(index), self.en_name, "vdd", "gnd"]) + for i in range(self.num_spare_cols): + index = self.word_size + i + if self.write_size: + offset = self.num_wmasks + else: + offset = 1 + name = "write_driver{}".format(self.columns + i) + self.driver_insts[index]=self.add_inst(name=name, + mod=self.driver) + + self.connect_inst([self.data_name + "_{0}".format(index), + self.get_bl_name() + "_{0}".format(index), + self.get_br_name() + "_{0}".format(index), + self.en_name + "_{0}".format(i + offset), "vdd", "gnd"]) + def place_write_array(self): from tech import cell_properties if self.bitcell.width > self.driver.width: @@ -128,7 +164,7 @@ class write_driver_array(design.design): index = int(i / self.words_per_row) xoffset = i * self.driver_spacing - if cell_properties.bitcell.mirror.y and i % 2: + if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: mirror = "MY" xoffset = xoffset + self.driver.width else: @@ -137,8 +173,22 @@ class write_driver_array(design.design): base = vector(xoffset, 0) self.driver_insts[index].place(offset=base, mirror=mirror) + # place spare write drivers (if spare columns are specified) + for i in range(self.num_spare_cols): + index = self.word_size + i + xoffset = (self.columns + i) * self.driver_spacing + + if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 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): + for i in range(self.word_size + self.num_spare_cols): inst = self.driver_insts[i] din_pin = inst.get_pin(inst.mod.din_name) self.add_layout_pin(text=self.data_name + "_{0}".format(i), @@ -183,6 +233,31 @@ class write_driver_array(design.design): offset=en_pin.ll(), width=wmask_en_len - en_gap, height=en_pin.height()) + + for i in range(self.num_spare_cols): + inst = self.driver_insts[self.word_size + i] + en_pin = inst.get_pin(inst.mod.en_name) + self.add_layout_pin(text=self.en_name + "_{0}".format(i + self.num_wmasks), + layer="m1", + offset=en_pin.lr() + vector(-drc("minwidth_m1"),0)) + + elif self.num_spare_cols and not self.write_size: + # shorten enable rail to accomodate those for spare write drivers + inst = self.driver_insts[0] + en_pin = inst.get_pin(inst.mod.en_name) + self.add_layout_pin(text=self.en_name + "_{0}".format(0), + layer="m1", + offset=en_pin.ll(), + width=self.width_regular_cols - self.words_per_row * en_pin.width()) + + # individual enables for every spare write driver + for i in range(self.num_spare_cols): + inst = self.driver_insts[self.word_size + i] + en_pin = inst.get_pin(inst.mod.en_name) + self.add_layout_pin(text=self.en_name + "_{0}".format(i + 1), + layer="m1", + offset=en_pin.lr() + vector(-drc("minwidth_m1"),0)) + else: inst = self.driver_insts[0] self.add_layout_pin(text=self.en_name, diff --git a/compiler/modules/write_mask_and_array.py b/compiler/modules/write_mask_and_array.py index 4d4e0c50..d48aefef 100644 --- a/compiler/modules/write_mask_and_array.py +++ b/compiler/modules/write_mask_and_array.py @@ -10,7 +10,7 @@ import debug from sram_factory import factory from vector import vector from globals import OPTS - +from tech import layer class write_mask_and_array(design.design): """ @@ -18,7 +18,7 @@ class write_mask_and_array(design.design): The write mask AND array goes between the write driver array and the sense amp array. """ - def __init__(self, name, columns, word_size, write_size, port=0): + def __init__(self, name, columns, word_size, write_size, column_offset=0): design.design.__init__(self, name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("columns: {0}".format(columns)) @@ -28,7 +28,7 @@ class write_mask_and_array(design.design): self.columns = columns self.word_size = word_size self.write_size = write_size - self.port = port + self.column_offset = column_offset self.words_per_row = int(columns / word_size) self.num_wmasks = int(word_size / write_size) @@ -60,7 +60,7 @@ class write_mask_and_array(design.design): # Size the AND gate for the number of write drivers it drives, which is equal to the write size. # Assume stage effort of 3 to compute the size self.and2 = factory.create(module_type="pand2", - size=self.write_size / 4.0) + size=max(self.write_size / 4.0, 1)) self.add_mod(self.and2) def create_and2_array(self): @@ -93,7 +93,7 @@ class write_mask_and_array(design.design): self.width = self.bitcell.width * self.columns self.height = self.and2.height - + for i in range(self.num_wmasks): base = vector(i * self.wmask_en_len, 0) self.and2_insts[i].place(base) @@ -108,8 +108,21 @@ class write_mask_and_array(design.design): end=vector(self.width, en_pin.cy())) for i in range(self.num_wmasks): + # Route the A pin over to the left so that it doesn't conflict with the sense + # amp output which is usually in the center + a_pin = self.and2_insts[i].get_pin("A") + a_pos = a_pin.center() + in_pos = vector(self.and2_insts[i].lx(), + a_pos.y) + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m2", + offset=in_pos) + self.add_layout_pin_rect_center(text="wmask_in_{0}".format(i), + layer="m2", + offset=in_pos) + self.add_path(a_pin.layer, [in_pos, a_pos]) + # Copy remaining layout pins - self.copy_layout_pin(self.and2_insts[i], "A", "wmask_in_{0}".format(i)) self.copy_layout_pin(self.and2_insts[i], "Z", "wmask_out_{0}".format(i)) # Add via connections to metal3 for AND array's B pin @@ -121,16 +134,14 @@ class write_mask_and_array(design.design): for supply in ["gnd", "vdd"]: supply_pin=self.and2_insts[i].get_pin(supply) - self.add_power_pin(supply, supply_pin.center()) + self.add_power_pin(supply, supply_pin.center(), start_layer=supply_pin.layer) 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()]) - + self.add_path(supply_pin_left.layer, [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. return self.and2.get_cin() * len(self.and2_insts) - - diff --git a/compiler/openram.py b/compiler/openram.py index 63a8cfd6..cf18f97b 100755 --- a/compiler/openram.py +++ b/compiler/openram.py @@ -53,7 +53,8 @@ from sram_config import sram_config # Configure the SRAM organization c = sram_config(word_size=OPTS.word_size, num_words=OPTS.num_words, - write_size=OPTS.write_size) + write_size=OPTS.write_size, + num_spare_rows=OPTS.num_spare_rows) debug.print_raw("Words per row: {}".format(c.words_per_row)) output_extensions = ["sp", "v", "lib", "py", "html", "log"] diff --git a/compiler/options.py b/compiler/options.py index d891ebd9..d229b96e 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -21,10 +21,10 @@ class options(optparse.Values): ################### # This is the technology directory. openram_tech = "" - + # This is the name of the technology. tech_name = "" - + # Port configuration (1-2 ports allowed) num_rw_ports = 1 num_r_ports = 0 @@ -32,7 +32,7 @@ class options(optparse.Values): # Write mask size, default will be overwritten with word_size if not user specified write_size = None - + # These will get initialized by the user or the tech file nominal_corner_only = False supply_voltages = "" @@ -44,23 +44,25 @@ class options(optparse.Values): # word_size = 0 # You can manually specify banks, but it is better to auto-detect it. num_banks = 1 + num_spare_rows = 0 + # num_spare_cols = 0 ################### # Optimization options ################### # Approximate percentage of delay compared to bitlines rbl_delay_percentage = 0.5 - + # Allow manual adjustment of the delay chain over automatic use_tech_delay_chain_size = False delay_chain_stages = 9 delay_chain_fanout_per_stage = 4 - - + + accuracy_requirement = 0.75 ################### # Debug options. - ################### + ################### # This is the temp directory where all intermediate results are stored. try: # If user defined the temporary location in their environment, use it @@ -91,7 +93,7 @@ class options(optparse.Values): # Run with extracted parasitics use_pex = False - + ################### # Tool options ################### @@ -108,7 +110,9 @@ class options(optparse.Values): drc_exe = None lvs_exe = None pex_exe = None - + # For sky130, we need magic for filtering. + magic_exe = None + # Should we print out the banner at startup print_banner = True @@ -121,9 +125,14 @@ class options(optparse.Values): analytical_delay = True # Purge the temp directory after a successful # run (doesn't purge on errors, anyhow) + + # Route the input/output pins to the perimeter + perimeter_pins = False + purge_temp = True # These are the default modules that can be over-riden + bitcell_suffix = "" bank_select = "bank_select" bitcell_array = "bitcell_array" bitcell = "bitcell" @@ -133,10 +142,12 @@ class options(optparse.Values): delay_chain = "delay_chain" dff_array = "dff_array" dff = "dff" - dummy_bitcell = "dummy_bitcell" + inv_dec = "pinv" + nand2_dec = "pnand2" + nand3_dec = "pnand3" + nand4_dec = "pnand4" # Not available right now precharge_array = "precharge_array" ptx = "ptx" - replica_bitcell = "replica_bitcell" replica_bitline = "replica_bitline" sense_amp_array = "sense_amp_array" sense_amp = "sense_amp" @@ -146,4 +157,3 @@ class options(optparse.Values): write_driver_array = "write_driver_array" write_driver = "write_driver" write_mask_and_array = "write_mask_and_array" - diff --git a/compiler/pgates/pand2.py b/compiler/pgates/pand2.py index 69af1e62..435ace1f 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -13,16 +13,16 @@ from sram_factory import factory class pand2(pgate.pgate): """ - This is a simple buffer used for driving loads. + This is an AND (or NAND) with configurable drive strength. """ - def __init__(self, name, size=1, height=None): - debug.info(1, "Creating pnand2 {}".format(name)) + def __init__(self, name, size=1, height=None, vertical=False, add_wells=True): + debug.info(1, "Creating pand2 {}".format(name)) self.add_comment("size: {}".format(size)) - + + self.vertical = vertical self.size = size - - # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): self.add_pins() @@ -30,17 +30,25 @@ class pand2(pgate.pgate): self.create_insts() def create_modules(self): - self.nand = factory.create(module_type="pnand2", height=self.height) - self.add_mod(self.nand) + self.nand = factory.create(module_type="pnand2", + height=self.height, + add_wells=self.vertical) self.inv = factory.create(module_type="pdriver", - neg_polarity=True, - fanout=self.size, - height=self.height) + size_list=[self.size], + height=self.height, + add_wells=self.add_wells) + + self.add_mod(self.nand) self.add_mod(self.inv) def create_layout(self): - self.width = self.nand.width + self.inv.width + if self.vertical: + self.height = 2 * self.nand.height + self.width = max(self.nand.width, self.inv.width) + else: + self.width = self.nand.width + self.inv.width + self.place_insts() self.add_wires() self.add_layout_pins() @@ -68,17 +76,60 @@ class pand2(pgate.pgate): # Add NAND to the right self.nand_inst.place(offset=vector(0, 0)) - # Add INV to the right - self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + if self.vertical: + # Add INV above + self.inv_inst.place(offset=vector(self.inv.width, + 2 * self.nand.height), + mirror="XY") + else: + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + + # Second gnd of the inverter gate + if self.vertical: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + if self.vertical: + # Shared between two gates + y_offset = 0.5 * self.height + else: + y_offset = self.height + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, y_offset), + width=self.width) + def add_wires(self): # nand Z to inv A z1_pin = self.nand_inst.get_pin("Z") 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(self.route_layer, - [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) + if self.vertical: + route_layer = "m2" + self.add_via_stack_center(offset=z1_pin.center(), + from_layer=z1_pin.layer, + to_layer=route_layer) + self.add_zjog(route_layer, + z1_pin.uc(), + a2_pin.bc(), + "V") + self.add_via_stack_center(offset=a2_pin.center(), + from_layer=a2_pin.layer, + to_layer=route_layer) + else: + route_layer = self.route_layer + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) def add_layout_pins(self): pin = self.inv_inst.get_pin("Z") diff --git a/compiler/pgates/pand3.py b/compiler/pgates/pand3.py index 841ac69d..92429921 100644 --- a/compiler/pgates/pand3.py +++ b/compiler/pgates/pand3.py @@ -15,14 +15,15 @@ class pand3(pgate.pgate): """ This is a simple buffer used for driving loads. """ - def __init__(self, name, size=1, height=None): + def __init__(self, name, size=1, height=None, vertical=False, add_wells=True): debug.info(1, "Creating pand3 {}".format(name)) self.add_comment("size: {}".format(size)) - + + self.vertical = vertical self.size = size # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): self.add_pins() @@ -31,16 +32,27 @@ class pand3(pgate.pgate): def create_modules(self): # Shield the cap, but have at least a stage effort of 4 - self.nand = factory.create(module_type="pnand3", height=self.height) - self.add_mod(self.nand) + self.nand = factory.create(module_type="pnand3", + height=self.height, + add_wells=self.vertical) - self.inv = factory.create(module_type="pinv", - size=self.size, - height=self.height) + # Add the well tap to the inverter because when stacked + # vertically it is sometimes narrower + self.inv = factory.create(module_type="pdriver", + size_list=[self.size], + height=self.height, + add_wells=self.add_wells) + + self.add_mod(self.nand) self.add_mod(self.inv) def create_layout(self): - self.width = self.nand.width + self.inv.width + if self.vertical: + self.height = 2 * self.nand.height + self.width = max(self.nand.width, self.inv.width) + else: + self.width = self.nand.width + self.inv.width + self.place_insts() self.add_wires() self.add_layout_pins() @@ -69,18 +81,61 @@ class pand3(pgate.pgate): # Add NAND to the right self.nand_inst.place(offset=vector(0, 0)) - # Add INV to the right - self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + if self.vertical: + # Add INV above + self.inv_inst.place(offset=vector(self.inv.width, + 2 * self.nand.height), + mirror="XY") + else: + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + + # Second gnd of the inverter gate + if self.vertical: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + if self.vertical: + # Shared between two gates + y_offset = 0.5 * self.height + else: + y_offset = self.height + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, y_offset), + width=self.width) + def add_wires(self): # nand Z to inv A z1_pin = self.nand_inst.get_pin("Z") 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(z1_pin.layer, - [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) - + if self.vertical: + route_layer = "m2" + self.add_via_stack_center(offset=z1_pin.center(), + from_layer=z1_pin.layer, + to_layer=route_layer) + self.add_zjog(route_layer, + z1_pin.uc(), + a2_pin.bc(), + "V") + self.add_via_stack_center(offset=a2_pin.center(), + from_layer=a2_pin.layer, + to_layer=route_layer) + else: + route_layer = self.route_layer + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + def add_layout_pins(self): pin = self.inv_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Z", diff --git a/compiler/pgates/pbuf.py b/compiler/pgates/pbuf.py index 4d90286d..8b9c4eab 100644 --- a/compiler/pgates/pbuf.py +++ b/compiler/pgates/pbuf.py @@ -56,7 +56,8 @@ class pbuf(pgate.pgate): self.inv2 = factory.create(module_type="pinv", size=self.size, - height=self.height) + height=self.height, + add_wells=False) self.add_mod(self.inv2) def create_insts(self): diff --git a/compiler/pgates/pdriver.py b/compiler/pgates/pdriver.py index 9bba7aee..578a11c4 100644 --- a/compiler/pgates/pdriver.py +++ b/compiler/pgates/pdriver.py @@ -17,13 +17,13 @@ class pdriver(pgate.pgate): sized for driving a load. """ - def __init__(self, name, neg_polarity=False, fanout=0, size_list=None, height=None): + def __init__(self, name, inverting=False, fanout=0, size_list=None, height=None, add_wells=True): debug.info(1, "creating pdriver {}".format(name)) self.stage_effort = 3 self.height = height - self.neg_polarity = neg_polarity + self.inverting = inverting self.size_list = size_list self.fanout = fanout @@ -31,11 +31,11 @@ class pdriver(pgate.pgate): debug.error("Either fanout or size list must be specified.", -1) if self.size_list and self.fanout != 0: debug.error("Cannot specify both size_list and fanout.", -1) - if self.size_list and self.neg_polarity: - debug.error("Cannot specify both size_list and neg_polarity.", -1) + if self.size_list and self.inverting: + debug.error("Cannot specify both size_list and inverting.", -1) # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def compute_sizes(self): # size_list specified @@ -47,9 +47,9 @@ class pdriver(pgate.pgate): int(round(self.fanout ** (1 / self.stage_effort)))) # Increase the number of stages if we need to fix polarity - if self.neg_polarity and (self.num_stages % 2 == 0): + if self.inverting and (self.num_stages % 2 == 0): self.num_stages += 1 - elif not self.neg_polarity and (self.num_stages % 2): + elif not self.inverting and (self.num_stages % 2): self.num_stages += 1 self.size_list = [] @@ -73,9 +73,9 @@ class pdriver(pgate.pgate): self.place_modules() self.route_wires() self.add_layout_pins() - self.width = self.inv_inst_list[-1].rx() self.height = self.inv_inst_list[0].height + self.extend_wells() self.route_supply_rails() self.add_boundary() @@ -87,10 +87,13 @@ class pdriver(pgate.pgate): def add_modules(self): self.inv_list = [] + add_well = self.add_wells for size in self.size_list: temp_inv = factory.create(module_type="pinv", size=size, - height=self.height) + height=self.height, + add_wells=add_well) + add_well=False self.inv_list.append(temp_inv) self.add_mod(temp_inv) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 5351be07..985c8a86 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -14,8 +14,8 @@ from tech import layer, drc from vector import vector from globals import OPTS -if(OPTS.tech_name == "s8"): - from tech import nmos_bins, pmos_bins, accuracy_requirement +if(OPTS.tech_name == "sky130"): + from tech import nmos_bins, pmos_bins class pgate(design.design): @@ -24,7 +24,7 @@ class pgate(design.design): functions for parameterized gates. """ - def __init__(self, name, height=None): + def __init__(self, name, height=None, add_wells=True): """ Creates a generic cell """ design.design.__init__(self, name) @@ -33,7 +33,8 @@ class pgate(design.design): elif not height: # By default, something simple self.height = 14 * self.m1_pitch - + self.add_wells = add_wells + if "li" in layer: self.route_layer = "li" else: @@ -42,11 +43,14 @@ class pgate(design.design): self.route_layer_space = getattr(self, "{}_space".format(self.route_layer)) self.route_layer_pitch = getattr(self, "{}_pitch".format(self.route_layer)) + # hack for enclosing input pin with npc + self.input_pin_vias = [] + # This is the space from a S/D contact to the supply rail - # Assume the contact starts at the active edge - contact_to_vdd_rail_space = 0.5 * self.m1_width + self.m1_space + contact_to_vdd_rail_space = 0.5 * self.route_layer_width + self.route_layer_space # This is a poly-to-poly of a flipped cell - poly_to_poly_gate_space = self.poly_extend_active + self.poly_space + poly_to_poly_gate_space = self.poly_extend_active + 0.5 * self.poly_space + self.top_bottom_space = max(contact_to_vdd_rail_space, poly_to_poly_gate_space) @@ -145,28 +149,31 @@ class pgate(design.design): height=contact.poly_contact.first_layer_width, width=left_gate_offset.x - contact_offset.x) + return via + def extend_wells(self): """ Extend the n/p wells to cover whole cell """ # This should match the cells in the cell library - self.nwell_y_offset = 0.48 * self.height - full_height = self.height + 0.5* self.m1_width + self.nwell_yoffset = 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, self.nwell_y_offset) - vector(self.well_extend_active, 0) - nwell_height = nwell_max_offset - self.nwell_y_offset + nwell_position = vector(0, self.nwell_yoffset) - vector(self.well_extend_active, 0) + nwell_height = nwell_max_offset - self.nwell_yoffset self.add_rect(layer="nwell", offset=nwell_position, - width=self.well_width, + width=self.width + 2 * self.well_extend_active, height=nwell_height) if "vtg" in layer: self.add_rect(layer="vtg", offset=nwell_position, - width=self.well_width, + width=self.width + 2 * self.well_extend_active, height=nwell_height) # Start this half a rail width below the cell @@ -174,17 +181,20 @@ class pgate(design.design): 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 = self.nwell_y_offset - pwell_position.y + pwell_height = self.nwell_yoffset - pwell_position.y self.add_rect(layer="pwell", offset=pwell_position, - width=self.well_width, + width=self.width + 2 * self.well_extend_active, height=pwell_height) if "vtg" in layer: self.add_rect(layer="vtg", offset=pwell_position, - width=self.well_width, + width=self.width + 2 * self.well_extend_active, height=pwell_height) + if OPTS.tech_name == "sky130": + self.extend_implants() + def add_nwell_contact(self, pmos, pmos_pos): """ Add an nwell contact next to the given pmos device. """ @@ -239,6 +249,52 @@ class pgate(design.design): # Return the top of the well + def extend_implants(self): + """ + Add top-to-bottom implants for adjacency issues in s8. + """ + if self.add_wells: + rightx = None + else: + rightx = self.width + + nmos_insts = self.get_tx_insts("nmos") + if len(nmos_insts) > 0: + self.add_enclosure(nmos_insts, + layer="nimplant", + extend=self.implant_enclose_active, + leftx=0, + rightx=rightx, + boty=0) + + pmos_insts = self.get_tx_insts("pmos") + if len(pmos_insts) > 0: + self.add_enclosure(pmos_insts, + layer="pimplant", + extend=self.implant_enclose_active, + leftx=0, + rightx=rightx, + topy=self.height) + + try: + ntap_insts = [self.nwell_contact] + self.add_enclosure(ntap_insts, + layer="nimplant", + extend=self.implant_enclose_active, + rightx=self.width, + topy=self.height) + except AttributeError: + pass + try: + ptap_insts = [self.pwell_contact] + self.add_enclosure(ptap_insts, + layer="pimplant", + extend=self.implant_enclose_active, + rightx=self.width, + boty=0) + except AttributeError: + pass + def add_pwell_contact(self, nmos, nmos_pos): """ Add an pwell contact next to the given nmos device. """ @@ -267,7 +323,7 @@ class pgate(design.design): offset=contact_offset.scale(1, 0.5), width=self.pwell_contact.mod.second_layer_width, height=contact_offset.y) - + # Now add the full active and implant for the NMOS # active_offset = nmos_pos + vector(nmos.active_width,0) # This might be needed if the spacing between the actives @@ -302,10 +358,18 @@ class pgate(design.design): def determine_width(self): """ Determine the width based on the well contacts (assumed to be on the right side) """ + + # It was already set or is left as default (minimum) # Width is determined by well contact and spacing and allowing a supply via between each cell - self.width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * contact.m1_via.width - self.well_width = self.width + 2 * self.nwell_enclose_active - # Height is an input parameter, so it is not recomputed. + if self.add_wells: + width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * contact.m1_via.width + # Height is an input parameter, so it is not recomputed. + else: + max_active_xoffset = self.find_highest_layer_coords("active").x + max_route_xoffset = self.find_highest_layer_coords(self.route_layer).x + 0.5 * self.m1_space + width = max(max_active_xoffset, max_route_xoffset) + + self.width = width @staticmethod def bin_width(tx_type, target_width): @@ -327,16 +391,20 @@ class pgate(design.design): base_bins = [] scaled_bins = [] scaling_factors = [] - scaled_bins.append(bins[-1]) - base_bins.append(bins[-1]) - scaling_factors.append(1) - for width in bins[0:-1]: + + for width in bins: m = math.ceil(target_width / width) base_bins.append(width) scaling_factors.append(m) scaled_bins.append(m * width) - - select = bisect_left(scaled_bins, target_width) + + select = -1 + for i in reversed(range(0, len(scaled_bins))): + if abs(target_width - scaled_bins[i])/target_width <= 1-OPTS.accuracy_requirement: + select = i + break + if select == -1: + debug.error("failed to bin tx size {}, try reducing accuracy requirement".format(target_width), 1) scaling_factor = scaling_factors[select] scaled_bin = scaled_bins[select] selected_bin = base_bins[select] @@ -366,4 +434,4 @@ class pgate(design.design): return(scaled_bins) def bin_accuracy(self, ideal_width, width): - return abs(1-(ideal_width - width)/ideal_width) + return 1-abs((ideal_width - width)/ideal_width) diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index e0e1ce81..aa0f7d9a 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -19,8 +19,8 @@ import logical_effort from sram_factory import factory from errors import drc_error -if(OPTS.tech_name == "s8"): - from tech import nmos_bins, pmos_bins, accuracy_requirement +if(OPTS.tech_name == "sky130"): + from tech import nmos_bins, pmos_bins class pinv(pgate.pgate): @@ -31,8 +31,11 @@ class pinv(pgate.pgate): height is usually the same as the 6t library cell and is measured from center of rail to rail. """ + # binning %error tracker + bin_count = 0 + bin_error = 0 - def __init__(self, name, size=1, beta=parameter["beta"], height=None): + def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True): debug.info(2, "creating pinv structure {0} with size of {1}".format(name, @@ -40,11 +43,12 @@ class pinv(pgate.pgate): self.add_comment("size: {}".format(size)) self.size = size + debug.check(self.size >= 1, "Must have a size greater than or equal to 1.") self.nmos_size = size self.pmos_size = beta * size self.beta = beta - - pgate.pgate.__init__(self, name, height) + + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): """ Calls all functions related to the generation of the netlist """ @@ -56,17 +60,18 @@ class pinv(pgate.pgate): def create_layout(self): """ Calls all functions related to the generation of the layout """ self.place_ptx() - self.add_well_contacts() + if self.add_wells: + self.add_well_contacts() self.determine_width() self.extend_wells() - self.route_supply_rails() - self.connect_rails() self.route_input_gate(self.pmos_inst, self.nmos_inst, self.output_pos.y, "A", position="farleft") self.route_outputs() + self.route_supply_rails() + self.connect_rails() self.add_boundary() def add_pins(self): @@ -86,7 +91,7 @@ class pinv(pgate.pgate): self.tx_mults = 1 self.nmos_width = self.nmos_size * drc("minwidth_tx") self.pmos_width = self.pmos_size * drc("minwidth_tx") - if OPTS.tech_name == "s8": + if OPTS.tech_name == "sky130": (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) return @@ -131,7 +136,7 @@ class pinv(pgate.pgate): # Determine the number of mults for each to fit width # into available space - if OPTS.tech_name != "s8": + if OPTS.tech_name != "sky130": self.nmos_width = self.nmos_size * drc("minwidth_tx") self.pmos_width = self.pmos_size * drc("minwidth_tx") nmos_required_mults = max(int(ceil(self.nmos_width / nmos_height_available)), 1) @@ -164,30 +169,37 @@ class pinv(pgate.pgate): valid_pmos = [] for bin in pmos_bins: - if self.bin_accuracy(self.pmos_width, bin[0]) > accuracy_requirement: + if abs(self.bin_accuracy(self.pmos_width, bin[0])) > OPTS.accuracy_requirement and abs(self.bin_accuracy(self.pmos_width, bin[0])) <= 1: valid_pmos.append(bin) valid_pmos.sort(key = operator.itemgetter(1)) valid_nmos = [] for bin in nmos_bins: - if self.bin_accuracy(self.nmos_width, bin[0]) > accuracy_requirement: + if abs(self.bin_accuracy(self.nmos_width, bin[0])) > OPTS.accuracy_requirement and abs(self.bin_accuracy(self.nmos_width, bin[0])) <= 1: valid_nmos.append(bin) valid_nmos.sort(key = operator.itemgetter(1)) for bin in valid_pmos: if bin[0]/bin[1] < pmos_height_available: self.pmos_width = bin[0]/bin[1] - pmos_mults = valid_pmos[0][1] + pmos_mults = bin[1] break for bin in valid_nmos: if bin[0]/bin[1] < nmos_height_available: self.nmos_width = bin[0]/bin[1] - nmos_mults = valid_pmos[0][1] + nmos_mults = bin[1] break self.tx_mults = max(pmos_mults, nmos_mults) - + debug.info(2, "prebinning {0} tx, target: {4}, found {1} x {2} = {3}".format("pmos", self.pmos_width, pmos_mults, self.pmos_width * pmos_mults, self.pmos_size * drc("minwidth_tx"))) + debug.info(2, "prebinning {0} tx, target: {4}, found {1} x {2} = {3}".format("nmos", self.nmos_width, nmos_mults, self.nmos_width * nmos_mults, self.nmos_size * drc("minwidth_tx"))) + pinv.bin_count += 1 + pinv.bin_error += abs(((self.pmos_width * pmos_mults) - (self.pmos_size * drc("minwidth_tx")))/(self.pmos_size * drc("minwidth_tx"))) + pinv.bin_count += 1 + pinv.bin_error += abs(((self.nmos_width * nmos_mults) - (self.nmos_size * drc("minwidth_tx")))/(self.nmos_size * drc("minwidth_tx"))) + debug.info(2, "pinv bin count: {0} pinv bin error: {1} percent error {2}".format(pinv.bin_count, pinv.bin_error, pinv.bin_error/pinv.bin_count)) + def add_ptx(self): """ Create the PMOS and NMOS transistors. """ self.nmos = factory.create(module_type="ptx", diff --git a/compiler/pgates/pinv_dec.py b/compiler/pgates/pinv_dec.py new file mode 100644 index 00000000..3ce6ad80 --- /dev/null +++ b/compiler/pgates/pinv_dec.py @@ -0,0 +1,240 @@ +# 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 contact +import pinv +import debug +from tech import drc, parameter, layer +from vector import vector +from globals import OPTS +from sram_factory import factory + +if(OPTS.tech_name == "sky130"): + from tech import nmos_bins, pmos_bins + + +class pinv_dec(pinv.pinv): + """ + This is another version of pinv but with layout for the decoder. + Other stuff is the same (netlist, sizes, etc.) + """ + + def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True): + + debug.info(2, + "creating pinv_dec structure {0} with size of {1}".format(name, + size)) + if not height: + b = factory.create(module_type="bitcell") + self.cell_height = b.height + else: + self.cell_height = height + + # Inputs to cells are on input layer + # Outputs from cells are on output layer + if OPTS.tech_name == "sky130": + self.supply_layer = "m1" + else: + self.supply_layer = "m2" + + pinv.pinv.__init__(self, name, size, beta, self.cell_height, add_wells) + + def determine_tx_mults(self): + """ + Determines the number of fingers needed to achieve the size within + the height constraint. This may fail if the user has a tight height. + """ + + # This is always 1 tx, because we have horizontal transistors. + self.tx_mults = 1 + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + if OPTS.tech_name == "sky130": + (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) + (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) + return + + # Over-ride the route input gate to call the horizontal version. + # Other top-level netlist and layout functions are not changed. + def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left", directions=None): + """ + Route the input gate to the left side of the cell for access. + Position is actually ignored and is left to be compatible with the pinv. + """ + + nmos_gate_pin = nmos_inst.get_pin("G") + 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().y != pmos_gate_pin.ll().y: + self.gds_write("unaliged_gates.gds") + debug.check(nmos_gate_pin.ll().y == pmos_gate_pin.ll().y, + "Connecting unaligned gates not supported. See unaligned_gates.gds.") + + # Pick point on the left of NMOS and up to PMOS + nmos_gate_pos = nmos_gate_pin.rc() + pmos_gate_pos = pmos_gate_pin.lc() + self.add_path("poly", [nmos_gate_pos, pmos_gate_pos]) + + # Center is completely symmetric. + contact_width = contact.poly_contact.width + contact_offset = nmos_gate_pin.lc() \ + - vector(self.poly_extend_active + 0.5 * contact_width, 0) + via = self.add_via_stack_center(from_layer="poly", + to_layer=self.route_layer, + offset=contact_offset, + directions=directions) + self.add_path("poly", [contact_offset, nmos_gate_pin.lc()]) + + self.add_layout_pin_rect_center(text=name, + layer=self.route_layer, + offset=contact_offset, + width=via.mod.second_layer_width, + height=via.mod.second_layer_height) + + def determine_width(self): + self.width = self.pmos_inst.rx() + self.well_extend_active + + def extend_wells(self): + """ Extend bottom to top for each well. """ + + if "pwell" in layer: + ll = (self.nmos_inst.ll() - vector(2 * [self.well_enclose_active])).scale(1, 0) + ur = self.nmos_inst.ur() + vector(2 * [self.well_enclose_active]) + self.add_rect(layer="pwell", + offset=ll, + width=ur.x - ll.x, + height=self.height - ll.y + 0.5 * self.pwell_contact.height + self.well_enclose_active) + + if "nwell" in layer: + ll = (self.pmos_inst.ll() - vector(2 * [self.well_enclose_active])).scale(1, 0) + ur = self.pmos_inst.ur() + vector(2 * [self.well_enclose_active]) + self.add_rect(layer="nwell", + offset=ll, + width=ur.x - ll.x, + height=self.height - ll.y + 0.5 * self.nwell_contact.height + self.well_enclose_active) + + def place_ptx(self): + """ + """ + # center the transistors in the y-dimension (it is rotated, so use the width) + y_offset = 0.5 * (self.height - self.nmos.width) + self.nmos.width + + # offset so that the input contact is over from the left edge by poly spacing + x_offset = self.nmos.active_offset.y + contact.poly_contact.width + self.poly_space + self.nmos_pos = vector(x_offset, y_offset) + self.nmos_inst.place(self.nmos_pos, + rotate=270) + # place PMOS so it is half a poly spacing down from the top + xoffset = self.nmos_inst.rx() + 2 * self.poly_extend_active + 2 * self.well_extend_active + drc("pwell_to_nwell") + self.pmos_pos = vector(xoffset, y_offset) + self.pmos_inst.place(self.pmos_pos, + rotate=270) + + # Output position will be in between the PMOS and NMOS drains + pmos_drain_pos = self.pmos_inst.get_pin("D").center() + nmos_drain_pos = self.nmos_inst.get_pin("D").center() + self.output_pos = vector(0.5 * (pmos_drain_pos.x + nmos_drain_pos.x), nmos_drain_pos.y) + + if OPTS.tech_name == "sky130": + self.add_implants() + + def add_implants(self): + """ + Add top-to-bottom implants for adjacency issues in s8. + """ + # Route to the bottom + ll = (self.nmos_inst.ll() - vector(2 * [self.implant_enclose_active])).scale(1, 0) + # Don't route to the top + ur = self.nmos_inst.ur() + vector(self.implant_enclose_active, 0) + self.add_rect("nimplant", + ll, + ur.x - ll.x, + ur.y - ll.y) + + # Route to the bottom + ll = (self.pmos_inst.ll() - vector(2 * [self.implant_enclose_active])).scale(1, 0) + # Don't route to the top + ur = self.pmos_inst.ur() + vector(self.implant_enclose_active, 0) + self.add_rect("pimplant", + ll, + ur.x - ll.x, + ur.y - ll.y) + + def route_outputs(self): + """ + Route the output (drains) together. + Optionally, routes output to edge. + """ + + # Get the drain pin + nmos_drain_pin = self.nmos_inst.get_pin("D") + + # Pick point at right most of NMOS and connect over to PMOS + nmos_drain_pos = nmos_drain_pin.lc() + right_side = vector(self.width, nmos_drain_pos.y) + + self.add_layout_pin_segment_center("Z", + self.route_layer, + nmos_drain_pos, + right_side) + + def add_well_contacts(self): + """ Add n/p well taps to the layout and connect to supplies """ + + source_pos = self.pmos_inst.get_pin("S").center() + contact_pos = vector(source_pos.x, self.height) + self.nwell_contact = self.add_via_center(layers=self.active_stack, + offset=contact_pos, + implant_type="n", + well_type="n") + self.add_via_stack_center(offset=contact_pos, + from_layer=self.active_stack[2], + to_layer=self.supply_layer) + + source_pos = self.nmos_inst.get_pin("S").center() + contact_pos = vector(source_pos.x, self.height) + self.pwell_contact= self.add_via_center(layers=self.active_stack, + offset=contact_pos, + implant_type="p", + well_type="p") + self.add_via_stack_center(offset=contact_pos, + from_layer=self.active_stack[2], + to_layer=self.supply_layer) + + def route_supply_rails(self): + pin = self.nmos_inst.get_pin("S") + source_pos = pin.center() + bottom_pos = source_pos.scale(1, 0) + top_pos = bottom_pos + vector(0, self.height) + self.add_layout_pin_segment_center("gnd", + self.supply_layer, + start=bottom_pos, + end=top_pos) + + pin = self.pmos_inst.get_pin("S") + source_pos = pin.center() + bottom_pos = source_pos.scale(1, 0) + top_pos = bottom_pos + vector(0, self.height) + self.add_layout_pin_segment_center("vdd", + self.supply_layer, + start=bottom_pos, + end=top_pos) + + def connect_rails(self): + """ Connect the nmos and pmos to its respective power rails """ + + source_pos = self.nmos_inst.get_pin("S").center() + self.add_via_stack_center(offset=source_pos, + from_layer=self.route_layer, + to_layer=self.supply_layer) + + source_pos = self.pmos_inst.get_pin("S").center() + self.add_via_stack_center(offset=source_pos, + from_layer=self.route_layer, + to_layer=self.supply_layer) + diff --git a/compiler/pgates/pinvbuf.py b/compiler/pgates/pinvbuf.py index fe376bc4..5b286e9b 100644 --- a/compiler/pgates/pinvbuf.py +++ b/compiler/pgates/pinvbuf.py @@ -9,7 +9,7 @@ import debug import pgate from vector import vector from sram_factory import factory - +from tech import layer class pinvbuf(pgate.pgate): """ @@ -111,33 +111,45 @@ class pinvbuf(pgate.pgate): mirror="MX") def route_wires(self): + if "li" in layer: + route_stack = self.li_stack + else: + route_stack = self.m1_stack + # inv1 Z to inv2 A 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("m1", [z1_pin.center(), mid_point, a2_pin.center()]) + self.add_path(z1_pin.layer, [z1_pin.center(), mid_point, a2_pin.center()]) + self.add_via_stack_center(from_layer=z1_pin.layer, + to_layer=a2_pin.layer, + offset=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("m1", [z2_pin.center(), mid_point, a3_pin.center()]) + self.add_path(z2_pin.layer, [z2_pin.center(), mid_point, a3_pin.center()]) + self.add_via_stack_center(from_layer=z2_pin.layer, + to_layer=a3_pin.layer, + offset=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(self.m1_stack, + self.add_wire(route_stack, [z1_pin.center(), mid_point, a4_pin.center()]) - self.add_via_center(layers=self.m1_stack, - offset=z1_pin.center()) + self.add_via_stack_center(from_layer=z1_pin.layer, + to_layer=route_stack[2], + offset=z1_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="m1", + layer=vdd_pin.layer, offset=vdd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) @@ -145,7 +157,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="m1", + layer=gnd_pin.layer, offset=gnd_pin.ll().scale(0, 1), width=self.width, height=gnd_pin.height()) @@ -153,31 +165,25 @@ 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="m1", + layer=gnd_pin.layer, 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="m2", + layer=z_pin.layer, offset=z_pin.center()) - 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="m2", + layer=zb_pin.layer, offset=zb_pin.center()) - 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="m2", + layer=a_pin.layer, offset=a_pin.center()) - 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): """Get the stage efforts of the clk -> clk_buf path""" diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index 5d20201e..3974d95b 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -5,7 +5,6 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import contact import pgate import debug from tech import drc, parameter, spice @@ -13,6 +12,7 @@ from globals import OPTS from vector import vector import logical_effort from sram_factory import factory +import contact class pnand2(pgate.pgate): @@ -20,7 +20,7 @@ class pnand2(pgate.pgate): This module generates gds of a parametrically sized 2-input nand. This model use ptx to generate a 2-input nand within a cetrain height. """ - def __init__(self, name, size=1, height=None): + def __init__(self, name, size=1, height=None, add_wells=True): """ Creates a cell for a simple 2 input nand """ debug.info(2, @@ -38,12 +38,12 @@ class pnand2(pgate.pgate): debug.check(size == 1, "Size 1 pnand2 is only supported now.") self.tx_mults = 1 - if OPTS.tech_name == "s8": + if OPTS.tech_name == "sky130": (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): self.add_pins() @@ -55,13 +55,14 @@ class pnand2(pgate.pgate): self.setup_layout_constants() self.place_ptx() - self.add_well_contacts() + if self.add_wells: + self.add_well_contacts() + self.route_output() self.determine_width() self.route_supply_rails() self.connect_rails() self.extend_wells() self.route_inputs() - self.route_output() self.add_boundary() def add_pins(self): @@ -175,33 +176,52 @@ class pnand2(pgate.pgate): def route_inputs(self): """ Route the A and B inputs """ - # Top of NMOS drain - nmos_pin = self.nmos2_inst.get_pin("D") - bottom_pin_offset = nmos_pin.uy() - self.inputA_yoffset = bottom_pin_offset + self.m1_pitch - - self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch + bottom_pin = self.nmos1_inst.get_pin("D") + # active contact metal to poly contact metal spacing + active_contact_to_poly_contact = bottom_pin.uy() + self.route_layer_space + 0.5 * contact.poly_contact.second_layer_height + # active diffusion to poly contact spacing + # doesn't use nmos uy because that is calculated using offset + poly height + active_top = self.nmos1_inst.by() + self.nmos1_inst.mod.active_height + active_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height + active_to_poly_contact2 = active_top + self.poly_contact_to_gate + 0.5 * self.route_layer_width + self.inputA_yoffset = max(active_contact_to_poly_contact, + active_to_poly_contact, + active_to_poly_contact2) + + apin = self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.inputA_yoffset, + "A", + position="center") + + self.inputB_yoffset = self.inputA_yoffset + 2 * self.m3_pitch + # # active contact metal to poly contact metal spacing + # active_contact_to_poly_contact = self.output_yoffset - self.route_layer_space - 0.5 * contact.poly_contact.second_layer_height + # active_bottom = self.pmos1_inst.by() + # active_to_poly_contact = active_bottom - self.poly_to_active - 0.5 * contact.poly_contact.first_layer_height + # active_to_poly_contact2 = active_bottom - self.poly_contact_to_gate - 0.5 * self.route_layer_width + # self.inputB_yoffset = min(active_contact_to_poly_contact, + # active_to_poly_contact, + # active_to_poly_contact2) # This will help with the wells and the input/output placement - self.route_input_gate(self.pmos2_inst, - self.nmos2_inst, - self.inputB_yoffset, - "B", - position="center") + bpin = self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + self.inputB_yoffset, + "B", + position="center") + + if OPTS.tech_name == "sky130": + self.add_enclosure([apin, bpin], "npc", drc("npc_enclose_poly")) - self.route_input_gate(self.pmos1_inst, - self.nmos1_inst, - self.inputA_yoffset, - "A", - position="center") def route_output(self): """ Route the Z output """ # One routing track layer below the PMOS contacts - route_layer_offset = 0.5 * self.route_layer_width + self.route_layer_space - output_yoffset = self.pmos1_inst.get_pin("D").by() - route_layer_offset + route_layer_offset = 0.5 * contact.poly_contact.second_layer_height + self.route_layer_space + self.output_yoffset = self.pmos1_inst.get_pin("D").by() - route_layer_offset # PMOS1 drain @@ -213,7 +233,7 @@ class pnand2(pgate.pgate): # Output pin out_offset = vector(nmos_pin.cx() + self.route_layer_pitch, - output_yoffset) + self.output_yoffset) # This routes on M2 # # Midpoints of the L routes go horizontal first then vertical diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 454b75dd..ff988d88 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -12,6 +12,7 @@ from vector import vector import logical_effort from sram_factory import factory from globals import OPTS +import contact class pnand3(pgate.pgate): @@ -19,7 +20,7 @@ class pnand3(pgate.pgate): This module generates gds of a parametrically sized 2-input nand. This model use ptx to generate a 2-input nand within a cetrain height. """ - def __init__(self, name, size=1, height=None): + def __init__(self, name, size=1, height=None, add_wells=True): """ Creates a cell for a simple 3 input nand """ debug.info(2, @@ -40,12 +41,12 @@ class pnand3(pgate.pgate): "Size 1 pnand3 is only supported now.") self.tx_mults = 1 - if OPTS.tech_name == "s8": + if OPTS.tech_name == "sky130": (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def add_pins(self): """ Adds pins for spice netlist """ @@ -63,13 +64,14 @@ class pnand3(pgate.pgate): self.setup_layout_constants() self.place_ptx() - self.add_well_contacts() + if self.add_wells: + self.add_well_contacts() + self.route_inputs() + self.route_output() self.determine_width() self.route_supply_rails() self.connect_rails() self.extend_wells() - self.route_inputs() - self.route_output() self.add_boundary() def add_ptx(self): @@ -208,30 +210,45 @@ class pnand3(pgate.pgate): def route_inputs(self): """ Route the A and B and C inputs """ + # We can use this pitch because the contacts and overlap won't be adjacent + non_contact_pitch = 0.5 * self.m1_width + self.m1_space + 0.5 * contact.poly_contact.second_layer_height pmos_drain_bottom = self.pmos1_inst.get_pin("D").by() self.output_yoffset = pmos_drain_bottom - 0.5 * self.route_layer_width - self.route_layer_space - self.inputA_yoffset = self.output_yoffset - 0.5 * self.route_layer_width - self.route_layer_space - self.route_input_gate(self.pmos1_inst, - self.nmos1_inst, - self.inputA_yoffset, - "A", - position="left") + bottom_pin = self.nmos1_inst.get_pin("D") + # active contact metal to poly contact metal spacing + active_contact_to_poly_contact = bottom_pin.uy() + self.m1_space + 0.5 * contact.poly_contact.second_layer_height + # active diffusion to poly contact spacing + # doesn't use nmos uy because that is calculated using offset + poly height + active_top = self.nmos1_inst.by() + self.nmos1_inst.mod.active_height + active_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height + active_to_poly_contact2 = active_top + self.poly_contact_to_gate + 0.5 * self.route_layer_width + self.inputA_yoffset = max(active_contact_to_poly_contact, + active_to_poly_contact, + active_to_poly_contact2) + + apin = self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.inputA_yoffset, + "A", + position="left") - # Put B right on the well line - self.inputB_yoffset = self.inputA_yoffset - self.m1_pitch - self.route_input_gate(self.pmos2_inst, - self.nmos2_inst, - self.inputB_yoffset, - "B", - position="center") + self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch + bpin = self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + self.inputB_yoffset, + "B", + position="center") - self.inputC_yoffset = self.inputB_yoffset - self.m1_pitch - self.route_input_gate(self.pmos3_inst, - self.nmos3_inst, - self.inputC_yoffset, - "C", - position="right") + self.inputC_yoffset = self.inputB_yoffset + self.m3_pitch + cpin = self.route_input_gate(self.pmos3_inst, + self.nmos3_inst, + self.inputC_yoffset, + "C", + position="right") + + if OPTS.tech_name == "sky130": + self.add_enclosure([apin, bpin, cpin], "npc", drc("npc_enclose_poly")) def route_output(self): """ Route the Z output """ diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index f522578e..908bba82 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -19,7 +19,7 @@ class pnor2(pgate.pgate): This module generates gds of a parametrically sized 2-input nor. This model use ptx to generate a 2-input nor within a cetrain height. """ - def __init__(self, name, size=1, height=None): + def __init__(self, name, size=1, height=None, add_wells=True): """ Creates a cell for a simple 2 input nor """ debug.info(2, @@ -37,12 +37,12 @@ class pnor2(pgate.pgate): debug.check(size==1, "Size 1 pnor2 is only supported now.") self.tx_mults = 1 - if OPTS.tech_name == "s8": + if OPTS.tech_name == "sky130": (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): self.add_pins() @@ -54,13 +54,14 @@ class pnor2(pgate.pgate): self.setup_layout_constants() self.place_ptx() - self.add_well_contacts() + if self.add_wells: + self.add_well_contacts() + self.route_inputs() + self.route_output() self.determine_width() self.route_supply_rails() self.connect_rails() self.extend_wells() - self.route_inputs() - self.route_output() self.add_boundary() def add_pins(self): @@ -194,22 +195,25 @@ class pnor2(pgate.pgate): self.inputB_yoffset = bottom_pin_offset + self.m1_nonpref_pitch self.inputA_yoffset = self.inputB_yoffset + self.m1_nonpref_pitch - self.route_input_gate(self.pmos2_inst, - self.nmos2_inst, - self.inputB_yoffset, - "B", - position="right", - directions=("V", "V")) + bpin = self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + self.inputB_yoffset, + "B", + position="right", + directions=("V", "V")) # This will help with the wells and the input/output placement - self.route_input_gate(self.pmos1_inst, - self.nmos1_inst, - self.inputA_yoffset, - "A", - directions=("V", "V")) + apin = self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.inputA_yoffset, + "A", + directions=("V", "V")) self.output_yoffset = self.inputA_yoffset + self.m1_nonpref_pitch + if OPTS.tech_name == "sky130": + self.add_enclosure([apin, bpin], "npc", drc("npc_enclose_poly")) + def route_output(self): """ Route the Z output """ # PMOS2 (right) drain diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index d47e445f..b3d25865 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -13,7 +13,7 @@ from tech import parameter from vector import vector from globals import OPTS from sram_factory import factory -from tech import drc +from tech import drc, layer class precharge(design.design): @@ -80,7 +80,7 @@ class precharge(design.design): """ Initializes the upper and lower pmos """ - if(OPTS.tech_name == "s8"): + if(OPTS.tech_name == "sky130"): (self.ptx_width, self.ptx_mults) = pgate.bin_width("pmos", self.ptx_width) self.pmos = factory.create(module_type="ptx", width=self.ptx_width, @@ -105,21 +105,16 @@ class precharge(design.design): # center of vdd rail pmos_vdd_pos = vector(pmos_pin.cx(), vdd_position.y) - self.add_path("m1", [pmos_pin.uc(), pmos_vdd_pos]) + self.add_path(self.en_layer, [pmos_pin.center(), pmos_vdd_pos]) - # if enable is not on M1, the supply can be - if self.en_layer != "m1": - self.add_via_center(layers=self.m1_stack, - offset=pmos_vdd_pos) - self.add_power_pin("vdd", self.well_contact_pos, directions=("V", "V")) - - # Hack for li layers - if hasattr(self, "li_stack"): - self.add_via_center(layers=self.li_stack, - offset=self.well_contact_pos) + + self.add_via_stack_center(from_layer=pmos_pin.layer, + to_layer=self.en_layer, + offset=pmos_pin.center(), + directions=("V", "V")) def create_ptx(self): """ @@ -148,13 +143,9 @@ class precharge(design.design): # Compute the other pmos2 location, # but determining offset to overlap the source and drain pins overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll() - # This is how much the contact is placed inside the ptx active - contact_xdiff = self.pmos.get_pin("S").lx() # adds the lower pmos to layout - bl_xoffset = self.bitcell_bl_pin.lx() - self.lower_pmos_position = vector(max(bl_xoffset - contact_xdiff, - self.nwell_enclose_active), + self.lower_pmos_position = vector(self.well_enclose_active + 0.5 * self.m1_width, self.initial_yoffset) self.lower_pmos_inst.place(self.lower_pmos_position) @@ -196,19 +187,17 @@ class precharge(design.design): """ # adds the en contact to connect the gates to the en rail - # midway in the 4 M2 tracks - offset = self.lower_pmos_inst.get_pin("G").ul() \ - + vector(0, 0.5 * self.m2_pitch) - self.add_via_center(layers=self.poly_stack, - offset=offset) - if self.en_layer == "m2": - self.add_via_center(layers=self.m1_stack, - offset=offset) - if hasattr(self, "li_stack"): - self.add_via_center(layers=self.li_stack, - offset=offset) - - # adds the en rail on metal1 + pin_offset = self.lower_pmos_inst.get_pin("G").lr() + # This is an extra space down for some techs with contact to active spacing + contact_space = max(self.poly_space, + self.poly_contact_to_gate) + 0.5 * contact.poly_contact.first_layer_height + offset = pin_offset - vector(0, contact_space) + self.add_via_stack_center(from_layer="poly", + to_layer=self.en_layer, + offset=offset) + self.add_path("poly", + [self.lower_pmos_inst.get_pin("G").bc(), offset]) + # adds the en rail self.add_layout_pin_segment_center(text="en_bar", layer=self.en_layer, start=offset.scale(0, 1), @@ -221,17 +210,17 @@ class precharge(design.design): # adds the contact from active to metal1 offset_height = self.upper_pmos1_inst.uy() + \ - 0.5 * contact.active_contact.height + \ + contact.active_contact.height + \ self.nwell_extend_active self.well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1, 0) + \ vector(0, offset_height) - self.add_via_center(layers=self.active_stack, - offset=self.well_contact_pos, - implant_type="n", - well_type="n") - if hasattr(self, "li_stack"): - self.add_via_center(layers=self.li_stack, - offset=self.well_contact_pos) + self.well_contact = self.add_via_center(layers=self.active_stack, + offset=self.well_contact_pos, + implant_type="n", + well_type="n") + self.add_via_stack_center(from_layer=self.active_stack[2], + to_layer=self.bitline_layer, + offset=self.well_contact_pos) self.height = self.well_contact_pos.y + contact.active_contact.height + self.m1_space @@ -245,11 +234,10 @@ class precharge(design.design): """ Adds both bit-line and bit-line-bar to the module """ - layer_width = drc("minwidth_" + self.bitline_layer) - layer_space = drc("{0}_to_{0}".format(self.bitline_layer)) + layer_pitch = getattr(self, "{}_pitch".format(self.bitline_layer)) # adds the BL - self.bl_xoffset = layer_space + 0.5 * layer_width + self.bl_xoffset = layer_pitch top_pos = vector(self.bl_xoffset, self.height) pin_pos = vector(self.bl_xoffset, 0) self.add_path(self.bitline_layer, [top_pos, pin_pos]) @@ -259,7 +247,7 @@ class precharge(design.design): end=top_pos) # adds the BR - self.br_xoffset = self.width - layer_space - 0.5 * layer_width + self.br_xoffset = self.width - layer_pitch top_pos = vector(self.br_xoffset, self.height) pin_pos = vector(self.br_xoffset, 0) self.add_path(self.bitline_layer, [top_pos, pin_pos]) @@ -288,31 +276,19 @@ class precharge(design.design): Adds contacts/via from metal1 to metal2 for bit-lines """ - # No contacts needed if M1 - if self.bitline_layer == "m1": - return - # BL - lower_pin = self.lower_pmos_inst.get_pin("S") - self.lower_via = self.add_via_center(layers=self.m1_stack, - offset=lower_pin.center(), - directions=("V", "V")) + for lower_pin in [self.lower_pmos_inst.get_pin("S"), self.lower_pmos_inst.get_pin("D")]: + self.add_via_stack_center(from_layer=lower_pin.layer, + to_layer=self.bitline_layer, + offset=lower_pin.center(), + directions=("V", "V")) - lower_pin = self.lower_pmos_inst.get_pin("D") - self.lower_via = self.add_via_center(layers=self.m1_stack, - offset=lower_pin.center(), - directions=("V", "V")) - # BR - upper_pin = self.upper_pmos1_inst.get_pin("S") - self.upper_via2 = self.add_via_center(layers=self.m1_stack, - offset=upper_pin.center(), - directions=("V", "V")) - - upper_pin = self.upper_pmos2_inst.get_pin("D") - self.upper_via2 = self.add_via_center(layers=self.m1_stack, - offset=upper_pin.center(), - directions=("V", "V")) + for upper_pin in [self.upper_pmos1_inst.get_pin("S"), self.upper_pmos2_inst.get_pin("D")]: + self.add_via_stack_center(from_layer=upper_pin.layer, + to_layer=self.bitline_layer, + offset=upper_pin.center(), + directions=("V", "V")) def connect_pmos(self, pmos_pin, bit_xoffset): """ diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index c584e70b..8be003b2 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -16,6 +16,7 @@ import os from globals import OPTS from pgate import pgate + class ptx(design.design): """ This module generates gds and spice of a parametrically NMOS or @@ -24,6 +25,10 @@ class ptx(design.design): given width. Total width is therefore mults*width. Options allow you to connect the fingered gates and active for parallel devices. The add_*_contact option tells which layer to bring source/drain up to. + + ll, ur, width and height refer to the active area. + Wells and poly may extend beyond this. + """ def __init__(self, name="", @@ -126,13 +131,13 @@ class ptx(design.design): # be decided in the layout later. area_sd = 2.5 * self.poly_width * self.tx_width perimeter_sd = 2 * self.poly_width + 2 * self.tx_width - if OPTS.tech_name == "s8": - # s8 technology is in microns + if OPTS.tech_name == "sky130" and OPTS.lvs_exe[0] == "calibre": + # sky130 simulation cannot use the mult parameter in simulation (self.tx_width, self.mults) = pgate.bin_width(self.tx_type, self.tx_width) main_str = "M{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type], - self.mults, - self.tx_width, - drc("minwidth_poly")) + self.mults, + self.tx_width, + drc("minwidth_poly")) # Perimeters are in microns # Area is in u since it is microns square area_str = "pd={0:.2f} ps={0:.2f} as={1:.2f}u ad={1:.2f}u".format(perimeter_sd, @@ -147,13 +152,17 @@ class ptx(design.design): self.spice_device = main_str + area_str self.spice.append("\n* ptx " + self.spice_device) - # LVS lib is always in SI units - if os.path.exists(OPTS.openram_tech + "lvs_lib"): + if OPTS.tech_name == "sky130" and OPTS.lvs_exe[0] == "calibre": + # sky130 requires mult parameter too + self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2} l={3} mult={1}".format(spice[self.tx_type], + self.mults, + self.tx_width, + drc("minwidth_poly")) + else: self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type], self.mults, self.tx_width, drc("minwidth_poly")) - def setup_layout_constants(self): """ @@ -187,7 +196,7 @@ class ptx(design.design): # This is the spacing between the poly gates self.min_poly_pitch = self.poly_space + self.poly_width self.contacted_poly_pitch = self.poly_space + contact.poly_contact.width - self.contact_pitch = 2 * self.contact_to_gate + self.poly_width + self.contact_width + self.contact_pitch = 2 * self.active_contact_to_gate + self.poly_width + self.contact_width self.poly_pitch = max(self.min_poly_pitch, self.contacted_poly_pitch, self.contact_pitch) @@ -197,7 +206,7 @@ class ptx(design.design): # 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_contact + self.active_contact.width \ - + 2 * self.contact_to_gate + self.poly_width + (self.mults - 1) * self.poly_pitch + + 2 * self.active_contact_to_gate + self.poly_width + (self.mults - 1) * self.poly_pitch # Active height is just the transistor width self.active_height = self.tx_width @@ -205,38 +214,23 @@ class ptx(design.design): # 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 - 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) + self.active_offset = vector([self.well_enclose_active] * 2) # Well enclosure of active, ensure minwidth as well well_name = "{}well".format(self.well_type) 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, + self.well_width = max(self.active_width + 2 * self.well_enclose_active, well_width_rule) - self.well_height = max(self.active_height + 2 * well_enclose_active, + self.well_height = max(self.active_height + 2 * self.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.well_height - self.active_offset.y - self.width = self.well_width - self.active_offset.x else: - # The well is not included in the height and width - self.height = self.poly_height - self.width = self.active_width self.well_height = self.height self.well_width = self.width + + # We are going to shift the 0,0, so include that in the width and height + self.height = self.active_height + self.width = self.active_width # This is the center of the first active contact offset (centered vertically) self.contact_offset = self.active_offset + vector(0.5 * self.active_contact.width, @@ -327,7 +321,7 @@ class ptx(design.design): """ # poly is one contacted spacing from the end and down an extension poly_offset = self.contact_offset \ - + vector(0.5 * self.active_contact.width + 0.5 * self.poly_width + self.contact_to_gate, 0) + + vector(0.5 * self.active_contact.width + 0.5 * self.poly_width + self.active_contact_to_gate, 0) # poly_positions are the bottom center of the poly gates self.poly_positions = [] @@ -360,18 +354,18 @@ class ptx(design.design): """ Adding the diffusion (active region = diffusion region) """ - self.add_rect(layer="active", - offset=self.active_offset, - width=self.active_width, - height=self.active_height) + self.active = self.add_rect(layer="active", + offset=self.active_offset, + width=self.active_width, + height=self.active_height) # If the implant must enclose the active, shift offset # and increase width/height 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, - width=self.active_width + 2 * enclose_width, - height=self.active_height + 2 * enclose_width) + self.implant = self.add_rect(layer="{}implant".format(self.implant_type), + offset=self.active_offset - enclose_offset, + width=self.active_width + 2 * enclose_width, + height=self.active_height + 2 * enclose_width) def add_well_implant(self): """ @@ -386,10 +380,12 @@ class ptx(design.design): 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) + well = self.add_rect(layer=well_name, + offset=well_ll, + width=self.well_width, + height=self.well_height) + setattr(self, well_name, well) + if "vtg" in layer: self.add_rect(layer="vtg", offset=well_ll, diff --git a/compiler/pgates/pwrite_driver.py b/compiler/pgates/pwrite_driver.py index da5eacdc..87db7b20 100644 --- a/compiler/pgates/pwrite_driver.py +++ b/compiler/pgates/pwrite_driver.py @@ -160,11 +160,11 @@ class pwrite_driver(design.design): track_xoff = self.get_m2_track(1) din_loc = self.din_inst.get_pin("A").center() - self.add_via_stack("m1", "m2", din_loc) + self.add_via_stack_center("m1", "m2", din_loc) din_track = vector(track_xoff,din_loc.y) br_in = self.br_inst.get_pin("in").center() - self.add_via_stack("m1", "m2", br_in) + self.add_via_stack_center("m1", "m2", br_in) br_track = vector(track_xoff,br_in.y) din_in = vector(track_xoff,0) @@ -181,11 +181,11 @@ class pwrite_driver(design.design): track_xoff = self.get_m4_track(self.din_bar_track) din_bar_in = self.din_inst.get_pin("Z").center() - self.add_via_stack("m1", "m3", din_bar_in) + self.add_via_stack_center("m1", "m3", din_bar_in) din_bar_track = vector(track_xoff,din_bar_in.y) bl_in = self.bl_inst.get_pin("in").center() - self.add_via_stack("m1", "m3", bl_in) + self.add_via_stack_center("m1", "m3", bl_in) bl_track = vector(track_xoff,bl_in.y) din_in = vector(track_xoff,0) @@ -204,15 +204,15 @@ class pwrite_driver(design.design): # This M2 pitch is a hack since the A and Z pins align horizontally en_bar_loc = self.en_inst.get_pin("Z").uc() en_bar_track = vector(track_xoff, en_bar_loc.y) - self.add_via_stack("m1", "m3", en_bar_loc) + self.add_via_stack_center("m1", "m3", en_bar_loc) # This is a U route to the right down then left bl_en_loc = self.bl_inst.get_pin("en_bar").center() bl_en_track = vector(track_xoff, bl_en_loc.y) - self.add_via_stack("m1", "m3", bl_en_loc) + self.add_via_stack_center("m1", "m3", bl_en_loc) br_en_loc = self.br_inst.get_pin("en_bar").center() br_en_track = vector(track_xoff, bl_en_loc.y) - self.add_via_stack("m1", "m3", br_en_loc) + self.add_via_stack_center("m1", "m3", br_en_loc) # L shape @@ -237,21 +237,21 @@ class pwrite_driver(design.design): en_loc = self.en_inst.get_pin("A").center() en_rail = vector(en_loc.x, vdd_yloc) - self.add_via_stack("m1", "m2", en_loc) + self.add_via_stack_center("m1", "m2", en_loc) self.add_path("m2", [en_loc, en_rail]) - self.add_via_stack("m2", "m3", en_rail) + self.add_via_stack_center("m2", "m3", en_rail) # Start point in the track on the pin rail en_track = vector(track_xoff, vdd_yloc) - self.add_via_stack("m3", "m4", en_track) + self.add_via_stack_center("m3", "m4", en_track) # This is a U route to the right down then left bl_en_loc = self.bl_inst.get_pin("en").center() bl_en_track = vector(track_xoff, bl_en_loc.y) - self.add_via_stack("m1", "m3", bl_en_loc) + self.add_via_stack_center("m1", "m3", bl_en_loc) br_en_loc = self.br_inst.get_pin("en").center() br_en_track = vector(track_xoff, bl_en_loc.y) - self.add_via_stack("m1", "m3", br_en_loc) + self.add_via_stack_center("m1", "m3", br_en_loc) # U shape self.add_wire(self.m3_stack, diff --git a/compiler/pgates/single_level_column_mux.py b/compiler/pgates/single_level_column_mux.py index 9e38287f..b594f2e4 100644 --- a/compiler/pgates/single_level_column_mux.py +++ b/compiler/pgates/single_level_column_mux.py @@ -11,6 +11,7 @@ from tech import drc, layer from vector import vector from sram_factory import factory import logical_effort +from globals import OPTS class single_level_column_mux(pgate.pgate): @@ -44,13 +45,22 @@ class single_level_column_mux(pgate.pgate): def create_layout(self): - self.pin_height = 2 * self.m2_width + # If li exists, use li and m1 for the mux, otherwise use m1 and m2 + if "li" in layer: + self.col_mux_stack = self.li_stack + else: + self.col_mux_stack = self.m1_stack + self.pin_layer = self.bitcell.get_pin(self.bitcell_bl).layer + self.pin_pitch = getattr(self, "{}_pitch".format(self.pin_layer)) + self.pin_width = getattr(self, "{}_width".format(self.pin_layer)) + self.pin_height = 2 * self.pin_width self.width = self.bitcell.width self.height = self.nmos_upper.uy() + self.pin_height + self.connect_poly() self.add_bitline_pins() self.connect_bitlines() - self.add_wells() + self.add_pn_wells() def add_modules(self): self.bitcell = factory.create(module_type="bitcell") @@ -58,9 +68,7 @@ class single_level_column_mux(pgate.pgate): # Adds nmos_lower,nmos_upper to the module self.ptx_width = self.tx_size * drc("minwidth_tx") self.nmos = factory.create(module_type="ptx", - width=self.ptx_width, - add_source_contact=False, - add_drain_contact=False) + width=self.ptx_width) self.add_mod(self.nmos) def add_pins(self): @@ -69,29 +77,26 @@ class single_level_column_mux(pgate.pgate): def add_bitline_pins(self): """ Add the top and bottom pins to this cell """ - bl_pin=self.bitcell.get_pin(self.bitcell_bl) - br_pin=self.bitcell.get_pin(self.bitcell_br) - - bl_pos = vector(bl_pin.lx(), 0) - br_pos = vector(br_pin.lx(), 0) + bl_pos = vector(self.pin_pitch, 0) + br_pos = vector(self.width - self.pin_pitch, 0) # bl and br self.add_layout_pin(text="bl", - layer=bl_pin.layer, + layer=self.pin_layer, offset=bl_pos + vector(0, self.height - self.pin_height), height=self.pin_height) self.add_layout_pin(text="br", - layer=br_pin.layer, + layer=self.pin_layer, offset=br_pos + vector(0, self.height - self.pin_height), height=self.pin_height) # bl_out and br_out self.add_layout_pin(text="bl_out", - layer=bl_pin.layer, + layer=self.pin_layer, offset=bl_pos, height=self.pin_height) self.add_layout_pin(text="br_out", - layer=br_pin.layer, + layer=self.pin_layer, offset=br_pos, height=self.pin_height) @@ -99,7 +104,7 @@ class single_level_column_mux(pgate.pgate): """ Create the two pass gate NMOS transistors to switch the bitlines""" # Space it in the center - nmos_lower_position = self.nmos.active_offset.scale(0,1) \ + nmos_lower_position = self.nmos.active_offset.scale(0, 1) \ + vector(0.5 * self.bitcell.width- 0.5 * self.nmos.active_width, 0) self.nmos_lower = self.add_inst(name="mux_tx1", mod=self.nmos, @@ -108,18 +113,21 @@ class single_level_column_mux(pgate.pgate): # This aligns it directly above the other tx with gates abutting nmos_upper_position = nmos_lower_position \ - + vector(0, self.nmos.active_height + max(self.active_space,self.poly_space)) + + vector(0, self.nmos.active_height + max(self.active_space, self.poly_space)) self.nmos_upper = self.add_inst(name="mux_tx2", mod=self.nmos, offset=nmos_upper_position) self.connect_inst(["br", "sel", "br_out", "gnd"]) + if OPTS.tech_name == "sky130": + self.add_implants() + def connect_poly(self): """ Connect the poly gate of the two pass transistors """ # offset is the top of the lower nmos' diffusion # height is the distance between the nmos' diffusions, which depends on max(self.active_space,self.poly_space) - offset = self.nmos_lower.get_pin("G").ul() - vector(0,self.poly_extend_active) + offset = self.nmos_lower.get_pin("G").ul() - vector(0, self.poly_extend_active) height = self.nmos_upper.get_pin("G").by() + self.poly_extend_active - offset.y self.add_rect(layer="poly", offset=offset, @@ -133,63 +141,30 @@ class single_level_column_mux(pgate.pgate): def connect_bitlines(self): """ Connect the bitlines to the mux transistors """ - - # If li exists, use li and m1 for the mux, otherwise use m1 and m2 - if "li" in layer: - self.col_mux_stack = self.li_stack - else: - self.col_mux_stack = self.m1_stack - # These are on metal2 bl_pin = self.get_pin("bl") br_pin = self.get_pin("br") bl_out_pin = self.get_pin("bl_out") br_out_pin = self.get_pin("br_out") - # These are on metal1 nmos_lower_s_pin = self.nmos_lower.get_pin("S") nmos_lower_d_pin = self.nmos_lower.get_pin("D") nmos_upper_s_pin = self.nmos_upper.get_pin("S") nmos_upper_d_pin = self.nmos_upper.get_pin("D") # Add vias to bl, br_out, nmos_upper/S, nmos_lower/D - self.add_via_center(layers=self.col_mux_stack, - offset=bl_pin.bc(), - directions=("V", "V")) - self.add_via_center(layers=self.col_mux_stack, - offset=br_out_pin.uc(), - directions=("V", "V")) - self.add_via_center(layers=self.col_mux_stack, - offset=nmos_upper_s_pin.center(), - directions=("V", "V")) - self.add_via_center(layers=self.col_mux_stack, - offset=nmos_lower_d_pin.center(), - directions=("V", "V")) - - # Add diffusion contacts - # These were previously omitted with the options: add_source_contact=False, add_drain_contact=False - # They are added now and not previously so that they do not include m1 (which is usually included by default) - # This is only a concern when the local interconnect (li) layer is being used - self.add_via_center(layers=self.active_stack, - offset=nmos_upper_d_pin.center(), - directions=("V", "V"), - implant_type="n", - well_type="nwell") - self.add_via_center(layers=self.active_stack, - offset=nmos_lower_s_pin.center(), - directions=("V", "V"), - implant_type="n", - well_type="nwell") - self.add_via_center(layers=self.active_stack, - offset=nmos_upper_s_pin.center(), - directions=("V", "V"), - implant_type="n", - well_type="nwell") - self.add_via_center(layers=self.active_stack, - offset=nmos_lower_d_pin.center(), - directions=("V", "V"), - implant_type="n", - well_type="nwell") + self.add_via_stack_center(from_layer=bl_pin.layer, + to_layer=self.col_mux_stack[0], + offset=bl_pin.bc()) + self.add_via_stack_center(from_layer=br_out_pin.layer, + to_layer=self.col_mux_stack[0], + offset=br_out_pin.uc()) + self.add_via_stack_center(from_layer=nmos_upper_s_pin.layer, + to_layer=self.col_mux_stack[2], + offset=nmos_upper_s_pin.center()) + self.add_via_stack_center(from_layer=nmos_lower_d_pin.layer, + to_layer=self.col_mux_stack[2], + offset=nmos_lower_d_pin.center()) # bl -> nmos_upper/D on metal1 # bl_out -> nmos_upper/S on metal2 @@ -211,14 +186,27 @@ class single_level_column_mux(pgate.pgate): vector(nmos_lower_s_pin.cx(), br_out_pin.uy()), nmos_lower_s_pin.center()]) # halfway up, move over - mid1 = br_pin.bc().scale(1,0.5) \ - + 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) + mid1 = br_pin.bc().scale(1, 0.5) \ + + 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(self.col_mux_stack[2], [br_pin.bc(), mid1, mid2, nmos_lower_d_pin.center()]) - - def add_wells(self): + + def add_implants(self): + """ + Add top-to-bottom implants for adjacency issues in s8. + """ + # Route to the bottom + ll = (self.nmos_lower.ll() - vector(2 * [self.implant_enclose_active])).scale(1, 0) + # Don't route to the top + ur = self.nmos_upper.ur() + vector(self.implant_enclose_active, 0) + self.add_rect("nimplant", + ll, + ur.x - ll.x, + ur.y - ll.y) + + def add_pn_wells(self): """ Add a well and implant over the whole cell. Also, add the pwell contact (if it exists) @@ -237,8 +225,8 @@ class single_level_column_mux(pgate.pgate): offset=active_pos) # Add the M1->..->power_grid_layer stack - self.add_power_pin(name = "gnd", - loc = active_pos, + self.add_power_pin(name="gnd", + loc=active_pos, start_layer="m1") # Add well enclosure over all the tx and contact diff --git a/compiler/pgates/wordline_driver.py b/compiler/pgates/wordline_driver.py new file mode 100644 index 00000000..c8cf1326 --- /dev/null +++ b/compiler/pgates/wordline_driver.py @@ -0,0 +1,151 @@ +# 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 +from vector import vector +import design +from sram_factory import factory +from globals import OPTS +from tech import layer + + +class wordline_driver(design.design): + """ + This is an AND (or NAND) with configurable drive strength to drive the wordlines. + It is matched to the bitcell height. + """ + def __init__(self, name, size=1, height=None): + debug.info(1, "Creating wordline_driver {}".format(name)) + self.add_comment("size: {}".format(size)) + design.design.__init__(self, name) + + if height is None: + b = factory.create(module_type="bitcell") + self.height = b.height + else: + self.height = height + self.size = size + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.add_pins() + self.create_modules() + self.create_insts() + + def create_modules(self): + self.nand = factory.create(module_type="nand2_dec", + height=self.height) + + self.driver = factory.create(module_type="inv_dec", + size=self.size, + height=self.nand.height) + + self.add_mod(self.nand) + self.add_mod(self.driver) + + def create_layout(self): + self.width = self.nand.width + self.driver.width + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + + self.place_insts() + self.route_wires() + self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + self.add_pin("A", "INPUT") + self.add_pin("B", "INPUT") + self.add_pin("Z", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def create_insts(self): + self.nand_inst = self.add_inst(name="wld_nand", + mod=self.nand) + self.connect_inst(["A", "B", "zb_int", "vdd", "gnd"]) + + self.driver_inst = self.add_inst(name="wl_driver", + mod=self.driver) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + + def place_insts(self): + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) + + # Add INV to the right + self.driver_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + if OPTS.tech_name == "sky130": + for name in ["vdd", "gnd"]: + for inst in [self.nand_inst, self.driver_inst]: + self.copy_layout_pin(inst, name) + else: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + + y_offset = self.height + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, y_offset), + width=self.width) + + def route_wires(self): + + # nand Z to inv A + z1_pin = self.nand_inst.get_pin("Z") + a2_pin = self.driver_inst.get_pin("A") + if OPTS.tech_name == "sky130": + mid1_point = vector(a2_pin.cx(), z1_pin.cy()) + else: + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(self.route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + + def add_layout_pins(self): + pin = self.driver_inst.get_pin("Z") + self.add_layout_pin_rect_center(text="Z", + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + for pin_name in ["A", "B"]: + pin = self.nand_inst.get_pin(pin_name) + self.add_layout_pin_rect_center(text=pin_name, + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + def get_stage_efforts(self, external_cout, inp_is_rise=False): + """Get the stage efforts of the A or B -> Z path""" + stage_effort_list = [] + stage1_cout = self.driver.get_cin() + stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) + stage_effort_list.append(stage1) + + stage2 = self.driver.get_stage_effort(external_cout, stage1.is_rise) + stage_effort_list.append(stage2) + + return stage_effort_list + + def get_cin(self): + """Return the relative input capacitance of a single input""" + return self.nand.get_cin() + diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 8dbcfaf4..a9731ed5 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -9,7 +9,7 @@ import debug from vector import vector from sram_base import sram_base from contact import m2_via - +from globals import OPTS class sram_1bank(sram_base): """ @@ -39,6 +39,11 @@ class sram_1bank(sram_base): else: self.data_dff_insts = self.create_data_dff() + if self.num_spare_cols: + self.spare_wen_dff_insts = self.create_spare_wen_dff() + else: + self.num_spare_cols = 0 + def place_instances(self): """ This places the instances for a single bank SRAM with control @@ -57,6 +62,7 @@ class sram_1bank(sram_base): row_addr_pos = [None] * len(self.all_ports) col_addr_pos = [None] * len(self.all_ports) wmask_pos = [None] * len(self.all_ports) + spare_wen_pos = [None] * len(self.all_ports) data_pos = [None] * len(self.all_ports) # These positions utilize the channel route sizes. @@ -64,61 +70,34 @@ class sram_1bank(sram_base): # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. # So, m3 non-pref pitch means that this is routed on the m2 layer. - if self.write_size: - self.data_bus_gap = self.m4_nonpref_pitch * 2 - self.data_bus_size = self.m4_nonpref_pitch * (self.word_size) + self.data_bus_gap - self.wmask_bus_gap = self.m2_nonpref_pitch * 2 - self.wmask_bus_size = self.m2_nonpref_pitch * (max(self.num_wmasks + 1, self.col_addr_size + 1)) + self.wmask_bus_gap - else: - self.data_bus_gap = self.m3_nonpref_pitch * 2 - self.data_bus_size = self.m3_nonpref_pitch * (max(self.word_size + 1, self.col_addr_size + 1)) + self.data_bus_gap + self.data_bus_gap = self.m4_nonpref_pitch * 2 - self.col_addr_bus_gap = self.m2_nonpref_pitch * 2 - self.col_addr_bus_size = self.m2_nonpref_pitch * (self.col_addr_size) + self.col_addr_bus_gap + # Spare wen are on a separate layer so not included + # Start with 1 track minimum + self.data_bus_size = [1] * len(self.all_ports) + for port in self.all_ports: + # All ports need the col addr flops + self.data_bus_size[port] += self.col_addr_size + # Write ports need the data input flops and write mask flops + if port in self.write_ports: + self.data_bus_size[port] += self.num_wmasks + self.word_size + # This is for the din pins that get routed in the same channel + # when we have dout and din together + if port in self.readwrite_ports: + self.data_bus_size[port] += self.word_size + # Convert to length + self.data_bus_size[port] *= self.m4_nonpref_pitch + # Add the gap in unit length + self.data_bus_size[port] += self.data_bus_gap # Port 0 port = 0 - if port in self.write_ports: - if self.write_size: - # Add the write mask flops below the write mask AND array. - wmask_pos[port] = vector(self.bank.bank_array_ll.x, - - self.wmask_bus_size - self.dff.height) - self.wmask_dff_insts[port].place(wmask_pos[port]) - - # Add the data flops below the write mask flops. - data_pos[port] = vector(self.bank.bank_array_ll.x, - - self.data_bus_size - self.wmask_bus_size - 2 * self.dff.height) - self.data_dff_insts[port].place(data_pos[port]) - else: - # Add the data flops below the bank to the right of the lower-left of bank array - # This relies on the lower-left of the array of the bank - # decoder in upper left, bank in upper right, sensing in lower right. - # These flops go below the sensing and leave a gap to channel route to the - # sense amps. - if port in self.write_ports: - data_pos[port] = vector(self.bank.bank_array_ll.x, - -self.data_bus_size - self.dff.height) - self.data_dff_insts[port].place(data_pos[port]) - else: - wmask_pos[port] = vector(self.bank.bank_array_ll.x, 0) - data_pos[port] = vector(self.bank.bank_array_ll.x, 0) - - # Add the col address flops below the bank to the left of the lower-left of bank array - if self.col_addr_dff: - if self.write_size: - col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap, - -self.wmask_bus_size - self.col_addr_dff_insts[port].height) - else: - col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap, - -self.data_bus_size - self.col_addr_dff_insts[port].height) - self.col_addr_dff_insts[port].place(col_addr_pos[port]) - else: - col_addr_pos[port] = vector(self.bank.bank_array_ll.x, 0) - # This includes 2 M2 pitches for the row addr clock line. + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch, - self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y - 2 * self.bank.m2_gap) + self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) self.control_logic_insts[port].place(control_pos[port]) # The row address bits are placed above the control logic aligned on the right. @@ -128,48 +107,56 @@ class sram_1bank(sram_base): row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port]) + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].rx() + self.dff.width + y_offset = - self.data_bus_size[port] - self.dff.height + if self.col_addr_dff: + col_addr_pos[port] = vector(x_offset, + y_offset) + self.col_addr_dff_insts[port].place(col_addr_pos[port]) + x_offset = self.col_addr_dff_insts[port].rx() + else: + col_addr_pos[port] = vector(x_offset, 0) + + if port in self.write_ports: + if self.write_size: + # Add the write mask flops below the write mask AND array. + wmask_pos[port] = vector(x_offset, + y_offset) + self.wmask_dff_insts[port].place(wmask_pos[port]) + x_offset = self.wmask_dff_insts[port].rx() + + # Add the data flops below the write mask flops. + data_pos[port] = vector(x_offset, + y_offset) + self.data_dff_insts[port].place(data_pos[port]) + x_offset = self.data_dff_insts[port].rx() + + # Add spare write enable flops to the right of data flops since the spare columns + # will be on the right + if self.num_spare_cols: + spare_wen_pos[port] = vector(x_offset, + y_offset) + self.spare_wen_dff_insts[port].place(spare_wen_pos[port]) + x_offset = self.spare_wen_dff_insts[port].rx() + + else: + wmask_pos[port] = vector(x_offset, y_offset) + data_pos[port] = vector(x_offset, y_offset) + spare_wen_pos[port] = vector(x_offset, y_offset) + if len(self.all_ports)>1: # Port 1 port = 1 - - if port in self.write_ports: - if self.write_size: - # Add the write mask flops below the write mask AND array. - wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.wmask_dff_insts[port].width, - self.bank.height + self.wmask_bus_size + self.dff.height) - self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX") - - # Add the data flops below the write mask flops - data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, - self.bank.height + self.wmask_bus_size + self.data_bus_size + 2 * self.dff.height) - self.data_dff_insts[port].place(data_pos[port], mirror="MX") - else: - # Add the data flops above the bank to the left of the upper-right of bank array - # This relies on the upper-right of the array of the bank - # decoder in upper left, bank in upper right, sensing in lower right. - # These flops go below the sensing and leave a gap to channel route to the - # sense amps. - data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, - self.bank.height + self.data_bus_size + self.dff.height) - self.data_dff_insts[port].place(data_pos[port], mirror="MX") - - # Add the col address flops above the bank to the right of the upper-right of bank array - if self.col_addr_dff: - if self.write_size: - col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap, - self.bank.height + self.wmask_bus_size + self.dff.height) - else: - col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap, - self.bank.height + self.data_bus_size + self.dff.height) - self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX") - else: - col_addr_pos[port] = self.bank_inst.ur() # This includes 2 M2 pitches for the row addr clock line + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch, - self.bank.bank_array_ur.y + self.control_logic_insts[port].height - \ - (self.control_logic_insts[port].height - self.control_logic_insts[port].mod.control_logic_center.y) - + 2 * self.bank.m2_gap) + self.bank.bank_array_ur.y + + self.control_logic_insts[port].height + - self.control_logic_insts[port].height + + self.control_logic_insts[port].mod.control_logic_center.y) self.control_logic_insts[port].place(control_pos[port], mirror="XY") # The row address bits are placed above the control logic aligned on the left. @@ -179,45 +166,171 @@ class sram_1bank(sram_base): row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="XY") + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width + y_offset = self.bank.height + self.data_bus_size[port] + self.dff.height + if self.col_addr_dff: + col_addr_pos[port] = vector(x_offset - self.col_addr_dff_insts[port].width, + y_offset) + self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX") + x_offset = self.col_addr_dff_insts[port].lx() + else: + col_addr_pos[port] = vector(x_offset, y_offset) + + if port in self.write_ports: + # Add spare write enable flops to the right of the data flops since the spare + # columns will be on the left + if self.num_spare_cols: + spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width, + y_offset) + self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX") + x_offset = self.spare_wen_dff_insts[port].lx() + + if self.write_size: + # Add the write mask flops below the write mask AND array. + wmask_pos[port] = vector(x_offset - self.wmask_dff_insts[port].width, + y_offset) + self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX") + x_offset = self.wmask_dff_insts[port].lx() + + # Add the data flops below the write mask flops. + data_pos[port] = vector(x_offset - self.data_dff_insts[port].width, + y_offset) + self.data_dff_insts[port].place(data_pos[port], mirror="MX") + else: + wmask_pos[port] = vector(x_offset, y_offset) + data_pos[port] = vector(x_offset, y_offset) + spare_wen_pos[port] = vector(x_offset, y_offset) + def add_layout_pins(self): """ Add the top-level pins for a single bank SRAM with control. """ + highest_coord = self.find_highest_coords() + lowest_coord = self.find_lowest_coords() + bbox = [lowest_coord, highest_coord] + for port in self.all_ports: + # Depending on the port, use the bottom/top or left/right sides + # Port 0 is left/bottom + # Port 1 is right/top + bottom_or_top = "bottom" if port==0 else "top" + left_or_right = "left" if port==0 else "right" + # Connect the control pins as inputs - for signal in self.control_logic_inputs[port] + ["clk"]: + for signal in self.control_logic_inputs[port]: + if signal == "clk": + continue + if OPTS.perimeter_pins: + self.add_perimeter_pin(name=signal + "{}".format(port), + pin=self.control_logic_insts[port].get_pin(signal), + side=left_or_right, + bbox=bbox) + else: + self.copy_layout_pin(self.control_logic_insts[port], + signal, + signal + "{}".format(port)) + + if OPTS.perimeter_pins: + self.add_perimeter_pin(name="clk{}".format(port), + pin=self.control_logic_insts[port].get_pin("clk"), + side=bottom_or_top, + bbox=bbox) + else: self.copy_layout_pin(self.control_logic_insts[port], - signal, - signal + "{}".format(port)) - - if port in self.read_ports: - for bit in range(self.word_size): - self.copy_layout_pin(self.bank_inst, - "dout{0}_{1}".format(port, bit), - "dout{0}[{1}]".format(port, bit)) - - # Lower address bits - for bit in range(self.col_addr_size): - self.copy_layout_pin(self.col_addr_dff_insts[port], - "din_{}".format(bit), - "addr{0}[{1}]".format(port, bit)) - # Upper address bits - for bit in range(self.row_addr_size): - self.copy_layout_pin(self.row_addr_dff_insts[port], - "din_{}".format(bit), - "addr{0}[{1}]".format(port, bit + self.col_addr_size)) + "clk", + "clk{}".format(port)) + # Data input pins go to BOTTOM/TOP + din_ports = [] if port in self.write_ports: - for bit in range(self.word_size): - self.copy_layout_pin(self.data_dff_insts[port], - "din_{}".format(bit), - "din{0}[{1}]".format(port, bit)) + for bit in range(self.word_size + self.num_spare_cols): + if OPTS.perimeter_pins: + p = self.add_perimeter_pin(name="din{0}[{1}]".format(port, bit), + pin=self.data_dff_insts[port].get_pin("din_{0}".format(bit)), + side=bottom_or_top, + bbox=bbox) + din_ports.append(p) + else: + self.copy_layout_pin(self.data_dff_insts[port], + "din_{}".format(bit), + "din{0}[{1}]".format(port, bit)) + + # Data output pins go to BOTTOM/TOP + if port in self.readwrite_ports and OPTS.perimeter_pins: + for bit in range(self.word_size + self.num_spare_cols): + # This should be routed next to the din pin + p = din_ports[bit] + self.add_layout_pin_rect_center(text="dout{0}[{1}]".format(port, bit), + layer=p.layer, + offset=p.center() + vector(self.m3_pitch, 0), + width=p.width(), + height=p.height()) + elif port in self.read_ports: + for bit in range(self.word_size + self.num_spare_cols): + if OPTS.perimeter_pins: + # This should have a clear route to the perimeter if there are no din routes + self.add_perimeter_pin(name="dout{0}[{1}]".format(port, bit), + pin=self.bank_inst.get_pin("dout{0}_{1}".format(port, bit)), + side=bottom_or_top, + bbox=bbox) + else: + self.copy_layout_pin(self.bank_inst, + "dout{0}_{1}".format(port, bit), + "dout{0}[{1}]".format(port, bit)) + + + # Lower address bits go to BOTTOM/TOP + for bit in range(self.col_addr_size): + if OPTS.perimeter_pins: + self.add_perimeter_pin(name="addr{0}[{1}]".format(port, bit), + pin=self.col_addr_dff_insts[port].get_pin("din_{}".format(bit)), + side=bottom_or_top, + bbox=bbox) + else: + self.copy_layout_pin(self.col_addr_dff_insts[port], + "din_{}".format(bit), + "addr{0}[{1}]".format(port, bit)) + + # Upper address bits go to LEFT/RIGHT + for bit in range(self.row_addr_size): + if OPTS.perimeter_pins: + self.add_perimeter_pin(name="addr{0}[{1}]".format(port, bit + self.col_addr_size), + pin=self.row_addr_dff_insts[port].get_pin("din_{}".format(bit)), + side=left_or_right, + bbox=bbox) + else: + self.copy_layout_pin(self.row_addr_dff_insts[port], + "din_{}".format(bit), + "addr{0}[{1}]".format(port, bit + self.col_addr_size)) + + # Write mask pins go to BOTTOM/TOP + if port in self.write_ports: if self.write_size: for bit in range(self.num_wmasks): - self.copy_layout_pin(self.wmask_dff_insts[port], + if OPTS.perimeter_pins: + self.add_perimeter_pin(name="wmask{0}[{1}]".format(port, bit), + pin=self.wmask_dff_insts[port].get_pin("din_{}".format(bit)), + side=bottom_or_top, + bbox=bbox) + else: + self.copy_layout_pin(self.wmask_dff_insts[port], + "din_{}".format(bit), + "wmask{0}[{1}]".format(port, bit)) + + # Spare wen pins go to BOTTOM/TOP + if port in self.write_ports: + for bit in range(self.num_spare_cols): + if OPTS.perimeter_pins: + self.add_perimeter_pin(name="spare_wen{0}[{1}]".format(port, bit), + pin=self.spare_wen_dff_insts[port].get_pin("din_{}".format(bit)), + side=left_or_right, + bbox=bbox) + else: + self.copy_layout_pin(self.spare_wen_dff_insts[port], "din_{}".format(bit), - "wmask{0}[{1}]".format(port, bit)) + "spare_wen{0}[{1}]".format(port, bit)) def route_layout(self): """ Route a single bank SRAM """ @@ -230,21 +343,81 @@ class sram_1bank(sram_base): self.route_row_addr_dff() - if self.col_addr_dff: - self.route_col_addr_dff() - - self.route_data_dff() - - if self.write_size: - self.route_wmask_dff() + for port in self.all_ports: + self.route_dff(port) + def route_dff(self, port): + + route_map = [] + + # column mux dff + if self.col_addr_size > 0: + dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] + dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + # wmask dff + if self.num_wmasks > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] + dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if port in self.write_ports: + # synchronized inputs from data dff + dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)] + dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if port in self.readwrite_ports and OPTS.perimeter_pins: + # outputs from sense amp + # These are the output pins which had their pin placed on the perimeter, so route from the + # sense amp which should not align with write driver input + sram_names = ["dout{0}[{1}]".format(port, x) for x in range(self.word_size + self.num_spare_cols)] + sram_pins = [self.get_pin(x) for x in sram_names] + bank_names = ["dout{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, sram_pins))) + + if self.num_wmasks > 0 and port in self.write_ports: + layer_stack = self.m3_stack + else: + layer_stack = self.m1_stack + + if port == 0: + offset = vector(self.control_logic_insts[port].rx() + self.dff.width, + - self.data_bus_size[port] + 2 * self.m1_pitch) + else: + offset = vector(0, + self.bank.height + 2 * self.m1_space) + + if len(route_map) > 0: + self.create_horizontal_channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack) + + # Route these separately because sometimes the pin pitch on the write driver is too narrow for M3 (FreePDK45) + # spare wen dff + if self.num_spare_cols > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] + dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map = zip(bank_pins, dff_pins) + self.create_horizontal_channel_route(netlist=route_map, + offset=offset, + layer_stack=self.m1_stack) + def route_clk(self): """ Route the clock network """ # This is the actual input to the SRAM for port in self.all_ports: - self.copy_layout_pin(self.control_logic_insts[port], "clk", "clk{}".format(port)) - # Connect all of these clock pins to the clock in the central bus # This is something like a "spine" clock distribution. The two spines # are clk_buf and clk_buf_bar @@ -282,8 +455,7 @@ class sram_1bank(sram_base): mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) self.add_wire(self.m2_stack[::-1], [dff_clk_pos, mid_pos, clk_steiner_pos]) - - if port in self.write_ports: + elif port in self.write_ports: data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") data_dff_clk_pos = data_dff_clk_pin.center() mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) @@ -295,15 +467,6 @@ class sram_1bank(sram_base): self.add_wire(self.m2_stack[::-1], [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") - wmask_dff_clk_pos = wmask_dff_clk_pin.center() - 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("m2", [mid_pos, clk_steiner_pos], width=max(m2_via.width, m2_via.height)) - self.add_wire(self.m2_stack[::-1], [wmask_dff_clk_pos, mid_pos, clk_steiner_pos]) - def route_control_logic(self): """ Route the control logic pins that are not inputs """ @@ -320,7 +483,14 @@ class sram_1bank(sram_base): # Only input (besides pins) is the replica bitline src_pin = self.control_logic_insts[port].get_pin("rbl_bl") dest_pin = self.bank_inst.get_pin("rbl_bl{}".format(port)) - self.connect_hbus(src_pin, dest_pin) + self.add_wire(self.m2_stack[::-1], + [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) + self.add_via_stack_center(from_layer=src_pin.layer, + to_layer="m2", + offset=src_pin.center()) + self.add_via_stack_center(from_layer=dest_pin.layer, + to_layer="m2", + offset=dest_pin.center()) def route_row_addr_dff(self): """ Connect the output of the row flops to the bank pins """ @@ -333,117 +503,14 @@ 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(self.m2_stack[::-1], - [flop_pos, mid_pos, bank_pos]) self.add_via_stack_center(from_layer=flop_pin.layer, to_layer="m3", offset=flop_pos) - - def route_col_addr_dff(self): - """ 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.col_addr_bus_size) - else: - offset = self.col_addr_dff_insts[port].ul() + vector(0, self.col_addr_bus_gap) - - bus_names = ["addr_{}".format(x) for x in range(self.col_addr_size)] - col_addr_bus_offsets = self.create_horizontal_bus(layer="m1", - offset=offset, - names=bus_names, - length=self.col_addr_dff_insts[port].width) - - dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] - data_dff_map = zip(dff_names, bus_names) - self.connect_horizontal_bus(data_dff_map, - self.col_addr_dff_insts[port], - col_addr_bus_offsets) - - bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] - data_bank_map = zip(bank_names, bus_names) - self.connect_horizontal_bus(data_bank_map, - self.bank_inst, - col_addr_bus_offsets) - - def route_data_dff(self): - """ Connect the output of the data flops to the write driver """ - # This is where the channel will start (y-dimension at least) - for port in self.write_ports: - if self.write_size: - if port % 2: - offset = self.data_dff_insts[port].ll() - vector(0, self.data_bus_size) - else: - offset = self.data_dff_insts[port].ul() + vector(0, self.data_bus_gap) - else: - if port % 2: - offset = self.data_dff_insts[port].ll() - vector(0, self.data_bus_size) - else: - offset = self.data_dff_insts[port].ul() + vector(0, self.data_bus_gap) - - dff_names = ["dout_{}".format(x) for x in range(self.word_size)] - dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] - if self.write_size: - for x in dff_names: - pin = self.data_dff_insts[port].get_pin(x) - pin_offset = pin.center() - self.add_via_center(layers=self.m1_stack, - offset=pin_offset, - directions=("V", "V")) - self.add_via_stack_center(from_layer="m2", - to_layer="m4", - offset=pin_offset) - - bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - if self.write_size: - for x in bank_names: - pin = self.bank_inst.get_pin(x) - if port % 2: - pin_offset = pin.uc() - else: - pin_offset = pin.bc() - self.add_via_stack_center(from_layer=pin.layer, - to_layer="m4", - offset=pin_offset) - - route_map = list(zip(bank_pins, dff_pins)) - if self.write_size: - layer_stack = self.m3_stack - else: - 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 """ - # This is where the channel will start (y-dimension at least) - for port in self.write_ports: - if port % 2: - offset = self.wmask_dff_insts[port].ll() - vector(0, self.wmask_bus_size) - else: - offset = self.wmask_dff_insts[port].ul() + vector(0, self.wmask_bus_gap) - - dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] - 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=self.m1_stack, - offset=offset_pin, - directions=("V", "V")) - - bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] - 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=self.m1_stack, - offset=offset_pin) - - route_map = list(zip(bank_pins, dff_pins)) - self.create_horizontal_channel_route(netlist=route_map, - offset=offset, - layer_stack=self.m1_stack) + self.add_path("m3", [flop_pos, mid_pos]) + self.add_via_stack_center(from_layer=bank_pin.layer, + to_layer="m3", + offset=mid_pos) + self.add_path(bank_pin.layer, [mid_pos, bank_pos]) def add_lvs_correspondence_points(self): """ @@ -466,6 +533,9 @@ class sram_1bank(sram_base): if self.write_size: for inst in self.wmask_dff_insts: self.graph_inst_exclude.add(inst) + if self.num_spare_cols: + for inst in self.spare_wen_dff_insts: + self.graph_inst_exclude.add(inst) def graph_exclude_addr_dff(self): """Removes data dff from search graph. """ diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index bcb85308..89ecfaaf 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -37,6 +37,9 @@ class sram_base(design, verilog, lef): else: self.num_wmasks = 0 + if not self.num_spare_cols: + self.num_spare_cols = 0 + # For logical effort delay calculations. self.all_mods_except_control_done = False @@ -44,7 +47,7 @@ class sram_base(design, verilog, lef): """ Add pins for entire SRAM. """ for port in self.write_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): self.add_pin("din{0}[{1}]".format(port, bit), "INPUT") for port in self.all_ports: @@ -75,8 +78,10 @@ class sram_base(design, verilog, lef): for port in self.write_ports: for bit in range(self.num_wmasks): self.add_pin("wmask{0}[{1}]".format(port, bit), "INPUT") + for bit in range(self.num_spare_cols): + self.add_pin("spare_wen{0}[{1}]".format(port, bit), "INPUT") for port in self.read_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): self.add_pin("dout{0}[{1}]".format(port, bit), "OUTPUT") self.add_pin("vdd", "POWER") @@ -119,6 +124,8 @@ class sram_base(design, verilog, lef): highest_coord = self.find_highest_coords() self.width = highest_coord[0] self.height = highest_coord[1] + self.add_boundary(ll=vector(0, 0), + ur=vector(self.width, self.height)) start_time = datetime.datetime.now() # We only enable final verification if we have routed the design @@ -137,7 +144,7 @@ class sram_base(design, verilog, lef): for inst in self.insts: self.copy_power_pins(inst, "vdd") self.copy_power_pins(inst, "gnd") - + if not OPTS.route_supplies: # Do not route the power supply (leave as must-connect pins) return @@ -274,12 +281,16 @@ class sram_base(design, verilog, lef): else: self.col_addr_dff = None - self.data_dff = factory.create("dff_array", module_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.num_spare_cols) self.add_mod(self.data_dff) if self.write_size: self.wmask_dff = factory.create("dff_array", module_name="wmask_dff", rows=1, columns=self.num_wmasks) self.add_mod(self.wmask_dff) + + if self.num_spare_cols: + self.spare_wen_dff = factory.create("dff_array", module_name="spare_wen_dff", rows=1, columns=self.num_spare_cols) + self.add_mod(self.spare_wen_dff) # Create the bank module (up to four are instantiated) self.bank = factory.create("bank", sram_config=self.sram_config, module_name="bank") @@ -303,6 +314,7 @@ class sram_base(design, verilog, lef): self.control_logic_rw = self.mod_control_logic(num_rows=self.num_rows, words_per_row=self.words_per_row, word_size=self.word_size, + spare_columns=self.num_spare_cols, sram=self, port_type="rw") self.add_mod(self.control_logic_rw) @@ -310,6 +322,7 @@ class sram_base(design, verilog, lef): self.control_logic_w = self.mod_control_logic(num_rows=self.num_rows, words_per_row=self.words_per_row, word_size=self.word_size, + spare_columns=self.num_spare_cols, sram=self, port_type="w") self.add_mod(self.control_logic_w) @@ -317,6 +330,7 @@ class sram_base(design, verilog, lef): self.control_logic_r = self.mod_control_logic(num_rows=self.num_rows, words_per_row=self.words_per_row, word_size=self.word_size, + spare_columns=self.num_spare_cols, sram=self, port_type="r") self.add_mod(self.control_logic_r) @@ -328,12 +342,12 @@ class sram_base(design, verilog, lef): temp = [] for port in self.read_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): temp.append("dout{0}[{1}]".format(port, bit)) for port in self.all_ports: temp.append("rbl_bl{0}".format(port)) for port in self.write_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): temp.append("bank_din{0}[{1}]".format(port, bit)) for port in self.all_ports: for bit in range(self.bank_addr_size): @@ -349,6 +363,8 @@ class sram_base(design, verilog, lef): temp.append("w_en{0}".format(port)) for bit in range(self.num_wmasks): temp.append("bank_wmask{}[{}]".format(port, bit)) + for bit in range(self.num_spare_cols): + temp.append("bank_spare_wen{0}[{1}]".format(port, bit)) for port in self.all_ports: temp.append("wl_en{0}".format(port)) temp.extend(["vdd", "gnd"]) @@ -436,7 +452,7 @@ class sram_base(design, verilog, lef): # inputs, outputs/output/bar inputs = [] outputs = [] - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): inputs.append("din{}[{}]".format(port, bit)) outputs.append("bank_din{}[{}]".format(port, bit)) @@ -465,7 +481,29 @@ class sram_base(design, verilog, lef): self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"]) return insts + + def create_spare_wen_dff(self): + """ Add all spare write enable flops """ + insts = [] + for port in self.all_ports: + if port in self.write_ports: + insts.append(self.add_inst(name="spare_wen_dff{}".format(port), + mod=self.spare_wen_dff)) + else: + insts.append(None) + continue + # inputs, outputs/output/bar + inputs = [] + outputs = [] + for bit in range(self.num_spare_cols): + inputs.append("spare_wen{}[{}]".format(port, bit)) + outputs.append("bank_spare_wen{}[{}]".format(port, bit)) + + self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"]) + + return insts + def create_control_logic(self): """ Add control logic instances """ diff --git a/compiler/sram/sram_config.py b/compiler/sram/sram_config.py index 3a7e6d39..3af2fd9c 100644 --- a/compiler/sram/sram_config.py +++ b/compiler/sram/sram_config.py @@ -14,18 +14,18 @@ from sram_factory import factory class sram_config: """ This is a structure that is used to hold the SRAM configuration options. """ - def __init__(self, word_size, num_words, write_size = None, num_banks=1, words_per_row=None): + def __init__(self, word_size, num_words, write_size = None, num_banks=1, words_per_row=None, num_spare_rows=0, num_spare_cols=None): self.word_size = word_size self.num_words = num_words self.write_size = write_size self.num_banks = num_banks + self.num_spare_rows = num_spare_rows + self.num_spare_cols = num_spare_cols # This will get over-written when we determine the organization self.words_per_row = words_per_row - self.compute_sizes() - - + self.compute_sizes() def set_local_config(self, module): """ Copy all of the member variables to the given module for convenience """ @@ -78,11 +78,12 @@ class sram_config: # Fix the number of columns and rows self.num_cols = int(self.words_per_row*self.word_size) - self.num_rows = int(self.num_words_per_bank/self.words_per_row) - debug.info(1,"Rows: {} Cols: {}".format(self.num_rows,self.num_cols)) + self.num_rows_temp = int(self.num_words_per_bank/self.words_per_row) + self.num_rows = self.num_rows_temp + self.num_spare_rows + debug.info(1,"Rows: {} Cols: {}".format(self.num_rows_temp,self.num_cols)) # Compute the address and bank sizes - self.row_addr_size = int(log(self.num_rows, 2)) + self.row_addr_size = ceil(log(self.num_rows, 2)) self.col_addr_size = int(log(self.words_per_row, 2)) self.bank_addr_size = self.col_addr_size + self.row_addr_size self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2)) diff --git a/compiler/sram_factory.py b/compiler/sram_factory.py index 110dbe15..0e9721c7 100644 --- a/compiler/sram_factory.py +++ b/compiler/sram_factory.py @@ -77,7 +77,9 @@ class sram_factory: """ tech_module_type, tm_overridden = self.get_techmodule_type(module_type) user_module_type, um_overridden = self.get_usermodule_type(module_type) - + # print(module_type, tech_module_type, tm_overridden) + # print(module_type, user_module_type, um_overridden) + # overridden user modules have priority if um_overridden: real_module_type = user_module_type @@ -109,11 +111,12 @@ class sram_factory: return obj_item # If no prefered module name is provided, we generate one. - if module_name is None: - # Use the default name if there are default arguments + if not module_name: + # Use the default name for the first cell. # This is especially for library cells so that the # spice and gds files can be found. - if len(kwargs) > 0: + # Subsequent objects will get unique names to help with GDS limitation. + if len(self.objects[real_module_type]) > 0: # Create a unique name and increment the index module_name = "{0}_{1}".format(real_module_type, self.module_indices[real_module_type]) diff --git a/compiler/tests/02_library_lvs_test.py b/compiler/tests/02_library_lvs_test.py index 0acc8926..ed15770d 100755 --- a/compiler/tests/02_library_lvs_test.py +++ b/compiler/tests/02_library_lvs_test.py @@ -35,7 +35,7 @@ class library_lvs_test(openram_test): debug.error("Missing GDS file {}".format(gds_name)) if not os.path.isfile(sp_name): lvs_errors += 1 - debug.error("Missing SPICE file {}".format(gds_name)) + debug.error("Missing SPICE file {}".format(sp_name)) drc_errors += verify.run_drc(name, gds_name) lvs_errors += verify.run_lvs(f, gds_name, sp_name) diff --git a/compiler/tests/04_and2_dec_test.py b/compiler/tests/04_and2_dec_test.py new file mode 100755 index 00000000..97ac5749 --- /dev/null +++ b/compiler/tests/04_and2_dec_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class and2_dec_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + global verify + import verify + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing and2_dec gate") + a = factory.create(module_type="and2_dec") + self.local_check(a) + + globals.end_openram() + +# instantiate a copdsay of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_and3_dec_test.py b/compiler/tests/04_and3_dec_test.py new file mode 100755 index 00000000..ec83335b --- /dev/null +++ b/compiler/tests/04_and3_dec_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class and3_dec_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + global verify + import verify + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing and3_dec gate") + a = factory.create(module_type="and3_dec") + self.local_check(a) + + globals.end_openram() + +# instantiate a copdsay of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_and4_dec_test.py b/compiler/tests/04_and4_dec_test.py new file mode 100755 index 00000000..ffd7788a --- /dev/null +++ b/compiler/tests/04_and4_dec_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +@unittest.skip("SKIPPING 04_and4_dec_test") +class and4_dec_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + global verify + import verify + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing and4_dec gate") + a = factory.create(module_type="and4_dec") + self.local_check(a) + + globals.end_openram() + +# instantiate a copdsay of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/11_dff_buf_test.py b/compiler/tests/04_dff_buf_test.py similarity index 100% rename from compiler/tests/11_dff_buf_test.py rename to compiler/tests/04_dff_buf_test.py diff --git a/compiler/tests/04_pdriver_test.py b/compiler/tests/04_pdriver_test.py index e65b6fad..41af86f6 100755 --- a/compiler/tests/04_pdriver_test.py +++ b/compiler/tests/04_pdriver_test.py @@ -32,13 +32,13 @@ class pdriver_test(openram_test): c = factory.create(module_type="pdriver", fanout = 50) self.local_check(c) - d = factory.create(module_type="pdriver", fanout = 50, neg_polarity = True) + d = factory.create(module_type="pdriver", fanout = 50, inverting = True) self.local_check(d) e = factory.create(module_type="pdriver", fanout = 64) self.local_check(e) - f = factory.create(module_type="pdriver", fanout = 64, neg_polarity = True) + f = factory.create(module_type="pdriver", fanout = 64, inverting = True) self.local_check(f) globals.end_openram() diff --git a/compiler/tests/04_pinv_dec_1x_test.py b/compiler/tests/04_pinv_dec_1x_test.py new file mode 100755 index 00000000..92772616 --- /dev/null +++ b/compiler/tests/04_pinv_dec_1x_test.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class pinv_dec_1x_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Checking 1x size decoder inverter") + tx = factory.create(module_type="pinv_dec", size=1) + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_pnand2_test.py b/compiler/tests/04_pnand2_test.py index f939738a..ae0668ae 100755 --- a/compiler/tests/04_pnand2_test.py +++ b/compiler/tests/04_pnand2_test.py @@ -25,6 +25,11 @@ class pnand2_test(openram_test): tx = factory.create(module_type="pnand2", size=1) self.local_check(tx) + # debug.info(2, "Checking 2-input nand gate") + # tx = factory.create(module_type="pnand2", size=1, add_wells=False) + # # Only DRC because well contacts will fail LVS + # self.local_drc_check(tx) + globals.end_openram() diff --git a/compiler/tests/04_pnand3_test.py b/compiler/tests/04_pnand3_test.py index f1af19ac..c03e32d6 100755 --- a/compiler/tests/04_pnand3_test.py +++ b/compiler/tests/04_pnand3_test.py @@ -25,6 +25,11 @@ class pnand3_test(openram_test): tx = factory.create(module_type="pnand3", size=1) self.local_check(tx) + # debug.info(2, "Checking 3-input nand gate") + # tx = factory.create(module_type="pnand3", size=1, add_wells=False) + # # Only DRC because well contacts will fail LVS + # self.local_drc_check(tx) + globals.end_openram() diff --git a/compiler/tests/04_precharge_1rw_1r_test.py b/compiler/tests/04_precharge_1rw_1r_test.py new file mode 100755 index 00000000..4765c77c --- /dev/null +++ b/compiler/tests/04_precharge_1rw_1r_test.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class precharge_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check precharge array in multi-port + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Checking precharge for 1rw1r port 0") + tx = factory.create(module_type="precharge", size=1, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(tx) + + factory.reset() + debug.info(2, "Checking precharge for 1rw1r port 1") + tx = factory.create(module_type="precharge", size=1, bitcell_bl="bl1", bitcell_br="br1") + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_single_level_column_mux_1rw_1r_test.py b/compiler/tests/04_single_level_column_mux_1rw_1r_test.py new file mode 100755 index 00000000..a7e79e9b --- /dev/null +++ b/compiler/tests/04_single_level_column_mux_1rw_1r_test.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class single_level_column_mux_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Checking column mux port 0") + tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(tx) + + debug.info(2, "Checking column mux port 1") + tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl1", bitcell_br="br1") + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_single_level_column_mux_pbitcell_test.py b/compiler/tests/04_single_level_column_mux_pbitcell_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/04_single_level_column_mux_test.py b/compiler/tests/04_single_level_column_mux_test.py index 2b437987..20dfe968 100755 --- a/compiler/tests/04_single_level_column_mux_test.py +++ b/compiler/tests/04_single_level_column_mux_test.py @@ -15,7 +15,6 @@ from globals import OPTS from sram_factory import factory import debug -#@unittest.skip("SKIPPING 04_driver_test") class single_level_column_mux_test(openram_test): diff --git a/compiler/tests/08_wordline_driver_test.py b/compiler/tests/04_wordline_driver_test.py similarity index 93% rename from compiler/tests/08_wordline_driver_test.py rename to compiler/tests/04_wordline_driver_test.py index 8a18a59d..ada65db7 100755 --- a/compiler/tests/08_wordline_driver_test.py +++ b/compiler/tests/04_wordline_driver_test.py @@ -25,7 +25,7 @@ class wordline_driver_test(openram_test): # check wordline driver for single port debug.info(2, "Checking driver") - tx = factory.create(module_type="wordline_driver", rows=8, cols=32) + tx = factory.create(module_type="wordline_driver") self.local_check(tx) globals.end_openram() diff --git a/compiler/tests/05_bitcell_1rw_1r_array_test.py b/compiler/tests/05_bitcell_array_1rw_1r_test.py similarity index 86% rename from compiler/tests/05_bitcell_1rw_1r_array_test.py rename to compiler/tests/05_bitcell_array_1rw_1r_test.py index 3426f0c5..0e7d5665 100755 --- a/compiler/tests/05_bitcell_1rw_1r_array_test.py +++ b/compiler/tests/05_bitcell_array_1rw_1r_test.py @@ -17,18 +17,16 @@ import debug #@unittest.skip("SKIPPING 05_bitcell_1rw_1r_array_test") -class bitcell_1rw_1r_array_test(openram_test): +class bitcell_array_1rw_1r_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell="dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 + globals.setup_bitcell() debug.info(2, "Testing 4x4 array for cell_1rw_1r") a = factory.create(module_type="bitcell_array", cols=4, rows=4) diff --git a/compiler/tests/06_hierarchical_decoder_1rw_1r_test.py b/compiler/tests/06_hierarchical_decoder_1rw_1r_test.py new file mode 100755 index 00000000..844160a6 --- /dev/null +++ b/compiler/tests/06_hierarchical_decoder_1rw_1r_test.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class hierarchical_decoder_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + # Checks 2x4 and 2-input NAND decoder + debug.info(1, "Testing 16 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=16) + self.local_check(a) + + # Checks 2x4 and 2-input NAND decoder with non-power-of-two + debug.info(1, "Testing 17 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=17) + self.local_check(a) + + # Checks 2x4 with 3x8 and 2-input NAND decoder + debug.info(1, "Testing 32 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=32) + self.local_check(a) + + # Checks 3 x 2x4 and 3-input NAND decoder + debug.info(1, "Testing 64 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=64) + self.local_check(a) + + # Checks 2x4 and 2 x 3x8 and 3-input NAND with non-power-of-two + debug.info(1, "Testing 132 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=132) + self.local_check(a) + + # Checks 3 x 3x8 and 3-input NAND decoder + debug.info(1, "Testing 512 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=512) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/06_hierarchical_decoder_pbitcell_test.py b/compiler/tests/06_hierarchical_decoder_pbitcell_test.py index 094e8b2e..e1605067 100755 --- a/compiler/tests/06_hierarchical_decoder_pbitcell_test.py +++ b/compiler/tests/06_hierarchical_decoder_pbitcell_test.py @@ -21,11 +21,11 @@ class hierarchical_decoder_pbitcell_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) # check hierarchical decoder for multi-port - OPTS.bitcell = "pbitcell" OPTS.num_rw_ports = 1 OPTS.num_w_ports = 0 OPTS.num_r_ports = 0 - + globals.setup_bitcell() + factory.reset() debug.info(1, "Testing 16 row sample for hierarchical_decoder (multi-port case)") a = factory.create(module_type="hierarchical_decoder", num_outputs=16) diff --git a/compiler/tests/06_hierarchical_decoder_test.py b/compiler/tests/06_hierarchical_decoder_test.py index 07d995f9..e2b34cba 100755 --- a/compiler/tests/06_hierarchical_decoder_test.py +++ b/compiler/tests/06_hierarchical_decoder_test.py @@ -25,7 +25,7 @@ class hierarchical_decoder_test(openram_test): debug.info(1, "Testing 16 row sample for hierarchical_decoder") a = factory.create(module_type="hierarchical_decoder", num_outputs=16) self.local_check(a) - + # Checks 2x4 and 2-input NAND decoder with non-power-of-two debug.info(1, "Testing 17 row sample for hierarchical_decoder") a = factory.create(module_type="hierarchical_decoder", num_outputs=17) diff --git a/compiler/tests/05_replica_bitcell_array_test.py b/compiler/tests/06_hierarchical_predecode2x4_1rw_1r_test.py old mode 100644 new mode 100755 similarity index 67% rename from compiler/tests/05_replica_bitcell_array_test.py rename to compiler/tests/06_hierarchical_predecode2x4_1rw_1r_test.py index d229b99c..a3ae2ed2 --- a/compiler/tests/05_replica_bitcell_array_test.py +++ b/compiler/tests/06_hierarchical_predecode2x4_1rw_1r_test.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 # See LICENSE for licensing information. # -# Copyright (c) 2016-2019 Regents of the University of California +# 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 unittest @@ -13,21 +15,22 @@ from globals import OPTS from sram_factory import factory import debug -class replica_bitcell_array_test(openram_test): + +class hierarchical_predecode2x4_1rw_1r_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) OPTS.num_rw_ports = 1 - OPTS.num_r_ports = 0 + OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 - - factory.reset() - debug.info(2, "Testing 4x4 array for bitcell") - a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=1, right_rbl=0, bitcell_ports=[0]) - self.local_check(a) + globals.setup_bitcell() + debug.info(1, "Testing sample for hierarchy_predecode2x4") + a = factory.create(module_type="hierarchical_predecode2x4") + self.local_check(a) + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/06_hierarchical_predecode2x4_pbitcell_test.py b/compiler/tests/06_hierarchical_predecode2x4_pbitcell_test.py index b5532891..0f70ddb1 100755 --- a/compiler/tests/06_hierarchical_predecode2x4_pbitcell_test.py +++ b/compiler/tests/06_hierarchical_predecode2x4_pbitcell_test.py @@ -22,11 +22,11 @@ class hierarchical_predecode2x4_pbitcell_test(openram_test): globals.init_openram(config_file) # checking hierarchical precode 2x4 for multi-port - OPTS.bitcell = "pbitcell" OPTS.num_rw_ports = 1 OPTS.num_w_ports = 0 OPTS.num_r_ports = 0 - + globals.setup_bitcell() + debug.info(1, "Testing sample for hierarchy_predecode2x4 (multi-port case)") a = factory.create(module_type="hierarchical_predecode2x4") self.local_check(a) diff --git a/compiler/tests/06_hierarchical_predecode2x4_test.py b/compiler/tests/06_hierarchical_predecode2x4_test.py index ebb06330..c2b51f10 100755 --- a/compiler/tests/06_hierarchical_predecode2x4_test.py +++ b/compiler/tests/06_hierarchical_predecode2x4_test.py @@ -21,7 +21,6 @@ class hierarchical_predecode2x4_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - # checking hierarchical precode 2x4 for single port debug.info(1, "Testing sample for hierarchy_predecode2x4") a = factory.create(module_type="hierarchical_predecode2x4") self.local_check(a) diff --git a/compiler/tests/06_hierarchical_predecode3x8_1rw_1r_test.py b/compiler/tests/06_hierarchical_predecode3x8_1rw_1r_test.py new file mode 100755 index 00000000..c0653243 --- /dev/null +++ b/compiler/tests/06_hierarchical_predecode3x8_1rw_1r_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class hierarchical_predecode3x8_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(1, "Testing sample for hierarchy_predecode3x8") + a = factory.create(module_type="hierarchical_predecode3x8") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/06_hierarchical_predecode3x8_pbitcell_test.py b/compiler/tests/06_hierarchical_predecode3x8_pbitcell_test.py index 1d5ab41b..9170d5c0 100755 --- a/compiler/tests/06_hierarchical_predecode3x8_pbitcell_test.py +++ b/compiler/tests/06_hierarchical_predecode3x8_pbitcell_test.py @@ -22,11 +22,11 @@ class hierarchical_predecode3x8_pbitcell_test(openram_test): globals.init_openram(config_file) # checking hierarchical precode 3x8 for multi-port - OPTS.bitcell = "pbitcell" OPTS.num_rw_ports = 1 OPTS.num_w_ports = 0 OPTS.num_r_ports = 0 - + globals.setup_bitcell() + debug.info(1, "Testing sample for hierarchy_predecode3x8 (multi-port case)") a = factory.create(module_type="hierarchical_predecode3x8") self.local_check(a) diff --git a/compiler/tests/06_hierarchical_predecode3x8_test.py b/compiler/tests/06_hierarchical_predecode3x8_test.py index 63acc416..c1471a40 100755 --- a/compiler/tests/06_hierarchical_predecode3x8_test.py +++ b/compiler/tests/06_hierarchical_predecode3x8_test.py @@ -21,7 +21,6 @@ class hierarchical_predecode3x8_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - # checking hierarchical precode 3x8 for single port debug.info(1, "Testing sample for hierarchy_predecode3x8") a = factory.create(module_type="hierarchical_predecode3x8") self.local_check(a) diff --git a/compiler/tests/06_hierarchical_predecode4x16_test.py b/compiler/tests/06_hierarchical_predecode4x16_test.py new file mode 100755 index 00000000..b4ebda38 --- /dev/null +++ b/compiler/tests/06_hierarchical_predecode4x16_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +@unittest.skip("SKIPPING hierarchical_predecode4x16_test") +class hierarchical_predecode4x16_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + debug.info(1, "Testing sample for hierarchy_predecode4x16") + a = factory.create(module_type="hierarchical_predecode4x16") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/07_single_level_column_mux_array_1rw_1r_test.py b/compiler/tests/07_single_level_column_mux_array_1rw_1r_test.py new file mode 100755 index 00000000..209133aa --- /dev/null +++ b/compiler/tests/07_single_level_column_mux_array_1rw_1r_test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# 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. +# +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class single_level_column_mux_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(1, "Testing sample for 2-way column_mux_array port 0") + a = factory.create(module_type="single_level_column_mux_array", columns=8, word_size=4, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(a) + + debug.info(1, "Testing sample for 2-way column_mux_array port 1") + a = factory.create(module_type="single_level_column_mux_array", columns=8, word_size=4, bitcell_bl="bl1", bitcell_br="br1") + self.local_check(a) + + debug.info(1, "Testing sample for 4-way column_mux_array port 0") + a = factory.create(module_type="single_level_column_mux_array", columns=8, word_size=2, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(a) + + debug.info(1, "Testing sample for 4-way column_mux_array port 1") + a = factory.create(module_type="single_level_column_mux_array", columns=8, word_size=2, bitcell_bl="bl1", bitcell_br="br1") + self.local_check(a) + + debug.info(1, "Testing sample for 8-way column_mux_array port 0") + a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=2, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(a) + + debug.info(1, "Testing sample for 8-way column_mux_array port 1") + a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=2, bitcell_bl="bl1", bitcell_br="br1") + self.local_check(a) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py b/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py old mode 100644 new mode 100755 index 84e62a96..663ff075 --- a/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py +++ b/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py @@ -41,7 +41,7 @@ class single_level_column_mux_pbitcell_test(openram_test): self.local_check(a) debug.info(1, "Testing sample for 8-way column_mux_array in multi-port (outermost connections)") - a = factory.create(module_type="single_level_column_mux_array", columns=32, word_size=4, bitcell_bl="bl2", bitcell_br="br2") + a = factory.create(module_type="single_level_column_mux_array", columns=32, word_size=4, bitcell_bl="bl2", bitcell_br="br2", column_offset=3) self.local_check(a) globals.end_openram() diff --git a/compiler/tests/07_single_level_column_mux_array_test.py b/compiler/tests/07_single_level_column_mux_array_test.py index c5d48689..c0476a4f 100755 --- a/compiler/tests/07_single_level_column_mux_array_test.py +++ b/compiler/tests/07_single_level_column_mux_array_test.py @@ -7,7 +7,7 @@ # All rights reserved. # from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS @@ -19,9 +19,7 @@ class single_level_column_mux_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - import single_level_column_mux_array - # check single level column mux array in single port debug.info(1, "Testing sample for 2-way column_mux_array") a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=8) self.local_check(a) diff --git a/compiler/tests/08_precharge_array_1rw1r_test.py b/compiler/tests/08_precharge_array_1rw_1r_test.py similarity index 68% rename from compiler/tests/08_precharge_array_1rw1r_test.py rename to compiler/tests/08_precharge_array_1rw_1r_test.py index 8f51c2d4..c5efb59b 100755 --- a/compiler/tests/08_precharge_array_1rw1r_test.py +++ b/compiler/tests/08_precharge_array_1rw_1r_test.py @@ -15,31 +15,27 @@ from globals import OPTS from sram_factory import factory import debug -class precharge_test(openram_test): +class precharge_1rw_1r_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) # check precharge array in multi-port - OPTS.bitcell = "bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 + globals.setup_bitcell() factory.reset() - debug.info(2, "Checking 3 column precharge array for 1RW/1R bitcell") + debug.info(2, "Checking 3 column precharge array for 1RW/1R bitcell (port 0)") pc = factory.create(module_type="precharge_array", columns=3, bitcell_bl="bl0", bitcell_br="br0") self.local_check(pc) - - # debug.info(2, "Checking 3 column precharge array for pbitcell (innermost connections)") - # pc = precharge_array.precharge_array(name="pre3", columns=3, bitcell_bl="bl0", bitcell_br="br0") - # self.local_check(pc) - - # debug.info(2, "Checking 3 column precharge array for pbitcell (outermost connections)") - # pc = precharge_array.precharge_array(name="pre4", columns=3, bitcell_bl="bl2", bitcell_br="br2") - # self.local_check(pc) + debug.info(2, "Checking 3 column precharge array for 1RW/1R bitcell (port 1)") + pc = factory.create(module_type="precharge_array", columns=3, bitcell_bl="bl0", bitcell_br="br0", column_offset=1) + self.local_check(pc) + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/08_precharge_array_test.py b/compiler/tests/08_precharge_array_test.py index b301a909..47843ca3 100755 --- a/compiler/tests/08_precharge_array_test.py +++ b/compiler/tests/08_precharge_array_test.py @@ -21,7 +21,6 @@ class precharge_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - # check precharge array in single port debug.info(2, "Checking 3 column precharge") pc = factory.create(module_type="precharge_array", columns=3) self.local_check(pc) diff --git a/compiler/tests/08_wordline_driver_array_1rw_1r_test.py b/compiler/tests/08_wordline_driver_array_1rw_1r_test.py new file mode 100755 index 00000000..cf1810d8 --- /dev/null +++ b/compiler/tests/08_wordline_driver_array_1rw_1r_test.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class wordline_driver_array_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + # check wordline driver for single port + debug.info(2, "Checking driver") + tx = factory.create(module_type="wordline_driver_array", rows=8, cols=32) + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/08_wordline_driver_pbitcell_test.py b/compiler/tests/08_wordline_driver_array_pbitcell_test.py old mode 100644 new mode 100755 similarity index 87% rename from compiler/tests/08_wordline_driver_pbitcell_test.py rename to compiler/tests/08_wordline_driver_array_pbitcell_test.py index 3dd5933d..267aaddf --- a/compiler/tests/08_wordline_driver_pbitcell_test.py +++ b/compiler/tests/08_wordline_driver_array_pbitcell_test.py @@ -15,9 +15,8 @@ from globals import OPTS from sram_factory import factory import debug -#@unittest.skip("SKIPPING 04_driver_test") -class wordline_driver_pbitcell_test(openram_test): +class wordline_driver_array_pbitcell_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) @@ -31,7 +30,7 @@ class wordline_driver_pbitcell_test(openram_test): factory.reset() debug.info(2, "Checking driver (multi-port case)") - tx = factory.create(module_type="wordline_driver", rows=8, cols=64) + tx = factory.create(module_type="wordline_driver_array", rows=8, cols=64) self.local_check(tx) globals.end_openram() diff --git a/compiler/tests/08_wordline_driver_array_test.py b/compiler/tests/08_wordline_driver_array_test.py new file mode 100755 index 00000000..3491cc4f --- /dev/null +++ b/compiler/tests/08_wordline_driver_array_test.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class wordline_driver_array_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check wordline driver for single port + debug.info(2, "Checking driver") + tx = factory.create(module_type="wordline_driver_array", rows=8, cols=32) + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/09_sense_amp_array_1rw_1r_test.py b/compiler/tests/09_sense_amp_array_1rw_1r_test.py new file mode 100755 index 00000000..a8ed4d2f --- /dev/null +++ b/compiler/tests/09_sense_amp_array_1rw_1r_test.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class sense_amp_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=1") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=1) + self.local_check(a) + + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=2") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=2) + self.local_check(a) + + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=4") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=4) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/09_sense_amp_array_spare_cols_test.py b/compiler/tests/09_sense_amp_array_spare_cols_test.py new file mode 100755 index 00000000..d8da1dc5 --- /dev/null +++ b/compiler/tests/09_sense_amp_array_spare_cols_test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class sense_amp_array_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check sense amp array for single port + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=2 and num_spare_cols=3") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=1, num_spare_cols=3) + self.local_check(a) + + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=4 and num_spare_cols=2") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=4, num_spare_cols=2) + self.local_check(a) + + # check sense amp array for multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + + factory.reset() + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=2, num_spare_cols=2 (multi-port case)") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=2, num_spare_cols=2) + self.local_check(a) + + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=4, num_spare_cols=3 (multi-port case)") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=4, num_spare_cols=3) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/09_sense_amp_array_test.py b/compiler/tests/09_sense_amp_array_test.py index 459113f2..c71a75e8 100755 --- a/compiler/tests/09_sense_amp_array_test.py +++ b/compiler/tests/09_sense_amp_array_test.py @@ -21,7 +21,6 @@ class sense_amp_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - # check sense amp array for single port debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=1") a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=1) self.local_check(a) diff --git a/compiler/tests/09_sense_amp_array_test_pbitcell.py b/compiler/tests/09_sense_amp_array_test_pbitcell.py old mode 100644 new mode 100755 diff --git a/compiler/tests/10_write_driver_array_1rw_1r_test.py b/compiler/tests/10_write_driver_array_1rw_1r_test.py new file mode 100755 index 00000000..4acbf053 --- /dev/null +++ b/compiler/tests/10_write_driver_array_1rw_1r_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class write_driver_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing write_driver_array for columns=8, word_size=8") + a = factory.create(module_type="write_driver_array", columns=8, word_size=8) + self.local_check(a) + + debug.info(2, "Testing write_driver_array for columns=16, word_size=8") + a = factory.create(module_type="write_driver_array", columns=16, word_size=8) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/10_write_driver_array_pbitcell_test.py b/compiler/tests/10_write_driver_array_pbitcell_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/10_write_driver_array_spare_cols_test.py b/compiler/tests/10_write_driver_array_spare_cols_test.py new file mode 100755 index 00000000..8b1256d0 --- /dev/null +++ b/compiler/tests/10_write_driver_array_spare_cols_test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class write_driver_array_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check write driver array for single port + debug.info(2, "Testing write_driver_array for columns=8, word_size=8 and num_spare_cols=3") + a = factory.create(module_type="write_driver_array", columns=8, word_size=8, num_spare_cols=3) + self.local_check(a) + + debug.info(2, "Testing write_driver_array for columns=16, word_size=8 and num_spare_cols=3") + a = factory.create(module_type="write_driver_array", columns=16, word_size=8, num_spare_cols=3) + self.local_check(a) + + # check write driver array for multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + + factory.reset() + debug.info(2, "Testing write_driver_array for columns=8, word_size=8 (multi-port case and num_spare_cols=3") + a = factory.create(module_type="write_driver_array", columns=8, word_size=8, num_spare_cols=3) + self.local_check(a) + + debug.info(2, "Testing write_driver_array for columns=16, word_size=8 (multi-port case and num_spare_cols=3") + a = factory.create(module_type="write_driver_array", columns=16, word_size=8, num_spare_cols=3) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/10_write_driver_array_wmask_pbitcell_test.py b/compiler/tests/10_write_driver_array_wmask_pbitcell_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/10_write_driver_array_wmask_spare_cols_test.py b/compiler/tests/10_write_driver_array_wmask_spare_cols_test.py new file mode 100755 index 00000000..8a1e4788 --- /dev/null +++ b/compiler/tests/10_write_driver_array_wmask_spare_cols_test.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class write_driver_array_wmask_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check write driver array for single port + debug.info(2, "Testing write_driver_array for columns=8, word_size=8, write_size=4") + a = factory.create(module_type="write_driver_array", columns=8, word_size=8, write_size=4, num_spare_cols=3) + self.local_check(a) + + debug.info(2, "Testing write_driver_array for columns=16, word_size=16, write_size=2") + a = factory.create(module_type="write_driver_array", columns=16, word_size=16, write_size=2, num_spare_cols=2) + self.local_check(a) + + debug.info(2, "Testing write_driver_array for columns=16, word_size=8, write_size=4") + a = factory.create(module_type="write_driver_array", columns=16, word_size=8, write_size=4, num_spare_cols=3) + self.local_check(a) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/10_write_mask_and_array_1rw_1r_test.py b/compiler/tests/10_write_mask_and_array_1rw_1r_test.py new file mode 100755 index 00000000..73988db9 --- /dev/null +++ b/compiler/tests/10_write_mask_and_array_1rw_1r_test.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class write_mask_and_array_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing write_mask_and_array for columns=8, word_size=8, write_size=4") + a = factory.create(module_type="write_mask_and_array", columns=8, word_size=8, write_size=4) + self.local_check(a) + + debug.info(2, "Testing write_mask_and_array for columns=16, word_size=16, write_size=4") + a = factory.create(module_type="write_mask_and_array", columns=16, word_size=16, write_size=4) + self.local_check(a) + + debug.info(2, "Testing write_mask_and_array for columns=16, word_size=8, write_size=2") + a = factory.create(module_type="write_mask_and_array", columns=16, word_size=8, write_size=2) + self.local_check(a) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/10_write_mask_and_array_pbitcell_test.py b/compiler/tests/10_write_mask_and_array_pbitcell_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/14_replica_bitcell_1rw_1r_array_test.py b/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py similarity index 67% rename from compiler/tests/14_replica_bitcell_1rw_1r_array_test.py rename to compiler/tests/14_replica_bitcell_array_1rw_1r_test.py index 9fbdf497..626725c4 100755 --- a/compiler/tests/14_replica_bitcell_1rw_1r_array_test.py +++ b/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # See LICENSE for licensing information. # -# Copyright (c) 2016-2019 Regents of the University of California +# Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # import unittest @@ -13,26 +13,26 @@ from globals import OPTS from sram_factory import factory import debug -class replica_bitcell_array_test(openram_test): +class replica_bitcell_array_1rw_1r_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell="dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 + globals.setup_bitcell() debug.info(2, "Testing 4x4 array for cell_1rw_1r") - a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=2, right_rbl=0, bitcell_ports=[0,1]) + a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=1, right_rbl=1, bitcell_ports=[0, 1]) self.local_check(a) - debug.info(2, "Testing 4x4 array for cell_1rw_1r") - a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=1, right_rbl=1, bitcell_ports=[0,1]) - self.local_check(a) + # Sky 130 has restrictions on the symmetries + if OPTS.tech_name != "sky130": + debug.info(2, "Testing 4x4 array for cell_1rw_1r") + a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=2, right_rbl=0, bitcell_ports=[0, 1]) + self.local_check(a) globals.end_openram() diff --git a/compiler/tests/14_replica_bitcell_array_test.py b/compiler/tests/14_replica_bitcell_array_test.py index 19413cd5..d229b99c 100755 --- a/compiler/tests/14_replica_bitcell_array_test.py +++ b/compiler/tests/14_replica_bitcell_array_test.py @@ -19,10 +19,15 @@ class replica_bitcell_array_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - debug.info(2, "Testing 4x4 array for 6t_cell") + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 0 + OPTS.num_w_ports = 0 + + factory.reset() + debug.info(2, "Testing 4x4 array for bitcell") a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=1, right_rbl=0, bitcell_ports=[0]) self.local_check(a) - + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/14_replica_column_1rw_1r_test.py b/compiler/tests/14_replica_column_1rw_1r_test.py new file mode 100755 index 00000000..3c1a0e1e --- /dev/null +++ b/compiler/tests/14_replica_column_1rw_1r_test.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class replica_column_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing replica column for 6t_cell") + a = factory.create(module_type="replica_column", rows=4, left_rbl=1, right_rbl=0, replica_bit=1) + self.local_check(a) + + debug.info(2, "Testing replica column for 6t_cell") + a = factory.create(module_type="replica_column", rows=4, left_rbl=1, right_rbl=1, replica_bit=6) + self.local_check(a) + + debug.info(2, "Testing replica column for 6t_cell") + a = factory.create(module_type="replica_column", rows=4, left_rbl=2, right_rbl=0, replica_bit=2) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/05_replica_pbitcell_array_test.py b/compiler/tests/14_replica_pbitcell_array_test.py similarity index 100% rename from compiler/tests/05_replica_pbitcell_array_test.py rename to compiler/tests/14_replica_pbitcell_array_test.py diff --git a/compiler/tests/18_port_address_1rw_1r_test.py b/compiler/tests/18_port_address_1rw_1r_test.py new file mode 100755 index 00000000..caf2cb96 --- /dev/null +++ b/compiler/tests/18_port_address_1rw_1r_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class port_address_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(1, "Port address 16 rows") + a = factory.create("port_address", cols=16, rows=16) + self.local_check(a) + + debug.info(1, "Port address 256 rows") + a = factory.create("port_address", cols=256, rows=256) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/18_port_address_test.py b/compiler/tests/18_port_address_test.py index 23f35540..11da333e 100755 --- a/compiler/tests/18_port_address_test.py +++ b/compiler/tests/18_port_address_test.py @@ -13,6 +13,7 @@ from globals import OPTS from sram_factory import factory import debug + class port_address_test(openram_test): def runTest(self): @@ -23,6 +24,10 @@ class port_address_test(openram_test): a = factory.create("port_address", cols=16, rows=16) self.local_check(a) + debug.info(1, "Port address 512 rows") + a = factory.create("port_address", cols=256, rows=512) + self.local_check(a) + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/18_port_data_1rw_1r_test.py b/compiler/tests/18_port_data_1rw_1r_test.py index 6201de6a..3a7687d6 100755 --- a/compiler/tests/18_port_data_1rw_1r_test.py +++ b/compiler/tests/18_port_data_1rw_1r_test.py @@ -13,6 +13,7 @@ from globals import OPTS from sram_factory import factory import debug + class port_data_1rw_1r_test(openram_test): def runTest(self): @@ -20,11 +21,11 @@ class port_data_1rw_1r_test(openram_test): globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1w_1r" - OPTS.num_rw_ports = 0 + OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 - OPTS.num_w_ports = 1 - + OPTS.num_w_ports = 0 + globals.setup_bitcell() + c = sram_config(word_size=4, num_words=16) @@ -36,7 +37,7 @@ class port_data_1rw_1r_test(openram_test): self.local_check(a) a = factory.create("port_data", sram_config=c, port=1) self.local_check(a) - + c.num_words=32 c.words_per_row=2 factory.reset() diff --git a/compiler/tests/18_port_data_spare_cols_test.py b/compiler/tests/18_port_data_spare_cols_test.py new file mode 100755 index 00000000..8c08ad0a --- /dev/null +++ b/compiler/tests/18_port_data_spare_cols_test.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class port_data_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + c = sram_config(word_size=8, + num_words=16, + num_spare_cols=3) + + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + c.num_words=64 + c.words_per_row=4 + c.num_spare_cols=3 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + c.word_size=2 + c.num_words=128 + c.words_per_row=8 + c.num_spare_cols=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + OPTS.bitcell = "bitcell_1w_1r" + OPTS.num_rw_ports = 0 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 1 + + c.num_words=16 + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.num_words=64 + c.words_per_row=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.word_size=2 + c.num_words=128 + c.words_per_row=8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/18_port_data_wmask_1rw_1r_test.py b/compiler/tests/18_port_data_wmask_1rw_1r_test.py new file mode 100755 index 00000000..74aa7fc0 --- /dev/null +++ b/compiler/tests/18_port_data_wmask_1rw_1r_test.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +import unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class port_data_wmask_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + c = sram_config(word_size=16, + write_size=4, + num_words=16) + + c.words_per_row = 1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + c.num_words = 32 + c.words_per_row = 2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + c.num_words = 64 + c.words_per_row = 4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + c.num_words = 128 + c.words_per_row = 8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + OPTS.bitcell = "bitcell_1w_1r" + OPTS.num_rw_ports = 0 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 1 + + c.num_words = 16 + c.words_per_row = 1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + # + c.num_words = 32 + c.words_per_row = 2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.num_words = 64 + c.words_per_row = 4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.word_size = 8 + c.num_words = 128 + c.words_per_row = 8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/18_port_data_wmask_test.py b/compiler/tests/18_port_data_wmask_test.py index f670990e..1c650c74 100755 --- a/compiler/tests/18_port_data_wmask_test.py +++ b/compiler/tests/18_port_data_wmask_test.py @@ -15,7 +15,7 @@ from sram_factory import factory import debug -class port_data_test(openram_test): +class port_data_wmask_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) diff --git a/compiler/tests/19_bank_select_pbitcell_test.py b/compiler/tests/19_bank_select_pbitcell_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/19_single_bank_1rw_1r_test.py b/compiler/tests/19_single_bank_1rw_1r_test.py index b3e18407..22f83f29 100755 --- a/compiler/tests/19_single_bank_1rw_1r_test.py +++ b/compiler/tests/19_single_bank_1rw_1r_test.py @@ -22,12 +22,10 @@ class single_bank_1rw_1r_test(openram_test): globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell="dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=16) diff --git a/compiler/tests/19_single_bank_1w_1r_test.py b/compiler/tests/19_single_bank_1w_1r_test.py index c1228e5a..e3a2d886 100755 --- a/compiler/tests/19_single_bank_1w_1r_test.py +++ b/compiler/tests/19_single_bank_1w_1r_test.py @@ -22,13 +22,10 @@ class single_bank_1w_1r_test(openram_test): globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1w_1r" - OPTS.replica_bitcell = "replica_bitcell_1w_1r" - OPTS.dummy_bitcell="dummy_bitcell_1w_1r" - OPTS.num_rw_ports = 0 OPTS.num_r_ports = 1 OPTS.num_w_ports = 1 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=16) diff --git a/compiler/tests/19_single_bank_spare_cols_test.py b/compiler/tests/19_single_bank_spare_cols_test.py new file mode 100755 index 00000000..52eeea52 --- /dev/null +++ b/compiler/tests/19_single_bank_spare_cols_test.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class single_bank_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + c = sram_config(word_size=4, + num_words=16, + num_spare_cols=3) + + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.num_words=64 + c.words_per_row=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.word_size=2 + c.num_words=128 + c.words_per_row=8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/19_single_bank_test.py b/compiler/tests/19_single_bank_test.py index 38a847a9..6cff481b 100755 --- a/compiler/tests/19_single_bank_test.py +++ b/compiler/tests/19_single_bank_test.py @@ -25,28 +25,28 @@ class single_bank_test(openram_test): c = sram_config(word_size=4, num_words=16) - # c.words_per_row=1 - # factory.reset() - # c.recompute_sizes() - # debug.info(1, "No column mux") - # a = factory.create("bank", sram_config=c) - # self.local_check(a) + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) - # c.num_words=32 - # c.words_per_row=2 - # factory.reset() - # c.recompute_sizes() - # debug.info(1, "Two way column mux") - # a = factory.create("bank", sram_config=c) - # self.local_check(a) + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) - # c.num_words=64 - # c.words_per_row=4 - # factory.reset() - # c.recompute_sizes() - # debug.info(1, "Four way column mux") - # a = factory.create("bank", sram_config=c) - # self.local_check(a) + c.num_words=64 + c.words_per_row=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) c.word_size=2 c.num_words=128 diff --git a/compiler/tests/19_single_bank_wmask_1rw_1r_test.py b/compiler/tests/19_single_bank_wmask_1rw_1r_test.py new file mode 100755 index 00000000..ddb97905 --- /dev/null +++ b/compiler/tests/19_single_bank_wmask_1rw_1r_test.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class single_bank_wmask_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + c = sram_config(word_size=8, + write_size=4, + num_words=16, + num_banks=1) + + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.num_words=64 + c.words_per_row=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.num_words=128 + c.words_per_row=8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_psram_1bank_2mux_1rw_1w_test.py b/compiler/tests/20_psram_1bank_2mux_1rw_1w_test.py index 599cb2ce..f521851b 100755 --- a/compiler/tests/20_psram_1bank_2mux_1rw_1w_test.py +++ b/compiler/tests/20_psram_1bank_2mux_1rw_1w_test.py @@ -24,11 +24,10 @@ class psram_1bank_2mux_1rw_1w_test(openram_test): from sram_config import sram_config OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell="replica_pbitcell" - OPTS.dummy_bitcell="dummy_pbitcell" OPTS.num_rw_ports = 1 OPTS.num_w_ports = 1 OPTS.num_r_ports = 0 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=32, diff --git a/compiler/tests/20_psram_1bank_2mux_1w_1r_test.py b/compiler/tests/20_psram_1bank_2mux_1w_1r_test.py index 30b951fb..35912823 100755 --- a/compiler/tests/20_psram_1bank_2mux_1w_1r_test.py +++ b/compiler/tests/20_psram_1bank_2mux_1w_1r_test.py @@ -24,11 +24,10 @@ class psram_1bank_2mux_1w_1r_test(openram_test): from sram_config import sram_config OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell="replica_pbitcell" - OPTS.dummy_bitcell="dummy_pbitcell" OPTS.num_rw_ports = 0 OPTS.num_w_ports = 1 OPTS.num_r_ports = 1 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=32, diff --git a/compiler/tests/20_psram_1bank_2mux_test.py b/compiler/tests/20_psram_1bank_2mux_test.py index 44272b2d..92403cd1 100755 --- a/compiler/tests/20_psram_1bank_2mux_test.py +++ b/compiler/tests/20_psram_1bank_2mux_test.py @@ -22,14 +22,12 @@ class psram_1bank_2mux_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell="replica_pbitcell" - OPTS.dummy_bitcell="dummy_pbitcell" - # testing layout of sram using pbitcell with 1 RW port (a 6T-cell equivalent) + OPTS.bitcell = "pbitcell" OPTS.num_rw_ports = 1 OPTS.num_w_ports = 0 OPTS.num_r_ports = 0 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=32, diff --git a/compiler/tests/20_psram_1bank_4mux_1rw_1r_test.py b/compiler/tests/20_psram_1bank_4mux_1rw_1r_test.py index 49e1a125..ecbd0863 100755 --- a/compiler/tests/20_psram_1bank_4mux_1rw_1r_test.py +++ b/compiler/tests/20_psram_1bank_4mux_1rw_1r_test.py @@ -15,7 +15,6 @@ from globals import OPTS from sram_factory import factory import debug -@unittest.skip("SKIPPING 20_psram_1bank_4mux_1rw_1r_test - Matt sucks, don't do this") class psram_1bank_4mux_1rw_1r_test(openram_test): def runTest(self): @@ -24,11 +23,10 @@ class psram_1bank_4mux_1rw_1r_test(openram_test): from sram_config import sram_config OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell="replica_pbitcell" - OPTS.dummy_bitcell="dummy_pbitcell" OPTS.num_rw_ports = 1 OPTS.num_w_ports = 0 OPTS.num_r_ports = 1 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=64, diff --git a/compiler/tests/20_sram_1bank_2mux_1rw_1r_spare_cols_test.py b/compiler/tests/20_sram_1bank_2mux_1rw_1r_spare_cols_test.py new file mode 100755 index 00000000..6adb3e8e --- /dev/null +++ b/compiler/tests/20_sram_1bank_2mux_1rw_1r_spare_cols_test.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class sram_1bank_2mux_1rw_1r_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + c = sram_config(word_size=4, + num_words=32, + num_spare_cols=3, + num_banks=1) + + c.words_per_row=2 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} words per " + "row, {} spare columns, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_spare_cols, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py index a8d635ba..0a2b7d32 100755 --- a/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py +++ b/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py @@ -22,12 +22,10 @@ class sram_1bank_2mux_1rw_1r_test(openram_test): globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell="dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=32, diff --git a/compiler/tests/20_sram_1bank_2mux_1w_1r_spare_cols_test.py b/compiler/tests/20_sram_1bank_2mux_1w_1r_spare_cols_test.py new file mode 100755 index 00000000..987a297e --- /dev/null +++ b/compiler/tests/20_sram_1bank_2mux_1w_1r_spare_cols_test.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +#@unittest.skip("SKIPPING 20_sram_1bank_2mux_1w_1r_spare_cols_test, odd supply routing error") +class sram_1bank_2mux_1w_1r_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 0 + OPTS.num_w_ports = 1 + OPTS.num_r_ports = 1 + globals.setup_bitcell() + + c = sram_config(word_size=4, + num_words=32, + num_spare_cols=3, + num_banks=1) + c.num_words=32 + c.words_per_row=2 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} words per " + "row, {} spare columns, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_spare_cols, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_2mux_1w_1r_test.py b/compiler/tests/20_sram_1bank_2mux_1w_1r_test.py index bf572700..2c4e29e6 100755 --- a/compiler/tests/20_sram_1bank_2mux_1w_1r_test.py +++ b/compiler/tests/20_sram_1bank_2mux_1w_1r_test.py @@ -23,12 +23,10 @@ class psram_1bank_2mux_1w_1r_test(openram_test): globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1w_1r" - OPTS.replica_bitcell="replica_bitcell_1w_1r" - OPTS.dummy_bitcell="dummy_bitcell_1w_1r" OPTS.num_rw_ports = 0 OPTS.num_w_ports = 1 OPTS.num_r_ports = 1 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=32, diff --git a/compiler/tests/20_sram_1bank_2mux_wmask_spare_cols_test.py b/compiler/tests/20_sram_1bank_2mux_wmask_spare_cols_test.py new file mode 100755 index 00000000..0488b93e --- /dev/null +++ b/compiler/tests/20_sram_1bank_2mux_wmask_spare_cols_test.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +# @unittest.skip("SKIPPING 20_sram_1bank_2mux_wmask_spare_cols_test") +class sram_1bank_2mux_wmask_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + c = sram_config(word_size=8, + write_size=4, + num_spare_cols=3, + num_words=64, + num_banks=1) + + c.words_per_row = 2 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} bit writes, {} words per " + "row, {} spare columns, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.write_size, + c.words_per_row, + c.num_spare_cols, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py index 69a623d2..1e4df34d 100755 --- a/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py +++ b/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py @@ -22,13 +22,11 @@ class sram_1bank_8mux_1rw_1r_test(openram_test): globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell="dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 - + globals.setup_bitcell() + c = sram_config(word_size=2, num_words=128, num_banks=1) diff --git a/compiler/tests/20_sram_1bank_nomux_1rw_1r_spare_cols_test.py b/compiler/tests/20_sram_1bank_nomux_1rw_1r_spare_cols_test.py new file mode 100755 index 00000000..dbeca8aa --- /dev/null +++ b/compiler/tests/20_sram_1bank_nomux_1rw_1r_spare_cols_test.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class sram_1bank_nomux_1rw_1r_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + c = sram_config(word_size=4, + num_words=16, + num_spare_cols=4, + num_banks=1) + + c.words_per_row=1 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} words per " + "row, {} spare columns, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_spare_cols, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py index f9b96b84..a516b4f0 100755 --- a/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py +++ b/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py @@ -22,12 +22,10 @@ class sram_1bank_nomux_1rw_1r_test(openram_test): globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell = "dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=16, diff --git a/compiler/tests/20_sram_1bank_nomux_spare_cols_test.py b/compiler/tests/20_sram_1bank_nomux_spare_cols_test.py new file mode 100755 index 00000000..135518e3 --- /dev/null +++ b/compiler/tests/20_sram_1bank_nomux_spare_cols_test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +# @unittest.skip("SKIPPING 20_sram_1bank_nomux_spare_cols_test") +class sram_1bank_nomux_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + c = sram_config(word_size=8, + num_spare_cols=3, + num_words=16, + num_banks=1) + + c.words_per_row = 1 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} spare cols, {} words per " + "row, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.num_spare_cols, + c.words_per_row, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_nomux_wmask_sparecols_test.py b/compiler/tests/20_sram_1bank_nomux_wmask_sparecols_test.py new file mode 100755 index 00000000..e22122bd --- /dev/null +++ b/compiler/tests/20_sram_1bank_nomux_wmask_sparecols_test.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +@unittest.skip("SKIPPING 20_sram_1bank_nomux_wmask_sparecols_test, not working yet") +class sram_1bank_nomux_wmask_sparecols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + c = sram_config(word_size=8, + write_size=4, + num_words=16, + num_spare_cols=3, + num_banks=1) + + c.words_per_row = 1 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} bit writes, {} words per " + "row, {} spare columns, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.write_size, + c.words_per_row, + c.num_spare_cols, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/21_ngspice_delay_extra_rows_test.py b/compiler/tests/21_ngspice_delay_extra_rows_test.py new file mode 100755 index 00000000..22812d52 --- /dev/null +++ b/compiler/tests/21_ngspice_delay_extra_rows_test.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class timing_sram_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.spice_name="ngspice" + OPTS.analytical_delay = False + OPTS.netlist_only = True + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import delay + from sram_config import sram_config + c = sram_config(word_size=1, + num_words=16, + num_banks=1, + num_spare_rows=3) + c.words_per_row=1 + c.recompute_sizes() + debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") + s = factory.create(module_type="sram", sram_config=c) + + tempspice = OPTS.openram_temp + "temp.sp" + s.sp_write(tempspice) + + probe_address = "0" + ("1" * (s.s.addr_size - 1)) + probe_data = s.s.word_size - 1 + debug.info(1, "Probe address {0} probe data bit {1}".format(probe_address, probe_data)) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + d = delay(s.s, tempspice, corner) + import tech + loads = [tech.spice["dff_in_cap"]*4] + slews = [tech.spice["rise_time"]*2] + data, port_data = d.analyze(probe_address, probe_data, slews, loads) + #Combine info about port into all data + data.update(port_data[0]) + + if OPTS.tech_name == "freepdk45": + golden_data = {'slew_lh': [0.2592187], + 'slew_hl': [0.2592187], + 'delay_lh': [0.2465583], + 'disabled_write0_power': [0.1924678], + 'disabled_read0_power': [0.152483], + 'write0_power': [0.3409064], + 'disabled_read1_power': [0.1737818], + 'read0_power': [0.3096708], + 'read1_power': [0.3107916], + 'delay_hl': [0.2465583], + 'write1_power': [0.26915849999999997], + 'leakage_power': 0.002044307, + 'min_period': 0.898, + 'disabled_write1_power': [0.201411]} + elif OPTS.tech_name == "scn4m_subm": + golden_data = {'read1_power': [12.11658], + 'write1_power': [10.52653], + 'read0_power': [11.956710000000001], + 'disabled_write0_power': [7.673665], + 'disabled_write1_power': [7.981922000000001], + 'slew_lh': [1.868836], + 'slew_hl': [1.868836], + 'delay_hl': [1.8598510000000001], + 'delay_lh': [1.8598510000000001], + 'leakage_power': 0.005457728, + 'disabled_read0_power': [5.904712], + 'min_period': 6.875, + 'disabled_read1_power': [7.132159], + 'write0_power': [13.406400000000001]} + else: + self.assertTrue(False) # other techs fail + + # Check if no too many or too few results + self.assertTrue(len(data.keys())==len(golden_data.keys())) + + self.assertTrue(self.check_golden_data(data,golden_data,0.25)) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py b/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py new file mode 100755 index 00000000..902cacdd --- /dev/null +++ b/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +#@unittest.skip("SKIPPING 22_sram_1bank_2mux_sparecols_func_test") +class sram_1bank_2mux_sparecols_func_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.analytical_delay = False + OPTS.netlist_only = True + OPTS.trim_netlist = False + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import functional, delay + from sram_config import sram_config + c = sram_config(word_size=4, + num_words=32, + num_spare_cols=3, + num_banks=1) + c.words_per_row=2 + c.recompute_sizes() + debug.info(1, "Functional test for sram with " + "{} bit words, {} words, {} words per row, {} spare columns, {} banks".format(c.word_size, + c.num_words, + c.words_per_row, + c.num_spare_cols, + c.num_banks)) + s = factory.create(module_type="sram", sram_config=c) + tempspice = OPTS.openram_temp + "sram.sp" + s.sp_write(tempspice) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + f = functional(s.s, tempspice, corner) + (fail, error) = f.run() + self.assertTrue(fail,error) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/22_sram_1rw_1r_1bank_nomux_func_test.py b/compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py similarity index 94% rename from compiler/tests/22_sram_1rw_1r_1bank_nomux_func_test.py rename to compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py index d271d1e5..f2958f9f 100755 --- a/compiler/tests/22_sram_1rw_1r_1bank_nomux_func_test.py +++ b/compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py @@ -24,12 +24,10 @@ class psram_1bank_nomux_func_test(openram_test): OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell="dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_w_ports = 0 OPTS.num_r_ports = 1 + globals.setup_bitcell() # This is a hack to reload the characterizer __init__ with the spice version from importlib import reload diff --git a/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py b/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py new file mode 100755 index 00000000..347d15d0 --- /dev/null +++ b/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +#@unittest.skip("SKIPPING 22_sram_func_test") +class sram_1bank_nomux_sparecols_func_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.analytical_delay = False + OPTS.netlist_only = True + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import functional + from sram_config import sram_config + c = sram_config(word_size=4, + num_words=16, + num_spare_cols=3, + num_banks=1) + c.words_per_row=1 + c.recompute_sizes() + debug.info(1, "Functional test for sram with " + "{} bit words, {} words, {} words per row, {} banks".format(c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) + s = factory.create(module_type="sram", sram_config=c) + tempspice = OPTS.openram_temp + "sram.sp" + s.sp_write(tempspice) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + f = functional(s.s, tempspice, corner) + (fail, error) = f.run() + self.assertTrue(fail,error) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/22_sram_wmask_1w_1r_func_test.py b/compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py similarity index 91% rename from compiler/tests/22_sram_wmask_1w_1r_func_test.py rename to compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py index 50acd5bf..07cff70e 100755 --- a/compiler/tests/22_sram_wmask_1w_1r_func_test.py +++ b/compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py @@ -26,14 +26,11 @@ class sram_wmask_1w_1r_func_test(openram_test): OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False - OPTS.bitcell = "bitcell_1w_1r" - OPTS.replica_bitcell = "replica_bitcell_1w_1r" - OPTS.dummy_bitcell = "dummy_bitcell_1w_1r" - - OPTS.num_rw_ports = 0 - OPTS.num_w_ports = 1 + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 OPTS.num_r_ports = 1 - + globals.setup_bitcell() + # This is a hack to reload the characterizer __init__ with the spice version from importlib import reload import characterizer diff --git a/compiler/tests/50_riscv_func_test.py b/compiler/tests/50_riscv_func_test.py new file mode 100755 index 00000000..1d5720f7 --- /dev/null +++ b/compiler/tests/50_riscv_func_test.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +@unittest.skip("SKIPPING 50_riscv_func_test") +class riscv_func_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.analytical_delay = False + OPTS.netlist_only = True + OPTS.trim_netlist = False + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 1 + globals.setup_bitcell() + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import functional, delay + from sram_config import sram_config + c = sram_config(word_size=32, + write_size=8, + num_words=256, + num_banks=1) + c.words_per_row=1 + c.recompute_sizes() + debug.info(1, "Functional test RISC-V memory" + "{} bit words, {} words, {} words per row, {} banks".format(c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) + s = factory.create(module_type="sram", sram_config=c) + tempspice = OPTS.openram_temp + "sram.sp" + s.sp_write(tempspice) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + f = functional(s.s, tempspice, corner) + (fail, error) = f.run() + self.assertTrue(fail,error) + + globals.end_openram() + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/50_riscv_phys_test.py b/compiler/tests/50_riscv_phys_test.py new file mode 100755 index 00000000..0ae11025 --- /dev/null +++ b/compiler/tests/50_riscv_phys_test.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# 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 unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +@unittest.skip("SKIPPING 50_riscv_phys_test") +class riscv_phys_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + OPTS.route_supplies=False + OPTS.perimeter_pins=False + + c = sram_config(word_size=32, + write_size=8, + num_words=256, + num_banks=1) + + c.words_per_row=2 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} words per " + "row, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/regress.py b/compiler/tests/regress.py index e60b010d..3fd5d6eb 100755 --- a/compiler/tests/regress.py +++ b/compiler/tests/regress.py @@ -22,14 +22,26 @@ header(__file__, OPTS.tech_name) # get a list of all files in the tests directory files = os.listdir(sys.path[0]) +# load a file with all tests to skip in a given technology +# since tech_name is dynamically loaded, we can't use @skip directives +try: + skip_file_name = "{0}/tests/skip_tests_{1}.txt".format(os.getenv("OPENRAM_HOME"), OPTS.tech_name) + skip_file = open(skip_file_name, "r") + skip_tests = skip_file.read().splitlines() + for st in skip_tests: + debug.warning("Skipping: " + st) +except FileNotFoundError: + skip_tests = [] + # assume any file that ends in "test.py" in it is a regression test nametest = re.compile("test\.py$", re.IGNORECASE) -tests = list(filter(nametest.search, files)) -tests.sort() +all_tests = list(filter(nametest.search, files)) +filtered_tests = list(filter(lambda i: i not in skip_tests, all_tests)) +filtered_tests.sort() # import all of the modules filenameToModuleName = lambda f: os.path.splitext(f)[0] -moduleNames = map(filenameToModuleName, tests) +moduleNames = map(filenameToModuleName, filtered_tests) modules = map(__import__, moduleNames) suite = unittest.TestSuite() load = unittest.defaultTestLoader.loadTestsFromModule diff --git a/compiler/tests/skip_tests_sky130.txt b/compiler/tests/skip_tests_sky130.txt new file mode 100644 index 00000000..68a6549b --- /dev/null +++ b/compiler/tests/skip_tests_sky130.txt @@ -0,0 +1,87 @@ +04_dummy_pbitcell_test.py +04_pbitcell_test.py +04_precharge_pbitcell_test.py +04_replica_pbitcell_test.py +04_single_level_column_mux_pbitcell_test.py +05_bitcell_1rw_1r_array_test.py +05_bitcell_array_test.py +05_dummy_array_test.py +05_pbitcell_array_test.py +06_hierarchical_decoder_pbitcell_test.py +06_hierarchical_decoder_test.py +06_hierarchical_predecode2x4_pbitcell_test.py +06_hierarchical_predecode2x4_test.py +06_hierarchical_predecode3x8_pbitcell_test.py +06_hierarchical_predecode3x8_test.py +06_hierarchical_predecode4x16_test.py +07_single_level_column_mux_array_pbitcell_test.py +08_wordline_driver_array_pbitcell_test.py +08_wordline_driver_array_test.py +09_sense_amp_array_test_pbitcell.py +09_sense_amp_array_test.py +10_write_driver_array_pbitcell_test.py +10_write_driver_array_test.py +10_write_driver_array_wmask_pbitcell_test.py +10_write_driver_array_wmask_test.py +10_write_mask_and_array_pbitcell_test.py +10_write_mask_and_array_test.py +12_tri_gate_array_test.py +14_replica_pbitcell_array_test.py +14_replica_bitcell_array_test.py +14_replica_column_test.py +14_replica_column_1rw_1r_test.py +18_port_address_test.py +18_port_data_test.py +18_port_data_wmask_test.py +19_bank_select_pbitcell_test.py +19_bank_select_test.py +19_psingle_bank_test.py +19_bank_select_pbitcell_test.py +19_pmulti_bank_test.py +19_multi_bank_test.py +19_psingle_bank_test.py +19_single_bank_1w_1r_test.py +19_single_bank_wmask_1rw_1r_test.py +19_single_bank_1rw_1r_test.py +19_single_bank_test.py +19_single_bank_wmask_test.py +20_psram_1bank_2mux_1rw_1w_test.py +20_psram_1bank_2mux_1rw_1w_wmask_test.py +20_psram_1bank_2mux_1w_1r_test.py +20_psram_1bank_2mux_test.py +20_psram_1bank_4mux_1rw_1r_test.py +20_sram_1bank_2mux_1w_1r_test.py +20_sram_1bank_2mux_test.py +20_sram_1bank_2mux_wmask_test.py +20_sram_1bank_32b_1024_wmask_test.py +20_sram_1bank_4mux_test.py +20_sram_1bank_8mux_test.py +20_sram_1bank_nomux_test.py +20_sram_1bank_nomux_wmask_test.py +20_sram_2bank_test.py +21_hspice_delay_test.py +21_hspice_setuphold_test.py +21_model_delay_test.py +21_ngspice_delay_test.py +21_ngspice_setuphold_test.py +22_psram_1bank_2mux_func_test.py +22_psram_1bank_4mux_func_test.py +22_psram_1bank_8mux_func_test.py +22_psram_1bank_nomux_func_test.py +22_sram_1bank_2mux_func_test.py +22_sram_1bank_4mux_func_test.py +22_sram_1bank_8mux_func_test.py +22_sram_1bank_nomux_func_test.py +22_sram_1rw_1r_1bank_nomux_func_test.py +22_sram_wmask_func_test.py +23_lib_sram_model_corners_test.py +23_lib_sram_model_test.py +23_lib_sram_prune_test.py +23_lib_sram_test.py +24_lef_sram_test.py +25_verilog_sram_test.py +26_hspice_pex_pinv_test.py +26_ngspice_pex_pinv_test.py +26_pex_test.py +30_openram_back_end_test.py +30_openram_front_end_test.py diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index 99d534d0..a8da9fb4 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -5,23 +5,21 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import unittest,warnings -import pdb,traceback -import sys,os,glob,copy -import shutil +import unittest +import sys, os, glob sys.path.append(os.getenv("OPENRAM_HOME")) from globals import OPTS import debug + class openram_test(unittest.TestCase): """ Base unit test that we have some shared classes in. """ - def local_drc_check(self, w): self.reset() - tempgds = "{0}{1}.gds".format(OPTS.openram_temp,w.name) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp, w.name) w.gds_write(tempgds) import verify @@ -36,8 +34,8 @@ class openram_test(unittest.TestCase): self.reset() - tempspice = "{0}{1}.sp".format(OPTS.openram_temp,a.name) - tempgds = "{0}{1}.gds".format(OPTS.openram_temp,a.name) + tempspice = "{0}{1}.sp".format(OPTS.openram_temp, a.name) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp, a.name) a.lvs_write(tempspice) # cannot write gds in netlist_only mode @@ -50,39 +48,40 @@ class openram_test(unittest.TestCase): # if we ignore things like minimum metal area of pins drc_result=verify.run_drc(a.name, tempgds, extract=True, final_verification=final_verification) - # Always run LVS if we are using magic - if "magic" in OPTS.drc_exe or drc_result == 0: - lvs_result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification) + # We can still run LVS even if DRC fails in Magic OR Calibre + lvs_result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification) # Only allow DRC to fail and LVS to pass if we are using magic - if "magic" in OPTS.drc_exe and lvs_result == 0 and drc_result != 0: - #zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid()) - #debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) - #shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) - debug.warning("DRC failed but LVS passed: {}".format(a.name)) + if lvs_result == 0 and drc_result != 0: + # import shutil + # zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid()) + # debug.info(0, "Archiving failed files to {}.zip".format(zip_file)) + # shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) + self.fail("DRC failed but LVS passed: {}".format(a.name)) elif drc_result != 0: - #zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid()) - #debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) - #shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) + # import shutil + # zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid()) + # debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) + # shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) self.fail("DRC failed: {}".format(a.name)) if lvs_result != 0: - #zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid()) - #debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) - #shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) + # import shutil + # zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid()) + # debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) + # shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) self.fail("LVS mismatch: {}".format(a.name)) - # For debug... - #import pdb; pdb.set_trace() + # import pdb; pdb.set_trace() if OPTS.purge_temp: self.cleanup() def run_pex(self, a, output=None): if output == None: output = OPTS.openram_temp + a.name + ".pex.netlist" - tempspice = "{0}{1}.sp".format(OPTS.openram_temp,a.name) - tempgds = "{0}{1}.gds".format(OPTS.openram_temp,a.name) + tempspice = "{0}{1}.sp".format(OPTS.openram_temp, a.name) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp, a.name) import verify result=verify.run_pex(a.name, tempgds, tempspice, output=output, final_verification=False) @@ -96,8 +95,8 @@ class openram_test(unittest.TestCase): """ debug.info(1, "Finding feasible period for current test.") delay_obj.set_load_slew(load, slew) - test_port = delay_obj.read_ports[0] #Only test one port, assumes other ports have similar period. - delay_obj.analysis_init(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1)) + test_port = delay_obj.read_ports[0] # Only test one port, assumes other ports have similar period. + delay_obj.analysis_init(probe_address="1" * sram.addr_size, probe_data=sram.word_size - 1) delay_obj.find_feasible_period_one_port(test_port) return delay_obj.period @@ -129,29 +128,27 @@ class openram_test(unittest.TestCase): for k in data.keys(): if type(data[k])==list: for i in range(len(data[k])): - if not self.isclose(k,data[k][i],golden_data[k][i],error_tolerance): + if not self.isclose(k, data[k][i], golden_data[k][i], error_tolerance): data_matches = False else: - if not self.isclose(k,data[k],golden_data[k],error_tolerance): + if not self.isclose(k, data[k], golden_data[k], error_tolerance): data_matches = False if not data_matches: import pprint data_string=pprint.pformat(data) - debug.error("Results exceeded {:.1f}% tolerance compared to golden results:\n".format(error_tolerance*100)+data_string) + debug.error("Results exceeded {:.1f}% tolerance compared to golden results:\n".format(error_tolerance * 100) + data_string) return data_matches - - - def isclose(self,key,value,actual_value,error_tolerance=1e-2): + def isclose(self, key, value, actual_value, error_tolerance=1e-2): """ This is used to compare relative values. """ import debug - relative_diff = self.relative_diff(value,actual_value) + relative_diff = self.relative_diff(value, actual_value) check = relative_diff <= error_tolerance if check: - debug.info(2,"CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key,value,actual_value,relative_diff*100)) + debug.info(2, "CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key, value, actual_value, relative_diff * 100)) return True else: - debug.error("NOT CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key,value,actual_value,relative_diff*100)) + debug.error("NOT CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key, value, actual_value, relative_diff * 100)) return False def relative_diff(self, value1, value2): @@ -168,18 +165,14 @@ class openram_test(unittest.TestCase): # Get normalization value norm_value = abs(max(value1, value2)) - # Edge case where greater is a zero - if norm_value == 0: - min_value = abs(min(value1, value2)) return abs(value1 - value2) / norm_value - - def relative_compare(self, value,actual_value,error_tolerance): + def relative_compare(self, value, actual_value, error_tolerance): """ This is used to compare relative values. """ if (value==actual_value): # if we don't need a relative comparison! return True - return (abs(value - actual_value) / max(value,actual_value) <= error_tolerance) + return (abs(value - actual_value) / max(value, actual_value) <= error_tolerance) def isapproxdiff(self, filename1, filename2, error_tolerance=0.001): """Compare two files. @@ -217,23 +210,22 @@ class openram_test(unittest.TestCase): line_num+=1 line1 = fp1.readline().decode('utf-8') line2 = fp2.readline().decode('utf-8') - #print("line1:",line1) - #print("line2:",line2) + # print("line1:", line1) + # print("line2:", line2) # 1. Find all of the floats using a regex line1_floats=rx.findall(line1) line2_floats=rx.findall(line2) - debug.info(3,"line1_floats: "+str(line1_floats)) - debug.info(3,"line2_floats: "+str(line2_floats)) - + debug.info(3, "line1_floats: " + str(line1_floats)) + debug.info(3, "line2_floats: " + str(line2_floats)) # 2. Remove the floats from the string for f in line1_floats: - line1=line1.replace(f,"",1) + line1=line1.replace(f, "", 1) for f in line2_floats: - line2=line2.replace(f,"",1) - #print("line1:",line1) - #print("line2:",line2) + line2=line2.replace(f, "", 1) + # print("line1:", line1) + # print("line2:", line2) # 3. Convert to floats rather than strings line1_floats = [float(x) for x in line1_floats] @@ -241,29 +233,29 @@ class openram_test(unittest.TestCase): # 4. Check if remaining string matches if line1 != line2: - #Uncomment if you want to see all the individual chars of the two lines - #print(str([i for i in line1])) - #print(str([i for i in line2])) + # Uncomment if you want to see all the individual chars of the two lines + # print(str([i for i in line1])) + # print(str([i for i in line2])) if mismatches==0: - debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2)) + debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1, filename2)) mismatches += 1 - debug.error("MISMATCH Line ({0}):\n{1}\n!=\n{2}".format(line_num,line1.rstrip('\n'),line2.rstrip('\n'))) + debug.error("MISMATCH Line ({0}):\n{1}\n!=\n{2}".format(line_num, line1.rstrip('\n'), line2.rstrip('\n'))) # 5. Now compare that the floats match elif len(line1_floats)!=len(line2_floats): if mismatches==0: - debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2)) + debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1, filename2)) mismatches += 1 - debug.error("MISMATCH Line ({0}) Length {1} != {2}".format(line_num,len(line1_floats),len(line2_floats))) + debug.error("MISMATCH Line ({0}) Length {1} != {2}".format(line_num, len(line1_floats), len(line2_floats))) else: - for (float1,float2) in zip(line1_floats,line2_floats): - relative_diff = self.relative_diff(float1,float2) + for (float1, float2) in zip(line1_floats, line2_floats): + relative_diff = self.relative_diff(float1, float2) check = relative_diff <= error_tolerance if not check: if mismatches==0: - debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2)) + debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1, filename2)) mismatches += 1 - debug.error("MISMATCH Line ({0}) Float {1} != {2} diff: {3:.1f}%".format(line_num,float1,float2,relative_diff*100)) + debug.error("MISMATCH Line ({0}) Float {1} != {2} diff: {3:.1f}%".format(line_num, float1, float2, relative_diff * 100)) # Only show the first 10 mismatch lines if not line1 and not line2 or mismatches>10: @@ -274,19 +266,18 @@ class openram_test(unittest.TestCase): # Never reached return False - - def isdiff(self,filename1,filename2): + def isdiff(self, filename1, filename2): """ This is used to compare two files and display the diff if they are different.. """ import debug import filecmp import difflib - check = filecmp.cmp(filename1,filename2) + check = filecmp.cmp(filename1, filename2) if not check: - debug.error("MISMATCH file1={0} file2={1}".format(filename1,filename2)) - f1 = open(filename1,mode="r",encoding='utf-8') + debug.error("MISMATCH file1={0} file2={1}".format(filename1, filename2)) + f1 = open(filename1, mode="r", encoding='utf-8') s1 = f1.readlines() f1.close() - f2 = open(filename2,mode="r",encoding='utf-8') + f2 = open(filename2, mode="r", encoding='utf-8') s2 = f2.readlines() f2.close() mismatches=0 @@ -301,10 +292,13 @@ class openram_test(unittest.TestCase): return False return False else: - debug.info(2,"MATCH {0} {1}".format(filename1,filename2)) + debug.info(2, "MATCH {0} {1}".format(filename1, filename2)) return True + def dbg(): + import pdb; pdb.set_trace() + def header(filename, technology): # Skip the header for gitlab regression import getpass @@ -318,14 +312,18 @@ def header(filename, technology): print("|=========" + tst.center(60) + "=========|") print("|=========" + technology.center(60) + "=========|") print("|=========" + filename.center(60) + "=========|") - from globals import OPTS + from globals import OPTS print("|=========" + OPTS.openram_temp.center(60) + "=========|") print("|==============================================================================|") + def debugTestRunner(post_mortem=None): """unittest runner doing post mortem debugging on failing tests""" + import pdb + import traceback if post_mortem is None and not OPTS.purge_temp: post_mortem = pdb.post_mortem + class DebugTestResult(unittest.TextTestResult): def addError(self, test, err): # called before tearDown() @@ -333,9 +331,10 @@ def debugTestRunner(post_mortem=None): if post_mortem: post_mortem(err[2]) super(DebugTestResult, self).addError(test, err) + def addFailure(self, test, err): traceback.print_exception(*err) - if post_mortem: + if post_mortem: post_mortem(err[2]) super(DebugTestResult, self).addFailure(test, err) return unittest.TextTestRunner(resultclass=DebugTestResult) diff --git a/compiler/verify/__init__.py b/compiler/verify/__init__.py index 59df42db..a28581d8 100644 --- a/compiler/verify/__init__.py +++ b/compiler/verify/__init__.py @@ -18,27 +18,27 @@ If not, OpenRAM will continue as if nothing happened! import os import debug from globals import OPTS -from globals import find_exe from globals import get_tool from tech import drc_name from tech import lvs_name from tech import pex_name -import sys -debug.info(1,"Initializing verify...") +debug.info(1, "Initializing verify...") if not OPTS.check_lvsdrc: - debug.info(1,"LVS/DRC/PEX disabled.") + debug.info(1, "LVS/DRC/PEX disabled.") OPTS.drc_exe = None OPTS.lvs_exe = None OPTS.pex_exe = None else: - debug.info(1, "Finding DRC/LVS/PEX tools.") - OPTS.drc_exe = get_tool("DRC", ["calibre","assura","magic"], drc_name) - OPTS.lvs_exe = get_tool("LVS", ["calibre","assura","netgen"], lvs_name) - OPTS.pex_exe = get_tool("PEX", ["calibre","magic"], pex_name) + debug.info(1, "Finding DRC/LVS/PEX tools.") + OPTS.drc_exe = get_tool("DRC", ["calibre", "assura", "magic"], drc_name) + OPTS.lvs_exe = get_tool("LVS", ["calibre", "assura", "netgen"], lvs_name) + OPTS.pex_exe = get_tool("PEX", ["calibre", "magic"], pex_name) + if OPTS.tech_name == "sky130": + OPTS.magic_exe = get_tool("GDS", ["magic"], None) -if OPTS.drc_exe == None: +if not OPTS.drc_exe: from .none import run_drc, print_drc_stats elif "calibre"==OPTS.drc_exe[0]: from .calibre import run_drc, print_drc_stats @@ -49,7 +49,7 @@ elif "magic"==OPTS.drc_exe[0]: else: debug.warning("Did not find a supported DRC tool.") -if OPTS.lvs_exe == None: +if not OPTS.lvs_exe: from .none import run_lvs, print_lvs_stats elif "calibre"==OPTS.lvs_exe[0]: from .calibre import run_lvs, print_lvs_stats @@ -61,7 +61,7 @@ else: debug.warning("Did not find a supported LVS tool.") -if OPTS.pex_exe == None: +if not OPTS.pex_exe: from .none import run_pex,print_pex_stats elif "calibre"==OPTS.pex_exe[0]: from .calibre import run_pex,print_pex_stats @@ -69,4 +69,10 @@ elif "magic"==OPTS.pex_exe[0]: from .magic import run_pex,print_pex_stats else: debug.warning("Did not find a supported PEX tool.") - + +if OPTS.tech_name == "sky130": + if "magic"==OPTS.magic_exe[0]: + from .magic import filter_gds + else: + debug.warning("Did not find Magic.") + diff --git a/compiler/verify/calibre.py b/compiler/verify/calibre.py index 8abca448..443c91ca 100644 --- a/compiler/verify/calibre.py +++ b/compiler/verify/calibre.py @@ -20,16 +20,16 @@ Calibre means pointing the code to the proper DRC and LVS rule files. import os import shutil import re -import time import debug from globals import OPTS -from run_script import * +from run_script import run_script # Keep track of statistics num_drc_runs = 0 num_lvs_runs = 0 num_pex_runs = 0 + def write_calibre_drc_script(cell_name, extract, final_verification): """ Write a Calibre runset file and script to run DRC """ # the runset file contains all the options to run calibre @@ -67,6 +67,7 @@ def write_calibre_drc_script(cell_name, extract, final_verification): os.system("chmod u+x {}".format(run_file)) return drc_runset + def write_calibre_lvs_script(cell_name, final_verification): """ Write a Calibre runset file and script to run LVS """ @@ -80,7 +81,7 @@ def write_calibre_lvs_script(cell_name, final_verification): 'lvsSourcePath': cell_name + ".sp", 'lvsSourcePrimary': cell_name, 'lvsSourceSystem': 'SPICE', - 'lvsSpiceFile': "extracted.sp", + 'lvsSpiceFile': "{}.spice".format(cell_name), 'lvsPowerNames': 'vdd', 'lvsGroundNames': 'gnd', 'lvsIncludeSVRFCmds': 1, @@ -130,8 +131,9 @@ def write_calibre_lvs_script(cell_name, final_verification): return lvs_runset -def write_calibre_pex_script(cell_name, extract, output, final_verification): +def write_calibre_pex_script(cell_name, extract, output, final_verification): + """ Write a pex script that can either just extract the netlist or the netlist+parasitics """ if output == None: output = name + ".pex.netlist" @@ -150,10 +152,9 @@ def write_calibre_pex_script(cell_name, extract, output, final_verification): 'pexRunDir': OPTS.openram_temp, 'pexLayoutPaths': cell_name + ".gds", 'pexLayoutPrimary': cell_name, - #'pexSourcePath' : OPTS.openram_temp+"extracted.sp", 'pexSourcePath': cell_name + ".sp", 'pexSourcePrimary': cell_name, - 'pexReportFile': cell_name + ".lvs.report", + 'pexReportFile': cell_name + ".pex.report", 'pexPexNetlistFile': cell_name + ".pex.netlist", 'pexPexReportFile': cell_name + ".pex.report", 'pexMaskDBFile': cell_name + ".maskdb", @@ -179,6 +180,7 @@ def write_calibre_pex_script(cell_name, extract, output, final_verification): return pex_runset + def run_drc(cell_name, gds_name, extract=False, final_verification=False): """Run DRC check on a given top-level name which is implemented in gds_name.""" @@ -186,9 +188,15 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False): global num_drc_runs num_drc_runs += 1 - # Copy file to local dir if it isn't already - if os.path.dirname(gds_name)!=OPTS.openram_temp.rstrip('/'): - shutil.copy(gds_name, OPTS.openram_temp) + # Filter the layouts through magic as a GDS filter for nsdm/psdm/nwell merging + if OPTS.tech_name == "sky130" and False: + shutil.copy(gds_name, OPTS.openram_temp + "temp.gds") + from magic import filter_gds + filter_gds(cell_name, OPTS.openram_temp + "temp.gds", OPTS.openram_temp + cell_name + ".gds") + else: + # Copy file to local dir if it isn't already + if os.path.dirname(gds_name)!=OPTS.openram_temp.rstrip('/'): + shutil.copy(gds_name, OPTS.openram_temp) drc_runset = write_calibre_drc_script(cell_name, extract, final_verification) @@ -211,16 +219,14 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False): errors = int(re.split(r'\W+', results[2])[5]) # always display this summary - if errors > 0: - debug.error("{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, + result_str = "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, geometries, rulechecks, - errors)) + errors) + if errors > 0: + debug.warning(result_str) else: - debug.info(1, "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, - geometries, - rulechecks, - errors)) + debug.info(1, result_str) return errors @@ -299,16 +305,15 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): out_errors = len(stdouterrors) total_errors = summary_errors + out_errors + ext_errors - if total_errors > 0: - debug.error("{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, + # always display this summary + result_str = "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, summary_errors, out_errors, - ext_errors)) + ext_errors) + if total_errors > 0: + debug.warning(result_str) else: - debug.info(1, "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, - summary_errors, - out_errors, - ext_errors)) + debug.info(1, result_str) return total_errors diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py index 1d3562cc..5df6e698 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -33,6 +33,39 @@ num_lvs_runs = 0 num_pex_runs = 0 +def filter_gds(cell_name, input_gds, output_gds): + """ Run the gds through magic for any layer processing """ + global OPTS + + # Copy .magicrc file into temp dir + magic_file = OPTS.openram_tech + "mag_lib/.magicrc" + if os.path.exists(magic_file): + shutil.copy(magic_file, OPTS.openram_temp) + else: + debug.warning("Could not locate .magicrc file: {}".format(magic_file)) + + + run_file = OPTS.openram_temp + "run_filter.sh" + f = open(run_file, "w") + f.write("#!/bin/sh\n") + f.write("{} -dnull -noconsole << EOF\n".format(OPTS.magic_exe[1])) + f.write("gds polygon subcell true\n") + f.write("gds warning default\n") + f.write("gds read {}\n".format(input_gds)) + f.write("load {}\n".format(cell_name)) + f.write("cellname delete \\(UNNAMED\\)\n") + #f.write("writeall force\n") + f.write("select top cell\n") + f.write("gds write {}\n".format(output_gds)) + f.write("quit -noprompt\n") + f.write("EOF\n") + + f.close() + os.system("chmod u+x {}".format(run_file)) + + (outfile, errfile, resultsfile) = run_script(cell_name, "filter") + + def write_magic_script(cell_name, extract=False, final_verification=False): """ Write a magic script to perform DRC and optionally extraction. """ @@ -68,7 +101,7 @@ def write_magic_script(cell_name, extract=False, final_verification=False): if final_verification: f.write(pre + "extract unique all\n".format(cell_name)) # Hack to work around unit scales in SkyWater - if OPTS.tech_name=="s8": + if OPTS.tech_name=="sky130": f.write(pre + "extract style ngspice(si)\n") f.write(pre + "extract\n".format(cell_name)) # f.write(pre + "ext2spice hierarchy on\n") @@ -167,13 +200,14 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False): # always display this summary + result_str = "DRC Errors {0}\t{1}".format(cell_name, errors) if errors > 0: for line in results: if "error tiles" in line: debug.info(1,line.rstrip("\n")) - debug.error("DRC Errors {0}\t{1}".format(cell_name, errors)) + debug.warning(result_str) else: - debug.info(1, "DRC Errors {0}\t{1}".format(cell_name, errors)) + debug.info(1, result_str) return errors diff --git a/technology/freepdk45/gds_lib/dff.gds b/technology/freepdk45/gds_lib/dff.gds index 526a1861..2a6004d1 100644 Binary files a/technology/freepdk45/gds_lib/dff.gds and b/technology/freepdk45/gds_lib/dff.gds differ diff --git a/technology/freepdk45/gds_lib/sense_amp.gds b/technology/freepdk45/gds_lib/sense_amp.gds index fecbfcb8..53c499f9 100644 Binary files a/technology/freepdk45/gds_lib/sense_amp.gds and b/technology/freepdk45/gds_lib/sense_amp.gds differ diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index c0379e44..6de01254 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -234,9 +234,9 @@ drc.add_enclosure("active", enclosure = 0.005) # CONTACT.6 Minimum spacing of contact and gate -drc["contact_to_gate"] = 0.0375 #changed from 0.035 +drc["active_contact_to_gate"] = 0.0375 #changed from 0.035 # CONTACT.7 Minimum spacing of contact and poly -drc["contact_to_poly"] = 0.090 +drc["poly_contact_to_gate"] = 0.090 # CONTACT.1 Minimum width of contact # CONTACT.2 Minimum spacing of contact diff --git a/technology/scn4m_subm/mag_lib/setup.tcl b/technology/scn4m_subm/mag_lib/setup.tcl index 95e7dbea..09bbea27 100644 --- a/technology/scn4m_subm/mag_lib/setup.tcl +++ b/technology/scn4m_subm/mag_lib/setup.tcl @@ -6,7 +6,7 @@ equate class {-circuit1 pfet} {-circuit2 p} flatten class {-circuit1 dummy_cell_6t} flatten class {-circuit1 dummy_cell_1rw_1r} flatten class {-circuit1 dummy_cell_1w_1r} -flatten class {-circuit1 bitcell_array_0} +flatten class {-circuit1 pbitcell} flatten class {-circuit1 pbitcell_0} flatten class {-circuit1 pbitcell_1} property {-circuit1 nfet} remove as ad ps pd diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py index 745b381e..55826ec5 100644 --- a/technology/scn4m_subm/tech/tech.py +++ b/technology/scn4m_subm/tech/tech.py @@ -217,9 +217,9 @@ drc.add_enclosure("active", layer = "contact", enclosure = _lambda_) # Reserved for other technologies -drc["contact_to_gate"] = 2*_lambda_ +drc["active_contact_to_gate"] = 2*_lambda_ # 5.4 Minimum spacing to gate of transistor -drc["contact_to_poly"] = 2*_lambda_ +drc["poly_contact_to_gate"] = 2*_lambda_ # 6.1 Exact contact size # 5.3 Minimum contact spacing