diff --git a/.gitignore b/.gitignore index b16e3d0b..948e7d19 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ .DS_Store *~ +*.orig +*.rej *.pyc *.aux *.out *.toc *.synctex.gz -**/model_data \ No newline at end of file +**/model_data diff --git a/README.md b/README.md index 64c8aa25..82fe75ed 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ An open-source static random access memory (SRAM) compiler. # What is OpenRAM? -OpenRAM is an open-source Python framework to create the layout, +OpenRAM is an award winning open-source Python framework to create the layout, netlists, timing and power models, placement and routing models, and other views necessary to use SRAMs in ASIC design. OpenRAM supports integration in both commercial and open-source flows with both @@ -195,7 +195,7 @@ specific technology (e.g., [FreePDK45]) should be a subdirectory + Report bugs by submitting [Github issues]. + Develop new features (see [how to contribute](./CONTRIBUTING.md)) + Submit code/fixes using a [Github pull request] -+ Follow our [project][Github projects]. ++ Follow our [project][Github project]. + Read and cite our [ICCAD paper][OpenRAMpaper] # Further Help @@ -214,15 +214,7 @@ OpenRAM is licensed under the [BSD 3-clause License](./LICENSE). - [Matthew Guthaus] from [VLSIDA] created the OpenRAM project and is the lead architect. - [James Stine] from [VLSIARCH] co-founded the project. -- Hunter Nichols maintains and updates the timing characterization. -- Michael Grimes created and maintains the multiport netlist code. -- Jennifer Sowash is creating the OpenRAM IP library. -- Jesse Cirimelli-Low created the datasheet generation. -- Samira Ataei created early multi-bank layouts and control logic. -- Bin Wu created early parameterized cells. -- Yusu Wang is porting parameterized cells to new technologies. -- Brian Chen created early prototypes of the timing characterizer. -- Jeff Butera created early prototypes of the bank layout. +- Many students: Hunter Nichols, Michael Grimes, Jennifer Sowash, Yusu Wang, Joey Kunzler, Jesse Cirimelli-Low, Samira Ataei, Bin Wu, Brian Chen, Jeff Butera If I forgot to add you, please let me know! @@ -236,7 +228,7 @@ If I forgot to add you, please let me know! [Github issues]: https://github.com/VLSIDA/OpenRAM/issues [Github pull request]: https://github.com/VLSIDA/OpenRAM/pulls -[Github projects]: https://github.com/VLSIDA/OpenRAM/projects +[Github project]: https://github.com/VLSIDA/OpenRAM [documentation]: https://docs.google.com/presentation/d/10InGB33N51I6oBHnqpU7_w9DXlx-qe9zdrlco2Yc5co/edit?usp=sharing [dev-group]: mailto:openram-dev-group@ucsc.edu diff --git a/compiler/base/custom_cell_properties.py b/compiler/base/custom_cell_properties.py index 45887561..ba671279 100644 --- a/compiler/base/custom_cell_properties.py +++ b/compiler/base/custom_cell_properties.py @@ -116,6 +116,15 @@ class cell_properties(): self._dff_buff_array = _dff_buff_array(use_custom_ports = False, add_body_contacts = False) + self._write_driver = _cell({'din': 'din', + 'bl' : 'bl', + 'br' : 'br', + 'en' : 'en'}) + self._sense_amp = _cell({'bl' : 'bl', + 'br' : 'br', + 'dout' : 'dout', + 'en' : 'en'}) + @property def bitcell(self): return self._bitcell @@ -132,3 +141,10 @@ class cell_properties(): def dff_buff_array(self): return self._dff_buff_array + @property + def write_driver(self): + return self._write_driver + + @property + def sense_amp(self): + return self._sense_amp diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index f354cf02..5d7f8d76 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -51,7 +51,7 @@ class geometry: y = item[0] * math.sin(angle) + item[1] * mirr * math.cos(angle) + offset[1] coordinate += [[x, y]] return coordinate - + def normalize(self): """ Re-find the LL and UR points after a transform """ (first, second) = self.boundary @@ -64,14 +64,14 @@ class geometry: def update_boundary(self): """ Update the boundary with a new placement. """ 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. """ if OPTS.netlist_only: self.boundary = [vector(0,0), vector(0,0)] return - + (ll, ur) = [vector(0, 0), vector(self.width, self.height)] if mirror == "MX": @@ -83,7 +83,7 @@ class geometry: elif mirror == "XY": ll = ll.scale(-1, -1) ur = ur.scale(-1, -1) - + if rotate == 90: ll = ll.rotate_scale(-1, 1) ur = ur.rotate_scale(-1, 1) @@ -96,19 +96,19 @@ class geometry: self.boundary = [offset + ll, offset + ur] self.normalize() - + def ll(self): """ Return the lower left corner """ return self.boundary[0] - + def ur(self): """ Return the upper right corner """ return self.boundary[1] - + def lr(self): """ Return the lower right corner """ return vector(self.boundary[1].x, self.boundary[0].y) - + def ul(self): """ Return the upper left corner """ return vector(self.boundary[0].x, self.boundary[1].y) @@ -132,12 +132,12 @@ class geometry: def cx(self): """ Return the center x """ return 0.5 * (self.boundary[0].x + self.boundary[1].x) - + def cy(self): """ Return the center y """ return 0.5 * (self.boundary[0].y + self.boundary[1].y) - + class instance(geometry): """ An instance of an instance/module with a specified location and @@ -148,7 +148,7 @@ class instance(geometry): geometry.__init__(self) debug.check(mirror not in ["R90", "R180", "R270"], "Please use rotation and not mirroring during instantiation.") - + self.name = name self.mod = mod self.gds = mod.gds @@ -166,7 +166,7 @@ class instance(geometry): self.width = round_to_grid(mod.width) self.height = round_to_grid(mod.height) self.compute_boundary(offset, mirror, rotate) - + debug.info(4, "creating instance: " + self.name) def get_blockages(self, lpp, top=False): @@ -202,11 +202,11 @@ class instance(geometry): 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) - # make sure to write out my module/structure + # make sure to write out my module/structure # (it will only be written the first time though) self.mod.gds_write_file(self.gds) # now write an instance of my module/structure @@ -215,7 +215,7 @@ class instance(geometry): offsetInMicrons=self.offset, mirror=self.mirror, rotate=self.rotate) - + def place(self, offset, mirror="R0", rotate=0): """ This updates the placement of an instance. """ # Update the placement of an already added instance @@ -224,8 +224,8 @@ class instance(geometry): self.rotate = rotate self.update_boundary() debug.info(3, "placing instance {}".format(self)) - - + + 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.""" @@ -243,20 +243,20 @@ class instance(geometry): 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): """ Return an absolute pin that is offset and transformed based on this instance location. """ - + import copy pin = copy.deepcopy(self.mod.get_pins(name)) - + 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 - + def __str__(self): """ override print function output """ return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")" @@ -293,7 +293,7 @@ class path(geometry): def get_blockages(self, layer): """ Fail since we don't support paths yet. """ assert(0) - + def __str__(self): """ override print function output """ return "path: layer=" + self.layerNumber + " w=" + self.width @@ -329,6 +329,7 @@ class label(geometry): debug.info(4, "writing label (" + str(self.layerNumber) + "): " + self.text) new_layout.addText(text=self.text, layerNumber=self.layerNumber, + purposeNumber=self.layerPurpose, offsetInMicrons=self.offset, magnification=self.zoom, rotate=None) @@ -336,7 +337,7 @@ class label(geometry): def get_blockages(self, layer): """ Returns an empty list since text cannot be blockages. """ return [] - + def __str__(self): """ override print function output """ return "label: " + self.text + " layer=" + str(self.layerNumber) @@ -345,7 +346,7 @@ class label(geometry): """ override print function output """ return "( label: " + self.text + " @" + str(self.offset) + " layer=" + str(self.layerNumber) + " )" - + class rectangle(geometry): """Represents a rectangular shape""" @@ -363,7 +364,7 @@ class rectangle(geometry): debug.info(4, "creating rectangle (" + str(self.layerNumber) + "): " + str(self.width) + "x" + str(self.height) + " @ " + str(self.offset)) - + def get_blockages(self, layer): """ Returns a list of one rectangle if it is on this layer""" if self.layerNumber == layer: diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 451d1356..049d2307 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -37,7 +37,7 @@ class layout(): self.objs = [] # Holds all other objects (labels, geometries, etc) self.pin_map = {} # Holds name->pin_layout map for all pins self.visited = [] # List of modules we have already visited - self.is_library_cell = False # Flag for library cells + self.is_library_cell = False # Flag for library cells self.gds_read() try: from tech import power_grid @@ -71,7 +71,7 @@ class layout(): (inv_num + 1) * height - \ (inv_num % 2) * drc["minwidth_m1"]) y_dir = -1 - + return (base_offset, y_dir) def find_lowest_coords(self): @@ -90,7 +90,7 @@ class layout(): lowesty2 = min(inst.by() for inst in self.insts) else: lowestx2 = lowesty2 = None - + if lowestx1 == None and lowestx2 == None: return None elif lowestx1 == None: @@ -146,7 +146,7 @@ class layout(): subcoord = inst.mod.find_highest_layer_coords(layer) + inst.offset highestx = max(highestx, subcoord.x) highesty = max(highesty, subcoord.y) - + return vector(highestx, highesty) def find_lowest_layer_coords(self, layer): @@ -172,7 +172,7 @@ class layout(): lowesty = min(lowesty, subcoord.y) return vector(lowestx, lowesty) - + def translate_all(self, offset): """ Translates all objects, instances, and pins by the given (x,y) offset @@ -189,7 +189,7 @@ class layout(): pin_list = self.pin_map[pin_name] for pin in pin_list: pin.rect = [pin.ll() - offset, pin.ur() - offset] - + def add_inst(self, name, mod, offset=[0, 0], mirror="R0", rotate=0): """ Adds an instance of a mod to this module """ self.insts.append(geometry.instance(name, mod, offset, mirror, rotate)) @@ -204,7 +204,7 @@ class layout(): if inst.name == name: return inst return None - + def add_rect(self, layer, offset, width=None, height=None): """ Adds a rectangle on a given layer,offset with width and height @@ -260,7 +260,7 @@ class layout(): start-offset, minwidth_layer, end.y-start.y) - + def get_pin(self, text): """ Return the pin or list of pins @@ -275,7 +275,7 @@ class layout(): except Exception: self.gds_write("missing_pin.gds") debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(text,self.name),-1) - + def get_pins(self, text): """ Return a pin list (instead of a single pin) @@ -290,17 +290,17 @@ class layout(): Return a pin list of all pins """ return self.pin_map.keys() - + def copy_layout_pin(self, instance, pin_name, new_name=""): """ Create a copied version of the layout pin at the current level. You can optionally rename the pin to a new name. """ pins = instance.get_pins(pin_name) - + debug.check(len(pins) > 0, "Could not find pin {}".format(pin_name)) - + for pin in pins: if new_name == "": new_name = pin.name @@ -316,18 +316,19 @@ class layout(): You can optionally rename the pin to a new name. """ for pin_name in self.pin_map.keys(): - self.copy_layout_pin(instance, pin_name, prefix+pin_name) - + self.copy_layout_pin(instance, pin_name, prefix + pin_name) + def add_layout_pin_segment_center(self, text, layer, start, end): """ Creates a path like pin with center-line convention """ - - debug.check(start.x == end.x or start.y == end.y, - "Cannot have a non-manhatten layout pin.") - + if start.x != end.x and start.y != end.y: + file_name = "non_rectilinear.gds" + self.gds_write(file_name) + debug.error("Cannot have a non-manhatten layout pin: {}".format(file_name), -1) + minwidth_layer = drc["minwidth_{}".format(layer)] - + # one of these will be zero width = max(start.x, end.x) - min(start.x, end.x) height = max(start.y, end.y) - min(start.y, end.y) @@ -341,7 +342,7 @@ class layout(): # This makes sure it is long enough, but also it is not 0 width! height = max(minwidth_layer, height) width = max(minwidth_layer, width) - + return self.add_layout_pin(text, layer, ll_offset, @@ -358,13 +359,13 @@ class layout(): ll_offset = offset - vector(0.5 * width, 0.5 * height) return self.add_layout_pin(text, layer, ll_offset, width, height) - + def remove_layout_pin(self, text): """ Delete a labeled pin (or all pins of the same name) """ self.pin_map[text] = set() - + def add_layout_pin(self, text, layer, offset, width=None, height=None): """ Create a labeled pin @@ -408,7 +409,7 @@ class layout(): layer=layer, offset=offset + vector(0.5 * width, 0.5 * height)) - + def add_label(self, text, layer, offset=[0, 0], zoom=-1): """Adds a text label on the given layer,offset, and zoom level""" # negative layers indicate "unused" layers in a given technology @@ -447,7 +448,7 @@ class layout(): layer_stack=layers, path=coordinates, layer_widths=layer_widths) - + def add_wire(self, layers, coordinates): """Connects a routing path on given layer,coordinates,width. The layers are the (horizontal, via, vertical). """ @@ -462,14 +463,14 @@ class layout(): """ 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): """ Add a three layer via structure. """ if not directions: directions = (self.get_preferred_direction(layers[0]), self.get_preferred_direction(layers[2])) - + from sram_factory import factory via = factory.create(module_type="contact", layer_stack=layers, @@ -494,7 +495,7 @@ class layout(): if not directions: directions = (self.get_preferred_direction(layers[0]), self.get_preferred_direction(layers[2])) - + from sram_factory import factory via = factory.create(module_type="contact", layer_stack=layers, @@ -504,7 +505,7 @@ class layout(): well_type=well_type) height = via.height width = via.width - + corrected_offset = offset + vector(-0.5 * width, -0.5 * height) @@ -516,12 +517,12 @@ class layout(): self.connect_inst([]) return inst - def add_via_stack(self, offset, direction, from_layer, to_layer, - size=[1,1]): + def add_via_stack(self, offset, from_layer, to_layer, + direction=None, + size=[1, 1]): """ Punch a stack of vias from a start layer to a target layer. """ - return self.__add_via_stack_internal(offset=offset, direction=direction, from_layer=from_layer, @@ -530,8 +531,9 @@ class layout(): last_via=None, size=size) - def add_via_stack_center(self, offset, direction, from_layer, to_layer, - size=[1,1]): + def add_via_stack_center(self, offset, from_layer, to_layer, + direction=None, + size=[1, 1]): """ Punch a stack of vias from a start layer to a target layer by the center coordinate accounting for mirroring and rotation. @@ -544,7 +546,6 @@ class layout(): last_via=None, size=size) - def __add_via_stack_internal(self, offset, direction, from_layer, to_layer, via_func, last_via, size): """ @@ -580,7 +581,6 @@ class layout(): last_via=via, size=size) - def add_ptx(self, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"): """Adds a ptx module to the design.""" import ptx @@ -602,11 +602,11 @@ class layout(): # This must be done for netlist only mode too if os.path.isfile(self.gds_file): self.is_library_cell = True - + if OPTS.netlist_only: self.gds = None return - + # open the gds file if it exists or else create a blank layout if os.path.isfile(self.gds_file): debug.info(3, "opening {}".format(self.gds_file)) @@ -662,7 +662,7 @@ class layout(): height=height, center=False) debug.info(2, "Adding {0} boundary {1}".format(self.name, boundary)) - + self.visited.append(self.name) def gds_write(self, gds_name): @@ -681,10 +681,10 @@ class layout(): # which may have been previously processed! # MRG: 10/4/18 We need to clear if we make changes and write a second GDS! self.clear_visited() - + # recursively create all the remaining objects self.gds_write_file(self.gds) - + # populates the xyTree data structure for gds # self.gds.prepareForWrite() writer.writeToFile(gds_name) @@ -706,7 +706,7 @@ class layout(): lpp = techlayer[layer] else: lpp = layer - + blockages = [] for i in self.objs: blockages += i.get_blockages(lpp) @@ -724,7 +724,7 @@ class layout(): pin_names = copy.deepcopy(self.pins) if self.name.startswith("pmos") or self.name.startswith("nmos"): pin_names.remove("B") - + blockages = [] for pin_name in pin_names: pin_list = self.get_pins(pin_name) @@ -784,7 +784,7 @@ class layout(): # half minwidth so we can return the center line offsets half_minwidth = 0.5 * drc["minwidth_{}".format(layer)] - + line_positions = {} if vertical: for i in range(len(names)): @@ -829,7 +829,7 @@ class layout(): layer_stack=("m1", "via1", "m2")): """ Vertical version of connect_bus. """ self.connect_bus(mapping, inst, bus_offsets, layer_stack, False) - + def connect_bus(self, mapping, inst, bus_offsets, layer_stack, horizontal): """ Connect a mapping of pin -> name for a bus. This could be @@ -842,7 +842,7 @@ class layout(): route_layer = vertical_layer else: route_layer = horizontal_layer - + for (pin_name, bus_name) in mapping: pin = inst.get_pin(pin_name) pin_pos = pin.center() @@ -854,7 +854,7 @@ class layout(): else: # left/right then up/down mid_pos = vector(bus_pos.x, pin_pos.y) - + self.add_wire(layer_stack, [bus_pos, mid_pos, pin_pos]) @@ -865,7 +865,7 @@ class layout(): 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): @@ -899,7 +899,7 @@ class layout(): self.m3_space) else: debug.error("Cannot find layer pitch.") - + def add_horizontal_trunk_route(self, pins, trunk_offset, @@ -915,7 +915,7 @@ class layout(): # 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), @@ -955,7 +955,7 @@ class layout(): 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), @@ -978,7 +978,7 @@ class layout(): self.add_path(self.horizontal_layer, [pin.center(), mid]) self.add_via_center(layers=layer_stack, offset=mid) - + def create_channel_route(self, netlist, offset, layer_stack, @@ -996,7 +996,7 @@ class layout(): 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(): @@ -1010,21 +1010,21 @@ class layout(): Check all the pin pairs on two nets and return a pin overlap if any pin overlaps. """ - + 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) 0, "Zero width pin.") debug.check(self.height() > 0, "Zero height pin.") - + # if it's a string, use the name if type(layer_name_pp) == str: self._layer = layer_name_pp @@ -93,17 +93,17 @@ class pin_layout: is a major speedup, if pin_layout is used as a key for dicts. """ return self._hash - + def __lt__(self, other): """ Provide a function for ordering items by the ll point """ (ll, ur) = self.rect (oll, our) = other.rect - + if ll.x < oll.x and ll.y < oll.y: return True - + return False - + def __eq__(self, other): """ Check if these are the same pins for duplicate checks """ if isinstance(other, self.__class__): @@ -128,14 +128,14 @@ class pin_layout: max_y = max(max_y, pin.ur().y) self.rect = [vector(min_x, min_y), vector(max_x, max_y)] - + def fix_minarea(self): """ Try to fix minimum area rule. """ min_area = drc("{}_minarea".format(self.layer)) pass - + def inflate(self, spacing=None): """ Inflate the rectangle by the spacing (or other rule) @@ -143,12 +143,12 @@ class pin_layout: """ if not spacing: spacing = 0.5*drc("{0}_to_{0}".format(self.layer)) - + (ll, ur) = self.rect spacing = vector(spacing, spacing) newll = ll - spacing newur = ur + spacing - + return (newll, newur) def intersection(self, other): @@ -191,7 +191,7 @@ class pin_layout: y_overlaps = True return y_overlaps - + def xcontains(self, other): """ Check if shape contains the x overlap """ (ll, ur) = self.rect @@ -205,13 +205,13 @@ class pin_layout: (oll, our) = other.rect return (oll.y >= ll.y and our.y <= ur.y) - + def contains(self, other): """ Check if a shape contains another rectangle """ # If it is the same shape entirely, it is contained! if self == other: return True - + # Can only overlap on the same layer if not self.same_lpp(self.lpp, other.lpp): return False @@ -230,13 +230,13 @@ class pin_layout: if shape.contains(self): return True return False - + def overlaps(self, other): """ Check if a shape overlaps with a rectangle """ # Can only overlap on the same layer if not self.same_lpp(self.lpp, other.lpp): return False - + x_overlaps = self.xoverlaps(other) y_overlaps = self.yoverlaps(other) @@ -245,11 +245,11 @@ class pin_layout: def area(self): """ Return the area. """ return self.height()*self.width() - + def height(self): """ Return height. Abs is for pre-normalized value.""" return abs(self.rect[1].y-self.rect[0].y) - + def width(self): """ Return width. Abs is for pre-normalized value.""" return abs(self.rect[1].x-self.rect[0].x) @@ -260,7 +260,7 @@ class pin_layout: ll = vector(min(first[0], second[0]), min(first[1], second[1])) ur = vector(max(first[0], second[0]), max(first[1], second[1])) self.rect=[ll, ur] - + def transform(self, offset, mirror, rotate): """ Transform with offset, mirror and rotation @@ -278,7 +278,7 @@ class pin_layout: elif mirror == "XY": ll = ll.scale(-1, -1) ur = ur.scale(-1, -1) - + if rotate == 90: ll = ll.rotate_scale(-1, 1) ur = ur.rotate_scale(-1, 1) @@ -303,7 +303,7 @@ class pin_layout: def cy(self): """ Center y """ return 0.5*(self.rect[0].y+self.rect[1].y) - + # The four possible corners def ll(self): """ Lower left point """ @@ -320,7 +320,7 @@ class pin_layout: def ur(self): """ Upper right point """ return self.rect[1] - + # The possible y edge values def uy(self): """ Upper y value """ @@ -331,15 +331,15 @@ class pin_layout: return self.rect[0].y # The possible x edge values - + def lx(self): """ Left x value """ return self.rect[0].x - + def rx(self): """ Right x value """ return self.rect[1].x - + # The edge centers def rc(self): """ Right center point """ @@ -350,7 +350,7 @@ class pin_layout: """ Left center point """ return vector(self.rect[0].x, 0.5*(self.rect[0].y+self.rect[1].y)) - + def uc(self): """ Upper center point """ return vector(0.5*(self.rect[0].x+self.rect[1].x), @@ -378,6 +378,7 @@ class pin_layout: # imported into Magic. newLayout.addText(text=self.name, layerNumber=layer_num, + purposeNumber=purpose, offsetInMicrons=self.center(), magnification=GDS["zoom"], rotate=None) @@ -392,7 +393,7 @@ class pin_layout: dy = min(r1_ur.y, r2_ur.y) - max(r1_ll.y, r2_ll.y) dx = min(r1_ur.x, r2_ur.x) - max(r1_ll.x, r2_ll.x) - + if dx >= 0 and dy >= 0: return [dx, dy] else: @@ -407,7 +408,7 @@ class pin_layout: def dist(x1, y1, x2, y2): return math.sqrt((x2-x1)**2 + (y2-y1)**2) - + left = r2_ur.x < r1_ll.x right = r1_ur.x < r2_ll.x bottom = r2_ur.y < r1_ll.y @@ -432,7 +433,7 @@ class pin_layout: else: # rectangles intersect return 0 - + def overlap_length(self, other): """ Calculate the intersection segment and determine its length @@ -453,7 +454,7 @@ class pin_layout: # This is where we had a corner intersection or none return 0 - + def compute_overlap_segment(self, other): """ Calculate the intersection segment of two rectangles @@ -469,19 +470,19 @@ class pin_layout: r2_lr = vector(r2_ur.x, r2_ll.y) from itertools import tee - + def pairwise(iterable): "s -> (s0,s1), (s1,s2), (s2, s3), ..." a, b = tee(iterable) next(b, None) return zip(a, b) - + # R1 edges CW r1_cw_points = [r1_ll, r1_ul, r1_ur, r1_lr, r1_ll] r1_edges = [] for (p, q) in pairwise(r1_cw_points): r1_edges.append([p, q]) - + # R2 edges CW r2_cw_points = [r2_ll, r2_ul, r2_ur, r2_lr, r2_ll] r2_edges = [] @@ -509,9 +510,9 @@ class pin_layout: q.y <= max(p.y, r.y) and \ q.y >= min(p.y, r.y): return True - + return False - + def segment_intersection(self, s1, s2): """ Determine the intersection point of two segments @@ -524,22 +525,22 @@ class pin_layout: a1 = b.y - a.y b1 = a.x - b.x c1 = a1*a.x + b1*a.y - + # Line CD represented as a2x + b2y = c2 a2 = d.y - c.y b2 = c.x - d.x c2 = a2*c.x + b2*c.y - + determinant = a1*b2 - a2*b1 if determinant != 0: x = (b2*c1 - b1*c2)/determinant y = (a1*c2 - a2*c1)/determinant - + r = vector(x, y).snap_to_grid() if self.on_segment(a, r, b) and self.on_segment(c, r, d): return r - + return None def same_lpp(self, lpp1, lpp2): @@ -549,5 +550,5 @@ class pin_layout: """ if lpp1[1] == None or lpp2[1] == None: return lpp1[0] == lpp2[0] - + return lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1] diff --git a/compiler/gdsMill/gdsMill/gdsPrimitives.py b/compiler/gdsMill/gdsMill/gdsPrimitives.py index 13b8acf9..8e07524c 100644 --- a/compiler/gdsMill/gdsMill/gdsPrimitives.py +++ b/compiler/gdsMill/gdsMill/gdsPrimitives.py @@ -1,12 +1,5 @@ import math -from globals import OPTS -# default purpose layer is used for addText() in vlsiLayout.py -if OPTS.tech_name == "s8": - purposeLayer=20 -else: - purposeLayer=0 - class GdsStructure: """Class represent a GDS Structure Object""" def __init__(self): @@ -147,7 +140,7 @@ class GdsText: self.elementFlags="" self.plex="" self.drawingLayer="" - self.purposeLayer=purposeLayer + self.purposeLayer=0 self.transFlags=[0,0,0] self.magFactor="" self.rotateAngle="" diff --git a/compiler/gdsMill/gdsMill/vlsiLayout.py b/compiler/gdsMill/gdsMill/vlsiLayout.py index 412a430c..979180cd 100644 --- a/compiler/gdsMill/gdsMill/vlsiLayout.py +++ b/compiler/gdsMill/gdsMill/vlsiLayout.py @@ -35,7 +35,7 @@ class VlsiLayout: modDate.hour, modDate.minute, modDate.second) - + self.info = dict() #information gathered from the GDSII header self.info['units']=self.units self.info['dates']=(modDate.year, @@ -52,12 +52,12 @@ class VlsiLayout: modDate.second) self.info['libraryName']=libraryName self.info['gdsVersion']=gdsVersion - + self.xyTree = [] #This will contain a list of all structure names #expanded to include srefs / arefs separately. #each structure will have an X,Y,offset, and rotate associated #with it. Populate via traverseTheHierarchy method. - + #temp variables used in delegate functions self.tempCoordinates=None self.tempPassFail = True @@ -73,14 +73,14 @@ class VlsiLayout: if(rotateAngle): angle = math.radians(float(rotateAngle)) - coordinatesRotate = [] #this will hold the rotated values + coordinatesRotate = [] #this will hold the rotated values for coordinate in coordinatesToRotate: # This is the CCW rotation matrix newX = coordinate[0]*math.cos(angle) - coordinate[1]*math.sin(angle) newY = coordinate[0]*math.sin(angle) + coordinate[1]*math.cos(angle) coordinatesRotate.extend((newX,newY)) return coordinatesRotate - + def rename(self,newName): #take the root structure and copy it to a new structure with the new name self.structures[newName] = self.structures[self.rootStructureName] @@ -129,8 +129,8 @@ class VlsiLayout: modDate.hour, modDate.minute, modDate.second) - - + + #repopulate the 2d map so drawing occurs correctly self.prepareForWrite() @@ -155,15 +155,15 @@ class VlsiLayout: debug.check(len(structureNames)==1,"Multiple possible root structures in the layout: {}".format(str(structureNames))) self.rootStructureName = structureNames[0] - - def traverseTheHierarchy(self, startingStructureName=None, delegateFunction = None, + + def traverseTheHierarchy(self, startingStructureName=None, delegateFunction = None, transformPath = [], rotateAngle = 0, transFlags = [0,0,0], coordinates = (0,0)): #since this is a recursive function, must deal with the default - #parameters explicitly + #parameters explicitly if startingStructureName == None: - startingStructureName = self.rootStructureName + startingStructureName = self.rootStructureName - #set up the rotation matrix + #set up the rotation matrix if(rotateAngle == None or rotateAngle == ""): angle = 0 else: @@ -193,10 +193,10 @@ class VlsiLayout: try: if(len(self.structures[startingStructureName].srefs)>0): #does this structure reference any others? #if so, go through each and call this function again - #if not, return back to the caller (caller can be this function) + #if not, return back to the caller (caller can be this function) for sref in self.structures[startingStructureName].srefs: - #here, we are going to modify the sref coordinates based on the parent objects rotation - self.traverseTheHierarchy(startingStructureName = sref.sName, + #here, we are going to modify the sref coordinates based on the parent objects rotation + self.traverseTheHierarchy(startingStructureName = sref.sName, delegateFunction = delegateFunction, transformPath = transformPath, rotateAngle = sref.rotateAngle, @@ -204,12 +204,12 @@ class VlsiLayout: coordinates = sref.coordinates) except KeyError: debug.error("Could not find structure {} in GDS file.".format(startingStructureName),-1) - + #MUST HANDLE AREFs HERE AS WELL #when we return, drop the last transform from the transformPath del transformPath[-1] return - + def initialize(self): self.deduceHierarchy() # self.traverseTheHierarchy() @@ -217,17 +217,17 @@ class VlsiLayout: for layerNumber in self.layerNumbersInUse: self.processLabelPins((layerNumber, None)) - - + + def populateCoordinateMap(self): def addToXyTree(startingStructureName = None,transformPath = None): uVector = np.array([[1.0],[0.0],[0.0]]) #start with normal basis vectors vVector = np.array([[0.0],[1.0],[0.0]]) origin = np.array([[0.0],[0.0],[1.0]]) #and an origin (Z component is 1.0 to indicate position instead of vector) - #make a copy of all the transforms and reverse it + #make a copy of all the transforms and reverse it reverseTransformPath = transformPath[:] if len(reverseTransformPath) > 1: - reverseTransformPath.reverse() + reverseTransformPath.reverse() #now go through each transform and apply them to our basis and origin in succession for transform in reverseTransformPath: origin = np.dot(transform[0], origin) #rotate @@ -237,20 +237,20 @@ class VlsiLayout: uVector = np.dot(transform[1], uVector) #scale vVector = np.dot(transform[1], vVector) #scale origin = np.dot(transform[2], origin) #translate - #we don't need to do a translation on the basis vectors + #we don't need to do a translation on the basis vectors #uVector = transform[2] * uVector #translate #vVector = transform[2] * vVector #translate #populate the xyTree with each structureName and coordinate space self.xyTree.append((startingStructureName,origin,uVector,vVector)) self.traverseTheHierarchy(delegateFunction = addToXyTree) - + def microns(self, userUnits): """Utility function to convert user units to microns""" userUnit = self.units[1]/self.units[0] userUnitsPerMicron = userUnit / userunit layoutUnitsPerMicron = userUnitsPerMicron / self.units[0] return userUnits / layoutUnitsPerMicron - + def userUnits(self, microns): """Utility function to convert microns to user units""" userUnit = self.units[1]/self.units[0] @@ -270,7 +270,7 @@ class VlsiLayout: if self.debug: debug.info(0,"DEBUG: GdsMill vlsiLayout: changeRoot: %s "%newRoot) - + # Determine if newRoot exists # layoutToAdd (default) or nameOfLayout if (newRoot == 0 | ((newRoot not in self.structures) & ~create)): @@ -282,19 +282,19 @@ class VlsiLayout: self.rootStructureName = newRoot - + def addInstance(self,layoutToAdd,nameOfLayout=0,offsetInMicrons=(0,0),mirror=None,rotate=None): """ Method to insert one layout into another at a particular offset. """ offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1])) - if self.debug: + if self.debug: debug.info(0,"DEBUG: GdsMill vlsiLayout: addInstance: type {0}, nameOfLayout {1}".format(type(layoutToAdd),nameOfLayout)) debug.info(0,"DEBUG: name={0} offset={1} mirror={2} rotate={3}".format(layoutToAdd.rootStructureName,offsetInMicrons, mirror, rotate)) - # Determine if we are instantiating the root design of + # Determine if we are instantiating the root design of # layoutToAdd (default) or nameOfLayout if nameOfLayout == 0: StructureFound = True @@ -303,7 +303,7 @@ class VlsiLayout: StructureName = nameOfLayout #layoutToAdd StructureFound = False for structure in layoutToAdd.structures: - if StructureName in structure: + if StructureName in structure: if self.debug: debug.info(1,"DEBUG: Structure %s Found"%StructureName) StructureFound = True @@ -311,7 +311,7 @@ class VlsiLayout: debug.check(StructureFound,"Could not find layout to instantiate {}".format(StructureName)) - # If layoutToAdd is a unique object (not this), then copy hierarchy, + # If layoutToAdd is a unique object (not this), then copy hierarchy, # otherwise, if it is a text name of an internal structure, use it. if layoutToAdd != self: @@ -330,7 +330,7 @@ class VlsiLayout: layoutToAddSref.coordinates = offsetInLayoutUnits if mirror or rotate: - + layoutToAddSref.transFlags = [0,0,0] # transFlags = (mirror around x-axis, magnification, rotation) # If magnification or rotation is true, it is the flags are then @@ -356,7 +356,7 @@ class VlsiLayout: #add the sref to the root structure self.structures[self.rootStructureName].srefs.append(layoutToAddSref) - + def addBox(self,layerNumber=0, purposeNumber=0, offsetInMicrons=(0,0), width=1.0, height=1.0,center=False): """ Method to add a box to a layout @@ -373,7 +373,7 @@ class VlsiLayout: (offsetInLayoutUnits[0],offsetInLayoutUnits[1]+heightInLayoutUnits), offsetInLayoutUnits] else: - startPoint = (offsetInLayoutUnits[0]-widthInLayoutUnits/2.0, offsetInLayoutUnits[1]-heightInLayoutUnits/2.0) + startPoint = (offsetInLayoutUnits[0]-widthInLayoutUnits/2.0, offsetInLayoutUnits[1]-heightInLayoutUnits/2.0) coordinates=[startPoint, (startPoint[0]+widthInLayoutUnits,startPoint[1]), (startPoint[0]+widthInLayoutUnits,startPoint[1]+heightInLayoutUnits), @@ -386,7 +386,7 @@ class VlsiLayout: boundaryToAdd.purposeLayer = purposeNumber #add the sref to the root structure self.structures[self.rootStructureName].boundaries.append(boundaryToAdd) - + def addPath(self, layerNumber=0, purposeNumber=0, coordinates=[(0,0)], width=1.0): """ Method to add a path to a layout @@ -405,11 +405,12 @@ class VlsiLayout: pathToAdd.coordinates = layoutUnitCoordinates #add the sref to the root structure self.structures[self.rootStructureName].paths.append(pathToAdd) - - def addText(self, text, layerNumber=0, offsetInMicrons=(0,0), magnification=0.1, rotate = None): + + def addText(self, text, layerNumber=0, purposeNumber=0, offsetInMicrons=(0,0), magnification=0.1, rotate = None): offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1])) textToAdd = GdsText() textToAdd.drawingLayer = layerNumber + textToAdd.purposeLayer = purposeNumber textToAdd.coordinates = [offsetInLayoutUnits] textToAdd.transFlags = [0,0,0] textToAdd.textString = self.padText(text) @@ -438,7 +439,7 @@ class VlsiLayout: return 1 else: return 0 - + def intersectionPoint(self,startPoint1,endPoint1,startPoint2,endPoint2): if((endPoint1[0]-startPoint1[0])!=0 and (endPoint2[0]-startPoint2[0])!=0): pSlope = (endPoint1[1]-startPoint1[1])/(endPoint1[0]-startPoint1[0]) @@ -458,7 +459,7 @@ class VlsiLayout: newY = None elif((endPoint1[0]-startPoint1[0])==0 and (endPoint2[0]-startPoint2[0])!=0): qSlope = (endPoint2[1]-startPoint2[1])/(endPoint2[0]-startPoint2[0]) - qIntercept = startPoint2[1]-qSlope*startPoint2[0] + qIntercept = startPoint2[1]-qSlope*startPoint2[0] newX=endPoint1[0] newY=qSlope*newX+qIntercept elif((endPoint1[0]-startPoint1[0])!=0 and (endPoint2[0]-startPoint2[0])==0): @@ -467,14 +468,14 @@ class VlsiLayout: newX=endPoint2[0] newY=pSlope*newX+pIntercept return (newX,newY) - + def isCollinear(self,testPoint,point1,point2): slope1 = (testPoint[1]-point1[1])/(testPoint[0]-point1[0]) slope2 = (point2[1]-point1[1])/(point2[0]-point1[0]) if slope1 == slope2: return True return False - + def doShapesIntersect(self,shape1Coordinates, shape2Coordinates): """ Utility function to determine if 2 arbitrary shapes intersect. @@ -491,7 +492,7 @@ class VlsiLayout: if(self.isBounded(intersect,startPoint1,endPoint1) and self.isBounded(intersect,startPoint2,endPoint2)): return True #these shapes overlap! return False #these shapes are ok - + def isPointInsideOfBox(self,pointCoordinates,boxCoordinates): """ Check if a point is contained in the shape @@ -516,7 +517,7 @@ class VlsiLayout: pointCoordinates[1] 1: for port in self.all_ports: - self.add_pin("bank_sel{}".format(port),"INPUT") + self.add_pin("bank_sel{}".format(port), "INPUT") for port in self.read_ports: self.add_pin("s_en{0}".format(port), "INPUT") for port in self.all_ports: @@ -102,12 +100,11 @@ class bank(design.design): for port in self.write_ports: 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") + self.add_pin("bank_wmask{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") - self.add_pin("gnd","GROUND") - + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def route_layout(self): """ Create routing amoung the modules """ @@ -124,18 +121,27 @@ class bank(design.design): self.route_supplies() - def route_rbl(self,port): + 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) - self.add_layout_pin(text="rbl_bl{0}".format(port), - layer=bl_pin.layer, - offset=bl_pin.ll(), - height=bl_pin.height(), - width=bl_pin.width()) - - + # This will ensure the pin is only on the top or bottom edge + if port % 2: + via_offset = bl_pin.uc() + vector(0, self.m2_pitch) + left_right_offset = vector(self.max_x_offset, via_offset.y) + else: + via_offset = bl_pin.bc() - vector(0, self.m2_pitch) + left_right_offset = vector(self.min_x_offset, via_offset.y) + if bl_pin == "m1": + self.add_via_center(layers=self.m1_stack, + offset=via_offset) + self.add_via_center(layers=self.m2_stack, + offset=via_offset) + self.add_layout_pin_segment_center(text="rbl_bl{0}".format(port), + layer="m3", + start=left_right_offset, + end=via_offset) def route_bitlines(self, port): """ Route the bitlines depending on the port type rw, w, or r. """ @@ -146,7 +152,6 @@ class bank(design.design): self.route_port_data_out(port) self.route_port_data_to_bitcell_array(port) - def create_instances(self): """ Create the instances of the netlist. """ @@ -161,19 +166,18 @@ class bank(design.design): Compute the empty instance offsets for port0 and port1 (if needed) """ - self.port_data_offsets = [None]*len(self.all_ports) - self.port_address_offsets = [None]*len(self.all_ports) - - self.column_decoder_offsets = [None]*len(self.all_ports) - self.bank_select_offsets = [None]*len(self.all_ports) + self.port_data_offsets = [None] * len(self.all_ports) + self.port_address_offsets = [None] * len(self.all_ports) + self.column_decoder_offsets = [None] * len(self.all_ports) + self.bank_select_offsets = [None] * len(self.all_ports) # The center point for these cells are the upper-right corner of # the bitcell array. # The port address decoder/driver logic is placed on the right and mirrored on Y-axis. # The port data write/sense/precharge/mux is placed on the top and mirrored on the X-axis. - self.bitcell_array_top = self.bitcell_array.height - self.bitcell_array_right = self.bitcell_array.width + self.bitcell_array_top = self.bitcell_array.height + self.bitcell_array_right = self.bitcell_array.width # These are the offsets of the main array (excluding dummy and replica rows/cols) self.main_bitcell_array_top = self.bitcell_array.bitcell_array_inst.uy() @@ -185,7 +189,6 @@ class bank(design.design): self.compute_instance_port0_offsets() if len(self.all_ports)==2: self.compute_instance_port1_offsets() - def compute_instance_port0_offsets(self): """ @@ -196,29 +199,30 @@ class bank(design.design): # UPPER RIGHT QUADRANT # Bitcell array is placed at (0,0) - self.bitcell_array_offset = vector(0,0) + self.bitcell_array_offset = vector(0, 0) # LOWER RIGHT QUADRANT # Below the bitcell array - self.port_data_offsets[port] = vector(self.main_bitcell_array_left - self.bitcell_array.cell.width,0) + self.port_data_offsets[port] = vector(self.main_bitcell_array_left - self.bitcell_array.cell.width, 0) # UPPER LEFT QUADRANT # To the left of the bitcell array - x_offset = self.m2_gap + self.port_address.width - self.port_address_offsets[port] = vector(-x_offset,self.main_bitcell_array_bottom) + x_offset = self.m2_gap + self.port_address.width + self.port_address_offsets[port] = vector(-x_offset, + self.main_bitcell_array_bottom) # LOWER LEFT QUADRANT - # Place the col decoder left aligned with wordline driver + # 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 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 = 0.5 * self.dff.height + self.column_decoder.height else: y_offset = 0 - self.column_decoder_offsets[port] = vector(-x_offset,-y_offset) + self.column_decoder_offsets[port] = vector(-x_offset, -y_offset) # Bank select gets placed below the column decoder (x_offset doesn't change) if self.col_addr_size > 0: @@ -227,7 +231,7 @@ class bank(design.design): y_offset = self.port_address_offsets[port].y if self.num_banks > 1: y_offset += self.bank_select.height + drc("well_to_well") - self.bank_select_offsets[port] = vector(-x_offset,-y_offset) + self.bank_select_offsets[port] = vector(-x_offset, -y_offset) def compute_instance_port1_offsets(self): """ @@ -246,18 +250,19 @@ class bank(design.design): # LOWER RIGHT QUADRANT # To the left of the bitcell array x_offset = self.bitcell_array_right + self.port_address.width + self.m2_gap - self.port_address_offsets[port] = vector(x_offset,self.main_bitcell_array_bottom) + self.port_address_offsets[port] = vector(x_offset, + self.main_bitcell_array_bottom) # UPPER RIGHT QUADRANT - # Place the col decoder right aligned with wordline driver + # Place the col decoder right aligned with wordline driver # Above the bitcell array with a well spacing - x_offset = self.bitcell_array_right + self.central_bus_width[port] + self.port_address.wordline_driver.width + 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 + 0.5 * self.dff.height + self.column_decoder.height else: y_offset = self.bitcell_array_top - self.column_decoder_offsets[port] = vector(x_offset,y_offset) + self.column_decoder_offsets[port] = vector(x_offset, y_offset) # Bank select gets placed above the column decoder (x_offset doesn't change) if self.col_addr_size > 0: @@ -265,7 +270,7 @@ class bank(design.design): self.port_data[port].column_mux_offset.y + self.port_data[port].column_mux_array.height) else: y_offset = self.port_address_offsets[port].y - self.bank_select_offsets[port] = vector(x_offset,y_offset) + self.bank_select_offsets[port] = vector(x_offset, y_offset) def place_instances(self): """ Place the instances. """ @@ -281,11 +286,10 @@ class bank(design.design): self.place_column_decoder(self.column_decoder_offsets) self.place_bank_select(self.bank_select_offsets) - def compute_sizes(self): """ Computes the required sizes to create the bank """ - self.num_cols = int(self.words_per_row*self.word_size) + self.num_cols = int(self.words_per_row * self.word_size) self.num_rows_temp = int(self.num_words / self.words_per_row) self.num_rows = self.num_rows_temp + self.num_spare_rows @@ -293,8 +297,10 @@ class bank(design.design): 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_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.") + 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.") # The order of the control signals on the control bus: self.input_control_signals = [] @@ -313,13 +319,13 @@ 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.m2_pitch * x + self.m2_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 = [] for port in self.all_ports: if self.num_banks > 1: - self.control_signals.append(["gated_"+str for str in self.input_control_signals[port]]) + self.control_signals.append(["gated_" + str for str in self.input_control_signals[port]]) else: self.control_signals.append(self.input_control_signals[port]) @@ -327,19 +333,18 @@ class bank(design.design): if self.col_addr_size>0: self.num_col_addr_lines = 2**self.col_addr_size else: - self.num_col_addr_lines = 0 - self.col_addr_bus_width = self.m2_pitch*self.num_col_addr_lines + self.num_col_addr_lines = 0 + self.col_addr_bus_width = self.m2_pitch * self.num_col_addr_lines # A space for wells or jogging m2 - self.m2_gap = max(2*drc("pwell_to_nwell") + drc("nwell_enclose_active"), - 3*self.m2_pitch) - + self.m2_gap = max(2 * drc("pwell_to_nwell") + drc("nwell_enclose_active"), + 3 * self.m2_pitch) def add_modules(self): """ Add all the modules using the class loader """ # create arrays of bitline and bitline_bar names for read, write, or all ports - self.bitcell = factory.create(module_type="bitcell") + self.bitcell = factory.create(module_type="bitcell") self.bl_names = self.bitcell.get_all_bl_names() self.br_names = self.bitcell.get_all_br_names() self.wl_names = self.bitcell.get_all_wl_names() @@ -353,13 +358,11 @@ class bank(design.design): self.port_data.append(temp_pre) self.add_mod(self.port_data[port]) - self.port_address = factory.create(module_type="port_address", cols=self.num_cols, rows=self.num_rows) self.add_mod(self.port_address) - self.port_rbl_map = self.all_ports self.num_rbl = len(self.all_ports) @@ -371,22 +374,20 @@ class bank(design.design): bitcell_ports=self.all_ports) self.add_mod(self.bitcell_array) - if(self.num_banks > 1): self.bank_select = factory.create(module_type="bank_select") self.add_mod(self.bank_select) - def create_bitcell_array(self): """ Creating Bitcell Array """ - self.bitcell_array_inst=self.add_inst(name="replica_bitcell_array", + self.bitcell_array_inst=self.add_inst(name="replica_bitcell_array", mod=self.bitcell_array) temp = [] for col in range(self.num_cols): for bitline in self.bitline_names: - temp.append("{0}_{1}".format(bitline,col)) + temp.append("{0}_{1}".format(bitline, col)) for rbl in range(self.num_rbl): rbl_bl_name=self.bitcell_array.get_rbl_bl_name(rbl) temp.append(rbl_bl_name) @@ -394,23 +395,21 @@ class bank(design.design): temp.append(rbl_br_name) for row in range(self.num_rows): for wordline in self.wl_names: - temp.append("{0}_{1}".format(wordline,row)) + temp.append("{0}_{1}".format(wordline, row)) for port in self.all_ports: temp.append("wl_en{0}".format(port)) temp.append("vdd") temp.append("gnd") self.connect_inst(temp) - def place_bitcell_array(self, offset): """ Placing Bitcell Array """ self.bitcell_array_inst.place(offset) - def create_port_data(self): """ Creating Port Data """ - self.port_data_inst = [None]*len(self.all_ports) + self.port_data_inst = [None] * len(self.all_ports) for port in self.all_ports: self.port_data_inst[port]=self.add_inst(name="port_data{}".format(port), mod=self.port_data[port]) @@ -420,17 +419,17 @@ 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): - temp.append("{0}_{1}".format(self.bl_names[port],col)) - temp.append("{0}_{1}".format(self.br_names[port],col)) + for col in range(self.num_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): - temp.append("dout{0}_{1}".format(port,bit)) + temp.append("dout{0}_{1}".format(port, bit)) if port in self.write_ports: for bit in range(self.word_size): - temp.append("din{0}_{1}".format(port,bit)) + 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)] + sel_names = ["sel{0}_{1}".format(port, x) for x in range(self.num_col_addr_lines)] temp.extend(sel_names) if port in self.read_ports: temp.append("s_en{0}".format(port)) @@ -439,17 +438,16 @@ 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)) - temp.extend(["vdd","gnd"]) + temp.extend(["vdd", "gnd"]) self.connect_inst(temp) - def place_port_data(self, offsets): """ Placing Port Data """ for port in self.all_ports: # Top one is unflipped, bottom is flipped along X direction - if port%2 == 1: + if port % 2 == 1: mirror = "R0" else: mirror = "MX" @@ -458,42 +456,40 @@ class bank(design.design): def create_port_address(self): """ Create the hierarchical row decoder """ - self.port_address_inst = [None]*len(self.all_ports) + self.port_address_inst = [None] * len(self.all_ports) for port in self.all_ports: - self.port_address_inst[port] = self.add_inst(name="port_address{}".format(port), + self.port_address_inst[port] = self.add_inst(name="port_address{}".format(port), mod=self.port_address) temp = [] for bit in range(self.row_addr_size): - temp.append("addr{0}_{1}".format(port,bit+self.col_addr_size)) + temp.append("addr{0}_{1}".format(port, bit + self.col_addr_size)) temp.append("wl_en{0}".format(port)) for row in range(self.num_rows): - temp.append("{0}_{1}".format(self.wl_names[port],row)) + temp.append("{0}_{1}".format(self.wl_names[port], row)) temp.extend(["vdd", "gnd"]) self.connect_inst(temp) - def place_port_address(self, offsets): """ Place the hierarchical row decoder """ debug.check(len(offsets)>=len(self.all_ports), "Insufficient offsets to place row decoder array.") - # The address and control bus will be in between decoder and the main memory array - # This bus will route address bits to the decoder input and column mux inputs. + # The address and control bus will be in between decoder and the main memory array + # This bus will route address bits to the decoder input and column mux inputs. # The wires are actually routed after we placed the stuff on both sides. # The predecoder is below the x-axis and the main decoder is above the x-axis # The address flop and decoder are aligned in the x coord. for port in self.all_ports: - if port%2: + if port % 2: mirror = "MY" else: mirror = "R0" self.port_address_inst[port].place(offset=offsets[port], mirror=mirror) - def create_column_decoder(self): - """ + """ Create a 2:4 or 3:8 column address decoder. """ @@ -511,40 +507,38 @@ class bank(design.design): self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", height=self.dff.height) else: # No error checking before? - debug.error("Invalid column decoder?",-1) + debug.error("Invalid column decoder?", -1) self.add_mod(self.column_decoder) - self.column_decoder_inst = [None]*len(self.all_ports) + self.column_decoder_inst = [None] * len(self.all_ports) for port in self.all_ports: - self.column_decoder_inst[port] = self.add_inst(name="col_address_decoder{}".format(port), + self.column_decoder_inst[port] = self.add_inst(name="col_address_decoder{}".format(port), mod=self.column_decoder) temp = [] for bit in range(self.col_addr_size): - temp.append("addr{0}_{1}".format(port,bit)) + temp.append("addr{0}_{1}".format(port, bit)) for bit in range(self.num_col_addr_lines): - temp.append("sel{0}_{1}".format(port,bit)) + temp.append("sel{0}_{1}".format(port, bit)) temp.extend(["vdd", "gnd"]) self.connect_inst(temp) - def place_column_decoder(self, offsets): - """ + """ Place a 2:4 or 3:8 column address decoder. """ if self.col_addr_size == 0: return - debug.check(len(offsets)>=len(self.all_ports), "Insufficient offsets to place column decoder.") + debug.check(len(offsets)>=len(self.all_ports), + "Insufficient offsets to place column decoder.") for port in self.all_ports: - if port%2 == 1: + if port % 2 == 1: mirror = "XY" else: mirror = "R0" self.column_decoder_inst[port].place(offset=offsets[port], mirror=mirror) - - def create_bank_select(self): """ Create the bank select logic. """ @@ -552,7 +546,7 @@ class bank(design.design): if not self.num_banks > 1: return - self.bank_select_inst = [None]*len(self.all_ports) + self.bank_select_inst = [None] * len(self.all_ports) for port in self.all_ports: self.bank_select_inst[port] = self.add_inst(name="bank_select{}".format(port), mod=self.bank_select) @@ -564,24 +558,23 @@ class bank(design.design): temp.extend(["vdd", "gnd"]) self.connect_inst(temp) - def place_bank_select(self, offsets): """ Place the bank select logic. """ if not self.num_banks > 1: return - debug.check(len(offsets)>=len(self.all_ports), "Insufficient offsets to place bank select logic.") + debug.check(len(offsets)>=len(self.all_ports), + "Insufficient offsets to place bank select logic.") for port in self.all_ports: self.bank_select_inst[port].place(offsets[port]) - def route_supplies(self): """ Propagate all vdd/gnd pins up to this level for all modules """ for inst in self.insts: - self.copy_power_pins(inst,"vdd") - self.copy_power_pins(inst,"gnd") + self.copy_power_pins(inst, "vdd") + self.copy_power_pins(inst, "gnd") def route_bank_select(self, port): """ Route the bank select logic. """ @@ -596,7 +589,7 @@ class bank(design.design): bank_sel_signals = ["clk_buf", "s_en", "p_en_bar", "bank_sel"] gated_bank_sel_signals = ["gated_clk_buf", "gated_s_en", "gated_p_en_bar"] - copy_control_signals = self.input_control_signals[port]+["bank_sel{}".format(port)] + copy_control_signals = self.input_control_signals[port] + ["bank_sel{}".format(port)] for signal in range(len(copy_control_signals)): self.copy_layout_pin(self.bank_select_inst[port], bank_sel_signals[signal], copy_control_signals[signal]) @@ -605,25 +598,24 @@ class bank(design.design): out_pos = self.bank_select_inst[port].get_pin(gated_bank_sel_signals[signal]).rc() name = self.control_signals[port][signal] bus_pos = vector(self.bus_xoffset[port][name].x, out_pos.y) - self.add_path("m3",[out_pos, bus_pos]) + self.add_path("m3", [out_pos, bus_pos]) self.add_via_center(layers=self.m2_stack, offset=bus_pos) self.add_via_center(layers=self.m1_stack, offset=out_pos) self.add_via_center(layers=self.m2_stack, offset=out_pos) - def setup_routing_constraints(self): - """ + """ After the modules are instantiated, find the dimensions for the - control bus, power ring, etc. + control bus, power ring, etc. """ - self.max_y_offset = max([x.uy() for x in self.insts]) + 3*self.m1_width + self.max_y_offset = max([x.uy() for x in self.insts]) + 3 * self.m1_width self.min_y_offset = min([x.by() for x in self.insts]) - self.max_x_offset = max([x.rx() for x in self.insts]) + 3*self.m1_width + self.max_x_offset = max([x.rx() for x in self.insts]) + 3 * self.m1_width self.min_x_offset = min([x.lx() for x in self.insts]) # # Create the core bbox for the power rings @@ -633,7 +625,6 @@ class bank(design.design): self.height = ur.y - ll.y self.width = ur.x - ll.x - def route_central_bus(self): """ Create the address, supply, and control signal central bus lines. """ @@ -641,13 +632,13 @@ 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_xoffset = [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 + # 2 pitches on the right for vias/jogs to access the inputs control_bus_offset = vector(-self.m2_pitch * self.num_control_lines[0] - self.m2_pitch, self.min_y_offset) # The control bus is routed up to two pitches below the bitcell array - control_bus_length = self.main_bitcell_array_bottom - self.min_y_offset - 2*self.m1_pitch + 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", pitch=self.m2_pitch, offset=control_bus_offset, @@ -659,7 +650,7 @@ class bank(design.design): # Port 1 if len(self.all_ports)==2: # The other control bus is routed up to two pitches above the bitcell array - control_bus_length = self.max_y_offset - self.main_bitcell_array_top - 2*self.m1_pitch + control_bus_length = self.max_y_offset - self.main_bitcell_array_top - 2 * self.m1_pitch control_bus_offset = vector(self.bitcell_array_right + self.m2_pitch, self.max_y_offset - control_bus_length) # The bus for the right port is reversed so that the rbl_wl is closest to the array @@ -670,7 +661,6 @@ class bank(design.design): length=control_bus_length, vertical=True, make_pins=(self.num_banks==1)) - def route_port_data_to_bitcell_array(self, port): """ Routing of BL and BR between port data and bitcell array """ @@ -678,15 +668,19 @@ class bank(design.design): # Connect the regular bitlines inst2 = self.port_data_inst[port] inst1 = self.bitcell_array_inst - inst1_bl_name = self.bl_names[port]+"_{}" - inst1_br_name = self.br_names[port]+"_{}" + inst1_bl_name = self.bl_names[port] + "_{}" + inst1_br_name = self.br_names[port] + "_{}" - inst2_bl_name = inst2.mod.get_bl_names()+"_{}" - inst2_br_name = inst2.mod.get_br_names()+"_{}" + inst2_bl_name = inst2.mod.get_bl_names() + "_{}" + inst2_br_name = inst2.mod.get_br_names() + "_{}" - self.connect_bitlines(inst1=inst1, inst2=inst2, num_bits=self.num_cols, - inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name, - inst2_bl_name=inst2_bl_name, inst2_br_name=inst2_br_name) + self.connect_bitlines(inst1=inst1, + inst2=inst2, + num_bits=self.num_cols, + inst1_bl_name=inst1_bl_name, + inst1_br_name=inst1_br_name, + inst2_bl_name=inst2_bl_name, + inst2_br_name=inst2_br_name) # Connect the replica bitlines rbl_bl_name=self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]) @@ -694,20 +688,17 @@ class bank(design.design): self.connect_bitline(inst1, inst2, rbl_bl_name, "rbl_bl") self.connect_bitline(inst1, inst2, rbl_br_name, "rbl_br") - - def route_port_data_out(self, port): """ Add pins for the port data out """ for bit in range(self.word_size): 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, + self.add_layout_pin_rect_center(text="dout{0}_{1}".format(port, bit), + layer=data_pin.layer, offset=data_pin.center(), height=data_pin.height(), width=data_pin.width()) - def route_port_address_in(self, port): """ Routes the row decoder inputs and supplies """ @@ -715,17 +706,15 @@ class bank(design.design): for row in range(self.row_addr_size): addr_idx = row + self.col_addr_size decoder_name = "addr_{}".format(row) - addr_name = "addr{0}_{1}".format(port,addr_idx) + addr_name = "addr{0}_{1}".format(port, addr_idx) self.copy_layout_pin(self.port_address_inst[port], decoder_name, addr_name) - - def route_port_data_in(self, port): """ Connecting port data in """ for row in range(self.word_size): data_name = "din_{}".format(row) - din_name = "din{0}_{1}".format(port,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: @@ -733,8 +722,6 @@ class bank(design.design): 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) - - def channel_route_bitlines(self, inst1, inst2, num_bits, inst1_bl_name="bl_{}", inst1_br_name="br_{}", @@ -752,19 +739,18 @@ class bank(design.design): (bottom_inst, bottom_bl_name, bottom_br_name) = (inst2, inst2_bl_name, inst2_br_name) (top_inst, top_bl_name, top_br_name) = (inst1, inst1_bl_name, inst1_br_name) - # 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! - offset = bottom_inst.ul() + vector(0,self.m1_pitch) + offset = bottom_inst.ul() + vector(0, self.m1_pitch) for bit in range(num_bits): bottom_names = [bottom_inst.get_pin(bottom_bl_name.format(bit)), bottom_inst.get_pin(bottom_br_name.format(bit))] - top_names = [top_inst.get_pin(top_bl_name.format(bit)), top_inst.get_pin(top_br_name.format(bit))] + top_names = [top_inst.get_pin(top_bl_name.format(bit)), top_inst.get_pin(top_br_name.format(bit))] route_map = list(zip(bottom_names, top_names)) self.create_horizontal_channel_route(route_map, offset, self.m1_stack) def connect_bitline(self, inst1, inst2, inst1_name, inst2_name): """ - Connect two pins of two modules. + Connect two pins of two modules. This assumes that they have sufficient space to create a jog in the middle between the two modules (if needed). """ @@ -785,11 +771,13 @@ class bank(design.design): bottom_loc = bottom_pin.uc() top_loc = top_pin.bc() - yoffset = 0.5*(top_loc.y+bottom_loc.y) - self.add_path(top_pin.layer,[bottom_loc, vector(bottom_loc.x,yoffset), - vector(top_loc.x,yoffset), top_loc]) + yoffset = 0.5 * (top_loc.y + bottom_loc.y) + self.add_path(top_pin.layer, + [bottom_loc, + vector(bottom_loc.x, yoffset), + vector(top_loc.x, yoffset), + top_loc]) - def connect_bitlines(self, inst1, inst2, num_bits, inst1_bl_name, inst1_br_name, inst2_bl_name, inst2_br_name): @@ -800,14 +788,13 @@ class bank(design.design): for col in range(num_bits): self.connect_bitline(inst1, inst2, inst1_bl_name.format(col), inst2_bl_name.format(col)) self.connect_bitline(inst1, inst2, inst1_br_name.format(col), inst2_br_name.format(col)) - def route_port_address(self, port): """ Connect Wordline driver to bitcell array wordline """ self.route_port_address_in(port) - if port%2: + if port % 2: self.route_port_address_right(port) else: self.route_port_address_left(port) @@ -818,21 +805,20 @@ 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() - 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) + bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row)).lc() + mid1 = driver_wl_pos.scale(0, 1) + vector(0.5 * self.port_address_inst[port].rx() + 0.5 * self.bitcell_array_inst.lx(), 0) + mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0.5, 1) self.add_path("m1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) - 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() - 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) + bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row)).rc() + mid1 = driver_wl_pos.scale(0, 1) + vector(0.5 * self.port_address_inst[port].lx() + 0.5 * self.bitcell_array_inst.rx(), 0) + mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0, 1) self.add_path("m1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) def route_column_address_lines(self, port): @@ -846,7 +832,7 @@ class bank(design.design): decode_names = ["Zb", "Z"] # The Address LSB - self.copy_layout_pin(self.column_decoder_inst[port], "A", "addr{}_0".format(port)) + self.copy_layout_pin(self.column_decoder_inst[port], "A", "addr{}_0".format(port)) elif self.col_addr_size > 1: decode_names = [] @@ -855,11 +841,11 @@ class bank(design.design): for i in range(self.col_addr_size): decoder_name = "in_{}".format(i) - addr_name = "addr{0}_{1}".format(port,i) + addr_name = "addr{0}_{1}".format(port, i) 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_pitch, 0) + if port % 2: + offset = self.column_decoder_inst[port].ll() - vector(self.num_col_addr_lines * self.m2_pitch, 0) else: offset = self.column_decoder_inst[port].lr() + vector(self.m2_pitch, 0) @@ -872,7 +858,8 @@ class bank(design.design): self.create_vertical_channel_route(route_map, offset, self.m1_stack) def add_lvs_correspondence_points(self): - """ This adds some points for easier debugging if LVS goes wrong. + """ + This adds some points for easier debugging if LVS goes wrong. These should probably be turned off by default though, since extraction will show these as ports in the extracted netlist. """ @@ -881,7 +868,7 @@ class bank(design.design): wl_name = "wl_{}".format(i) wl_pin = self.bitcell_array_inst.get_pin(wl_name) self.add_label(text=wl_name, - layer="m1", + layer="m1", offset=wl_pin.center()) # Add the bitline names @@ -891,35 +878,35 @@ class bank(design.design): bl_pin = self.bitcell_array_inst.get_pin(bl_name) br_pin = self.bitcell_array_inst.get_pin(br_name) self.add_label(text=bl_name, - layer="m2", + layer="m2", offset=bl_pin.center()) self.add_label(text=br_name, - layer="m2", + layer="m2", offset=br_pin.center()) - # # Add the data output names to the sense amp output + # # Add the data output names to the sense amp output # for i in range(self.word_size): # data_name = "data_{}".format(i) # data_pin = self.sense_amp_array_inst.get_pin(data_name) # self.add_label(text="sa_out_{}".format(i), - # layer="m2", + # layer="m2", # offset=data_pin.center()) # Add labels on the decoder for port in self.write_ports: for i in range(self.word_size): data_name = "dec_out_{}".format(i) - pin_name = "in_{}".format(i) + pin_name = "in_{}".format(i) data_pin = self.wordline_driver_inst[port].get_pin(pin_name) self.add_label(text=data_name, - layer="m1", + layer="m1", offset=data_pin.center()) def route_control_lines(self, port): """ Route the control lines of the entire bank """ # Make a list of tuples that we will connect. - # From control signal to the module pin + # From control signal to the module pin # Connection from the central bus to the main control block crosses # pre-decoder and this connection is in metal3 connection = [] @@ -944,58 +931,59 @@ class bank(design.design): for (control_signal, pin_pos) in connection: control_mid_pos = self.bus_xoffset[port][control_signal] - control_pos = vector(self.bus_xoffset[port][control_signal].x ,pin_pos.y) + control_pos = vector(self.bus_xoffset[port][control_signal].x, pin_pos.y) self.add_wire(self.m1_stack, [control_mid_pos, control_pos, pin_pos]) self.add_via_center(layers=self.m1_stack, offset=control_pos) - # clk to wordline_driver - control_signal = self.prefix+"wl_en{}".format(port) - if port%2: + control_signal = self.prefix + "wl_en{}".format(port) + if port % 2: pin_pos = self.port_address_inst[port].get_pin("wl_en").uc() - mid_pos = pin_pos + vector(0,2*self.m2_gap) # to route down to the top of the bus + mid_pos = pin_pos + vector(0, 2 * self.m2_gap) # to route down to the top of the bus 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 + mid_pos = pin_pos - vector(0, 2 * self.m2_gap) # to route down to the top of the bus control_x_offset = self.bus_xoffset[port][control_signal].x control_pos = vector(control_x_offset, mid_pos.y) - self.add_wire(self.m1_stack,[pin_pos, mid_pos, control_pos]) + self.add_wire(self.m1_stack, [pin_pos, mid_pos, control_pos]) self.add_via_center(layers=self.m1_stack, offset=control_pos) def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True): """Get the all the stage efforts for each stage in the path within the bank clk_buf to a wordline""" - #Decoder is assumed to have settled before the negative edge of the clock. Delay model relies on this assumption + # Decoder is assumed to have settled before the negative edge of the clock. + # Delay model relies on this assumption stage_effort_list = [] wordline_cout = self.bitcell_array.get_wordline_cin() + external_cout - stage_effort_list += self.port_address.wordline_driver.determine_wordline_stage_efforts(wordline_cout,inp_is_rise) + stage_effort_list += self.port_address.wordline_driver.determine_wordline_stage_efforts(wordline_cout, + inp_is_rise) return stage_effort_list def get_wl_en_cin(self): """Get the relative capacitance of all the clk connections in the bank""" - #wl_en only used in the wordline driver. + # wl_en only used in the wordline driver. return self.port_address.wordline_driver.get_wl_en_cin() def get_w_en_cin(self): """Get the relative capacitance of all the clk connections in the bank""" - #wl_en only used in the wordline driver. - port = self.write_ports[0] + # wl_en only used in the wordline driver. + port = self.write_ports[0] return self.port_data[port].write_driver.get_w_en_cin() def get_clk_bar_cin(self): """Get the relative capacitance of all the clk_bar connections in the bank""" - #Current bank only uses clock bar (clk_buf_bar) as an enable for the precharge array. + # Current bank only uses clock bar (clk_buf_bar) as an enable for the precharge array. - #Precharges are the all the same in Mulitport, one is picked + # Precharges are the all the same in Mulitport, one is picked port = self.read_ports[0] return self.port_data[port].precharge_array.get_en_cin() def get_sen_cin(self): """Get the relative capacitance of all the sense amp enable connections in the bank""" - #Current bank only uses sen as an enable for the sense amps. - port = self.read_ports[0] + # Current bank only uses sen as an enable for the sense amps. + port = self.read_ports[0] return self.port_data[port].sense_amp_array.get_en_cin() def graph_exclude_precharge(self): @@ -1006,5 +994,7 @@ class bank(design.design): def get_cell_name(self, inst_name, row, col): """Gets the spice name of the target bitcell.""" - return self.bitcell_array_inst.mod.get_cell_name(inst_name+'.x'+self.bitcell_array_inst.name, row, col) + return self.bitcell_array_inst.mod.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, + row, + col) diff --git a/compiler/modules/bitcell_base_array.py b/compiler/modules/bitcell_base_array.py index 45bcfe0c..8ee9336b 100644 --- a/compiler/modules/bitcell_base_array.py +++ b/compiler/modules/bitcell_base_array.py @@ -101,13 +101,20 @@ class bitcell_base_array(design.design): width=self.width, height=wl_pin.height()) - # For every second row and column, add a via for gnd and vdd + # 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: + force_power_pins_vertical = cell_properties.bitcell_force_power_pins_vertical + except AttributeError: + force_power_pins_vertical = None + + # 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(), vertical=True, start_layer=pin.layer) + self.add_power_pin(name=pin_name, loc=pin.center(), vertical=force_power_pins_vertical, start_layer=pin.layer) def _adjust_x_offset(self, xoffset, col, col_offset): tempx = xoffset diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 242ef476..6e98c5e0 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -20,8 +20,7 @@ class hierarchical_decoder(design.design): def __init__(self, name, rows): design.design.__init__(self, name) - self.NAND_FORMAT = "DEC_NAND_{0}" - self.INV_FORMAT = "DEC_INV_{0}" + self.AND_FORMAT = "DEC_AND_{0}" self.pre2x4_inst = [] self.pre3x8_inst = [] @@ -58,12 +57,12 @@ class hierarchical_decoder(design.design): self.inv = factory.create(module_type="pinv", height=self.cell_height) self.add_mod(self.inv) - self.nand2 = factory.create(module_type="pnand2", + self.and2 = factory.create(module_type="pand2", + height=self.cell_height) + self.add_mod(self.and2) + self.and3 = factory.create(module_type="pand3", height=self.cell_height) - self.add_mod(self.nand2) - self.nand3 = factory.create(module_type="pnand3", - height=self.cell_height) - self.add_mod(self.nand3) + self.add_mod(self.and3) self.add_decoders() @@ -77,27 +76,27 @@ class hierarchical_decoder(design.design): height=self.cell_height) self.add_mod(self.pre3_8) - def determine_predecodes(self,num_inputs): + def determine_predecodes(self, num_inputs): """ Determines the number of 2:4 pre-decoder and 3:8 pre-decoder needed based on the number of inputs """ if (num_inputs == 2): - return (1,0) + return (1, 0) elif (num_inputs == 3): - return(0,1) + return(0, 1) elif (num_inputs == 4): - return(2,0) + return(2, 0) elif (num_inputs == 5): - return(1,1) + return(1, 1) elif (num_inputs == 6): - return(3,0) + return(3, 0) elif (num_inputs == 7): - return(2,1) + return(2, 1) elif (num_inputs == 8): - return(1,2) + return(1, 2) elif (num_inputs == 9): - return(0,3) + return(0, 3) else: - debug.error("Invalid number of inputs for hierarchical decoder",-1) + debug.error("Invalid number of inputs for hierarchical decoder", -1) def setup_netlist_constants(self): self.predec_groups = [] # This array is a 2D array. @@ -122,35 +121,35 @@ class hierarchical_decoder(design.design): index = index + 1 self.predec_groups.append(lines) - def setup_layout_constants(self): """ Calculate the overall dimensions of the hierarchical decoder """ # If we have 4 or fewer rows, the predecoder is the decoder itself if self.num_inputs>=4: - self.total_number_of_predecoder_outputs = 4*self.no_of_pre2x4 + 8*self.no_of_pre3x8 + self.total_number_of_predecoder_outputs = 4 * self.no_of_pre2x4 + 8 * self.no_of_pre3x8 else: - self.total_number_of_predecoder_outputs = 0 - debug.error("Not enough rows ({}) for a hierarchical decoder. Non-hierarchical not supported yet.".format(self.num_inputs),-1) + self.total_number_of_predecoder_outputs = 0 + debug.error("Not enough rows ({}) for a hierarchical decoder. Non-hierarchical not supported yet.".format(self.num_inputs), + -1) # Calculates height and width of pre-decoder, if self.no_of_pre3x8 > 0: - self.predecoder_width = self.pre3_8.width + 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 + self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 - # Calculates height and width of row-decoder + # Calculates height and width of row-decoder if (self.num_inputs == 4 or self.num_inputs == 5): - nand_width = self.nand2.width + nand_width = self.and2.width else: - nand_width = self.nand3.width - self.internal_routing_width = self.m2_pitch*self.total_number_of_predecoder_outputs + nand_width = self.and3.width + self.internal_routing_width = self.m2_pitch * self.total_number_of_predecoder_outputs self.row_decoder_height = self.inv.height * self.rows - self.input_routing_width = (self.num_inputs+1) * self.m2_pitch - # Calculates height and width of hierarchical decoder + self.input_routing_width = (self.num_inputs + 1) * self.m2_pitch + # Calculates height and width of hierarchical decoder self.height = self.row_decoder_height self.width = self.input_routing_width + self.predecoder_width \ + self.internal_routing_width + nand_width + self.inv.width @@ -158,7 +157,7 @@ class hierarchical_decoder(design.design): def route_input_rails(self): """ Create input rails 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 + 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 @@ -166,7 +165,7 @@ class hierarchical_decoder(design.design): min_x = min(min_x, -self.pre2_4.width) if self.no_of_pre3x8 > 0: min_x = min(min_x, -self.pre3_8.width) - input_offset=vector(min_x - self.input_routing_width,0) + input_offset=vector(min_x - self.input_routing_width, 0) input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)] self.input_rails = self.create_vertical_pin_bus(layer="m2", @@ -177,7 +176,6 @@ class hierarchical_decoder(design.design): self.route_input_to_predecodes() - def route_input_to_predecodes(self): """ Route the vertical input rail to the predecoders """ for pre_num in range(self.no_of_pre2x4): @@ -191,11 +189,10 @@ class hierarchical_decoder(design.design): # 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) - input_offset = input_pos.scale(1,0) + decoder_offset.scale(0,1) + decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height) + input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) self.route_input_rail(decoder_offset, input_offset) - for pre_num in range(self.no_of_pre3x8): for i in range(3): @@ -208,11 +205,10 @@ class hierarchical_decoder(design.design): # 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) - input_offset = input_pos.scale(1,0) + decoder_offset.scale(0,1) + decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height) + input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) - self.route_input_rail(decoder_offset, input_offset) - + self.route_input_rail(decoder_offset, input_offset) def route_input_rail(self, input_offset, output_offset): """ Route a vertical M2 coordinate to another vertical M2 coordinate to the predecode inputs """ @@ -222,7 +218,6 @@ class hierarchical_decoder(design.design): self.add_via_center(layers=self.m2_stack, offset=output_offset) self.add_path(("m3"), [input_offset, output_offset]) - def add_pins(self): """ Add the module pins """ @@ -235,7 +230,6 @@ class hierarchical_decoder(design.design): self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") - def create_pre_decoder(self): """ Creates pre-decoder and places labels input address [A] """ @@ -245,7 +239,7 @@ class hierarchical_decoder(design.design): for i in range(self.no_of_pre3x8): self.create_pre3x8(i) - def create_pre2x4(self,num): + def create_pre2x4(self, num): """ Add a 2x4 predecoder to the left of the origin """ if (self.num_inputs == 2): @@ -265,8 +259,7 @@ class hierarchical_decoder(design.design): mod=self.pre2_4)) self.connect_inst(pins) - - def create_pre3x8(self,num): + def create_pre3x8(self, num): """ Add 3x8 predecoder to the left of the origin and above any 2x4 decoders """ # If we had 2x4 predecodes, those are used as the lower # decode output bits @@ -280,11 +273,10 @@ class hierarchical_decoder(design.design): pins.append("out_{0}".format(output_index + out_index_offset)) pins.extend(["vdd", "gnd"]) - self.pre3x8_inst.append(self.add_inst(name="pre3x8_{0}".format(num), + self.pre3x8_inst.append(self.add_inst(name="pre3x8_{0}".format(num), mod=self.pre3_8)) self.connect_inst(pins) - def place_pre_decoder(self): """ Creates pre-decoder and places labels input address [A] """ @@ -294,196 +286,131 @@ class hierarchical_decoder(design.design): for i in range(self.no_of_pre3x8): self.place_pre3x8(i) - def place_pre2x4(self,num): + def place_pre2x4(self, num): """ Place 2x4 predecoder to the left of the origin """ if (self.num_inputs == 2): - base = vector(-self.pre2_4.width,0) + base = vector(-self.pre2_4.width, 0) else: base= vector(-self.pre2_4.width, num * self.pre2_4.height) self.pre2x4_inst[num].place(base) - def place_pre3x8(self,num): + 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) - mirror ="R0" + offset = vector(-self.pre_3_8.width, 0) + mirror = "R0" else: - height = self.no_of_pre2x4*self.pre2_4.height + num*self.pre3_8.height + height = self.no_of_pre2x4 * self.pre2_4.height + num * self.pre3_8.height offset = vector(-self.pre3_8.width, height) self.pre3x8_inst[num].place(offset) def create_row_decoder(self): - """ Create the row-decoder by placing NAND2/NAND3 and Inverters + """ Create the row-decoder by placing AND2/AND3 and Inverters and add the primary decoder output pins. """ if (self.num_inputs >= 4): - self.create_decoder_nand_array() - self.create_decoder_inv_array() + self.create_decoder_and_array() - def create_decoder_nand_array(self): - """ Add a column of NAND gates for final decode """ + def create_decoder_and_array(self): + """ Add a column of AND gates for final decode """ - self.nand_inst = [] + self.and_inst = [] - # Row Decoder NAND GATE array for address inputs <5. + # Row Decoder AND GATE array for address inputs <5. if (self.num_inputs == 4 or self.num_inputs == 5): for i in range(len(self.predec_groups[0])): for j in range(len(self.predec_groups[1])): - row = len(self.predec_groups[0])*j + i + row = len(self.predec_groups[0]) * j + i if (row < self.rows): - name = self.NAND_FORMAT.format(row) - self.nand_inst.append(self.add_inst(name=name, - mod=self.nand2)) + name = self.AND_FORMAT.format(row) + self.and_inst.append(self.add_inst(name=name, + mod=self.and2)) pins =["out_{0}".format(i), "out_{0}".format(j + len(self.predec_groups[0])), - "Z_{0}".format(row), + "decode_{0}".format(row), "vdd", "gnd"] self.connect_inst(pins) - - # Row Decoder NAND GATE array for address inputs >5. + # Row Decoder AND GATE array for address inputs >5. elif (self.num_inputs > 5): for i in range(len(self.predec_groups[0])): for j in range(len(self.predec_groups[1])): for k in range(len(self.predec_groups[2])): - row = (len(self.predec_groups[0])*len(self.predec_groups[1])) * k \ - + len(self.predec_groups[0])*j + i + row = (len(self.predec_groups[0]) * len(self.predec_groups[1])) * k \ + + len(self.predec_groups[0]) * j + i if (row < self.rows): - name = self.NAND_FORMAT.format(row) - self.nand_inst.append(self.add_inst(name=name, - mod=self.nand3)) + name = self.AND_FORMAT.format(row) + self.and_inst.append(self.add_inst(name=name, + mod=self.and3)) pins = ["out_{0}".format(i), "out_{0}".format(j + len(self.predec_groups[0])), "out_{0}".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])), - "Z_{0}".format(row), + "decode_{0}".format(row), "vdd", "gnd"] self.connect_inst(pins) - - def create_decoder_inv_array(self): - """ - Add a column of INV gates for the decoder. - """ - - self.inv_inst = [] - for row in range(self.rows): - name = self.INV_FORMAT.format(row) - self.inv_inst.append(self.add_inst(name=name, - mod=self.inv)) - self.connect_inst(args=["Z_{0}".format(row), - "decode_{0}".format(row), - "vdd", "gnd"]) - - - def place_decoder_inv_array(self): - """ - Place the column of INV gates for the decoder above the predecoders - and to the right of the NAND decoders. - """ - - z_pin = self.inv.get_pin("Z") - - if (self.num_inputs == 4 or self.num_inputs == 5): - x_off = self.internal_routing_width + self.nand2.width - else: - x_off = self.internal_routing_width + self.nand3.width - - for row in range(self.rows): - if (row % 2 == 0): - inv_row_height = self.inv.height * row - mirror = "R0" - y_dir = 1 - else: - inv_row_height = self.inv.height * (row + 1) - mirror = "MX" - y_dir = -1 - y_off = inv_row_height - offset = vector(x_off,y_off) - self.inv_inst[row].place(offset=offset, - mirror=mirror) - def place_row_decoder(self): - """ - Place the row-decoder by placing NAND2/NAND3 and Inverters - and add the primary decoder output pins. + """ + Place the row-decoder by placing AND2/AND3 and Inverters + and add the primary decoder output pins. """ if (self.num_inputs >= 4): - self.place_decoder_nand_array() - self.place_decoder_inv_array() + self.place_decoder_and_array() self.route_decoder() - - def place_decoder_nand_array(self): - """ Add a column of NAND gates for final decode """ + def place_decoder_and_array(self): + """ Add a column of AND gates for final decode """ - # Row Decoder NAND GATE array for address inputs <5. + # Row Decoder AND GATE array for address inputs <5. if (self.num_inputs == 4 or self.num_inputs == 5): - self.place_nand_array(nand_mod=self.nand2) + self.place_and_array(and_mod=self.and2) - # Row Decoder NAND GATE array for address inputs >5. + # Row Decoder AND GATE array for address inputs >5. # FIXME: why this correct offset?) elif (self.num_inputs > 5): - self.place_nand_array(nand_mod=self.nand3) + self.place_and_array(and_mod=self.and3) - def place_nand_array(self, nand_mod): - """ Add a column of NAND gates for the decoder above the predecoders.""" + def place_and_array(self, and_mod): + """ Add a column of AND gates for the decoder above the predecoders.""" for row in range(self.rows): - name = self.NAND_FORMAT.format(row) if ((row % 2) == 0): - y_off = nand_mod.height*row - y_dir = 1 + y_off = and_mod.height * row mirror = "R0" else: - y_off = nand_mod.height*(row + 1) - y_dir = -1 + y_off = and_mod.height * (row + 1) mirror = "MX" - self.nand_inst[row].place(offset=[self.internal_routing_width, y_off], - mirror=mirror) - - - + self.and_inst[row].place(offset=[self.internal_routing_width, y_off], + mirror=mirror) def route_decoder(self): - """ Route the nand to inverter in the decoder and add the pins. """ + """ Add the pins. """ for row in range(self.rows): - - # route nand output to output inv input - zr_pos = self.nand_inst[row].get_pin("Z").rc() - al_pos = self.inv_inst[row].get_pin("A").lc() - # ensure the bend is in the middle - mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y) - mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y) - self.add_path("m1", [zr_pos, mid1_pos, mid2_pos, al_pos]) - - z_pin = self.inv_inst[row].get_pin("Z") + z_pin = self.and_inst[row].get_pin("Z") self.add_layout_pin(text="decode_{0}".format(row), layer="m1", offset=z_pin.ll(), width=z_pin.width(), height=z_pin.height()) - - def route_predecode_rails(self): """ Creates vertical metal 2 rails to connect predecoder and decoder stages.""" # This is not needed for inputs <4 since they have no pre/decode stages. if (self.num_inputs >= 4): - input_offset = vector(0.5*self.m2_width,0) + input_offset = vector(0.5 * self.m2_width, 0) input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)] self.predecode_rails = self.create_vertical_pin_bus(layer="m2", pitch=self.m2_pitch, offset=input_offset, names=input_bus_names, length=self.height) - self.route_rails_to_predecodes() self.route_rails_to_decoder() @@ -497,8 +424,7 @@ 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) - self.route_predecode_rail_m3(predecode_name, pin) - + self.route_predecode_rail_m3(predecode_name, pin) # FIXME: convert to connect_bus for pre_num in range(self.no_of_pre3x8): @@ -506,17 +432,15 @@ 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) - self.route_predecode_rail_m3(predecode_name, pin) + self.route_predecode_rail_m3(predecode_name, pin) - - def route_rails_to_decoder(self): - """ Use the self.predec_groups to determine the connections to the decoder NAND gates. - Inputs of NAND2/NAND3 gates come from different groups. + """ Use the self.predec_groups to determine the connections to the decoder AND gates. + Inputs of AND2/AND3 gates come from different groups. For example for these groups [ [0,1,2,3] ,[4,5,6,7], - [8,9,10,11,12,13,14,15] ] the first NAND3 inputs are connected to - [0,4,8] and second NAND3 is connected to [0,4,9] ........... and the - 128th NAND3 is connected to [3,7,15] + [8,9,10,11,12,13,14,15] ] the first AND3 inputs are connected to + [0,4,8] and second AND3 is connected to [0,4,9] ........... and the + 128th AND3 is connected to [3,7,15] """ row_index = 0 if (self.num_inputs == 4 or self.num_inputs == 5): @@ -525,9 +449,9 @@ class hierarchical_decoder(design.design): # FIXME: convert to connect_bus? if (row_index < self.rows): predecode_name = "predecode_{}".format(index_A) - self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("A")) - predecode_name = "predecode_{}".format(index_B) - self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B")) + self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("A")) + predecode_name = "predecode_{}".format(index_B) + self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("B")) row_index = row_index + 1 elif (self.num_inputs > 5): @@ -536,61 +460,48 @@ class hierarchical_decoder(design.design): for index_A in self.predec_groups[0]: # FIXME: convert to connect_bus? if (row_index < self.rows): - predecode_name = "predecode_{}".format(index_A) - self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("A")) - predecode_name = "predecode_{}".format(index_B) - self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B")) - predecode_name = "predecode_{}".format(index_C) - self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("C")) + predecode_name = "predecode_{}".format(index_A) + self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("A")) + predecode_name = "predecode_{}".format(index_B) + self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("B")) + predecode_name = "predecode_{}".format(index_C) + self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("C")) row_index = row_index + 1 def route_vdd_gnd(self): """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ # The vias will be placed in the center and right of the cells, respectively. - xoffset = self.nand_inst[0].rx() - for num in range(0,self.rows): + xoffset = self.and_inst[0].rx() + for num in range(0, self.rows): for pin_name in ["vdd", "gnd"]: # The nand and inv are the same height rows... - supply_pin = self.nand_inst[num].get_pin(pin_name) + supply_pin = self.and_inst[num].get_pin(pin_name) pin_pos = vector(xoffset, supply_pin.cy()) self.add_power_pin(name=pin_name, loc=pin_pos) - - # Make a redundant rail too - for num in range(0,self.rows,2): - for pin_name in ["vdd", "gnd"]: - start = self.nand_inst[num].get_pin(pin_name).lc() - end = self.inv_inst[num].get_pin(pin_name).rc() - mid = (start+end).scale(0.5,0.5) - self.add_rect_center(layer="m1", - offset=mid, - width=end.x-start.x) - # 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") - def route_predecode_rail(self, rail_name, pin): """ Connect the routing rail to the given metal1 pin """ - rail_pos = vector(self.predecode_rails[rail_name].x,pin.lc().y) + rail_pos = vector(self.predecode_rails[rail_name].x, pin.lc().y) self.add_path("m1", [rail_pos, pin.lc()]) self.add_via_center(layers=self.m1_stack, offset=rail_pos) - def route_predecode_rail_m3(self, rail_name, pin): """ Connect the routing rail to the given metal1 pin """ # This routes the pin up to the rail, basically, to avoid conflicts. # It would be fixed with a channel router. - mid_point = vector(pin.cx(), pin.cy()+self.inv.height/2) - rail_pos = vector(self.predecode_rails[rail_name].x,mid_point.y) + mid_point = vector(pin.cx(), pin.cy() + self.inv.height / 2) + rail_pos = vector(self.predecode_rails[rail_name].x, mid_point.y) self.add_via_center(layers=self.m1_stack, offset=pin.center()) - self.add_wire(("m3","via2","m2"), [rail_pos, mid_point, pin.uc()]) + self.add_wire(("m3", "via2", "m2"), [rail_pos, mid_point, pin.uc()]) self.add_via_center(layers=self.m2_stack, offset=rail_pos) diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index 88bae534..ccee34a9 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -32,61 +32,58 @@ class hierarchical_predecode(design.design): self.add_pin("gnd", "GROUND") def add_modules(self): - """ Add the INV and NAND gate modules """ + """ 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_nand(self.number_of_inputs) - self.add_mod(self.nand) + self.add_and(self.number_of_inputs) + self.add_mod(self.and_mod) - def add_nand(self, inputs): + def add_and(self, inputs): """ Create the NAND for the predecode input stage """ if inputs==2: - self.nand = factory.create(module_type="pnand2", - height=self.cell_height) + self.and_mod = factory.create(module_type="pand2", + height=self.cell_height) elif inputs==3: - self.nand = factory.create(module_type="pnand3", - height=self.cell_height) + self.and_mod = factory.create(module_type="pand3", + height=self.cell_height) else: debug.error("Invalid number of predecode inputs: {}".format(inputs), -1) def setup_layout_constraints(self): - self.height = self.number_of_outputs * self.nand.height + 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 - # x offset to NAND decoder includes the left rails, mid rails and inverters, plus two extra m2 pitches - self.x_off_nand = 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 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 output inverters - self.x_off_inv_2 = self.x_off_nand + self.nand.width - - # Height width are computed - self.width = self.x_off_inv_2 + self.inv.width + self.width = self.x_off_and + self.and_mod.width 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,2*self.m1_width) + offset = vector(0.5 * self.m2_width, self.m1_pitch) self.input_rails = self.create_vertical_pin_bus(layer="m2", pitch=self.m2_pitch, offset=offset, names=input_names, - length=self.height - 2*self.m1_width) + length=self.height - 2 * self.m1_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, 2*self.m1_width) + offset = vector(self.x_off_inv_1 + self.inv.width + 2 * self.m2_pitch, self.m1_pitch) self.decode_rails = self.create_vertical_bus(layer="m2", pitch=self.m2_pitch, offset=offset, names=decode_names, - length=self.height - 2*self.m1_width) + length=self.height - 2 * self.m1_pitch) def create_input_inverters(self): """ Create the input inverters to invert input signals for the decode stage. """ @@ -112,62 +109,35 @@ class hierarchical_predecode(design.design): self.in_inst[inv_num].place(offset=offset, mirror=mirror) - def create_output_inverters(self): - """ Create inverters for the inverted output decode signals. """ - self.inv_inst = [] - for inv_num in range(self.number_of_outputs): - name = "pre_nand_inv_{}".format(inv_num) - self.inv_inst.append(self.add_inst(name=name, - mod=self.inv)) - self.connect_inst(["Z_{}".format(inv_num), - "out_{}".format(inv_num), - "vdd", "gnd"]) + def create_and_array(self, connections): + """ Create the AND stage for the decodes """ + self.and_inst = [] + for and_input in range(self.number_of_outputs): + inout = str(self.number_of_inputs) + "x" + str(self.number_of_outputs) + name = "Xpre{0}_and_{1}".format(inout, and_input) + self.and_inst.append(self.add_inst(name=name, + mod=self.and_mod)) + self.connect_inst(connections[and_input]) - - def place_output_inverters(self): - """ Place inverters for the inverted output decode signals. """ - for inv_num in range(self.number_of_outputs): - if (inv_num % 2 == 0): - y_off = inv_num * self.inv.height + def place_and_array(self): + """ Place the AND stage for the decodes """ + for and_input in range(self.number_of_outputs): + # inout = str(self.number_of_inputs) + "x" + str(self.number_of_outputs) + if (and_input % 2 == 0): + y_off = and_input * self.and_mod.height mirror = "R0" else: - y_off =(inv_num + 1)*self.inv.height + y_off = (and_input + 1) * self.and_mod.height mirror = "MX" - offset = vector(self.x_off_inv_2, y_off) - self.inv_inst[inv_num].place(offset=offset, - mirror=mirror) - - def create_nand_array(self,connections): - """ Create the NAND stage for the decodes """ - self.nand_inst = [] - for nand_input in range(self.number_of_outputs): - inout = str(self.number_of_inputs)+"x"+str(self.number_of_outputs) - name = "Xpre{0}_nand_{1}".format(inout,nand_input) - self.nand_inst.append(self.add_inst(name=name, - mod=self.nand)) - self.connect_inst(connections[nand_input]) - - - def place_nand_array(self): - """ Place the NAND stage for the decodes """ - for nand_input in range(self.number_of_outputs): - inout = str(self.number_of_inputs)+"x"+str(self.number_of_outputs) - if (nand_input % 2 == 0): - y_off = nand_input * self.inv.height - mirror = "R0" - else: - y_off = (nand_input + 1) * self.inv.height - mirror = "MX" - offset = vector(self.x_off_nand, y_off) - self.nand_inst[nand_input].place(offset=offset, - mirror=mirror) - + offset = vector(self.x_off_and, y_off) + self.and_inst[and_input].place(offset=offset, + mirror=mirror) def route(self): self.route_input_inverters() self.route_inputs_to_rails() - self.route_nand_to_rails() - self.route_output_inverters() + self.route_and_to_rails() + self.route_output_and() self.route_vdd_gnd() def route_inputs_to_rails(self): @@ -175,39 +145,30 @@ class hierarchical_predecode(design.design): 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 nand gates. - y_offset = (num+self.number_of_inputs) * self.inv.height + contact.m1_via.width + self.m1_space - in_pin = "in_{}".format(num) + # pins in the and gates. + y_offset = (num + self.number_of_inputs) * self.inv.height + contact.m1_via.width + self.m1_space + in_pin = "in_{}".format(num) a_pin = "A_{}".format(num) - in_pos = vector(self.input_rails[in_pin].x,y_offset) - a_pos = vector(self.decode_rails[a_pin].x,y_offset) - self.add_path("m1",[in_pos, a_pos]) - self.add_via_center(layers = self.m1_stack, + 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_center(layers=self.m1_stack, offset=[self.input_rails[in_pin].x, y_offset]) - self.add_via_center(layers = self.m1_stack, + self.add_via_center(layers=self.m1_stack, offset=[self.decode_rails[a_pin].x, y_offset]) - def route_output_inverters(self): + def route_output_and(self): """ - Route all conections of the outputs inverters + Route all conections of the outputs and gates """ for num in range(self.number_of_outputs): - # route nand output to output inv input - zr_pos = self.nand_inst[num].get_pin("Z").rc() - al_pos = self.inv_inst[num].get_pin("A").lc() - # ensure the bend is in the middle - mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y) - mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y) - self.add_path("m1", [zr_pos, mid1_pos, mid2_pos, al_pos]) - - z_pin = self.inv_inst[num].get_pin("Z") + z_pin = self.and_inst[num].get_pin("Z") self.add_layout_pin(text="out_{}".format(num), layer="m1", offset=z_pin.ll(), height=z_pin.height(), width=z_pin.width()) - def route_input_inverters(self): """ @@ -219,7 +180,7 @@ class hierarchical_predecode(design.design): #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 nand gates. + # pins in the and gates. y_offset = (inv_num+1) * self.inv.height - 3*self.m1_space inv_out_pos = self.in_inst[inv_num].get_pin("Z").rc() right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").lx(),0) @@ -236,13 +197,12 @@ class hierarchical_predecode(design.design): self.add_via_center(layers=self.m1_stack, offset=in_pos) - - def route_nand_to_rails(self): - # This 2D array defines the connection mapping - nand_input_line_combination = self.get_nand_input_line_combination() + def route_and_to_rails(self): + # This 2D array defines the connection mapping + and_input_line_combination = self.get_and_input_line_combination() for k in range(self.number_of_outputs): - # create x offset list - index_lst= nand_input_line_combination[k] + # create x offset list + index_lst= and_input_line_combination[k] if self.number_of_inputs == 2: gate_lst = ["A","B"] @@ -251,35 +211,32 @@ class hierarchical_predecode(design.design): # this will connect pins A,B or A,B,C for rail_pin,gate_pin in zip(index_lst,gate_lst): - pin_pos = self.nand_inst[k].get_pin(gate_pin).lc() + pin_pos = self.and_inst[k].get_pin(gate_pin).lc() rail_pos = vector(self.decode_rails[rail_pin].x, pin_pos.y) self.add_path("m1", [rail_pos, pin_pos]) self.add_via_center(layers=self.m1_stack, offset=rail_pos) - - - 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.inv_inst[0].lx() - self.m1_space - for num in range(0,self.number_of_outputs): + # 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 for n in ["vdd", "gnd"]: - nand_pin = self.nand_inst[num].get_pin(n) - supply_offset = nand_pin.ll().scale(0,1) + and_pin = self.and_inst[num].get_pin(n) + supply_offset = and_pin.ll().scale(0, 1) self.add_rect(layer="m1", offset=supply_offset, - width=self.inv_inst[num].rx()) + width=self.and_inst[num].rx()) # Add pins in two locations - for xoffset in [in_xoffset, out_xoffset]: - pin_pos = vector(xoffset, nand_pin.cy()) + for xoffset in [in_xoffset]: + pin_pos = vector(xoffset, and_pin.cy()) self.add_power_pin(n, pin_pos) diff --git a/compiler/modules/hierarchical_predecode2x4.py b/compiler/modules/hierarchical_predecode2x4.py index f05f54b0..b1aacc5a 100644 --- a/compiler/modules/hierarchical_predecode2x4.py +++ b/compiler/modules/hierarchical_predecode2x4.py @@ -27,33 +27,31 @@ class hierarchical_predecode2x4(hierarchical_predecode): self.add_pins() self.add_modules() self.create_input_inverters() - self.create_output_inverters() - connections =[["inbar_0", "inbar_1", "Z_0", "vdd", "gnd"], - ["in_0", "inbar_1", "Z_1", "vdd", "gnd"], - ["inbar_0", "in_1", "Z_2", "vdd", "gnd"], - ["in_0", "in_1", "Z_3", "vdd", "gnd"]] - self.create_nand_array(connections) + connections =[["inbar_0", "inbar_1", "out_0", "vdd", "gnd"], + ["in_0", "inbar_1", "out_1", "vdd", "gnd"], + ["inbar_0", "in_1", "out_2", "vdd", "gnd"], + ["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 NAND gates for inversion + 4) a set of AND gates for inversion """ self.setup_layout_constraints() self.route_rails() self.place_input_inverters() - self.place_output_inverters() - self.place_nand_array() + self.place_and_array() self.route() self.add_boundary() - self.DRC_LVS() + self.DRC_LVS() - def get_nand_input_line_combination(self): - """ These are the decoder connections of the NAND gates to the A,B pins """ + 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"], ["A_0", "Abar_1"], ["Abar_0", "A_1"], ["A_0", "A_1"]] - return combination \ No newline at end of file + return combination diff --git a/compiler/modules/hierarchical_predecode3x8.py b/compiler/modules/hierarchical_predecode3x8.py index 20b629bb..4f2294f1 100644 --- a/compiler/modules/hierarchical_predecode3x8.py +++ b/compiler/modules/hierarchical_predecode3x8.py @@ -20,26 +20,25 @@ class hierarchical_predecode3x8(hierarchical_predecode): hierarchical_predecode.__init__(self, name, 3, height) self.create_netlist() - if not OPTS.netlist_only: + if not OPTS.netlist_only: self.create_layout() def create_netlist(self): self.add_pins() self.add_modules() self.create_input_inverters() - self.create_output_inverters() - connections=[["inbar_0", "inbar_1", "inbar_2", "Z_0", "vdd", "gnd"], - ["in_0", "inbar_1", "inbar_2", "Z_1", "vdd", "gnd"], - ["inbar_0", "in_1", "inbar_2", "Z_2", "vdd", "gnd"], - ["in_0", "in_1", "inbar_2", "Z_3", "vdd", "gnd"], - ["inbar_0", "inbar_1", "in_2", "Z_4", "vdd", "gnd"], - ["in_0", "inbar_1", "in_2", "Z_5", "vdd", "gnd"], - ["inbar_0", "in_1", "in_2", "Z_6", "vdd", "gnd"], - ["in_0", "in_1", "in_2", "Z_7", "vdd", "gnd"]] - self.create_nand_array(connections) + connections=[["inbar_0", "inbar_1", "inbar_2", "out_0", "vdd", "gnd"], + ["in_0", "inbar_1", "inbar_2", "out_1", "vdd", "gnd"], + ["inbar_0", "in_1", "inbar_2", "out_2", "vdd", "gnd"], + ["in_0", "in_1", "inbar_2", "out_3", "vdd", "gnd"], + ["inbar_0", "inbar_1", "in_2", "out_4", "vdd", "gnd"], + ["in_0", "inbar_1", "in_2", "out_5", "vdd", "gnd"], + ["inbar_0", "in_1", "in_2", "out_6", "vdd", "gnd"], + ["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 @@ -49,20 +48,19 @@ class hierarchical_predecode3x8(hierarchical_predecode): self.setup_layout_constraints() self.route_rails() self.place_input_inverters() - self.place_output_inverters() - self.place_nand_array() + self.place_and_array() self.route() self.add_boundary() self.DRC_LVS() - def get_nand_input_line_combination(self): + 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"], ["A_0", "Abar_1", "Abar_2"], ["Abar_0", "A_1", "Abar_2"], ["A_0", "A_1", "Abar_2"], - ["Abar_0", "Abar_1", "A_2"], + ["Abar_0", "Abar_1", "A_2"], ["A_0", "Abar_1", "A_2"], ["Abar_0", "A_1", "A_2"], ["A_0", "A_1", "A_2"]] - return combination \ No newline at end of file + return combination diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index d1c2c671..fbf5223a 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -1,17 +1,17 @@ # 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 sys -from tech import drc, parameter +from tech import drc import debug import design from sram_factory import factory +from collections import namedtuple from vector import vector - from globals import OPTS + class port_data(design.design): """ Create the data port (column mux, sense amps, write driver, etc.) for the given port number. @@ -23,18 +23,21 @@ class port_data(design.design): sram_config.set_local_config(self) self.port = port if self.write_size is not None: - self.num_wmasks = int(self.word_size/self.write_size) + self.num_wmasks = int(self.word_size / self.write_size) else: self.num_wmasks = 0 if name == "": name = "port_data_{0}".format(self.port) design.design.__init__(self, name) - debug.info(2, "create data port of size {0} with {1} words per row".format(self.word_size,self.words_per_row)) + debug.info(2, + "create data port of size {0} with {1} words per row".format(self.word_size, + self.words_per_row)) self.create_netlist() if not OPTS.netlist_only: - debug.check(len(self.all_ports)<=2,"Bank layout cannot handle more than two ports.") + debug.check(len(self.all_ports)<=2, + "Bank layout cannot handle more than two ports.") self.create_layout() self.add_boundary() @@ -46,7 +49,19 @@ class port_data(design.design): # br lines are connect from the precharger return self.precharge.get_br_names() + def get_bl_name(self, port=0): + bl_name = "bl" + if len(self.all_ports) == 1: + return bl_name + else: + return bl_name + "{}".format(port) + def get_br_name(self, port=0): + br_name = "br" + if len(self.all_ports) == 1: + return br_name + else: + return br_name + "{}".format(port) def create_netlist(self): self.precompute_constants() @@ -80,8 +95,6 @@ class port_data(design.design): else: self.column_mux_array_inst = None - - def create_layout(self): self.compute_instance_offsets() self.place_instances() @@ -91,33 +104,32 @@ class port_data(design.design): def add_pins(self): """ Adding pins for port address module""" - self.add_pin("rbl_bl","INOUT") - self.add_pin("rbl_br","INOUT") + self.add_pin("rbl_bl", "INOUT") + self.add_pin("rbl_br", "INOUT") for bit in range(self.num_cols): - bl_name = self.precharge_array.get_bl_name(self.port) - br_name = self.precharge_array.get_br_name(self.port) - self.add_pin("{0}_{1}".format(bl_name, bit),"INOUT") - self.add_pin("{0}_{1}".format(br_name, bit),"INOUT") + bl_name = self.get_bl_name(self.port) + 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") if self.port in self.read_ports: for bit in range(self.word_size): - self.add_pin("dout_{}".format(bit),"OUTPUT") + self.add_pin("dout_{}".format(bit), "OUTPUT") if self.port in self.write_ports: for bit in range(self.word_size): - self.add_pin("din_{}".format(bit),"INPUT") + 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)] for pin_name in sel_names: - self.add_pin(pin_name,"INPUT") + self.add_pin(pin_name, "INPUT") if self.port in self.read_ports: self.add_pin("s_en", "INPUT") self.add_pin("p_en_bar", "INPUT") if self.port in self.write_ports: self.add_pin("w_en", "INPUT") for bit in range(self.num_wmasks): - self.add_pin("bank_wmask_{}".format(bit),"INPUT") - self.add_pin("vdd","POWER") - self.add_pin("gnd","GROUND") - + self.add_pin("bank_wmask_{}".format(bit), "INPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def route_layout(self): """ Create routing among the modules """ @@ -159,8 +171,8 @@ class port_data(design.design): """ Propagate all vdd/gnd pins up to this level for all modules """ for inst in self.insts: - self.copy_power_pins(inst,"vdd") - self.copy_power_pins(inst,"gnd") + self.copy_power_pins(inst, "vdd") + self.copy_power_pins(inst, "gnd") def add_modules(self): @@ -180,7 +192,6 @@ class port_data(design.design): else: self.sense_amp_array = None - if self.col_addr_size > 0: self.column_mux_array = factory.create(module_type="column_mux_array", columns=self.num_cols, @@ -191,7 +202,6 @@ class port_data(design.design): else: self.column_mux_array = None - if self.port in self.write_ports: self.write_driver_array = factory.create(module_type="write_driver_array", columns=self.num_cols, @@ -221,29 +231,27 @@ class port_data(design.design): else: self.num_col_addr_lines = 0 - # A space for wells or jogging m2 between modules - self.m2_gap = max(2*drc("pwell_to_nwell") + drc("nwell_enclose_active"), - 3*self.m2_pitch) + self.m2_gap = max(2 * drc("pwell_to_nwell") + drc("nwell_enclose_active"), + 3 * self.m2_pitch) - # create arrays of bitline and bitline_bar names for read, write, or all ports + # create arrays of bitline and bitline_bar names for read, + # write, or all ports self.bitcell = factory.create(module_type="bitcell") self.bl_names = self.bitcell.get_all_bl_names() self.br_names = self.bitcell.get_all_br_names() self.wl_names = self.bitcell.get_all_wl_names() # used for bl/br names self.precharge = factory.create(module_type="precharge", - bitcell_bl = self.bl_names[0], - bitcell_br = self.br_names[0]) + 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]) - - + 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 """ @@ -253,8 +261,8 @@ class port_data(design.design): self.precharge_array_inst = self.add_inst(name="precharge_array{}".format(self.port), mod=self.precharge_array) - bl_name = self.precharge_array.get_bl_name(self.port) - br_name = self.precharge_array.get_br_name(self.port) + bl_name = self.get_bl_name(self.port) + br_name = self.get_br_name(self.port) temp = [] # Use left BLs for RBL @@ -272,21 +280,19 @@ class port_data(design.design): temp.extend(["p_en_bar", "vdd"]) self.connect_inst(temp) - def place_precharge_array(self, offset): """ Placing Precharge """ self.precharge_array_inst.place(offset=offset, mirror="MX") - def create_column_mux_array(self): """ Creating Column Mux when words_per_row > 1 . """ self.column_mux_array_inst = self.add_inst(name="column_mux_array{}".format(self.port), mod=self.column_mux_array) - bl_name = self.column_mux_array.get_bl_name(self.port) - br_name = self.column_mux_array.get_br_name(self.port) + bl_name = self.get_bl_name(self.port) + br_name = self.get_br_name(self.port) temp = [] for col in range(self.num_cols): temp.append("{0}_{1}".format(bl_name, col)) @@ -301,7 +307,6 @@ class port_data(design.design): temp.append("gnd") self.connect_inst(temp) - def place_column_mux_array(self, offset): """ Placing Column Mux when words_per_row > 1 . """ if self.col_addr_size == 0: @@ -309,14 +314,13 @@ class port_data(design.design): self.column_mux_array_inst.place(offset=offset, mirror="MX") - def create_sense_amp_array(self): """ Creating Sense amp """ self.sense_amp_array_inst = self.add_inst(name="sense_amp_array{}".format(self.port), mod=self.sense_amp_array) - bl_name = self.sense_amp_array.get_bl_name(self.port) - br_name = self.sense_amp_array.get_br_name(self.port) + bl_name = self.get_bl_name(self.port) + br_name = self.get_br_name(self.port) temp = [] for bit in range(self.word_size): temp.append("dout_{}".format(bit)) @@ -327,22 +331,19 @@ class port_data(design.design): temp.append("{0}_out_{1}".format(bl_name, bit)) temp.append("{0}_out_{1}".format(br_name, bit)) - temp.extend(["s_en", "vdd", "gnd"]) self.connect_inst(temp) - def place_sense_amp_array(self, offset): """ Placing Sense amp """ self.sense_amp_array_inst.place(offset=offset, mirror="MX") - def create_write_driver_array(self): """ Creating Write Driver """ self.write_driver_array_inst = self.add_inst(name="write_driver_array{}".format(self.port), mod=self.write_driver_array) - bl_name = self.write_driver_array.get_bl_name(self.port) - br_name = self.write_driver_array.get_br_name(self.port) + bl_name = self.get_bl_name(self.port) + br_name = self.get_br_name(self.port) temp = [] for bit in range(self.word_size): @@ -365,12 +366,10 @@ class port_data(design.design): self.connect_inst(temp) - def place_write_driver_array(self, offset): """ Placing Write Driver """ self.write_driver_array_inst.place(offset=offset, mirror="MX") - def create_write_mask_and_array(self): """ Creating Write Mask AND Array """ self.write_mask_and_array_inst = self.add_inst(name="write_mask_and_array{}".format(self.port), @@ -385,12 +384,10 @@ class port_data(design.design): temp.extend(["vdd", "gnd"]) self.connect_inst(temp) - def place_write_mask_and_array(self, offset): """ Placing Write Mask AND array """ self.write_mask_and_array_inst.place(offset=offset, mirror="MX") - def compute_instance_offsets(self): """ Compute the empty instance offsets for port0 and port1 (if needed) @@ -445,19 +442,17 @@ class port_data(design.design): if self.column_mux_offset: self.place_column_mux_array(self.column_mux_offset) - def route_sense_amp_out(self, port): """ Add pins for the sense amp output """ for bit in range(self.word_size): 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, + layer=data_pin.layer, offset=data_pin.center(), height=data_pin.height(), width=data_pin.width()) - def route_write_driver_in(self, port): """ Connecting write driver """ @@ -466,7 +461,6 @@ class port_data(design.design): din_name = "din_{}".format(row) self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name) - def route_write_mask_and_array_in(self, port): """ Add pins for the write mask and array input """ @@ -475,7 +469,6 @@ 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 """ @@ -498,15 +491,15 @@ class port_data(design.design): 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.") + 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 - + 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()) + middle_pos = vector(length, wmask_out_pin.cy()) end_pos = vector(length, wdriver_en_pin.cy()) # Add via for the write driver array's enable input @@ -516,21 +509,22 @@ class port_data(design.design): # Route between write mask AND array and write driver array self.add_wire(self.m1_stack, [beg_pos, middle_pos, end_pos]) - def route_column_mux_to_precharge_array(self, port): """ Routing of BL and BR between col mux and precharge array """ # Only do this if we have a column mux! if self.col_addr_size==0: return - + inst1 = self.column_mux_array_inst inst2 = self.precharge_array_inst - if self.port==0: - self.connect_bitlines(inst1, inst2, self.num_cols, inst2_start_bit=1) - else: - self.connect_bitlines(inst1, inst2, self.num_cols) + insn2_start_bit = 1 if self.port == 0 else 0 + + self.connect_bitlines(inst1=inst1, + inst2=inst2, + num_bits=self.num_cols, + inst2_start_bit=insn2_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 """ @@ -539,47 +533,46 @@ class port_data(design.design): if self.col_addr_size>0: # Sense amp is connected to the col mux inst1 = self.column_mux_array_inst - inst1_bl_name = "bl_out_{}" - inst1_br_name = "br_out_{}" + inst1_bls_templ = "{inst}_out_{bit}" start_bit = 0 else: # Sense amp is directly connected to the precharge array inst1 = self.precharge_array_inst - inst1_bl_name = "bl_{}" - inst1_br_name = "br_{}" + inst1_bls_templ="{inst}_{bit}" + if self.port==0: start_bit=1 else: start_bit=0 - - self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size, - inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name, inst1_start_bit=start_bit) - + self.channel_route_bitlines(inst1=inst1, + inst1_bls_template=inst1_bls_templ, + inst2=inst2, + num_bits=self.word_size, + 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 - + if self.col_addr_size>0: # Write driver is connected to the col mux inst1 = self.column_mux_array_inst - inst1_bl_name = "bl_out_{}" - inst1_br_name = "br_out_{}" + inst1_bls_templ = "{inst}_out_{bit}" start_bit = 0 else: # Sense amp is directly connected to the precharge array inst1 = self.precharge_array_inst - inst1_bl_name = "bl_{}" - inst1_br_name = "br_{}" + inst1_bls_templ="{inst}_{bit}" if self.port==0: start_bit=1 else: start_bit=0 - - self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size, - inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name, inst1_start_bit=start_bit) + self.channel_route_bitlines(inst1=inst1, inst2=inst2, + num_bits=self.word_size, + 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 """ @@ -589,8 +582,9 @@ class port_data(design.design): # 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) - + self.channel_route_bitlines(inst1=inst1, + inst2=inst2, + num_bits=self.word_size) def route_bitline_pins(self): """ Add the bitline pins for the given port """ @@ -609,12 +603,15 @@ class port_data(design.design): for bit in range(self.num_cols): if self.precharge_array_inst: - self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(bit+bit_offset), "bl_{}".format(bit)) - self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(bit+bit_offset), "br_{}".format(bit)) + self.copy_layout_pin(self.precharge_array_inst, + "bl_{}".format(bit + bit_offset), + "bl_{}".format(bit)) + self.copy_layout_pin(self.precharge_array_inst, + "br_{}".format(bit + bit_offset), + "br_{}".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 """ if self.precharge_array_inst: @@ -635,65 +632,107 @@ class port_data(design.design): if self.write_mask_and_array_inst: self.copy_layout_pin(self.write_mask_and_array_inst, "en", "w_en") - - - def channel_route_bitlines(self, inst1, inst2, num_bits, - inst1_bl_name="bl_{}", inst1_br_name="br_{}", inst1_start_bit=0, - inst2_bl_name="bl_{}", inst2_br_name="br_{}", inst2_start_bit=0): + def _group_bitline_instances(self, inst1, inst2, num_bits, + inst1_bls_template, + inst1_start_bit, + inst2_bls_template, + inst2_start_bit): """ - Route the bl and br of two modules using the channel router. + Groups all the parameters into a named tuple and seperates them into + top and bottom instances. """ - + inst_group = namedtuple('InstanceGroup', ('inst', 'bls_template', + 'bl_name', 'br_name', 'start_bit')) + + inst1_group = inst_group(inst1, inst1_bls_template, + inst1.mod.get_bl_name(), + inst1.mod.get_br_name(), + inst1_start_bit) + inst2_group = inst_group(inst2, inst2_bls_template, + inst2.mod.get_bl_name(), + inst2.mod.get_br_name(), + inst2_start_bit) # determine top and bottom automatically. # since they don't overlap, we can just check the bottom y coordinate. if inst1.by() < inst2.by(): - (bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit) - (top_inst, top_bl_name, top_br_name, top_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit) + bot_inst_group = inst1_group + top_inst_group = inst2_group else: - (bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit) - (top_inst, top_bl_name, top_br_name, top_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit) + bot_inst_group = inst2_group + top_inst_group = inst1_group + return (bot_inst_group, top_inst_group) + + def _get_bitline_pins(self, inst_group, bit): + """ + Extracts bl/br pins from an InstanceGroup based on the bit modifier. + """ + full_bl_name = inst_group.bls_template.format( + **{'inst': inst_group.bl_name, + 'bit': inst_group.start_bit + bit} + ) + full_br_name = inst_group.bls_template.format( + **{'inst': inst_group.br_name, + 'bit': inst_group.start_bit + bit} + ) + return (inst_group.inst.get_pin(full_bl_name), + inst_group.inst.get_pin(full_br_name)) + + def channel_route_bitlines(self, inst1, inst2, num_bits, + inst1_bls_template="{inst}_{bit}", + inst1_start_bit=0, + inst2_bls_template="{inst}_{bit}", + inst2_start_bit=0): + """ + 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) # 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! - offset = bottom_inst.ul() + vector(0,self.m1_pitch) + offset = bot_inst_group.inst.ul() + vector(0, self.m1_pitch) for bit in range(num_bits): - bottom_names = [bottom_inst.get_pin(bottom_bl_name.format(bit+bottom_start_bit)), bottom_inst.get_pin(bottom_br_name.format(bit+bottom_start_bit))] - top_names = [top_inst.get_pin(top_bl_name.format(bit+top_start_bit)), top_inst.get_pin(top_br_name.format(bit+top_start_bit))] + bottom_names = self._get_bitline_pins(bot_inst_group, bit) + top_names = self._get_bitline_pins(top_inst_group, bit) + route_map = list(zip(bottom_names, top_names)) self.create_horizontal_channel_route(route_map, offset, self.m1_stack) - def connect_bitlines(self, inst1, inst2, num_bits, - inst1_bl_name="bl_{}", inst1_br_name="br_{}", inst1_start_bit=0, - inst2_bl_name="bl_{}", inst2_br_name="br_{}", inst2_start_bit=0): - + inst1_bls_template="{inst}_{bit}", + inst1_start_bit=0, + inst2_bls_template="{inst}_{bit}", + inst2_start_bit=0): """ Connect the bl and br of two modules. This assumes that they have sufficient space to create a jog in the middle between the two modules (if needed). """ - # determine top and bottom automatically. - # since they don't overlap, we can just check the bottom y coordinate. - if inst1.by() < inst2.by(): - (bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit) - (top_inst, top_bl_name, top_br_name, top_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit) - else: - (bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit) - (top_inst, top_bl_name, top_br_name, top_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_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): - bottom_bl = bottom_inst.get_pin(bottom_bl_name.format(col+bottom_start_bit)).uc() - bottom_br = bottom_inst.get_pin(bottom_br_name.format(col+bottom_start_bit)).uc() - top_bl = top_inst.get_pin(top_bl_name.format(col+top_start_bit)).bc() - top_br = top_inst.get_pin(top_br_name.format(col+top_start_bit)).bc() + bot_bl_pin, bot_br_pin = self._get_bitline_pins(bot_inst_group, col) + top_bl_pin, top_br_pin = self._get_bitline_pins(top_inst_group, col) + 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+bottom_bl.y) - self.add_path("m2",[bottom_bl, vector(bottom_bl.x,yoffset), - vector(top_bl.x,yoffset), top_bl]) - self.add_path("m2",[bottom_br, vector(bottom_br.x,yoffset), - vector(top_br.x,yoffset), top_br]) + 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]) def graph_exclude_precharge(self): """Precharge adds a loop between bitlines, can be excluded to reduce complexity""" diff --git a/compiler/modules/precharge_array.py b/compiler/modules/precharge_array.py index 5ac97b4d..d5b0e799 100644 --- a/compiler/modules/precharge_array.py +++ b/compiler/modules/precharge_array.py @@ -12,6 +12,7 @@ from vector import vector from sram_factory import factory from globals import OPTS + class precharge_array(design.design): """ Dynamically generated precharge array of all bitlines. Cols is number @@ -32,20 +33,13 @@ class precharge_array(design.design): if not OPTS.netlist_only: self.create_layout() - def get_bl_name(self, port=0): + def get_bl_name(self): bl_name = self.pc_cell.get_bl_names() - if len(self.all_ports) == 1: - return bl_name - else: - return bl_name + "{}".format(port) + return bl_name - def get_br_name(self, port=0): + def get_br_name(self): br_name = self.pc_cell.get_br_names() - if len(self.all_ports) == 1: - return br_name - else: - return br_name + "{}".format(port) - + return br_name def add_pins(self): """Adds pins for spice file""" @@ -75,11 +69,8 @@ class precharge_array(design.design): size=self.size, bitcell_bl=self.bitcell_bl, bitcell_br=self.bitcell_br) - - self.add_mod(self.pc_cell) - def add_layout_pins(self): self.add_layout_pin(text="en_bar", @@ -93,20 +84,9 @@ class precharge_array(design.design): for i in range(len(self.local_insts)): inst = self.local_insts[i] - bl_pin = inst.get_pin("bl") - self.add_layout_pin(text="bl_{0}".format(i), - layer="m2", - offset=bl_pin.ll(), - width=drc("minwidth_m2"), - height=bl_pin.height()) - br_pin = inst.get_pin("br") - self.add_layout_pin(text="br_{0}".format(i), - layer="m2", - offset=br_pin.ll(), - width=drc("minwidth_m2"), - height=bl_pin.height()) + self.copy_layout_pin(inst, "bl", "bl_{0}".format(i)) + self.copy_layout_pin(inst, "br", "br_{0}".format(i)) - def create_insts(self): """Creates a precharge array by horizontally tiling the precharge cell""" self.local_insts = [] @@ -119,7 +99,6 @@ class precharge_array(design.design): self.local_insts.append(inst) self.connect_inst(["bl_{0}".format(i), "br_{0}".format(i), "en_bar", "vdd"]) - def place_insts(self): """ Places precharge array by horizontally tiling the precharge cell""" from tech import cell_properties @@ -137,8 +116,11 @@ class precharge_array(design.design): xoffset = xoffset + self.pc_cell.width def get_en_cin(self): - """Get the relative capacitance of all the clk connections in the precharge array""" - #Assume single port + """ + Get the relative capacitance of all the clk connections + in the precharge array + """ + # Assume single port precharge_en_cin = self.pc_cell.get_en_cin() - return precharge_en_cin*self.columns + return precharge_en_cin * self.columns diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py index eb624111..ff5638ba 100644 --- a/compiler/modules/sense_amp.py +++ b/compiler/modules/sense_amp.py @@ -9,6 +9,7 @@ import design import debug import utils from tech import GDS,layer, parameter,drc +from tech import cell_properties as props from globals import OPTS import logical_effort @@ -19,8 +20,12 @@ class sense_amp(design.design): the technology library. Sense amplifier to read a pair of bit-lines. """ - - pin_names = ["bl", "br", "dout", "en", "vdd", "gnd"] + pin_names = [props.sense_amp.pin.bl, + props.sense_amp.pin.br, + props.sense_amp.pin.dout, + props.sense_amp.pin.en, + props.sense_amp.pin.vdd, + 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"]) @@ -30,10 +35,18 @@ class sense_amp(design.design): pin_map = [] def get_bl_names(self): - return "bl" + return props.sense_amp.pin.bl def get_br_names(self): - return "br" + return props.sense_amp.pin.br + + @property + def dout_name(self): + return props.sense_amp.pin.dout + + @property + def en_name(self): + return props.sense_amp.pin.en def __init__(self, name): design.design.__init__(self, name) @@ -79,11 +92,10 @@ class sense_amp(design.design): def get_enable_name(self): """Returns name used for enable net""" #FIXME: A better programmatic solution to designate pins - enable_name = "en" + 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): """Adds edges based on inputs/outputs. Overrides base class function.""" self.add_graph_edges(graph, port_nets) - \ No newline at end of file diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index 8ac5146c..322cc1b3 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -33,20 +33,21 @@ class sense_amp_array(design.design): if not OPTS.netlist_only: self.create_layout() + def get_bl_name(self): + bl_name = "bl" + return bl_name - def get_bl_name(self, port=0): - bl_name = self.amp.get_bl_names() - if len(self.all_ports) == 1: - return bl_name - else: - return bl_name + "{}".format(port) + def get_br_name(self): + br_name = "br" + return br_name - def get_br_name(self, port=0): - br_name = self.amp.get_br_names() - if len(self.all_ports) == 1: - return br_name - else: - return br_name + "{}".format(port) + @property + def data_name(self): + return "data" + + @property + def en_name(self): + return "en" def create_netlist(self): self.add_modules() @@ -69,10 +70,10 @@ class sense_amp_array(design.design): def add_pins(self): for i in range(0,self.word_size): - self.add_pin("data_{0}".format(i), "OUTPUT") - self.add_pin("bl_{0}".format(i), "INPUT") - self.add_pin("br_{0}".format(i), "INPUT") - self.add_pin("en", "INPUT") + 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") + self.add_pin(self.en_name, "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") @@ -92,10 +93,10 @@ class sense_amp_array(design.design): name = "sa_d{0}".format(i) self.local_insts.append(self.add_inst(name=name, mod=self.amp)) - self.connect_inst(["bl_{0}".format(i), - "br_{0}".format(i), - "data_{0}".format(i), - "en", "vdd", "gnd"]) + self.connect_inst([self.get_bl_name() + "_{0}".format(i), + self.get_br_name() + "_{0}".format(i), + self.data_name + "_{0}".format(i), + self.en_name, "vdd", "gnd"]) def place_sense_amp_array(self): from tech import cell_properties @@ -135,22 +136,22 @@ class sense_amp_array(design.design): start_layer="m2", vertical=True) - bl_pin = inst.get_pin("bl") - br_pin = inst.get_pin("br") - dout_pin = inst.get_pin("dout") - - self.add_layout_pin(text="bl_{0}".format(i), + bl_pin = inst.get_pin(inst.mod.get_bl_names()) + br_pin = inst.get_pin(inst.mod.get_br_names()) + dout_pin = inst.get_pin(inst.mod.dout_name) + + self.add_layout_pin(text=self.get_bl_name() + "_{0}".format(i), layer="m2", offset=bl_pin.ll(), width=bl_pin.width(), height=bl_pin.height()) - self.add_layout_pin(text="br_{0}".format(i), + self.add_layout_pin(text=self.get_br_name() + "_{0}".format(i), layer="m2", offset=br_pin.ll(), width=br_pin.width(), height=br_pin.height()) - - self.add_layout_pin(text="data_{0}".format(i), + + self.add_layout_pin(text=self.data_name + "_{0}".format(i), layer="m2", offset=dout_pin.ll(), width=dout_pin.width(), @@ -159,8 +160,8 @@ class sense_amp_array(design.design): def route_rails(self): # add sclk rail across entire array - sclk_offset = self.amp.get_pin("en").ll().scale(0,1) - self.add_layout_pin(text="en", + sclk_offset = self.amp.get_pin(self.amp.en_name).ll().scale(0,1) + self.add_layout_pin(text=self.en_name, layer="m1", offset=sclk_offset, width=self.width, diff --git a/compiler/modules/single_level_column_mux_array.py b/compiler/modules/single_level_column_mux_array.py index 1c181574..324b7415 100644 --- a/compiler/modules/single_level_column_mux_array.py +++ b/compiler/modules/single_level_column_mux_array.py @@ -37,20 +37,13 @@ class single_level_column_mux_array(design.design): if not OPTS.netlist_only: self.create_layout() - def get_bl_name(self, port=0): + def get_bl_name(self): bl_name = self.mux.get_bl_names() - if len(self.all_ports) == 1: - return bl_name - else: - return bl_name + "{}".format(port) + return bl_name def get_br_name(self, port=0): br_name = self.mux.get_br_names() - if len(self.all_ports) == 1: - return br_name - else: - return br_name + "{}".format(port) - + return br_name def create_netlist(self): self.add_modules() diff --git a/compiler/modules/wordline_driver.py b/compiler/modules/wordline_driver.py index 27671fd2..d84457ba 100644 --- a/compiler/modules/wordline_driver.py +++ b/compiler/modules/wordline_driver.py @@ -75,8 +75,7 @@ class wordline_driver(design.design): """ # Find the x offsets for where the vias/pins should be placed - a_xoffset = self.nand_inst[0].rx() - b_xoffset = self.inv2_inst[0].lx() + a_xoffset = self.nand_inst[0].lx() for num in range(self.rows): # this will result in duplicate polygons for rails, but who cares @@ -90,7 +89,7 @@ class wordline_driver(design.design): supply_pin = self.inv2_inst[num].get_pin(name) # Add pins in two locations - for xoffset in [a_xoffset, b_xoffset]: + for xoffset in [a_xoffset]: pin_pos = vector(xoffset, supply_pin.cy()) self.add_power_pin(name, pin_pos) @@ -116,7 +115,7 @@ class wordline_driver(design.design): "vdd", "gnd"]) def place_drivers(self): - nand2_xoffset = 2*self.m1_width + 5*self.m1_space + nand2_xoffset = 2 * self.m1_width + 5 * self.m1_space inv2_xoffset = nand2_xoffset + self.nand2.width self.width = inv2_xoffset + self.inv.width @@ -124,10 +123,10 @@ class wordline_driver(design.design): for row in range(self.rows): if (row % 2): - y_offset = self.inv.height*(row + 1) + y_offset = self.inv.height * (row + 1) inst_mirror = "MX" else: - y_offset = self.inv.height*row + y_offset = self.inv.height * row inst_mirror = "R0" nand2_offset = [nand2_xoffset, y_offset] @@ -169,8 +168,8 @@ class wordline_driver(design.design): zr_pos = nand_inst.get_pin("Z").rc() al_pos = inv2_inst.get_pin("A").lc() # ensure the bend is in the middle - mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y) - mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y) + mid1_pos = vector(0.5 * (zr_pos.x + al_pos.x), zr_pos.y) + mid2_pos = vector(0.5 * (zr_pos.x + al_pos.x), al_pos.y) self.add_path("m1", [zr_pos, mid1_pos, mid2_pos, al_pos]) # connect the decoder input pin to nand2 B @@ -181,7 +180,7 @@ class wordline_driver(design.design): up_or_down = self.m2_space if row % 2 else -self.m2_space input_offset = vector(0, b_pos.y + up_or_down) base_offset = vector(clk_offset.x, input_offset.y) - contact_offset = vector(0.5 * self.m2_width + self.m2_space + 0.5 * contact.m1_via.width, 0) + contact_offset = vector(0.5 * self.m2_width + self.m2_space + 0.5 * contact.m1_via.width, 0) mid_via_offset = base_offset + contact_offset # must under the clk line in M1 @@ -208,7 +207,7 @@ class wordline_driver(design.design): end=wl_offset - vector(self.m1_width, 0)) def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True): - """ + """ Follows the clk_buf to a wordline signal adding each stages stage effort to a list. """ @@ -225,7 +224,7 @@ class wordline_driver(design.design): return stage_effort_list def get_wl_en_cin(self): - """ + """ Get the relative capacitance of all the enable connections in the bank """ diff --git a/compiler/modules/write_driver.py b/compiler/modules/write_driver.py index db08bcf4..9afac81b 100644 --- a/compiler/modules/write_driver.py +++ b/compiler/modules/write_driver.py @@ -10,6 +10,7 @@ import design import utils from globals import OPTS from tech import GDS,layer +from tech import cell_properties as props class write_driver(design.design): """ @@ -19,7 +20,13 @@ class write_driver(design.design): the technology library. """ - pin_names = ["din", "bl", "br", "en", "vdd", "gnd"] + pin_names = [props.write_driver.pin.din, + props.write_driver.pin.bl, + props.write_driver.pin.br, + props.write_driver.pin.en, + props.write_driver.pin.vdd, + props.write_driver.pin.gnd] + type_list = ["INPUT", "OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] if not OPTS.netlist_only: (width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"]) @@ -38,10 +45,18 @@ class write_driver(design.design): self.add_pin_types(self.type_list) def get_bl_names(self): - return "bl" + return props.write_driver.pin.bl def get_br_names(self): - return "br" + return props.write_driver.pin.br + + @property + def din_name(self): + return props.write_driver.pin.din + + @property + def en_name(self): + return props.write_driver.pin.en def get_w_en_cin(self): """Get the relative capacitance of a single input""" diff --git a/compiler/modules/write_driver_array.py b/compiler/modules/write_driver_array.py index dd7430a9..08a2e007 100644 --- a/compiler/modules/write_driver_array.py +++ b/compiler/modules/write_driver_array.py @@ -37,19 +37,21 @@ class write_driver_array(design.design): if not OPTS.netlist_only: self.create_layout() - def get_bl_name(self, port=0): - bl_name = self.driver.get_bl_names() - if len(self.all_ports) == 1: - return bl_name - else: - return bl_name + "{}".format(port) + def get_bl_name(self): + bl_name = "bl" + return bl_name - def get_br_name(self, port=0): - br_name = self.driver.get_br_names() - if len(self.all_ports) == 1: - return br_name - else: - return br_name + "{}".format(port) + def get_br_name(self): + br_name = "br" + return br_name + + @property + def data_name(self): + return "data" + + @property + def en_name(self): + return "en" def create_netlist(self): self.add_modules() @@ -71,15 +73,15 @@ class write_driver_array(design.design): def add_pins(self): for i in range(self.word_size): - self.add_pin("data_{0}".format(i), "INPUT") - for i in range(self.word_size): - self.add_pin("bl_{0}".format(i), "OUTPUT") - self.add_pin("br_{0}".format(i), "OUTPUT") + self.add_pin(self.data_name + "_{0}".format(i), "INPUT") + for i in range(self.word_size): + 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): - self.add_pin("en_{0}".format(i), "INPUT") + self.add_pin(self.en_name + "_{0}".format(i), "INPUT") else: - self.add_pin("en", "INPUT") + self.add_pin(self.en_name, "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") @@ -102,20 +104,20 @@ class write_driver_array(design.design): mod=self.driver) if self.write_size: - self.connect_inst(["data_{0}".format(index), - "bl_{0}".format(index), - "br_{0}".format(index), - "en_{0}".format(windex), "vdd", "gnd"]) + 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(windex), "vdd", "gnd"]) w+=1 # when w equals write size, the next en pin can be connected since we are now at the next wmask bit if w == self.write_size: w = 0 windex+=1 else: - self.connect_inst(["data_{0}".format(index), - "bl_{0}".format(index), - "br_{0}".format(index), - "en", "vdd", "gnd"]) + 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"]) def place_write_array(self): @@ -140,21 +142,22 @@ class write_driver_array(design.design): def add_layout_pins(self): for i in range(self.word_size): - din_pin = self.driver_insts[i].get_pin("din") - self.add_layout_pin(text="data_{0}".format(i), + 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), layer="m2", offset=din_pin.ll(), width=din_pin.width(), height=din_pin.height()) - bl_pin = self.driver_insts[i].get_pin("bl") - self.add_layout_pin(text="bl_{0}".format(i), + bl_pin = inst.get_pin(inst.mod.get_bl_names()) + self.add_layout_pin(text=self.get_bl_name() + "_{0}".format(i), layer="m2", offset=bl_pin.ll(), width=bl_pin.width(), height=bl_pin.height()) - - br_pin = self.driver_insts[i].get_pin("br") - self.add_layout_pin(text="br_{0}".format(i), + + br_pin = inst.get_pin(inst.mod.get_br_names()) + self.add_layout_pin(text=self.get_br_name() + "_{0}".format(i), layer="m2", offset=br_pin.ll(), width=br_pin.width(), @@ -169,7 +172,8 @@ class write_driver_array(design.design): start_layer = "m2") if self.write_size: for bit in range(self.num_wmasks): - en_pin = self.driver_insts[bit*self.write_size].get_pin("en") + inst = self.driver_insts[bit*self.write_size] + en_pin = inst.get_pin(inst.mod.en_name) # Determine width of wmask modified en_pin with/without col mux wmask_en_len = self.words_per_row*(self.write_size * self.driver_spacing) if (self.words_per_row == 1): @@ -177,15 +181,16 @@ class write_driver_array(design.design): else: en_gap = self.driver_spacing - self.add_layout_pin(text="en_{0}".format(bit), + self.add_layout_pin(text=self.en_name + "_{0}".format(bit), layer=en_pin.layer, offset=en_pin.ll(), width=wmask_en_len-en_gap, height=en_pin.height()) else: - self.add_layout_pin(text="en", + inst = self.driver_insts[0] + self.add_layout_pin(text=self.en_name, layer="m1", - offset=self.driver_insts[0].get_pin("en").ll().scale(0,1), + offset=inst.get_pin(inst.mod.en_name).ll().scale(0,1), width=self.width) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 6d385d09..7693fa07 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -26,8 +26,8 @@ class pgate(design.design): if height: self.height = height elif not height: - # By default, we make it 8 M1 pitch tall - self.height = 8*self.m1_pitch + # By default, we make it 10 M1 pitch tall + self.height = 10*self.m1_pitch self.create_netlist() if not OPTS.netlist_only: @@ -103,7 +103,7 @@ class pgate(design.design): - vector(0.5 * contact_width - 0.5 * self.poly_width, 0) elif position == "right": contact_offset = left_gate_offset \ - + vector(0.5 * contact.width + 0.5 * self.poly_width, 0) + + vector(0.5 * contact_width + 0.5 * self.poly_width, 0) else: debug.error("Invalid contact placement option.", -1) @@ -128,16 +128,16 @@ class pgate(design.design): """ Extend the n/p wells to cover whole cell """ # This should match the cells in the cell library - nwell_y_offset = 0.48 * self.height - full_height = self.height + 0.5*self.m1_width + self.nwell_y_offset = 0.48 * self.height + full_height = self.height + 0.5* self.m1_width # FIXME: float rounding problem if "nwell" in layer: # Add a rail width to extend the well to the top of the rail nwell_max_offset = max(self.find_highest_layer_coords("nwell").y, full_height) - nwell_position = vector(0, nwell_y_offset) - vector(self.well_extend_active, 0) - nwell_height = nwell_max_offset - nwell_y_offset + nwell_position = vector(0, self.nwell_y_offset) - vector(self.well_extend_active, 0) + nwell_height = nwell_max_offset - self.nwell_y_offset self.add_rect(layer="nwell", offset=nwell_position, width=self.well_width, @@ -153,7 +153,7 @@ 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 = nwell_y_offset - pwell_position.y + pwell_height = self.nwell_y_offset - pwell_position.y self.add_rect(layer="pwell", offset=pwell_position, width=self.well_width, @@ -264,3 +264,12 @@ class pgate(design.design): # offset=implant_offset, # width=implant_width, # height=implant_height) + + def determine_width(self): + """ Determine the width based on the well contacts (assumed to be on the right side) """ + # 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. + + diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index c0f1f955..3c2e41a2 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -51,9 +51,9 @@ class pinv(pgate.pgate): def create_layout(self): """ Calls all functions related to the generation of the layout """ - self.setup_layout_constants() self.place_ptx() self.add_well_contacts() + self.determine_width() self.extend_wells() self.route_supply_rails() self.connect_rails() @@ -147,21 +147,6 @@ class pinv(pgate.pgate): debug.check(self.pmos_width >= drc("minwidth_tx"), "Cannot finger PMOS transistors to fit cell height.") - def setup_layout_constants(self): - """ - Compute the width and height - """ - - # the width is determined the multi-finger PMOS device width plus - # the well contact width, spacing between them - # space is for power supply contact to nwell m1 spacing - self.width = self.pmos.active_offset.x + self.pmos.active_width \ - + self.active_space + contact.nwell_contact.width \ - + 0.5 * self.nwell_enclose_active \ - + self.m1_space - # This includes full enclosures on each end - self.well_width = self.width + 2*self.nwell_enclose_active - # Height is an input parameter, so it is not recomputed. def add_ptx(self): """ Create the PMOS and NMOS transistors. """ diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index dd04ccb9..8ecc115e 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -49,10 +49,11 @@ class pnand2(pgate.pgate): """ Calls all functions related to the generation of the layout """ self.setup_layout_constants() - self.route_supply_rails() self.place_ptx() - self.connect_rails() self.add_well_contacts() + self.determine_width() + self.route_supply_rails() + self.connect_rails() self.extend_wells() self.route_inputs() self.route_output() @@ -96,15 +97,6 @@ class pnand2(pgate.pgate): # source and drain pins self.overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll() - # Two PMOS devices and a well contact. Separation between each. - # Enclosure space on the sides. - self.width = 2 * self.pmos.active_width + contact.active_contact.width \ - + 2 * self.active_space \ - + 0.5 * self.nwell_enclose_active - - self.well_width = self.width + 2 * self.nwell_enclose_active - # Height is an input parameter, so it is not recomputed. - # This is the extra space needed to ensure DRC rules # to the active contacts extra_contact_space = max(-self.nmos.get_pin("D").by(), 0) @@ -190,16 +182,16 @@ class pnand2(pgate.pgate): def route_inputs(self): """ Route the A and B inputs """ - inputB_yoffset = self.nmos2_pos.y + self.nmos.active_height \ - + self.m2_space + 0.5 * self.m2_width + inputB_yoffset = self.nmos2_inst.uy() + 0.5 * contact.poly_contact.height self.route_input_gate(self.pmos2_inst, self.nmos2_inst, inputB_yoffset, "B", - position="center") + position="right") # This will help with the wells and the input/output placement - self.inputA_yoffset = inputB_yoffset + self.input_spacing + self.inputA_yoffset = self.pmos2_inst.by() - self.poly_extend_active \ + - contact.poly_contact.height self.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.inputA_yoffset, @@ -242,8 +234,8 @@ class pnand2(pgate.pgate): self.add_layout_pin_rect_center(text="Z", layer="m1", offset=out_offset, - width=contact.m1_via.first_layer_height, - height=contact.m1_via.first_layer_width) + width=contact.m1_via.first_layer_width, + height=contact.m1_via.first_layer_height) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 9ec9ff60..927ac6fd 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -36,7 +36,8 @@ class pnand3(pgate.pgate): self.pmos_width = self.pmos_size * drc("minwidth_tx") # FIXME: Allow these to be sized - debug.check(size == 1,"Size 1 pnand3 is only supported now.") + debug.check(size == 1, + "Size 1 pnand3 is only supported now.") self.tx_mults = 1 # Creates the netlist and layout @@ -57,14 +58,15 @@ class pnand3(pgate.pgate): """ Calls all functions related to the generation of the layout """ self.setup_layout_constants() - self.route_supply_rails() self.place_ptx() - self.connect_rails() self.add_well_contacts() + self.determine_width() + self.route_supply_rails() + self.connect_rails() self.extend_wells() self.route_inputs() self.route_output() - + def add_ptx(self): """ Create the PMOS and NMOS transistors. """ self.nmos = factory.create(module_type="ptx", @@ -87,16 +89,9 @@ class pnand3(pgate.pgate): """ Pre-compute some handy layout parameters. """ # Compute the overlap of the source and drain pins - self.overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll() - - # Two PMOS devices and a well contact. Separation between each. - # Enclosure space on the sides. - self.width = 3 * self.pmos.active_width + self.pmos.active_contact.width \ - + 2 * self.active_space + 0.5 * self.nwell_enclose_active \ - - self.overlap_offset.x - self.well_width = self.width + 2 * self.nwell_enclose_active - # Height is an input parameter, so it is not recomputed. - + overlap_xoffset = self.pmos.get_pin("D").ll().x - self.pmos.get_pin("S").ll().x + self.ptx_offset = vector(overlap_xoffset, 0) + # This is the extra space needed to ensure DRC rules # to the active contacts nmos = factory.create(module_type="ptx", tx_type="nmos") @@ -153,30 +148,25 @@ class pnand3(pgate.pgate): """ pmos1_pos = vector(self.pmos.active_offset.x, - self.height - self.pmos.active_height \ - - self.top_bottom_space) + self.height - self.pmos.active_height - self.top_bottom_space) self.pmos1_inst.place(pmos1_pos) - pmos2_pos = pmos1_pos + self.overlap_offset + pmos2_pos = pmos1_pos + self.ptx_offset self.pmos2_inst.place(pmos2_pos) - self.pmos3_pos = pmos2_pos + self.overlap_offset + self.pmos3_pos = pmos2_pos + self.ptx_offset self.pmos3_inst.place(self.pmos3_pos) - nmos1_pos = vector(self.pmos.active_offset.x, self.top_bottom_space) self.nmos1_inst.place(nmos1_pos) - nmos2_pos = nmos1_pos + self.overlap_offset + nmos2_pos = nmos1_pos + self.ptx_offset self.nmos2_inst.place(nmos2_pos) - self.nmos3_pos = nmos2_pos + self.overlap_offset + self.nmos3_pos = nmos2_pos + self.ptx_offset self.nmos3_inst.place(self.nmos3_pos) - # This will help with the wells and the input/output placement - self.output_pos = vector(0, 0.5*self.height) - def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ @@ -188,40 +178,36 @@ class pnand3(pgate.pgate): self.connect_pin_to_rail(self.nmos1_inst, "S", "gnd") - self.connect_pin_to_rail(self.pmos1_inst, "S", "vdd") + self.connect_pin_to_rail(self.pmos1_inst, "S", "vdd") self.connect_pin_to_rail(self.pmos2_inst, "D", "vdd") def route_inputs(self): - """ Route the A and B inputs """ - # wire space or wire and one contact space - metal_spacing = max(self.m1_space + self.m1_width, - self.m2_space + self.m2_width, - self.m1_space + 0.5 *contact.poly_contact.width + 0.5 * self.m1_width) + """ Route the A and B and C inputs """ - active_spacing = max(self.m1_space, - 0.5 * contact.poly_contact.first_layer_width + self.poly_to_active) - inputC_yoffset = self.nmos3_pos.y + self.nmos.active_height + active_spacing + m1_pitch = self.m1_space + contact.m1_via.first_layer_height + # Put B right on the well line + self.inputB_yoffset = self.nwell_y_offset + self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + self.inputB_yoffset, + "B", + position="center") + + self.inputC_yoffset = self.inputB_yoffset - m1_pitch self.route_input_gate(self.pmos3_inst, self.nmos3_inst, - inputC_yoffset, + self.inputC_yoffset, "C", position="center") - inputB_yoffset = inputC_yoffset + metal_spacing - self.route_input_gate(self.pmos2_inst, - self.nmos2_inst, - inputB_yoffset, - "B", - position="center") - - self.inputA_yoffset = inputB_yoffset + metal_spacing + self.inputA_yoffset = self.inputB_yoffset + m1_pitch self.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.inputA_yoffset, "A", position="center") - + def route_output(self): """ Route the Z output """ # PMOS1 drain @@ -243,7 +229,7 @@ class pnand3(pgate.pgate): directions=("V", "V")) # PMOS3 and NMOS3 are drain aligned - self.add_path("m2", [pmos3_pin.center(), nmos3_pin.uc()]) + self.add_path("m2", [pmos3_pin.center(), nmos3_pin.center()]) # Route in the A input track (top track) mid_offset = vector(nmos3_pin.center().x, self.inputA_yoffset) self.add_path("m2", [pmos1_pin.center(), mid_offset, nmos3_pin.uc()]) @@ -273,7 +259,7 @@ class pnand3(pgate.pgate): # In fF c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) transition_prob = 0.1094 - return transition_prob *(c_load + c_para) + return transition_prob * (c_load + c_para) def input_load(self): """Return the relative input capacitance of a single input""" diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index d6605d68..3d998d25 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -48,10 +48,11 @@ class pnor2(pgate.pgate): """ Calls all functions related to the generation of the layout """ self.setup_layout_constants() - self.route_supply_rails() self.place_ptx() - self.connect_rails() self.add_well_contacts() + self.determine_width() + self.route_supply_rails() + self.connect_rails() self.extend_wells() self.route_inputs() self.route_output() diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index acb2062f..56382712 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -12,6 +12,7 @@ from tech import parameter from vector import vector from globals import OPTS from sram_factory import factory +from tech import drc class precharge(design.design): @@ -30,7 +31,16 @@ class precharge(design.design): self.width = self.bitcell.width self.bitcell_bl = bitcell_bl self.bitcell_br = bitcell_br - + self.bitcell_bl_pin =self.bitcell.get_pin(self.bitcell_bl) + self.bitcell_br_pin =self.bitcell.get_pin(self.bitcell_br) + + if self.bitcell_bl_pin.layer == "m1": + self.bitline_layer = "m1" + self.en_layer = "m2" + else: + self.bitline_layer = "m2" + self.en_layer = "m1" + # Creates the netlist and layout # Since it has variable height, it is not a pgate. self.create_netlist() @@ -50,6 +60,7 @@ class precharge(design.design): self.create_ptx() def create_layout(self): + self.place_ptx() self.connect_poly() self.route_en() @@ -78,18 +89,24 @@ class precharge(design.design): # Adds the rail across the width of the cell vdd_position = vector(0.5 * self.width, self.height) - self.add_rect_center(layer="m1", + layer_width = drc("minwidth_" + self.en_layer) + self.add_rect_center(layer=self.en_layer, offset=vdd_position, width=self.width, - height=self.m1_width) + height=layer_width) pmos_pin = self.upper_pmos2_inst.get_pin("S") # center of vdd rail pmos_vdd_pos = vector(pmos_pin.cx(), vdd_position.y) self.add_path("m1", [pmos_pin.uc(), pmos_vdd_pos]) + if self.en_layer != "m1": + self.add_via_center(layers=self.m1_stack, + offset=pmos_vdd_pos) + + # Add vdd pin above the transistor - self.add_power_pin("vdd", pmos_pin.center(), vertical=True) + self.add_power_pin("vdd", self.well_contact_pos, vertical=True) def create_ptx(self): """ @@ -113,6 +130,8 @@ class precharge(design.design): Place both the upper_pmos and lower_pmos to the module """ + # reserve some offset to jog the bitlines + self.initial_yoffset = self.pmos.active_offset.y + self.m2_pitch # 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() @@ -120,17 +139,18 @@ class precharge(design.design): contact_xdiff = self.pmos.get_pin("S").lx() # adds the lower pmos to layout - bl_xoffset = self.bitcell.get_pin(self.bitcell_bl).lx() + bl_xoffset = self.bitcell_bl_pin.lx() self.lower_pmos_position = vector(max(bl_xoffset - contact_xdiff, self.nwell_enclose_active), - self.pmos.active_offset.y) + self.initial_yoffset) self.lower_pmos_inst.place(self.lower_pmos_position) - # adds the upper pmos(s) to layout - ydiff = self.pmos.height + 2 * self.m1_space + contact.poly_contact.width + # adds the upper pmos(s) to layout with 2 M2 tracks + ydiff = self.pmos.height + self.m2_pitch self.upper_pmos1_pos = self.lower_pmos_position + vector(0, ydiff) self.upper_pmos1_inst.place(self.upper_pmos1_pos) + # Second pmos to the right of the first upper_pmos2_pos = self.upper_pmos1_pos + overlap_offset self.upper_pmos2_inst.place(upper_pmos2_pos) @@ -161,16 +181,21 @@ class precharge(design.design): """ Adds the en input rail, en contact/vias, and connects to the pmos """ - - # adds the en contact to connect the gates to the en rail on metal1 + + # 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.poly_space) + + 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) + # adds the en rail on metal1 self.add_layout_pin_segment_center(text="en_bar", - layer="m1", + layer=self.en_layer, start=offset.scale(0, 1), end=offset.scale(0, 1) + vector(self.width, 0)) @@ -180,16 +205,15 @@ class precharge(design.design): """ # adds the contact from active to metal1 - well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1, 0) \ - + vector(0, self.upper_pmos1_inst.uy() + contact.active_contact.height / 2 \ - + self.nwell_extend_active) + self.well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1, 0) \ + + vector(0, self.upper_pmos1_inst.uy() + contact.active_contact.height / 2 \ + + self.nwell_extend_active) self.add_via_center(layers=self.active_stack, - offset=well_contact_pos, + offset=self.well_contact_pos, implant_type="n", well_type="n") - # leave an extra pitch for the height - self.height = well_contact_pos.y + contact.active_contact.height + self.m1_pitch + self.height = self.well_contact_pos.y + contact.active_contact.height + self.m1_space # nwell should span the whole design since it is pmos only self.add_rect(layer="nwell", @@ -201,80 +225,86 @@ class precharge(design.design): """ Adds both bit-line and bit-line-bar to the module """ - - # adds the BL on metal 2 - offset = vector(self.bitcell.get_pin(self.bitcell_bl).cx(), 0) \ - - vector(0.5 * self.m2_width, 0) - self.bl_pin = self.add_layout_pin(text="bl", - layer="m2", - offset=offset, - height=self.height) + layer_width = drc("minwidth_" + self.bitline_layer) + layer_space = drc("{0}_to_{0}".format(self.bitline_layer)) - # adds the BR on metal 2 - offset = vector(self.bitcell.get_pin(self.bitcell_br).cx(), 0) \ - - vector(0.5 * self.m2_width, 0) - self.br_pin = self.add_layout_pin(text="br", - layer="m2", - offset=offset, - height=self.height) + # adds the BL + self.bl_xoffset = layer_space + 0.5 * layer_width + 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]) + self.bl_pin = self.add_layout_pin_segment_center(text="bl", + layer=self.bitline_layer, + start=pin_pos, + end=top_pos) + + # adds the BR + self.br_xoffset = self.width - layer_space - 0.5 * layer_width + 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]) + self.br_pin = self.add_layout_pin_segment_center(text="br", + layer=self.bitline_layer, + start=pin_pos, + end=top_pos) def connect_to_bitlines(self): """ Connect the bitlines to the devices """ self.add_bitline_contacts() - self.connect_pmos_m2(self.lower_pmos_inst.get_pin("S"), - self.get_pin("bl")) - self.connect_pmos_m2(self.upper_pmos1_inst.get_pin("S"), - self.get_pin("bl")) - self.connect_pmos_m1(self.lower_pmos_inst.get_pin("D"), - self.get_pin("br")) - self.connect_pmos_m1(self.upper_pmos2_inst.get_pin("D"), - self.get_pin("br")) + self.connect_pmos(self.lower_pmos_inst.get_pin("S"), + self.bl_xoffset) + self.connect_pmos(self.lower_pmos_inst.get_pin("D"), + self.br_xoffset) + + self.connect_pmos(self.upper_pmos1_inst.get_pin("S"), + self.bl_xoffset) + self.connect_pmos(self.upper_pmos2_inst.get_pin("D"), + self.br_xoffset) def add_bitline_contacts(self): """ Adds contacts/via from metal1 to metal2 for bit-lines """ - upper_pin = self.upper_pmos1_inst.get_pin("S") - lower_pin = self.lower_pmos_inst.get_pin("S") + # No contacts needed if M1 + if self.bitline_layer == "m1": + return - # BL goes up to M2 at the transistor - self.bl_contact =self.add_via_center(layers=self.m1_stack, - offset=upper_pin.center(), + # 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")) - self.add_via_center(layers=self.m1_stack, - offset=lower_pin.center(), - directions=("V", "V")) - # BR routes over on M1 first - self.add_via_center(layers=self.m1_stack, - offset=vector(self.br_pin.cx(), upper_pin.cy()), - directions=("V", "V")) - self.add_via_center(layers=self.m1_stack, - offset=vector(self.br_pin.cx(), lower_pin.cy()), - 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")) - def connect_pmos_m1(self, pmos_pin, bit_pin): + 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")) + + def connect_pmos(self, pmos_pin, bit_xoffset): """ Connect a pmos pin to bitline pin """ - left_pos = vector(min(pmos_pin.cx(), bit_pin.cx()), pmos_pin.cy()) - right_pos = vector(max(pmos_pin.cx(), bit_pin.cx()), pmos_pin.cy()) + left_pos = vector(min(pmos_pin.cx(), bit_xoffset), pmos_pin.cy()) + right_pos = vector(max(pmos_pin.cx(), bit_xoffset), pmos_pin.cy()) - self.add_path("m1", [left_pos, right_pos] ) - - def connect_pmos_m2(self, pmos_pin, bit_pin): - """ - Connect a pmos pin to bitline pin - """ - - left_pos = vector(min(pmos_pin.cx(), bit_pin.cx()), pmos_pin.cy()) - right_pos = vector(max(pmos_pin.cx(), bit_pin.cx()), pmos_pin.cy()) - - self.add_path("m2", [left_pos, right_pos], self.bl_contact.height) + self.add_path(self.bitline_layer, + [left_pos, right_pos], + width=pmos_pin.height()) def get_en_cin(self): """Get the relative capacitance of the enable in the precharge cell""" diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index f331dc28..46726b37 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -10,6 +10,7 @@ import debug from tech import layer, drc, spice from vector import vector from sram_factory import factory +import contact class ptx(design.design): @@ -28,6 +29,7 @@ class ptx(design.design): tx_type="nmos", connect_active=False, connect_poly=False, + series_devices=False, num_contacts=None): # We need to keep unique names because outputting to GDSII # will use the last record with a given name. I.e., you will @@ -50,6 +52,7 @@ class ptx(design.design): self.tx_width = width self.connect_active = connect_active self.connect_poly = connect_poly + self.series_devices = series_devices self.num_contacts = num_contacts # Since it has variable height, it is not a pgate. @@ -127,27 +130,25 @@ class ptx(design.design): directions=("V", "V"), dimensions=(1, self.num_contacts)) - # The contacted poly pitch - self.poly_pitch = max(2 * self.contact_to_gate + self.contact_width + self.poly_width, - self.poly_space) - - # The contacted poly pitch - self.contact_spacing = 2 * self.contact_to_gate + \ - self.contact_width + self.poly_width - - # This is measured because of asymmetric enclosure rules - active_enclose_contact = 0.5*(self.active_contact.width - self.contact_width) + # This is the extra poly spacing due to the poly contact to poly contact pitch + # of contacted gates + extra_poly_contact_width = contact.poly_contact.width - self.poly_width - # This is the distance from the side of - # poly gate to the contacted end of active - # (i.e. the "outside" contacted diffusion sizes) - self.end_to_poly = self.active_contact.width - active_enclose_contact + \ - self.contact_to_gate + # This is the spacing between S/D contacts + # 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.poly_pitch = max(self.min_poly_pitch, + self.contacted_poly_pitch, + self.contact_pitch) + + self.end_to_contact = 0.5 * self.active_contact.width # Active width is determined by enclosure on both ends and contacted pitch, # at least one poly and n-1 poly pitches - self.active_width = 2 * self.end_to_poly + self.poly_width + \ - (self.mults - 1) * self.poly_pitch + self.active_width = 2 * self.end_to_contact + self.active_contact.width \ + + 2 * self.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 @@ -287,13 +288,13 @@ class ptx(design.design): Add the poly gates(s) and (optionally) connect them. """ # poly is one contacted spacing from the end and down an extension - poly_offset = self.active_offset \ - + vector(self.poly_width, self.poly_height).scale(0.5, 0.5) \ - + vector(self.end_to_poly, -self.poly_extend_active) - + poly_offset = self.contact_offset \ + + vector(0.5 * self.active_contact.width + 0.5 * self.poly_width + self.contact_to_gate, 0) + # poly_positions are the bottom center of the poly gates - poly_positions = [] - + self.poly_positions = [] + self.poly_gates = [] + # It is important that these are from left to right, # so that the pins are in the right # order for the accessors @@ -304,16 +305,18 @@ class ptx(design.design): offset=poly_offset, height=self.poly_height, width=self.poly_width) - self.add_layout_pin_rect_center(text="G", - layer="poly", - offset=poly_offset, - height=self.poly_height, - width=self.poly_width) - poly_positions.append(poly_offset) - poly_offset = poly_offset + vector(self.poly_pitch,0) + gate = self.add_layout_pin_rect_center(text="G", + layer="poly", + offset=poly_offset, + height=self.poly_height, + width=self.poly_width) + self.poly_positions.append(poly_offset) + self.poly_gates.append(gate) + + poly_offset = poly_offset + vector(self.poly_pitch, 0) if self.connect_poly: - self.connect_fingered_poly(poly_positions) + self.connect_fingered_poly(self.poly_positions) def add_active(self): """ @@ -362,59 +365,88 @@ class ptx(design.design): """ return 1 - def get_contact_positions(self): - """ - Create a list of the centers of drain and source contact positions. - """ - # The first one will always be a source - source_positions = [self.contact_offset] - drain_positions = [] - # It is important that these are from left to right, - # so that the pins are in the right - # order for the accessors. - for i in range(self.mults): - if i%2: - # It's a source... so offset from previous drain. - source_positions.append(drain_positions[-1] + vector(self.contact_spacing, 0)) - else: - # It's a drain... so offset from previous source. - drain_positions.append(source_positions[-1] + vector(self.contact_spacing, 0)) - - return [source_positions,drain_positions] - def add_active_contacts(self): """ Add the active contacts to the transistor. """ + drain_positions = [] + source_positions = [] - [source_positions,drain_positions] = self.get_contact_positions() - - for pos in source_positions: - contact=self.add_via_center(layers=self.active_stack, + # Keep a list of the source/drain contacts + self.source_contacts = [] + self.drain_contacts = [] + + # First one is always a SOURCE + label = "S" + pos = self.contact_offset + contact=self.add_via_center(layers=self.active_stack, + offset=pos, + size=(1, self.num_contacts), + directions=("V","V"), + implant_type=self.implant_type, + well_type=self.well_type) + self.add_layout_pin_rect_center(text=label, + layer="m1", offset=pos, - size=(1, self.num_contacts), - directions=("V","V"), - implant_type=self.implant_type, - well_type=self.well_type) - self.add_layout_pin_rect_center(text="S", - layer="m1", - offset=pos, - width=contact.mod.second_layer_width, - height=contact.mod.second_layer_height) + width=contact.mod.second_layer_width, + height=contact.mod.second_layer_height) + self.source_contacts.append(contact) + source_positions.append(pos) + # Skip these if they are going to be in series + if not self.series_devices: + for (poly1, poly2) in zip(self.poly_positions, self.poly_positions[1:]): + pos = vector(0.5 * (poly1.x + poly2.x), + self.contact_offset.y) + # Alternate source and drains + if label == "S": + label = "D" + drain_positions.append(pos) + else: + label = "S" + source_positions.append(pos) + + contact=self.add_via_center(layers=self.active_stack, + offset=pos, + size=(1, self.num_contacts), + directions=("V","V"), + implant_type=self.implant_type, + well_type=self.well_type) + self.add_layout_pin_rect_center(text=label, + layer="m1", + offset=pos, + width=contact.mod.second_layer_width, + height=contact.mod.second_layer_height) - for pos in drain_positions: - contact=self.add_via_center(layers=self.active_stack, + if label == "S": + self.source_contacts.append(contact) + else: + self.drain_contacts.append(contact) + + pos = vector(self.active_offset.x + self.active_width - 0.5 * self.active_contact.width, + self.contact_offset.y) + # Last one is the opposite of previous + if label == "S": + label = "D" + drain_positions.append(pos) + else: + label = "S" + source_positions.append(pos) + contact=self.add_via_center(layers=self.active_stack, + offset=pos, + size=(1, self.num_contacts), + directions=("V","V"), + implant_type=self.implant_type, + well_type=self.well_type) + self.add_layout_pin_rect_center(text=label, + layer="m1", offset=pos, - size=(1, self.num_contacts), - directions=("V","V"), - implant_type=self.implant_type, - well_type=self.well_type) - self.add_layout_pin_rect_center(text="D", - layer="m1", - offset=pos, - width=contact.mod.second_layer_width, - height=contact.mod.second_layer_height) + width=contact.mod.second_layer_width, + height=contact.mod.second_layer_height) + if label == "S": + self.source_contacts.append(contact) + else: + self.drain_contacts.append(contact) if self.connect_active: self.connect_fingered_active(drain_positions, source_positions) diff --git a/compiler/sram/sram.py b/compiler/sram/sram.py index dfde78f7..0efad5cc 100644 --- a/compiler/sram/sram.py +++ b/compiler/sram/sram.py @@ -5,13 +5,11 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys import datetime -import getpass import debug from globals import OPTS, print_time -from sram_config import sram_config - + + class sram(): """ This is not a design module, but contains an SRAM design instance. @@ -28,7 +26,7 @@ class sram(): from design import design design.name_map=[] - debug.info(2, "create sram of size {0} with {1} num of words {2} banks".format(self.word_size, + debug.info(2, "create sram of size {0} with {1} num of words {2} banks".format(self.word_size, self.num_words, self.num_banks)) start_time = datetime.datetime.now() @@ -40,30 +38,28 @@ class sram(): elif self.num_banks == 2: from sram_2bank import sram_2bank as sram else: - debug.error("Invalid number of banks.",-1) + debug.error("Invalid number of banks.", -1) - self.s = sram(name, sram_config) + self.s = sram(name, sram_config) self.s.create_netlist() if not OPTS.netlist_only: self.s.create_layout() if not OPTS.is_unit_test: print_time("SRAM creation", datetime.datetime.now(), start_time) - - def sp_write(self,name): + def sp_write(self, name): self.s.sp_write(name) - def lef_write(self,name): + def lef_write(self, name): self.s.lef_write(name) - def gds_write(self,name): + def gds_write(self, name): self.s.gds_write(name) - def verilog_write(self,name): + def verilog_write(self, name): self.s.verilog_write(name) - def save(self): """ Save all the output files while reporting time to do it as well. """ @@ -107,7 +103,6 @@ class sram(): debug.print_raw("LIB: Characterizing... ") lib(out_dir=OPTS.output_path, sram=self.s, sp_file=sp_file) print_time("Characterization", datetime.datetime.now(), start_time) - # Write the config file start_time = datetime.datetime.now() diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 2f37d927..76e9f805 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -19,9 +19,9 @@ class sram_1bank(sram_base): sram_base.__init__(self, name, sram_config) def create_modules(self): - """ + """ This adds the modules for a single bank SRAM with control - logic. + logic. """ self.bank_inst=self.create_bank(0) @@ -40,7 +40,7 @@ class sram_1bank(sram_base): self.data_dff_insts = self.create_data_dff() def place_instances(self): - """ + """ This places the instances for a single bank SRAM with control logic and up to 2 ports. """ @@ -53,19 +53,19 @@ class sram_1bank(sram_base): # the sense amps/column mux and cell array) # The x-coordinate is placed to allow a single clock wire (plus an extra pitch) # up to the row address DFFs. - control_pos = [None]*len(self.all_ports) - row_addr_pos = [None]*len(self.all_ports) - col_addr_pos = [None]*len(self.all_ports) - wmask_pos = [None]*len(self.all_ports) - data_pos = [None]*len(self.all_ports) + control_pos = [None] * len(self.all_ports) + row_addr_pos = [None] * len(self.all_ports) + col_addr_pos = [None] * len(self.all_ports) + wmask_pos = [None] * len(self.all_ports) + data_pos = [None] * len(self.all_ports) if self.write_size: - max_gap_size = self.m3_pitch*self.word_size + 2*self.m1_pitch - max_gap_size_wmask = self.m2_pitch*max(self.num_wmasks+1,self.col_addr_size+1) + 2*self.m1_pitch + max_gap_size = self.m3_pitch * self.word_size + 2 * self.m1_pitch + max_gap_size_wmask = self.m2_pitch * max(self.num_wmasks + 1, self.col_addr_size + 1) + 2 * self.m1_pitch else: # This is M2 pitch even though it is on M1 to help stem via spacings on the trunk # The M1 pitch is for supply rail spacings - max_gap_size = self.m2_pitch*max(self.word_size+1,self.col_addr_size+1) + 2*self.m1_pitch + max_gap_size = self.m2_pitch * max(self.word_size + 1,self.col_addr_size + 1) + 2 * self.m1_pitch # Port 0 port = 0 @@ -79,7 +79,7 @@ class sram_1bank(sram_base): # Add the data flops below the write mask flops. data_pos[port] = vector(self.bank.bank_array_ll.x, - -max_gap_size - max_gap_size_wmask - 2*self.dff.height) + -max_gap_size - max_gap_size_wmask - 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 @@ -93,8 +93,7 @@ class sram_1bank(sram_base): 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) - + 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: @@ -106,11 +105,11 @@ class sram_1bank(sram_base): -max_gap_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) + col_addr_pos[port] = vector(self.bank.bank_array_ll.x, 0) # This includes 2 M2 pitches for the row addr clock line. - 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 ) + 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.control_logic_insts[port].place(control_pos[port]) # The row address bits are placed above the control logic aligned on the right. @@ -133,7 +132,7 @@ class sram_1bank(sram_base): # 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 + max_gap_size_wmask + max_gap_size + 2*self.dff.height) + self.bank.height + max_gap_size_wmask + max_gap_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 @@ -158,11 +157,10 @@ class sram_1bank(sram_base): col_addr_pos[port] = self.bank_inst.ur() # This includes 2 M2 pitches for the row addr clock line - 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 - + 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) - #import pdb; pdb.set_trace() + + 2 * self.bank.m2_gap) 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. @@ -172,7 +170,6 @@ 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") - def add_layout_pins(self): """ Add the top-level pins for a single bank SRAM with control. @@ -180,28 +177,39 @@ class sram_1bank(sram_base): for port in self.all_ports: # Connect the control pins as inputs for signal in self.control_logic_inputs[port] + ["clk"]: - self.copy_layout_pin(self.control_logic_insts[port], signal, signal+"{}".format(port)) + 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)) + 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)) + 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)) + self.copy_layout_pin(self.row_addr_dff_insts[port], + "din_{}".format(bit), + "addr{0}[{1}]".format(port, bit + self.col_addr_size)) 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)) + self.copy_layout_pin(self.data_dff_insts[port], + "din_{}".format(bit), + "din{0}[{1}]".format(port, bit)) if self.write_size: for bit in range(self.num_wmasks): - self.copy_layout_pin(self.wmask_dff_insts[port], "din_{}".format(bit), "wmask{0}[{1}]".format(port,bit)) + self.copy_layout_pin(self.wmask_dff_insts[port], + "din_{}".format(bit), + "wmask{0}[{1}]".format(port, bit)) - def route_layout(self): """ Route a single bank SRAM """ @@ -255,13 +263,15 @@ class sram_1bank(sram_base): offset=clk_steiner_pos) # Note, the via to the control logic is taken care of above - self.add_wire(("m3","via2","m2"),[row_addr_clk_pos, mid1_pos, clk_steiner_pos]) + self.add_wire(("m3", "via2", "m2"), + [row_addr_clk_pos, mid1_pos, clk_steiner_pos]) if self.col_addr_dff: dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk") dff_clk_pos = dff_clk_pin.center() mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) - self.add_wire(("m3","via2","m2"),[dff_clk_pos, mid_pos, clk_steiner_pos]) + self.add_wire(("m3", "via2", "m2"), + [dff_clk_pos, mid_pos, clk_steiner_pos]) if port in self.write_ports: data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") @@ -269,8 +279,11 @@ class sram_1bank(sram_base): mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) # In some designs, the steiner via will be too close to the mid_pos via # so make the wire as wide as the contacts - self.add_path("m2",[mid_pos, clk_steiner_pos], width=max(m2_via.width,m2_via.height)) - self.add_wire(("m3","via2","m2"),[data_dff_clk_pos, mid_pos, clk_steiner_pos]) + self.add_path("m2", + [mid_pos, clk_steiner_pos], + width=max(m2_via.width, m2_via.height)) + self.add_wire(("m3", "via2", "m2"), + [data_dff_clk_pos, mid_pos, clk_steiner_pos]) if self.write_size: wmask_dff_clk_pin = self.wmask_dff_insts[port].get_pin("clk") @@ -280,7 +293,6 @@ class sram_1bank(sram_base): # 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(("m3", "via2", "m2"), [wmask_dff_clk_pos, mid_pos, clk_steiner_pos]) - def route_control_logic(self): """ Route the control logic pins that are not inputs """ @@ -291,28 +303,29 @@ class sram_1bank(sram_base): if "clk" in signal: continue src_pin = self.control_logic_insts[port].get_pin(signal) - dest_pin = self.bank_inst.get_pin(signal+"{}".format(port)) + dest_pin = self.bank_inst.get_pin(signal + "{}".format(port)) self.connect_vbus_m2m3(src_pin, dest_pin) for port in self.all_ports: # 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_vbus_m2m3(src_pin, dest_pin) - - + dest_pin = self.bank_inst.get_pin("rbl_bl{}".format(port)) + + self.connect_hbus_m2m3(src_pin, dest_pin) + def route_row_addr_dff(self): """ Connect the output of the row flops to the bank pins """ for port in self.all_ports: for bit in range(self.row_addr_size): flop_name = "dout_{}".format(bit) - bank_name = "addr{0}_{1}".format(port,bit+self.col_addr_size) + bank_name = "addr{0}_{1}".format(port, bit + self.col_addr_size) flop_pin = self.row_addr_dff_insts[port].get_pin(flop_name) bank_pin = self.bank_inst.get_pin(bank_name) flop_pos = flop_pin.center() bank_pos = bank_pin.center() - mid_pos = vector(bank_pos.x,flop_pos.y) - self.add_wire(("m3","via2","m2"),[flop_pos, mid_pos,bank_pos]) + mid_pos = vector(bank_pos.x, flop_pos.y) + self.add_wire(("m3", "via2", "m2"), + [flop_pos, mid_pos, bank_pos]) self.add_via_center(layers=self.m2_stack, offset=flop_pos) @@ -320,11 +333,11 @@ class sram_1bank(sram_base): """ 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_size+2)*self.m1_pitch) + offset = self.col_addr_dff_insts[port].ll() - vector(0, (self.col_addr_size + 2) * self.m1_pitch) else: - offset = self.col_addr_dff_insts[port].ul() + vector(0, 2*self.m1_pitch) + offset = self.col_addr_dff_insts[port].ul() + vector(0, 2 * self.m1_pitch) - bus_names = ["addr_{}".format(x) for x in range(self.col_addr_size)] + bus_names = ["addr_{}".format(x) for x in range(self.col_addr_size)] col_addr_bus_offsets = self.create_horizontal_bus(layer="m1", pitch=self.m1_pitch, offset=offset, @@ -335,10 +348,9 @@ class sram_1bank(sram_base): 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)] + 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 """ @@ -346,14 +358,14 @@ class sram_1bank(sram_base): for port in self.write_ports: if self.write_size: if port % 2: - offset = self.data_dff_insts[port].ll() - vector(0, (self.word_size + 2)*self.m3_pitch) + offset = self.data_dff_insts[port].ll() - vector(0, (self.word_size + 2) * self.m3_pitch) else: offset = self.data_dff_insts[port].ul() + vector(0, 2 * self.m3_pitch) else: - if port%2: - offset = self.data_dff_insts[port].ll() - vector(0, (self.word_size+2)*self.m1_pitch) + if port % 2: + offset = self.data_dff_insts[port].ll() - vector(0, (self.word_size + 2) * self.m1_pitch) else: - offset = self.data_dff_insts[port].ul() + vector(0, 2*self.m1_pitch) + offset = self.data_dff_insts[port].ul() + vector(0, 2 * self.m1_pitch) 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] @@ -362,13 +374,13 @@ class sram_1bank(sram_base): pin_offset = self.data_dff_insts[port].get_pin(x).center() self.add_via_center(layers=self.m1_stack, offset=pin_offset, - directions = ("V", "V")) + directions=("V", "V")) self.add_via_center(layers=self.m2_stack, offset=pin_offset) self.add_via_center(layers=self.m3_stack, offset=pin_offset) - bank_names = ["din{0}_{1}".format(port,x) for x in range(self.word_size)] + 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: @@ -397,7 +409,7 @@ class sram_1bank(sram_base): # 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.num_wmasks+2) * self.m1_pitch) + offset = self.wmask_dff_insts[port].ll() - vector(0, (self.num_wmasks + 2) * self.m1_pitch) else: offset = self.wmask_dff_insts[port].ul() + vector(0, 2 * self.m1_pitch) @@ -416,16 +428,14 @@ class sram_1bank(sram_base): 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) - def add_lvs_correspondence_points(self): - """ - This adds some points for easier debugging if LVS goes wrong. + """ + This adds some points for easier debugging if LVS goes wrong. These should probably be turned off by default though, since extraction will show these as ports in the extracted netlist. """ @@ -438,7 +448,7 @@ class sram_1bank(sram_base): def graph_exclude_data_dff(self): """Removes data dff and wmask dff (if applicable) from search graph. """ - #Data dffs and wmask dffs are only for writing so are not useful for evaluating read delay. + # Data dffs and wmask dffs are only for writing so are not useful for evaluating read delay. for inst in self.data_dff_insts: self.graph_inst_exclude.add(inst) if self.write_size: @@ -447,7 +457,7 @@ class sram_1bank(sram_base): def graph_exclude_addr_dff(self): """Removes data dff from search graph. """ - #Address is considered not part of the critical path, subjectively removed + # Address is considered not part of the critical path, subjectively removed for inst in self.row_addr_dff_insts: self.graph_inst_exclude.add(inst) @@ -457,27 +467,27 @@ class sram_1bank(sram_base): def graph_exclude_ctrl_dffs(self): """Exclude dffs for CSB, WEB, etc from graph""" - #Insts located in control logic, exclusion function called here + # Insts located in control logic, exclusion function called here for inst in self.control_logic_insts: inst.mod.graph_exclude_dffs() def get_sen_name(self, sram_name, port=0): """Returns the s_en spice name.""" - #Naming scheme is hardcoded using this function, should be built into the - #graph in someway. + # Naming scheme is hardcoded using this function, should be built into the + # graph in someway. sen_name = "s_en{}".format(port) control_conns = self.get_conns(self.control_logic_insts[port]) - #Sanity checks + # Sanity checks if sen_name not in control_conns: - debug.error("Signal={} not contained in control logic connections={}"\ - .format(sen_name, control_conns)) + debug.error("Signal={} not contained in control logic connections={}".format(sen_name, + control_conns)) if sen_name in self.pins: - debug.error("Internal signal={} contained in port list. Name defined by the parent.") + debug.error("Internal signal={} contained in port list. Name defined by the parent.".format(sen_name)) return "X{}.{}".format(sram_name, sen_name) def get_cell_name(self, inst_name, row, col): """Gets the spice name of the target bitcell.""" - #Sanity check in case it was forgotten + # Sanity check in case it was forgotten if inst_name.find('x') != 0: - inst_name = 'x'+inst_name - return self.bank_inst.mod.get_cell_name(inst_name+'.x'+self.bank_inst.name, row, col) + inst_name = 'x' + inst_name + return self.bank_inst.mod.get_cell_name(inst_name + '.x' + self.bank_inst.name, row, col) diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index c7c91e90..e8a91486 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -5,20 +5,16 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys import datetime -import getpass import debug -from datetime import datetime from importlib import reload from vector import vector from globals import OPTS, print_time -import logical_effort from design import design from verilog import verilog from lef import lef from sram_factory import factory -import logical_effort + class sram_base(design, verilog, lef): """ @@ -36,11 +32,11 @@ class sram_base(design, verilog, lef): self.bank_insts = [] if self.write_size: - self.num_wmasks = int(self.word_size/self.write_size) + self.num_wmasks = int(self.word_size / self.write_size) else: self.num_wmasks = 0 - #For logical effort delay calculations. + # For logical effort delay calculations. self.all_mods_except_control_done = False def add_pins(self): @@ -48,11 +44,11 @@ class sram_base(design, verilog, lef): for port in self.write_ports: for bit in range(self.word_size): - self.add_pin("din{0}[{1}]".format(port,bit),"INPUT") + self.add_pin("din{0}[{1}]".format(port, bit), "INPUT") for port in self.all_ports: for bit in range(self.addr_size): - self.add_pin("addr{0}[{1}]".format(port,bit),"INPUT") + self.add_pin("addr{0}[{1}]".format(port, bit), "INPUT") # These are used to create the physical pins self.control_logic_inputs = [] @@ -69,27 +65,26 @@ class sram_base(design, verilog, lef): self.control_logic_outputs.append(self.control_logic_r.get_outputs()) for port in self.all_ports: - self.add_pin("csb{}".format(port),"INPUT") + self.add_pin("csb{}".format(port), "INPUT") for port in self.readwrite_ports: - self.add_pin("web{}".format(port),"INPUT") + self.add_pin("web{}".format(port), "INPUT") for port in self.all_ports: - self.add_pin("clk{}".format(port),"INPUT") + self.add_pin("clk{}".format(port), "INPUT") # add the optional write mask pins for port in self.write_ports: for bit in range(self.num_wmasks): - self.add_pin("wmask{0}[{1}]".format(port,bit),"INPUT") + self.add_pin("wmask{0}[{1}]".format(port, bit), "INPUT") for port in self.read_ports: for bit in range(self.word_size): - self.add_pin("dout{0}[{1}]".format(port,bit),"OUTPUT") + self.add_pin("dout{0}[{1}]".format(port, bit), "OUTPUT") - self.add_pin("vdd","POWER") - self.add_pin("gnd","GROUND") - + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def create_netlist(self): """ Netlist creation """ - start_time = datetime.now() + start_time = datetime.datetime.now() # Must create the control logic before pins to get the pins self.add_modules() @@ -100,23 +95,21 @@ class sram_base(design, verilog, lef): self.width=0 self.height=0 - if not OPTS.is_unit_test: - print_time("Submodules",datetime.now(), start_time) - + print_time("Submodules", datetime.datetime.now(), start_time) def create_layout(self): - """ Layout creation """ - start_time = datetime.now() + """ Layout creation """ + start_time = datetime.datetime.now() self.place_instances() if not OPTS.is_unit_test: - print_time("Placement",datetime.now(), start_time) + print_time("Placement", datetime.datetime.now(), start_time) - start_time = datetime.now() + start_time = datetime.datetime.now() self.route_layout() self.route_supplies() if not OPTS.is_unit_test: - print_time("Routing",datetime.now(), start_time) + print_time("Routing", datetime.datetime.now(), start_time) self.add_lvs_correspondence_points() @@ -126,14 +119,14 @@ class sram_base(design, verilog, lef): self.width = highest_coord[0] self.height = highest_coord[1] - start_time = datetime.now() + start_time = datetime.datetime.now() # We only enable final verification if we have routed the design self.DRC_LVS(final_verification=OPTS.route_supplies, top_level=True) if not OPTS.is_unit_test: - print_time("Verification",datetime.now(), start_time) + print_time("Verification", datetime.datetime.now(), start_time) def create_modules(self): - debug.error("Must override pure virtual function.",-1) + debug.error("Must override pure virtual function.", -1) def route_supplies(self): """ Route the supply grid and connect the pins to them. """ @@ -141,8 +134,8 @@ class sram_base(design, verilog, lef): # Copy the pins to the top level # This will either be used to route or left unconnected. for inst in self.insts: - self.copy_power_pins(inst,"vdd") - self.copy_power_pins(inst,"gnd") + self.copy_power_pins(inst, "vdd") + self.copy_power_pins(inst, "gnd") import tech if not OPTS.route_supplies: @@ -164,33 +157,31 @@ class sram_base(design, verilog, lef): from supply_grid_router import supply_grid_router as router rtr=router(grid_stack, self) rtr.route() - def compute_bus_sizes(self): """ Compute the independent bus widths shared between two and four bank SRAMs """ # address size + control signals + one-hot bank select signals - self.num_vertical_line = self.addr_size + self.control_size + log(self.num_banks,2) + 1 + self.num_vertical_line = self.addr_size + self.control_size + log(self.num_banks, 2) + 1 # data bus size self.num_horizontal_line = self.word_size - self.vertical_bus_width = self.m2_pitch*self.num_vertical_line + self.vertical_bus_width = self.m2_pitch * self.num_vertical_line # vertical bus height depends on 2 or 4 banks - self.data_bus_height = self.m3_pitch*self.num_horizontal_line - self.data_bus_width = 2*(self.bank.width + self.bank_to_bus_distance) + self.vertical_bus_width + self.data_bus_height = self.m3_pitch * self.num_horizontal_line + self.data_bus_width = 2 * (self.bank.width + self.bank_to_bus_distance) + self.vertical_bus_width - self.control_bus_height = self.m1_pitch*(self.control_size+2) + self.control_bus_height = self.m1_pitch * (self.control_size + 2) self.control_bus_width = self.bank.width + self.bank_to_bus_distance + self.vertical_bus_width - self.supply_bus_height = self.m1_pitch*2 # 2 for vdd/gnd placed with control bus + self.supply_bus_height = self.m1_pitch * 2 # 2 for vdd/gnd placed with control bus self.supply_bus_width = self.data_bus_width # Sanity check to ensure we can fit the control logic above a single bank (0.9 is a hack really) - debug.check(self.bank.width + self.vertical_bus_width > 0.9*self.control_logic.width, + debug.check(self.bank.width + self.vertical_bus_width > 0.9 * self.control_logic.width, "Bank is too small compared to control logic.") - def add_busses(self): """ Add the horizontal and vertical busses """ # Vertical bus @@ -213,24 +204,22 @@ class sram_base(design, verilog, lef): names=self.control_bus_names[port], length=self.vertical_bus_height) - self.addr_bus_names=["A{0}[{1}]".format(port,i) for i in range(self.addr_size)] + self.addr_bus_names=["A{0}[{1}]".format(port, i) for i in range(self.addr_size)] self.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="m2", pitch=self.m2_pitch, offset=self.addr_bus_offset, names=self.addr_bus_names, length=self.addr_bus_height)) - - self.bank_sel_bus_names = ["bank_sel{0}_{1}".format(port,i) for i in range(self.num_banks)] + self.bank_sel_bus_names = ["bank_sel{0}_{1}".format(port, i) for i in range(self.num_banks)] self.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="m2", pitch=self.m2_pitch, offset=self.bank_sel_bus_offset, names=self.bank_sel_bus_names, length=self.vertical_bus_height)) - # Horizontal data bus - self.data_bus_names = ["DATA{0}[{1}]".format(port,i) for i in range(self.word_size)] + self.data_bus_names = ["DATA{0}[{1}]".format(port, i) for i in range(self.word_size)] self.data_bus_positions = self.create_horizontal_pin_bus(layer="m3", pitch=self.m3_pitch, offset=self.data_bus_offset, @@ -249,7 +238,7 @@ class sram_base(design, verilog, lef): # the decoder in 4-bank SRAMs self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="m1", pitch=self.m1_pitch, - offset=self.supply_bus_offset+vector(0,self.m1_pitch), + offset=self.supply_bus_offset + vector(0, self.m1_pitch), names=["gnd"], length=self.supply_bus_width)) self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="m1", @@ -258,20 +247,17 @@ class sram_base(design, verilog, lef): names=self.control_bus_names[port], length=self.control_bus_width)) - - def add_multi_bank_modules(self): """ Create the multibank address flops and bank decoder """ from dff_buf_array import dff_buf_array self.msb_address = dff_buf_array(name="msb_address", rows=1, - columns=self.num_banks/2) + columns=self.num_banks / 2) self.add_mod(self.msb_address) if self.num_banks>2: self.msb_decoder = self.bank.decoder.pre2_4 self.add_mod(self.msb_decoder) - def add_modules(self): self.bitcell = factory.create(module_type=OPTS.bitcell) @@ -293,7 +279,6 @@ class sram_base(design, verilog, lef): 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) - # Create the bank module (up to four are instantiated) self.bank = factory.create("bank", sram_config=self.sram_config, module_name="bank") @@ -305,7 +290,8 @@ class sram_base(design, verilog, lef): self.bank_count = 0 - #The control logic can resize itself based on the other modules. Requires all other modules added before control logic. + # The control logic can resize itself based on the other modules. + # Requires all other modules added before control logic. self.all_mods_except_control_done = True c = reload(__import__(OPTS.control_logic)) @@ -320,40 +306,40 @@ class sram_base(design, verilog, lef): port_type="rw") self.add_mod(self.control_logic_rw) if len(self.writeonly_ports)>0: - self.control_logic_w = self.mod_control_logic(num_rows=self.num_rows, + 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, sram=self, port_type="w") self.add_mod(self.control_logic_w) if len(self.readonly_ports)>0: - self.control_logic_r = self.mod_control_logic(num_rows=self.num_rows, + 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, sram=self, port_type="r") self.add_mod(self.control_logic_r) - def create_bank(self,bank_num): - """ Create a bank """ + def create_bank(self, bank_num): + """ Create a bank """ self.bank_insts.append(self.add_inst(name="bank{0}".format(bank_num), mod=self.bank)) temp = [] for port in self.read_ports: for bit in range(self.word_size): - temp.append("dout{0}[{1}]".format(port,bit)) + 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): - temp.append("bank_din{0}[{1}]".format(port,bit)) + temp.append("bank_din{0}[{1}]".format(port, bit)) for port in self.all_ports: for bit in range(self.bank_addr_size): - temp.append("a{0}[{1}]".format(port,bit)) + temp.append("a{0}[{1}]".format(port, bit)) if(self.num_banks > 1): for port in self.all_ports: - temp.append("bank_sel{0}[{1}]".format(port,bank_num)) + temp.append("bank_sel{0}[{1}]".format(port, bank_num)) for port in self.read_ports: temp.append("s_en{0}".format(port)) for port in self.all_ports: @@ -369,7 +355,6 @@ class sram_base(design, verilog, lef): return self.bank_insts[-1] - def place_bank(self, bank_inst, position, x_flip, y_flip): """ Place a bank at the given position with orientations """ @@ -400,7 +385,6 @@ class sram_base(design, verilog, lef): return bank_inst - def create_row_addr_dff(self): """ Add all address flops for the main decoder """ insts = [] @@ -412,13 +396,12 @@ class sram_base(design, verilog, lef): inputs = [] outputs = [] for bit in range(self.row_addr_size): - inputs.append("addr{}[{}]".format(port,bit+self.col_addr_size)) - outputs.append("a{}[{}]".format(port,bit+self.col_addr_size)) + inputs.append("addr{}[{}]".format(port, bit + self.col_addr_size)) + outputs.append("a{}[{}]".format(port, bit + self.col_addr_size)) self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"]) return insts - def create_col_addr_dff(self): """ Add and place all address flops for the column decoder """ @@ -431,14 +414,13 @@ class sram_base(design, verilog, lef): inputs = [] outputs = [] for bit in range(self.col_addr_size): - inputs.append("addr{}[{}]".format(port,bit)) - outputs.append("a{}[{}]".format(port,bit)) + inputs.append("addr{}[{}]".format(port, bit)) + outputs.append("a{}[{}]".format(port, bit)) self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"]) return insts - def create_data_dff(self): """ Add and place all data flops """ insts = [] @@ -454,8 +436,8 @@ class sram_base(design, verilog, lef): inputs = [] outputs = [] for bit in range(self.word_size): - inputs.append("din{}[{}]".format(port,bit)) - outputs.append("bank_din{}[{}]".format(port,bit)) + inputs.append("din{}[{}]".format(port, bit)) + outputs.append("bank_din{}[{}]".format(port, bit)) self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"]) @@ -483,7 +465,6 @@ class sram_base(design, verilog, lef): return insts - def create_control_logic(self): """ Add control logic instances """ @@ -516,12 +497,13 @@ class sram_base(design, verilog, lef): return insts - def connect_vbus_m2m3(self, src_pin, dest_pin): - """ Helper routine to connect an instance to a vertical bus. + """ + Helper routine to connect an instance to a vertical bus. Routes horizontal then vertical L shape. Dest pin is assumed to be on M2. - Src pin can be on M1/M2/M3.""" + Src pin can be on M1/M2/M3. + """ if src_pin.cx()