diff --git a/.gitignore b/.gitignore index b16e3d0b..3d6e4f92 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,12 @@ .DS_Store *~ +*.orig +*.rej *.pyc *.aux *.out *.toc *.synctex.gz -**/model_data \ No newline at end of file +**/model_data +outputs +technology/freepdk45/ncsu_basekit diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 27c341aa..27431cb2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ before_script: - . /home/gitlab-runner/setup-paths.sh - export OPENRAM_HOME="`pwd`/compiler" - - export OPENRAM_TECH="`pwd`/technology" + - export OPENRAM_TECH="`pwd`/technology:/home/PDKs/skywater-tech" stages: - test @@ -25,6 +25,15 @@ scn4m_subm: - .coverage.* expire_in: 1 week +# s8: +# stage: test +# script: +# - coverage run -p $OPENRAM_HOME/tests/regress.py -t s8 +# artifacts: +# paths: +# - .coverage.* +# expire_in: 1 week + coverage: stage: coverage script: diff --git a/HINTS.md b/HINTS.md index b12704e5..6f29c16b 100644 --- a/HINTS.md +++ b/HINTS.md @@ -37,7 +37,15 @@ to run Calibre or Magic+Netgen. To debug, you will need a layout viewer. I prefer to use Glade on my Mac, but you can also use Calibre, Magic, etc. -1. Calibre +1. Klayout + + You can view the designs in [Klayout](https://www.klayout.de/) with the configuration + file provided in the tech directories. For example, +``` + klayout temp.gds -l /home/vagrant/openram/technology/freepdk45/tf/FreePDK45.lyp +``` + +2. Calibre Start the Calibre DESIGNrev viewer in the temp directory and load your GDS file: ``` @@ -52,10 +60,9 @@ on my Mac, but you can also use Calibre, Magic, etc. In the viewer ">" opens the layout down a level. -2. Glade +3. Glade - You can view errors in Glade as well. I like this because it is on my laptop. - You can get it from: http://www.peardrop.co.uk/glade/ + You can view errors in [Glade](http://www.peardrop.co.uk/glade/) as well. To remote display over X windows, you need to disable OpenGL acceleration or use vnc or something. You can disable by adding this to your .bashrc in bash: @@ -82,16 +89,16 @@ ui().importCds("default", To load the errors, you simply do Verify->Import Calibre Errors select the .results file from Calibre. -3. Magic +4. Magic Magic is only supported in SCMOS. You will need to install the MOSIS SCMOS rules - and Magic from: http://opencircuitdesign.com/ + and [Magic](http://opencircuitdesign.com/) When running DRC or extraction, OpenRAM will load the GDS file, save the .ext/.mag files, and export an extracted netlist (.spice). -4. It is possible to use other viewers as well, such as: - * LayoutEditor http://www.layouteditor.net/ +5. It is possible to use other viewers as well, such as: + * [LayoutEditor](http://www.layouteditor.net/) # Example to output/input .gds layout files from/to Cadence diff --git a/LICENSE b/LICENSE index 761f6e8b..6fdf3ae0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,23 +1,21 @@ BSD 3-Clause License -Copyright (c) 2019 Regents of the University of California and The Board -of Regents for the Oklahoma Agricultural and Mechanical College -(acting for and on behalf of Oklahoma State University) +Copyright (c) 2019, Regents of the University of California and The Board of Regents for the Oklahoma Agricultural and Mechanical College (acting for and on behalf of Oklahoma State University) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE diff --git a/README.md b/README.md index 05bb7772..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 @@ -33,15 +33,6 @@ things that need to be fixed. # Basic Setup -## Docker Image - -We have a pre-configured Ubuntu [Docker](https://www.docker.com/) image -available that has all tools installed for the [SCMOS] process. It is -available at [docker hub](https://hub.docker.com/r/vlsida/openram-ubuntu). -Please see -[our README.md](https://github.com/VLSIDA/openram-docker-images/blob/master/README.md) -for information on how to use this docker image. - ## Dependencies The OpenRAM compiler has very few dependencies: @@ -88,6 +79,23 @@ You may get the entire [FreePDK45 PDK here][FreePDK45]. If you are using [SCMOS], you should install [Magic] and [Netgen]. We have included the most recent SCN4M_SUBM design rules from [Qflow]. +## Docker Image + +We have a pre-configured Ubuntu [Docker](https://www.docker.com/) image +available that has all tools installed for the [SCMOS] process. It is +available at [docker hub](https://hub.docker.com/r/vlsida/openram-ubuntu). +Please see +[our README.md](https://github.com/VLSIDA/openram-docker-images/blob/master/README.md) +for information on how to use this docker image. + +## Vagrant Image + +We have a pre-configured Ubuntu [Vagrant](https://www.vagrantup.com/) image +available that has all tools installed for the [SCMOS] process. +Please see +[our README.md](https://github.com/VLSIDA/openram-vagrant-image/blob/master/README.md) +for information on how to use this image. + # Basic Usage Once you have defined the environment, you can run OpenRAM from the command line @@ -104,12 +112,16 @@ num_words = 16 # Technology to use in $OPENRAM_TECH tech_name = "scn4m_subm" + +# You can use the technology nominal corner only +nominal_corner_only = True +# Or you can specify particular corners # Process corners to characterize -process_corners = ["TT"] +# process_corners = ["SS", "TT", "FF"] # Voltage corners to characterize -supply_voltages = [ 3.3 ] +# supply_voltages = [ 3.0, 3.3, 3.5 ] # Temperature corners to characterize -temperatures = [ 25 ] +# temperatures = [ 0, 25 100] # Output directory for the results output_path = "temp" @@ -119,11 +131,6 @@ output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name) # Disable analytical models for full characterization (WARNING: slow!) # analytical_delay = False -# To force this to use magic and netgen for DRC/LVS/PEX -# Could be calibre for FreePDK45 -drc_name = "magic" -lvs_name = "netgen" -pex_name = "magic" ``` You can then run OpenRAM by executing: @@ -188,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 @@ -207,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! @@ -229,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/_config.yml b/_config.yml index 2f7efbea..9da9a029 100644 --- a/_config.yml +++ b/_config.yml @@ -1 +1 @@ -theme: jekyll-theme-minimal \ No newline at end of file +theme: jekyll-theme-dinky \ No newline at end of file diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py new file mode 100644 index 00000000..f2711222 --- /dev/null +++ b/compiler/base/channel_route.py @@ -0,0 +1,398 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import collections +import debug +from tech import drc +from vector import vector +import design + + +class channel_net(): + def __init__(self, net_name, pins, vertical): + self.name = net_name + self.pins = pins + self.vertical = vertical + + # Keep track of the internval + if vertical: + self.min_value = min(i.by() for i in pins) + self.max_value = max(i.uy() for i in pins) + else: + self.min_value = min(i.lx() for i in pins) + self.max_value = max(i.rx() for i in pins) + + # Keep track of the conflicts + self.conflicts = [] + + def __str__(self): + return self.name + + def __repr__(self): + return self.name + + def __lt__(self, other): + return self.min_value < other.min_value + + def vcg_pin_overlap(self, pin1, pin2, pitch): + """ Check for vertical or horizontal overlap of the two pins """ + + # FIXME: If the pins are not in a row, this may break. + # However, a top pin shouldn't overlap another top pin, + # for example, so the extra comparison *shouldn't* matter. + + # Pin 1 must be in the "BOTTOM" set + x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch + + # Pin 1 must be in the "LEFT" set + y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch + overlaps = (not self.vertical and x_overlap) or (self.vertical and y_overlap) + return overlaps + + def vcg_nets_overlap(self, other, pitch): + """ + Check all the pin pairs on two nets and return a pin + overlap if any pin overlaps. + """ + + for pin1 in self.pins: + for pin2 in other.pins: + if self.vcg_pin_overlap(pin1, pin2, pitch): + return True + + return False + + def hcg_nets_overlap(self, other): + """ + Check if the horizontal span of the two nets overlaps eachother. + """ + min_overlap = self.min_value >= other.min_value and self.min_value <= other.max_value + max_overlap = self.max_value >= other.min_value and self.max_value <= other.max_value + return min_overlap or max_overlap + + +class channel_route(design.design): + + unique_id = 0 + + def __init__(self, + netlist, + offset, + layer_stack, + directions=None, + vertical=False): + """ + The net list is a list of the nets with each net being a list of pins + to be connected. The offset is the lower-left of where the + routing channel will start. This does NOT try to minimize the + number of tracks -- instead, it picks an order to avoid the + vertical conflicts between pins. The track size must be the number of + nets times the *nonpreferred* routing of the non-track layer pitch. + + """ + name = "cr_{0}".format(channel_route.unique_id) + channel_route.unique_id += 1 + design.design.__init__(self, name) + + self.netlist = netlist + self.offset = offset + self.layer_stack = layer_stack + self.directions = directions + self.vertical = vertical + + if not directions or directions == "pref": + # Use the preferred layer directions + if self.get_preferred_direction(layer_stack[0]) == "V": + self.vertical_layer = layer_stack[0] + self.horizontal_layer = layer_stack[2] + else: + self.vertical_layer = layer_stack[2] + self.horizontal_layer = layer_stack[0] + elif directions == "nonpref": + # Use the preferred layer directions + if self.get_preferred_direction(layer_stack[0]) == "V": + self.vertical_layer = layer_stack[2] + self.horizontal_layer = layer_stack[0] + else: + self.vertical_layer = layer_stack[0] + self.horizontal_layer = layer_stack[2] + else: + # Use the layer directions specified to the router rather than + # the preferred directions + debug.check(directions[0] != directions[1], "Must have unique layer directions.") + if directions[0] == "V": + self.vertical_layer = layer_stack[0] + self.horizontal_layer = layer_stack[2] + else: + self.horizontal_layer = layer_stack[0] + self.vertical_layer = layer_stack[2] + + layer_stuff = self.get_layer_pitch(self.vertical_layer) + (self.vertical_nonpref_pitch, self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff + + layer_stuff = self.get_layer_pitch(self.horizontal_layer) + (self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff + + self.route() + + def remove_net_from_graph(self, pin, g): + """ + Remove the pin from the graph and all conflicts + """ + g.pop(pin, None) + + # Remove the pin from all conflicts + # FIXME: This is O(n^2), so maybe optimize it. + for other_pin, conflicts in g.items(): + if pin in conflicts: + g[other_pin].remove(pin) + return g + + def route(self): + # Create names for the nets for the graphs + nets = [] + index = 0 + # print(self.netlist) + for pin_list in self.netlist: + nets.append(channel_net("n{}".format(index), pin_list, self.vertical)) + index += 1 + + # Create the (undirected) horizontal constraint graph + hcg = collections.OrderedDict() + for net1 in nets: + for net2 in nets: + if net1.name == net2.name: + continue + if net1.hcg_nets_overlap(net2): + try: + hcg[net1.name].add(net2.name) + except KeyError: + hcg[net1.name] = set([net2.name]) + try: + hcg[net2.name].add(net1.name) + except KeyError: + hcg[net2.name] = set([net1.name]) + + + # Initialize the vertical conflict graph (vcg) + # and make a list of all pins + vcg = collections.OrderedDict() + + # print("Nets:") + # for net_name in nets: + # print(net_name, [x.name for x in nets[net_name]]) + + # Find the vertical pin conflicts + # FIXME: O(n^2) but who cares for now + if self.vertical: + pitch = self.horizontal_nonpref_pitch + else: + pitch = self.vertical_nonpref_pitch + + for net in nets: + vcg[net.name] = set() + + for net1 in nets: + for net2 in nets: + # Skip yourself + if net1.name == net2.name: + continue + + if net1.vcg_nets_overlap(net2, pitch): + vcg[net2.name].add(net1.name) + + # Check if there are any cycles net1 <---> net2 in the VCG + + + # Some of the pins may be to the left/below the channel offset, + # so adjust if this is the case + min_value = min([n.min_value for n in nets]) + if self.vertical: + real_channel_offset = vector(self.offset.x, min_value) + else: + real_channel_offset = vector(min_value, self.offset.y) + current_offset = real_channel_offset + + # Sort nets by left edge value + nets.sort() + while len(nets) > 0: + + current_offset_value = current_offset.y if self.vertical else current_offset.x + + # from pprint import pformat + # print("VCG:\n", pformat(vcg)) + # for name,net in vcg.items(): + # print(name, net.min_value, net.max_value, net.conflicts) + # print(current_offset) + # get a route from conflict graph with empty fanout set + for net in nets: + # If it has no conflicts and the interval is to the right of the current offset in the track + if net.min_value >= current_offset_value and len(vcg[net.name]) == 0: + # print("Routing {}".format(net.name)) + # Add the trunk routes from the bottom up for + # horizontal or the left to right for vertical + if self.vertical: + self.add_vertical_trunk_route(net.pins, + current_offset, + self.vertical_nonpref_pitch) + current_offset = vector(current_offset.x, net.max_value + self.horizontal_nonpref_pitch) + else: + self.add_horizontal_trunk_route(net.pins, + current_offset, + self.horizontal_nonpref_pitch) + current_offset = vector(net.max_value + self.vertical_nonpref_pitch, current_offset.y) + + # Remove the net from other constriants in the VCG + vcg = self.remove_net_from_graph(net.name, vcg) + nets.remove(net) + + break + else: + # If we made a full pass and the offset didn't change... + current_offset_value = current_offset.y if self.vertical else current_offset.x + initial_offset_value = real_channel_offset.y if self.vertical else real_channel_offset.x + if current_offset_value == initial_offset_value: + # FIXME: We don't support cyclic VCGs right now. + debug.error("Cyclic VCG in channel router.", -1) + + # Increment the track and reset the offset to the start (like a typewriter) + if self.vertical: + current_offset = vector(current_offset.x + self.horizontal_nonpref_pitch, real_channel_offset.y) + else: + current_offset = vector(real_channel_offset.x, current_offset.y + self.vertical_nonpref_pitch) + + # Return the size of the channel + if self.vertical: + self.width = 0 + self.height = current_offset.y + return current_offset.y + self.vertical_nonpref_pitch - self.offset.y + else: + self.width = current_offset.x + self.height = 0 + return current_offset.x + self.horizontal_nonpref_pitch - self.offset.x + + def get_layer_pitch(self, layer): + """ Return the track pitch on a given layer """ + try: + # FIXME: Using non-pref pitch here due to overlap bug in VCG constraints. + # It should just result in inefficient channel width but will work. + pitch = getattr(self, "{}_pitch".format(layer)) + nonpref_pitch = getattr(self, "{}_nonpref_pitch".format(layer)) + space = getattr(self, "{}_space".format(layer)) + except AttributeError: + debug.error("Cannot find layer pitch.", -1) + return (nonpref_pitch, pitch, pitch - space, space) + + def add_horizontal_trunk_route(self, + pins, + trunk_offset, + pitch): + """ + Create a trunk route for all pins with + the trunk located at the given y offset. + """ + max_x = max([pin.center().x for pin in pins]) + min_x = min([pin.center().x for pin in pins]) + + # if we are less than a pitch, just create a non-preferred layer jog + non_preferred_route = max_x - min_x <= pitch + + if non_preferred_route: + half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] + # Add the horizontal trunk on the vertical layer! + self.add_path(self.vertical_layer, + [vector(min_x - half_layer_width, trunk_offset.y), + vector(max_x + half_layer_width, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + if pin.cy() < trunk_offset.y: + pin_pos = pin.uc() + else: + pin_pos = pin.bc() + + # No bend needed here + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + else: + # Add the horizontal trunk + self.add_path(self.horizontal_layer, + [vector(min_x, trunk_offset.y), + vector(max_x, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + # Find the correct side of the pin + if pin.cy() < trunk_offset.y: + pin_pos = pin.uc() + else: + pin_pos = pin.bc() + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + if not non_preferred_route: + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin_pos) + + def add_vertical_trunk_route(self, + pins, + trunk_offset, + pitch): + """ + Create a trunk route for all pins with the + trunk located at the given x offset. + """ + max_y = max([pin.center().y for pin in pins]) + min_y = min([pin.center().y for pin in pins]) + + # if we are less than a pitch, just create a non-preferred layer jog + non_preferred_route = max_y - min_y <= pitch + + if non_preferred_route: + half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)] + # Add the vertical trunk on the horizontal layer! + self.add_path(self.horizontal_layer, + [vector(trunk_offset.x, min_y - half_layer_width), + vector(trunk_offset.x, max_y + half_layer_width)]) + + # Route each pin to the trunk + for pin in pins: + # Find the correct side of the pin + if pin.cx() < trunk_offset.x: + pin_pos = pin.rc() + else: + pin_pos = pin.lc() + # No bend needed here + mid = vector(trunk_offset.x, pin_pos.y) + self.add_path(self.horizontal_layer, [pin_pos, mid]) + else: + # Add the vertical trunk + self.add_path(self.vertical_layer, + [vector(trunk_offset.x, min_y), + vector(trunk_offset.x, max_y)]) + + # Route each pin to the trunk + for pin in pins: + # Find the correct side of the pin + if pin.cx() < trunk_offset.x: + pin_pos = pin.rc() + else: + pin_pos = pin.lc() + mid = vector(trunk_offset.x, pin_pos.y) + self.add_path(self.horizontal_layer, [pin_pos, mid]) + if not non_preferred_route: + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.horizontal_layer, + offset=pin_pos) + + diff --git a/compiler/base/contact.py b/compiler/base/contact.py index 30f6870b..cc1ca27a 100644 --- a/compiler/base/contact.py +++ b/compiler/base/contact.py @@ -8,7 +8,10 @@ import hierarchy_design import debug from tech import drc, layer +import tech from vector import vector +from sram_factory import factory +import sys class contact(hierarchy_design.hierarchy_design): @@ -26,22 +29,42 @@ class contact(hierarchy_design.hierarchy_design): """ - def __init__(self, layer_stack, dimensions=(1, 1), directions=("V", "V"), + def __init__(self, layer_stack, dimensions=(1, 1), directions=None, implant_type=None, well_type=None, name=""): # This will ignore the name parameter since # we can guarantee a unique name here hierarchy_design.hierarchy_design.__init__(self, name) debug.info(4, "create contact object {0}".format(name)) + self.add_comment("layers: {0}".format(layer_stack)) self.add_comment("dimensions: {0}".format(dimensions)) if implant_type or well_type: self.add_comment("implant type: {}\n".format(implant_type)) self.add_comment("well_type: {}\n".format(well_type)) - + + self.is_well_contact = implant_type == well_type + + # If we have a special tap layer, use it self.layer_stack = layer_stack self.dimensions = dimensions - self.directions = directions + + # Non-preferred directions + if directions == "nonpref": + first_dir = "H" if self.get_preferred_direction(layer_stack[0])=="V" else "V" + second_dir = "H" if self.get_preferred_direction(layer_stack[2])=="V" else "V" + self.directions = (first_dir, second_dir) + # Preferred directions + elif directions == "pref": + self.directions = (tech.preferred_directions[layer_stack[0]], + tech.preferred_directions[layer_stack[2]]) + # User directions + elif directions: + self.directions = directions + # Preferred directions + else: + self.directions = (tech.preferred_directions[layer_stack[0]], + tech.preferred_directions[layer_stack[2]]) self.offset = vector(0, 0) self.implant_type = implant_type self.well_type = well_type @@ -56,30 +79,39 @@ class contact(hierarchy_design.hierarchy_design): self.create_contact_array() self.create_first_layer_enclosure() self.create_second_layer_enclosure() + self.create_nitride_cut_enclosure() - self.height = max(obj.offset.y + obj.height for obj in self.objs) - self.width = max(obj.offset.x + obj.width for obj in self.objs) + self.height = max(self.first_layer_position.y + self.first_layer_height, + self.second_layer_position.y + self.second_layer_height) + self.width = max(self.first_layer_position.x + self.first_layer_width, + self.second_layer_position.x + self.second_layer_width) # Do not include the select layer in the height/width if self.implant_type and self.well_type: self.create_implant_well_enclosures() elif self.implant_type or self.well_type: - debug.error(-1, "Must define both implant and well type or none at all.") + debug.error(-1, + "Must define both implant and well type or none.") def setup_layers(self): """ Locally assign the layer names. """ (first_layer, via_layer, second_layer) = self.layer_stack self.first_layer_name = first_layer - self.via_layer_name = via_layer - # Some technologies have a separate active - # contact from the poly contact - # We will use contact for DRC, but active_contact for output - if first_layer == "active" or second_layer == "active": - self.via_layer_name_expanded = "active_" + via_layer - else: - self.via_layer_name_expanded = via_layer self.second_layer_name = second_layer + + # Contacts will have unique per first layer + if via_layer in tech.layer: + self.via_layer_name = via_layer + elif via_layer == "contact": + if first_layer in ("active", "poly"): + self.via_layer_name = first_layer + "_" + via_layer + elif second_layer in ("active", "poly"): + self.via_layer_name = second_layer + "_" + via_layer + else: + debug.error("Invalid via layer {}".format(via_layer), -1) + else: + debug.error("Invalid via layer {}".format(via_layer), -1) def setup_layout_constants(self): """ Determine the design rules for the enclosure layers """ @@ -95,70 +127,105 @@ class contact(hierarchy_design.hierarchy_design): # The extend rule applies to asymmetric enclosures in one direction. # The enclosure rule applies to symmetric enclosure component. - first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name)) - first_layer_enclosure = drc("{0}_enclosure_{1}".format(self.first_layer_name, self.via_layer_name)) - first_layer_extend = drc("{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name)) + self.first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name)) + self.first_layer_enclosure = drc("{0}_enclose_{1}".format(self.first_layer_name, self.via_layer_name)) + # If there's a different rule for active + # FIXME: Make this more elegant + if self.is_well_contact and self.first_layer_name == "active" and "tap_extend_contact" in drc.keys(): + self.first_layer_extend = drc("tap_extend_contact") + else: + self.first_layer_extend = drc("{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name)) - second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name)) - second_layer_enclosure = drc("{0}_enclosure_{1}".format(self.second_layer_name, self.via_layer_name)) - second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name)) + self.second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name)) + self.second_layer_enclosure = drc("{0}_enclose_{1}".format(self.second_layer_name, self.via_layer_name)) + self.second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name)) # In some technologies, the minimum width may be larger # than the overlap requirement around the via, so # check this for each dimension. if self.directions[0] == "V": - self.first_layer_horizontal_enclosure = max(first_layer_enclosure, - (first_layer_minwidth - self.contact_array_width) / 2) - self.first_layer_vertical_enclosure = max(first_layer_extend, - (first_layer_minwidth - self.contact_array_height) / 2) + self.first_layer_horizontal_enclosure = max(self.first_layer_enclosure, + (self.first_layer_minwidth - self.contact_array_width) / 2) + self.first_layer_vertical_enclosure = max(self.first_layer_extend, + (self.first_layer_minwidth - self.contact_array_height) / 2) elif self.directions[0] == "H": - self.first_layer_horizontal_enclosure = max(first_layer_extend, - (first_layer_minwidth - self.contact_array_width) / 2) - self.first_layer_vertical_enclosure = max(first_layer_enclosure, - (first_layer_minwidth - self.contact_array_height) / 2) + self.first_layer_horizontal_enclosure = max(self.first_layer_extend, + (self.first_layer_minwidth - self.contact_array_width) / 2) + self.first_layer_vertical_enclosure = max(self.first_layer_enclosure, + (self.first_layer_minwidth - self.contact_array_height) / 2) else: - debug.error("Invalid first layer direction.", -1) + debug.error("Invalid first layer direction: ".format(self.directions[0]), -1) - # In some technologies, the minimum width may be larger than the overlap requirement around the via, so + # In some technologies, the minimum width may be larger + # than the overlap requirement around the via, so # check this for each dimension. if self.directions[1] == "V": - self.second_layer_horizontal_enclosure = max(second_layer_enclosure, - (second_layer_minwidth - self.contact_array_width) / 2) - self.second_layer_vertical_enclosure = max(second_layer_extend, - (second_layer_minwidth - self.contact_array_height) / 2) + self.second_layer_horizontal_enclosure = max(self.second_layer_enclosure, + (self.second_layer_minwidth - self.contact_array_width) / 2) + self.second_layer_vertical_enclosure = max(self.second_layer_extend, + (self.second_layer_minwidth - self.contact_array_height) / 2) elif self.directions[1] == "H": - self.second_layer_horizontal_enclosure = max(second_layer_extend, - (second_layer_minwidth - self.contact_array_height) / 2) - self.second_layer_vertical_enclosure = max(second_layer_enclosure, - (second_layer_minwidth - self.contact_array_width) / 2) + self.second_layer_horizontal_enclosure = max(self.second_layer_extend, + (self.second_layer_minwidth - self.contact_array_height) / 2) + self.second_layer_vertical_enclosure = max(self.second_layer_enclosure, + (self.second_layer_minwidth - self.contact_array_width) / 2) else: - debug.error("Invalid second layer direction.", -1) + debug.error("Invalid secon layer direction: ".format(self.directions[1]), -1) def create_contact_array(self): """ Create the contact array at the origin""" # offset for the via array self.via_layer_position = vector( - max(self.first_layer_horizontal_enclosure, self.second_layer_horizontal_enclosure), - max(self.first_layer_vertical_enclosure, self.second_layer_vertical_enclosure)) + max(self.first_layer_horizontal_enclosure, + self.second_layer_horizontal_enclosure), + max(self.first_layer_vertical_enclosure, + self.second_layer_vertical_enclosure)) for i in range(self.dimensions[1]): - offset = self.via_layer_position + vector(0, self.contact_pitch * i) + offset = self.via_layer_position + vector(0, + self.contact_pitch * i) for j in range(self.dimensions[0]): - self.add_rect(layer=self.via_layer_name_expanded, + self.add_rect(layer=self.via_layer_name, offset=offset, width=self.contact_width, height=self.contact_width) offset = offset + vector(self.contact_pitch, 0) + def create_nitride_cut_enclosure(self): + """ Special layer that encloses poly contacts in some processes """ + # Check if there is a special poly nitride cut layer + if "npc" not in tech.layer: + return + + npc_enclose_poly = drc("npc_enclose_poly") + npc_enclose_offset = vector(npc_enclose_poly, npc_enclose_poly) + # Only add for poly layers + if self.first_layer_name == "poly": + self.add_rect(layer="npc", + offset=self.first_layer_position - npc_enclose_offset, + width=self.first_layer_width + 2 * npc_enclose_poly, + height=self.first_layer_height + 2 * npc_enclose_poly) + elif self.second_layer_name == "poly": + self.add_rect(layer="npc", + offset=self.second_layer_position - npc_enclose_offset, + width=self.second_layer_width + 2 * npc_enclose_poly, + height=self.second_layer_height + 2 * npc_enclose_poly) + def create_first_layer_enclosure(self): # this is if the first and second layers are different self.first_layer_position = vector( max(self.second_layer_horizontal_enclosure - self.first_layer_horizontal_enclosure, 0), max(self.second_layer_vertical_enclosure - self.first_layer_vertical_enclosure, 0)) - self.first_layer_width = self.contact_array_width + 2 * self.first_layer_horizontal_enclosure - self.first_layer_height = self.contact_array_height + 2 * self.first_layer_vertical_enclosure - self.add_rect(layer=self.first_layer_name, + self.first_layer_width = max(self.contact_array_width + 2 * self.first_layer_horizontal_enclosure, + self.first_layer_minwidth) + self.first_layer_height = max(self.contact_array_height + 2 * self.first_layer_vertical_enclosure, + self.first_layer_minwidth) + if self.is_well_contact and self.first_layer_name == "active" and "tap" in layer: + first_layer_name = "tap" + else: + first_layer_name = self.first_layer_name + self.add_rect(layer=first_layer_name, offset=self.first_layer_position, width=self.first_layer_width, height=self.first_layer_height) @@ -169,57 +236,72 @@ class contact(hierarchy_design.hierarchy_design): max(self.first_layer_horizontal_enclosure - self.second_layer_horizontal_enclosure, 0), max(self.first_layer_vertical_enclosure - self.second_layer_vertical_enclosure, 0)) - self.second_layer_width = self.contact_array_width + 2 * self.second_layer_horizontal_enclosure - self.second_layer_height = self.contact_array_height + 2 * self.second_layer_vertical_enclosure + self.second_layer_width = max(self.contact_array_width + 2 * self.second_layer_horizontal_enclosure, + self.second_layer_minwidth) + self.second_layer_height = max(self.contact_array_height + 2 * self.second_layer_vertical_enclosure, + self.second_layer_minwidth) self.add_rect(layer=self.second_layer_name, offset=self.second_layer_position, width=self.second_layer_width, height=self.second_layer_height) def create_implant_well_enclosures(self): - implant_position = self.first_layer_position - [drc("implant_enclosure_active")] * 2 - implant_width = self.first_layer_width + 2 * drc("implant_enclosure_active") - implant_height = self.first_layer_height + 2 * drc("implant_enclosure_active") + implant_position = self.first_layer_position - [drc("implant_enclose_active")] * 2 + implant_width = self.first_layer_width + 2 * drc("implant_enclose_active") + implant_height = self.first_layer_height + 2 * drc("implant_enclose_active") self.add_rect(layer="{}implant".format(self.implant_type), offset=implant_position, width=implant_width, height=implant_height) - well_position = self.first_layer_position - [drc("well_enclosure_active")] * 2 - well_width = self.first_layer_width + 2 * drc("well_enclosure_active") - well_height = self.first_layer_height + 2 * drc("well_enclosure_active") - self.add_rect(layer="{}well".format(self.well_type), - offset=well_position, - width=well_width, - height=well_height) + + # Optionally implant well if layer exists + well_layer = "{}well".format(self.well_type) + if well_layer in tech.layer: + well_width_rule = drc("minwidth_" + well_layer) + self.well_enclose_active = drc(well_layer + "_enclose_active") + self.well_width = max(self.first_layer_width + 2 * self.well_enclose_active, + well_width_rule) + self.well_height = max(self.first_layer_height + 2 * self.well_enclose_active, + well_width_rule) + center_pos = vector(0.5*self.width, 0.5*self.height) + well_position = center_pos - vector(0.5*self.well_width, 0.5*self.well_height) + self.add_rect(layer=well_layer, + offset=well_position, + width=self.well_width, + height=self.well_height) def analytical_power(self, corner, load): """ Get total power of a module """ return self.return_power() -from sram_factory import factory +# Set up a static for each layer to be used for measurements +for layer_stack in tech.layer_stacks: + (layer1, via, layer2) = layer_stack + cont = factory.create(module_type="contact", + layer_stack=layer_stack) + module = sys.modules[__name__] + # Also create a contact that is just the first layer + if layer1 == "poly" or layer1 == "active": + setattr(module, layer1 + "_contact", cont) + else: + setattr(module, layer1 + "_via", cont) + +# Set up a static for each well contact for measurements +if "nwell" in tech.layer: + cont = factory.create(module_type="contact", + layer_stack=tech.active_stack, + implant_type="n", + well_type="n") + module = sys.modules[__name__] + setattr(module, "nwell_contact", cont) + +if "pwell" in tech.layer: + cont = factory.create(module_type="contact", + layer_stack=tech.active_stack, + implant_type="p", + well_type="p") + module = sys.modules[__name__] + setattr(module, "pwell_contact", cont) -# This is not instantiated and used for calculations only. -# These are static 1x1 contacts to reuse in all the design modules. -well = factory.create(module_type="contact", - layer_stack=("active", "contact", "metal1"), - directions=("H", "V")) -active = factory.create(module_type="contact", - layer_stack=("active", "contact", "metal1"), - directions=("H", "V")) -poly = factory.create(module_type="contact", - layer_stack=("poly", "contact", "metal1"), - directions=("V", "H")) -m1m2 = factory.create(module_type="contact", - layer_stack=("metal1", "via1", "metal2"), - directions=("H", "V")) -m2m3 = factory.create(module_type="contact", - layer_stack=("metal2", "via2", "metal3"), - directions=("V", "H")) -if "metal4" in layer.keys(): - m3m4 = factory.create(module_type="contact", - layer_stack=("metal3", "via3", "metal4"), - directions=("H", "V")) -else: - m3m4 = None diff --git a/compiler/base/custom_cell_properties.py b/compiler/base/custom_cell_properties.py new file mode 100644 index 00000000..ba671279 --- /dev/null +++ b/compiler/base/custom_cell_properties.py @@ -0,0 +1,150 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2020 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# + +class _pins: + def __init__(self, pin_dict): + # make the pins elements of the class to allow "." access. + # For example: props.bitcell.cell_6t.pin.bl = "foobar" + for k,v in pin_dict.items(): + self.__dict__[k] = v + +class _cell: + def __init__(self, pin_dict): + pin_dict.update(self._default_power_pins()) + self._pins = _pins(pin_dict) + + @property + def pin(self): + return self._pins + + def _default_power_pins(self): + return { 'vdd' : 'vdd', 'gnd' : 'gnd' } + +class _mirror_axis: + def __init__(self, x, y): + self.x = x + self.y = y + +class _bitcell: + def __init__(self, mirror, split_wl, cell_6t, cell_1rw1r, cell_1w1r): + self.mirror = mirror + self.split_wl = split_wl + self._6t = cell_6t + self._1rw1r = cell_1rw1r + self._1w1r = cell_1w1r + + def _default(): + axis = _mirror_axis(True, False) + cell_6t = _cell({'bl' : 'bl', + 'br' : 'br', + 'wl' : 'wl'}) + + cell_1rw1r = _cell({'bl0' : 'bl0', + 'br0' : 'br0', + 'bl1' : 'bl1', + 'br1' : 'br1', + 'wl0' : 'wl0', + 'wl1' : 'wl1'}) + cell_1w1r = _cell({'bl0' : 'bl0', + 'br0' : 'br0', + 'bl1' : 'bl1', + 'br1' : 'br1', + 'wl0' : 'wl0', + 'wl1' : 'wl1'}) + return _bitcell(cell_6t=cell_6t, + cell_1rw1r=cell_1rw1r, + cell_1w1r=cell_1w1r, + split_wl = False, + mirror=axis) + + @property + def cell_6t(self): + return self._6t + + @property + def cell_1rw1r(self): + return self._1rw1r + + @property + def cell_1w1r(self): + return self._1w1r + + +class _dff: + def __init__(self, use_custom_ports, custom_port_list, custom_type_list, clk_pin): + self.use_custom_ports = use_custom_ports + self.custom_port_list = custom_port_list + self.custom_type_list = custom_type_list + self.clk_pin = clk_pin + +class _dff_buff: + def __init__(self, use_custom_ports, custom_buff_ports, add_body_contacts): + self.use_custom_ports = use_custom_ports + self.buf_ports = custom_buff_ports + self.add_body_contacts = add_body_contacts + +class _dff_buff_array: + def __init__(self, use_custom_ports, add_body_contacts): + self.use_custom_ports = use_custom_ports + self.add_body_contacts = add_body_contacts + +class cell_properties(): + """ + This contains meta information about the custom designed cells. For + instance, pin names, or the axis on which they need to be mirrored. These + can be overriden in the tech.py file. + """ + def __init__(self): + self.names = {} + + self._bitcell = _bitcell._default() + + self._dff = _dff(use_custom_ports = False, + custom_port_list = ["D", "Q", "clk", "vdd", "gnd"], + custom_type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"], + clk_pin= "clk") + + self._dff_buff = _dff_buff(use_custom_ports = False, + custom_buff_ports = ["D", "qint", "clk", "vdd", "gnd"], + add_body_contacts = False) + + self._dff_buff_array = _dff_buff_array(use_custom_ports = False, + add_body_contacts = False) + + 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 + + @property + def dff(self): + return self._dff + + @property + def dff_buff(self): + return self._dff_buff + + @property + def dff_buff_array(self): + return self._dff_buff_array + + @property + def write_driver(self): + return self._write_driver + + @property + def sense_amp(self): + return self._sense_amp diff --git a/compiler/base/design.py b/compiler/base/design.py index 33914358..2b2d7711 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -6,14 +6,16 @@ # All rights reserved. # from hierarchy_design import hierarchy_design +from utils import round_to_grid import contact from globals import OPTS +import re class design(hierarchy_design): """ This is the same as the hierarchy_design class except it contains - some DRC constants and analytical models for other modules to reuse. + some DRC/layer constants and analytical models for other modules to reuse. """ @@ -21,42 +23,195 @@ class design(hierarchy_design): hierarchy_design.__init__(self, name) self.setup_drc_constants() + self.setup_layer_constants() self.setup_multiport_constants() - from tech import layer - self.m1_pitch = max(contact.m1m2.width, contact.m1m2.height) + max(self.m1_space, self.m2_space) - self.m2_pitch = max(contact.m2m3.width, contact.m2m3.height) + max(self.m2_space, self.m3_space) - if "metal4" in layer: - self.m3_pitch = max(contact.m3m4.width, contact.m3m4.height) + max(self.m3_space, self.m4_space) + def setup_layer_constants(self): + """ + These are some layer constants used + in many places in the compiler. + """ + + from tech import layer_indices + import tech + for layer in layer_indices: + key = "{}_stack".format(layer) + + # Set the stack as a local helper + try: + layer_stack = getattr(tech, key) + setattr(self, key, layer_stack) + except AttributeError: + pass + + # Skip computing the pitch for active + if layer == "active": + continue + + # Add the pitch + setattr(self, + "{}_pitch".format(layer), + self.compute_pitch(layer, True)) + + # Add the non-preferrd pitch (which has vias in the "wrong" way) + setattr(self, + "{}_nonpref_pitch".format(layer), + self.compute_pitch(layer, False)) + + if False: + from tech import preferred_directions + print(preferred_directions) + from tech import layer, layer_indices + for name in layer_indices: + if name == "active": + continue + try: + print("{0} width {1} space {2}".format(name, + getattr(self, "{}_width".format(name)), + getattr(self, "{}_space".format(name)))) + + print("pitch {0} nonpref {1}".format(getattr(self, "{}_pitch".format(name)), + getattr(self, "{}_nonpref_pitch".format(name)))) + except AttributeError: + pass + import sys + sys.exit(1) + + def compute_pitch(self, layer, preferred=True): + + """ + This is the preferred direction pitch + i.e. we take the minimum or maximum contact dimension + """ + # Find the layer stacks this is used in + from tech import layer_stacks + pitches = [] + for stack in layer_stacks: + # Compute the pitch with both vias above and below (if they exist) + if stack[0] == layer: + pitches.append(self.compute_layer_pitch(stack, preferred)) + if stack[2] == layer: + pitches.append(self.compute_layer_pitch(stack[::-1], True)) + + return max(pitches) + + def compute_layer_pitch(self, layer_stack, preferred): + + (layer1, via, layer2) = layer_stack + try: + if layer1 == "poly" or layer1 == "active": + contact1 = getattr(contact, layer1 + "_contact") + else: + contact1 = getattr(contact, layer1 + "_via") + except AttributeError: + contact1 = getattr(contact, layer2 + "_via") + + if preferred: + if self.get_preferred_direction(layer1) == "V": + contact_width = contact1.first_layer_width + else: + contact_width = contact1.first_layer_height else: - self.m3_pitch = self.m2_pitch + if self.get_preferred_direction(layer1) == "V": + contact_width = contact1.first_layer_height + else: + contact_width = contact1.first_layer_width + layer_space = getattr(self, layer1 + "_space") + #print(layer_stack) + #print(contact1) + pitch = contact_width + layer_space + + return round_to_grid(pitch) + def setup_drc_constants(self): - """ These are some DRC constants used in many places in the compiler.""" - from tech import drc, layer - self.well_width = drc("minwidth_well") - self.poly_width = drc("minwidth_poly") - self.poly_space = drc("poly_to_poly") - self.m1_width = drc("minwidth_metal1") - self.m1_space = drc("metal1_to_metal1") - self.m2_width = drc("minwidth_metal2") - self.m2_space = drc("metal2_to_metal2") - self.m3_width = drc("minwidth_metal3") - self.m3_space = drc("metal3_to_metal3") - if "metal4" in layer: - self.m4_width = drc("minwidth_metal4") - self.m4_space = drc("metal4_to_metal4") - self.active_width = drc("minwidth_active") - self.active_space = drc("active_to_body_active") - self.contact_width = drc("minwidth_contact") + """ + These are some DRC constants used in many places + in the compiler. + """ + # Make some local rules for convenience + from tech import drc + for rule in drc.keys(): + # Single layer width rules + match = re.search(r"minwidth_(.*)", rule) + if match: + if match.group(1) == "active_contact": + setattr(self, "contact_width", drc(match.group(0))) + else: + setattr(self, match.group(1) + "_width", drc(match.group(0))) - self.poly_to_active = drc("poly_to_active") - self.poly_extend_active = drc("poly_extend_active") - self.poly_to_polycontact = drc("poly_to_polycontact") - self.contact_to_gate = drc("contact_to_gate") - self.well_enclose_active = drc("well_enclosure_active") - self.implant_enclose_active = drc("implant_enclosure_active") - self.implant_space = drc("implant_to_implant") + # Single layer area rules + match = re.search(r"minarea_(.*)", rule) + if match: + setattr(self, match.group(0), drc(match.group(0))) + + # Single layer spacing rules + match = re.search(r"(.*)_to_(.*)", rule) + if match and match.group(1) == match.group(2): + setattr(self, match.group(1) + "_space", drc(match.group(0))) + elif match and match.group(1) != match.group(2): + if match.group(2) == "poly_active": + setattr(self, match.group(1) + "_to_contact", + drc(match.group(0))) + else: + setattr(self, match.group(0), drc(match.group(0))) + + match = re.search(r"(.*)_enclose_(.*)", rule) + if match: + setattr(self, match.group(0), drc(match.group(0))) + + match = re.search(r"(.*)_extend_(.*)", rule) + if match: + setattr(self, match.group(0), drc(match.group(0))) + + # Create the maximum well extend active that gets used + # by cells to extend the wells for interaction with other cells + from tech import layer + self.well_extend_active = 0 + if "nwell" in layer: + self.well_extend_active = max(self.well_extend_active, self.nwell_extend_active) + if "pwell" in layer: + self.well_extend_active = max(self.well_extend_active, self.pwell_extend_active) + + # The active offset is due to the well extension + if "pwell" in layer: + self.pwell_enclose_active = drc("pwell_enclose_active") + else: + self.pwell_enclose_active = 0 + if "nwell" in layer: + self.nwell_enclose_active = drc("nwell_enclose_active") + else: + self.nwell_enclose_active = 0 + # Use the max of either so that the poly gates will align properly + self.well_enclose_active = max(self.pwell_enclose_active, + self.nwell_enclose_active, + self.active_space) + + # These are for debugging previous manual rules + if False: + print("poly_width", self.poly_width) + print("poly_space", self.poly_space) + print("m1_width", self.m1_width) + print("m1_space", self.m1_space) + print("m2_width", self.m2_width) + print("m2_space", self.m2_space) + print("m3_width", self.m3_width) + print("m3_space", self.m3_space) + print("m4_width", self.m4_width) + print("m4_space", self.m4_space) + print("active_width", self.active_width) + print("active_space", self.active_space) + print("contact_width", self.contact_width) + print("poly_to_active", self.poly_to_active) + print("poly_extend_active", self.poly_extend_active) + print("poly_to_contact", self.poly_to_contact) + print("active_contact_to_gate", self.active_contact_to_gate) + print("poly_contact_to_gate", self.poly_contact_to_gate) + print("well_enclose_active", self.well_enclose_active) + print("implant_enclose_active", self.implant_enclose_active) + print("implant_space", self.implant_space) + import sys + sys.exit(1) def setup_multiport_constants(self): """ diff --git a/compiler/base/errors.py b/compiler/base/errors.py new file mode 100644 index 00000000..e2b9e5ec --- /dev/null +++ b/compiler/base/errors.py @@ -0,0 +1,15 @@ + + +class drc_error(Exception): + """Exception raised for DRC errors. + + Attributes: + expression -- input expression in which the error occurred + message -- explanation of the error + """ + + # def __init__(self, expression, message): + # self.expression = expression + # self.message = message + def __init__(self, message): + self.message = message diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index 02b100ad..99490eb3 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -53,7 +53,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 @@ -66,14 +66,19 @@ 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. """ + """ + 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)] + # Mirroring is performed before rotation if mirror == "MX": ll = ll.scale(1, -1) ur = ur.scale(1, -1) @@ -83,8 +88,14 @@ class geometry: elif mirror == "XY": ll = ll.scale(-1, -1) ur = ur.scale(-1, -1) - - if rotate == 90: + elif mirror == "" or mirror == "R0": + pass + else: + debug.error("Invalid mirroring: {}".format(mirror), -1) + + if rotate == 0: + pass + elif rotate == 90: ll = ll.rotate_scale(-1, 1) ur = ur.rotate_scale(-1, 1) elif rotate == 180: @@ -93,22 +104,24 @@ class geometry: elif rotate == 270: ll = ll.rotate_scale(1, -1) ur = ur.rotate_scale(1, -1) + else: + debug.error("Invalid rotation: {}".format(rotate), -1) self.boundary = [offset + ll, offset + ur] self.normalize() - + 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,11 +145,16 @@ 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) + def center(self): + """ Return the center coordinate """ + return vector(self.cx(), self.cy()) + + class instance(geometry): """ An instance of an instance/module with a specified location and @@ -147,7 +165,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 @@ -165,10 +183,10 @@ 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, layer, top=False): + def get_blockages(self, lpp, top=False): """ Retrieve blockages of all modules in this instance. Apply the transform of the instance placement to give absolute blockages.""" angle = math.radians(float(self.rotate)) @@ -192,20 +210,19 @@ class instance(geometry): if self.mod.is_library_cell: # Writes library cell blockages as shapes instead of a large metal blockage blockages = [] - blockages = self.mod.gds.getBlockages(layer) + blockages = self.mod.gds.getBlockages(lpp) for b in blockages: - new_blockages.append(self.transform_coords(b,self.offset, mirr, angle)) + new_blockages.append(self.transform_coords(b, self.offset, mirr, angle)) else: - blockages = self.mod.get_blockages(layer) + blockages = self.mod.get_blockages(lpp) for b in blockages: - new_blockages.append(self.transform_coords(b,self.offset, mirr, angle)) + new_blockages.append(self.transform_coords(b, self.offset, mirr, angle)) return new_blockages - def gds_write_file(self, new_layout): """Recursively writes all the sub-modules in this instance""" debug.info(4, "writing instance: " + self.name) - # 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 @@ -214,7 +231,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 @@ -222,27 +239,27 @@ class instance(geometry): self.mirror = mirror self.rotate = rotate self.update_boundary() - debug.info(3, "placing instance {}".format(self)) + debug.info(3, "placing instance {}".format(self)) - def get_pin(self,name,index=-1): + def get_pin(self, name, index=-1): """ Return an absolute pin that is offset and transformed based on this instance location. Index will return one of several pins.""" import copy if index == -1: pin = copy.deepcopy(self.mod.get_pin(name)) - pin.transform(self.offset,self.mirror,self.rotate) + pin.transform(self.offset, self.mirror, self.rotate) return pin else: pins = copy.deepcopy(self.mod.get_pin(name)) - pin.transform(self.offset,self.mirror,self.rotate) + pins.transform(self.offset, self.mirror, self.rotate) return pin[index] def get_num_pins(self, name): """ Return the number of pins of a given name """ return len(self.mod.get_pins(name)) - def get_pins(self,name): + def get_pins(self, name): """ Return an absolute pin that is offset and transformed based on this instance location. """ @@ -251,7 +268,7 @@ class instance(geometry): new_pins = [] for p in pin: - p.transform(self.offset,self.mirror,self.rotate) + p.transform(self.offset, self.mirror, self.rotate) new_pins.append(p) return new_pins @@ -391,14 +408,16 @@ class instance(geometry): """ override print function output """ return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")" + class path(geometry): """Represents a Path""" - def __init__(self, layerNumber, coordinates, path_width): + def __init__(self, lpp, coordinates, path_width): """Initializes a path for the specified layer""" geometry.__init__(self) self.name = "path" - self.layerNumber = layerNumber + self.layerNumber = lpp[0] + self.layerPurpose = lpp[1] self.coordinates = map(lambda x: [x[0], x[1]], coordinates) self.coordinates = vector(self.coordinates).snap_to_grid() self.path_width = path_width @@ -411,32 +430,33 @@ class path(geometry): """Writes the path to GDS""" debug.info(4, "writing path (" + str(self.layerNumber) + "): " + self.coordinates) new_layout.addPath(layerNumber=self.layerNumber, - purposeNumber=0, + purposeNumber=self.layerPurpose, coordinates=self.coordinates, width=self.path_width) 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 + return "path: layer=" + self.layerNumber + " purpose=" + str(self.layerPurpose) + " w=" + self.width def __repr__(self): """ override print function output """ - return "( path: layer=" + self.layerNumber + " w=" + self.width + " coords=" + str(self.coordinates) + " )" + return "( path: layer=" + self.layerNumber + " purpose=" + str(self.layerPurpose) + " w=" + self.width + " coords=" + str(self.coordinates) + " )" class label(geometry): """Represents a text label""" - def __init__(self, text, layerNumber, offset, zoom=-1): + def __init__(self, text, lpp, offset, zoom=-1): """Initializes a text label for specified layer""" geometry.__init__(self) self.name = "label" self.text = text - self.layerNumber = layerNumber + self.layerNumber = lpp[0] + self.layerPurpose = lpp[1] self.offset = vector(offset).snap_to_grid() if zoom<0: @@ -446,14 +466,14 @@ class label(geometry): self.size = 0 - debug.info(4,"creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset)) + debug.info(4, "creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset)) def gds_write_file(self, new_layout): """Writes the text label to GDS""" debug.info(4, "writing label (" + str(self.layerNumber) + "): " + self.text) new_layout.addText(text=self.text, layerNumber=self.layerNumber, - purposeNumber=0, + purposeNumber=self.layerPurpose, offsetInMicrons=self.offset, magnification=self.zoom, rotate=None) @@ -461,24 +481,25 @@ 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) + return "label: " + self.text + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) def __repr__(self): """ override print function output """ - return "( label: " + self.text + " @" + str(self.offset) + " layer=" + str(self.layerNumber) + " )" + return "( label: " + self.text + " @" + str(self.offset) + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) + " )" + - class rectangle(geometry): """Represents a rectangular shape""" - def __init__(self, layerNumber, offset, width, height): + def __init__(self, lpp, offset, width, height): """Initializes a rectangular shape for specified layer""" geometry.__init__(self) self.name = "rect" - self.layerNumber = layerNumber + self.layerNumber = lpp[0] + self.layerPurpose = lpp[1] self.offset = vector(offset).snap_to_grid() self.size = vector(width, height).snap_to_grid() self.width = round_to_grid(self.size.x) @@ -487,7 +508,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: @@ -502,7 +523,7 @@ class rectangle(geometry): debug.info(4, "writing rectangle (" + str(self.layerNumber) + "):" + str(self.width) + "x" + str(self.height) + " @ " + str(self.offset)) new_layout.addBox(layerNumber=self.layerNumber, - purposeNumber=0, + purposeNumber=self.layerPurpose, offsetInMicrons=self.offset, width=self.width, height=self.height, @@ -514,4 +535,4 @@ class rectangle(geometry): def __repr__(self): """ override print function output """ - return "( rect: @" + str(self.offset) + " WxH=" + str(self.width) + "x" + str(self.height) + " layer=" + str(self.layerNumber) + " )" + return "( rect: @" + str(self.offset) + " WxH=" + str(self.width) + "x" + str(self.height) + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) + " )" diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index e4c77987..79b5a53a 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -7,15 +7,10 @@ # import hierarchy_layout import hierarchy_spice -import globals -import verify import debug import os from globals import OPTS -import graph_util - -total_drc_errors = 0 -total_lvs_errors = 0 +import tech class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): """ @@ -28,126 +23,165 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): self.gds_file = OPTS.openram_tech + "gds_lib/" + name + ".gds" self.sp_file = OPTS.openram_tech + "sp_lib/" + name + ".sp" + # If we have a separate lvs directory, then all the lvs files + # should be in there (all or nothing!) + try: + lvs_subdir = tech.lvs_lib + except AttributeError: + lvs_subdir = "lvs_lib" + lvs_dir = OPTS.openram_tech + lvs_subdir + "/" + + if os.path.exists(lvs_dir): + self.lvs_file = lvs_dir + name + ".sp" + else: + self.lvs_file = self.sp_file + + self.drc_errors = "skipped" + self.lvs_errors = "skipped" + self.name = name hierarchy_spice.spice.__init__(self, name) hierarchy_layout.layout.__init__(self, name) self.init_graph_params() - def get_layout_pins(self,inst): + def get_layout_pins(self, inst): """ Return a map of pin locations of the instance offset """ # find the instance for i in self.insts: if i.name == inst.name: break else: - debug.error("Couldn't find instance {0}".format(inst_name),-1) + debug.error("Couldn't find instance {0}".format(inst.name), -1) inst_map = inst.mod.pin_map return inst_map - - def DRC_LVS(self, final_verification=False, top_level=False): + def DRC_LVS(self, final_verification=False, force_check=False): """Checks both DRC and LVS for a module""" - - # Final verification option does not allow nets to be connected by label. - # Unit tests will check themselves. - if OPTS.is_unit_test: + import verify + + # No layout to check + if OPTS.netlist_only: return - if not OPTS.check_lvsdrc: + # Unit tests will check themselves. + elif not force_check and OPTS.is_unit_test: + return + elif not force_check and not OPTS.check_lvsdrc: return # Do not run if disabled in options. - if (OPTS.inline_lvsdrc or top_level): + elif (OPTS.inline_lvsdrc or force_check or final_verification): - global total_drc_errors - global total_lvs_errors - tempspice = "{0}/{1}.sp".format(OPTS.openram_temp,self.name) - tempgds = "{0}/{1}.gds".format(OPTS.openram_temp,self.name) - self.sp_write(tempspice) + tempspice = "{0}/{1}.sp".format(OPTS.openram_temp, self.name) + tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name) + self.lvs_write(tempspice) self.gds_write(tempgds) + # Final verification option does not allow nets to be connected by label. + self.drc_errors = verify.run_drc(self.name, tempgds, extract=True, final_verification=final_verification) + self.lvs_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification) - num_drc_errors = verify.run_drc(self.name, tempgds, extract=True, final_verification=final_verification) - num_lvs_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification) - debug.check(num_drc_errors == 0,"DRC failed for {0} with {1} error(s)".format(self.name,num_drc_errors)) - debug.check(num_lvs_errors == 0,"LVS failed for {0} with {1} errors(s)".format(self.name,num_lvs_errors)) - total_drc_errors += num_drc_errors - total_lvs_errors += num_lvs_errors - - os.remove(tempspice) - os.remove(tempgds) + # force_check is used to determine decoder height and other things, so we shouldn't fail + # if that flag is set + if OPTS.inline_lvsdrc and not force_check: + debug.check(self.drc_errors == 0, + "DRC failed for {0} with {1} error(s)".format(self.name, + self.drc_errors)) + debug.check(self.lvs_errors == 0, + "LVS failed for {0} with {1} errors(s)".format(self.name, + self.lvs_errors)) + if OPTS.purge_temp: + os.remove(tempspice) + os.remove(tempgds) + def DRC(self, final_verification=False): """Checks DRC for a module""" + import verify + # Unit tests will check themselves. # Do not run if disabled in options. - if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)): - global total_drc_errors - tempgds = "{0}/{1}.gds".format(OPTS.openram_temp,self.name) + # No layout to check + if OPTS.netlist_only: + return + elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)): + tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name) self.gds_write(tempgds) - num_errors = verify.run_drc(self.name, tempgds, final_verification=final_verification) - total_drc_errors += num_errors - debug.check(num_errors == 0,"DRC failed for {0} with {1} error(s)".format(self.name,num_error)) + num_errors = verify.run_drc(self.name, tempgds, final_verification=final_verification) + debug.check(num_errors == 0, + "DRC failed for {0} with {1} error(s)".format(self.name, + num_errors)) - os.remove(tempgds) + if OPTS.purge_temp: + os.remove(tempgds) def LVS(self, final_verification=False): """Checks LVS for a module""" + import verify + # Unit tests will check themselves. # Do not run if disabled in options. - if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)): - global total_lvs_errors - tempspice = "{0}/{1}.sp".format(OPTS.openram_temp,self.name) - tempgds = "{0}/{1}.gds".format(OPTS.openram_temp,self.name) - self.sp_write(tempspice) + # No layout to check + if OPTS.netlist_only: + return + elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)): + tempspice = "{0}/{1}.sp".format(OPTS.openram_temp, self.name) + tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name) + self.lvs_write(tempspice) self.gds_write(tempgds) num_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification) - total_lvs_errors += num_errors - debug.check(num_errors == 0,"LVS failed for {0} with {1} error(s)".format(self.name,num_errors)) - os.remove(tempspice) - os.remove(tempgds) + debug.check(num_errors == 0, + "LVS failed for {0} with {1} error(s)".format(self.name, + num_errors)) + if OPTS.purge_temp: + os.remove(tempspice) + os.remove(tempgds) def init_graph_params(self): """Initializes parameters relevant to the graph creation""" - #Only initializes a set for checking instances which should not be added + # Only initializes a set for checking instances which should not be added self.graph_inst_exclude = set() - def build_graph(self, graph, inst_name, port_nets): + def build_graph(self, graph, inst_name, port_nets): """Recursively create graph from instances in module.""" - #Translate port names to external nets + # Translate port names to external nets if len(port_nets) != len(self.pins): - debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1) - port_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets, + self.pins), + 1) + port_dict = {pin: port for pin, port in zip(self.pins, port_nets)} debug.info(3, "Instance name={}".format(inst_name)) for subinst, conns in zip(self.insts, self.conns): if subinst in self.graph_inst_exclude: continue - subinst_name = inst_name+'.X'+subinst.name + subinst_name = inst_name + '.X' + subinst.name subinst_ports = self.translate_nets(conns, port_dict, inst_name) subinst.mod.build_graph(graph, subinst_name, subinst_ports) def build_names(self, name_dict, inst_name, port_nets): """Collects all the nets and the parent inst of that net.""" - #Translate port names to external nets + # Translate port names to external nets if len(port_nets) != len(self.pins): - debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1) - port_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets, + self.pins), + 1) + port_dict = {pin: port for pin, port in zip(self.pins, port_nets)} debug.info(3, "Instance name={}".format(inst_name)) for subinst, conns in zip(self.insts, self.conns): - subinst_name = inst_name+'.X'+subinst.name + subinst_name = inst_name + '.X' + subinst.name subinst_ports = self.translate_nets(conns, port_dict, inst_name) for si_port, conn in zip(subinst_ports, conns): - #Only add for first occurrence + # Only add for first occurrence if si_port.lower() not in name_dict: - mod_info = {'mod':self, 'int_net':conn} + mod_info = {'mod': self, 'int_net': conn} name_dict[si_port.lower()] = mod_info - subinst.mod.build_names(name_dict, subinst_name, subinst_ports) + subinst.mod.build_names(name_dict, subinst_name, subinst_ports) def find_aliases(self, inst_name, port_nets, path_nets, alias, alias_mod, exclusion_set=None): """Given a list of nets, will compare the internal alias of a mod to determine if the nets have a connection to this mod's net (but not inst). """ - if exclusion_set == None: + if not exclusion_set: exclusion_set = set() try: self.name_dict @@ -161,17 +195,17 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): int_mod = self.name_dict[net]['mod'] if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set): aliases.append(net) - return aliases + return aliases def is_net_alias(self, known_net, net_alias, mod, exclusion_set): """Checks if the alias_net in input mod is the same as the input net for this mod (self).""" if self in exclusion_set: return False - #Check ports of this mod + # Check ports of this mod for pin in self.pins: if self.is_net_alias_name_check(known_net, pin, net_alias, mod): return True - #Check connections of all other subinsts + # Check connections of all other subinsts mod_set = set() for subinst, inst_conns in zip(self.insts, self.conns): for inst_conn, mod_pin in zip(inst_conns, subinst.mod.pins): @@ -181,7 +215,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): if subinst.mod.is_net_alias(mod_pin, net_alias, mod, exclusion_set): return True mod_set.add(subinst.mod) - return False + return False def is_net_alias_name_check(self, parent_net, child_net, alias_net, mod): """Utility function for checking single net alias.""" @@ -190,8 +224,10 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): parent_net.lower() == alias_net.lower() def get_mod_net(self, parent_net, child_inst, child_conns): - """Given an instance and net, returns the internal net in the mod - corresponding to input net.""" + """ + Given an instance and net, returns the internal net in the mod + corresponding to input net. + """ for conn, pin in zip(child_conns, child_inst.mod.pins): if parent_net.lower() == conn.lower(): return pin @@ -205,27 +241,27 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): converted_conns.append(port_dict[conn]) else: converted_conns.append("{}.{}".format(inst_name, conn)) - return converted_conns + return converted_conns def add_graph_edges(self, graph, port_nets): """For every input, adds an edge to every output. Only intended to be used for gates and other simple modules.""" - #The final pin names will depend on the spice hierarchy, so - #they are passed as an input. - pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + # The final pin names will depend on the spice hierarchy, so + # they are passed as an input. + pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)} input_pins = self.get_inputs() output_pins = self.get_outputs() inout_pins = self.get_inouts() - for inp in input_pins+inout_pins: - for out in output_pins+inout_pins: - if inp != out: #do not add self loops - graph.add_edge(pin_dict[inp], pin_dict[out], self) + for inp in input_pins + inout_pins: + for out in output_pins + inout_pins: + if inp != out: # do not add self loops + graph.add_edge(pin_dict[inp], pin_dict[out], self) def __str__(self): """ override print function output """ pins = ",".join(self.pins) insts = [" {}".format(x) for x in self.insts] - objs = [" {}".format(x) for x in self.objs] + objs = [" {}".format(x) for x in self.objs] s = "********** design {0} **********".format(self.name) s += "\n pins ({0})={1}\n".format(len(self.pins), pins) s += "\n objs ({0})=\n{1}\n".format(len(self.objs), "\n".join(objs)) @@ -236,8 +272,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): """ override print function output """ text="( design: " + self.name + " pins=" + str(self.pins) + " " + str(self.width) + "x" + str(self.height) + " )\n" for i in self.objs: - text+=str(i)+",\n" + text+=str(i) + ",\n" for i in self.insts: - text+=str(i)+",\n" + text+=str(i) + ",\n" return text diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index ae288d2e..40f56263 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -5,18 +5,21 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import itertools -import collections import geometry import gdsMill import debug +from math import sqrt from tech import drc, GDS from tech import layer as techlayer +from tech import layer_indices +from tech import layer_stacks +from tech import preferred_directions import os from globals import OPTS from vector import vector from pin_layout import pin_layout -import lef +from utils import round_to_grid + class layout(): """ @@ -32,82 +35,152 @@ class layout(): self.name = name self.width = None self.height = None + self.bounding_box = None self.insts = [] # Holds module/cell layout instances self.objs = [] # Holds all other objects (labels, geometries, etc) self.pin_map = {} # Holds name->pin_layout map for all pins self.visited = [] # List of modules we have already visited - self.is_library_cell = False # Flag for library cells + self.is_library_cell = False # Flag for library cells self.gds_read() + try: + from tech import power_grid + self.pwr_grid_layer = power_grid[0] + except ImportError: + self.pwr_grid_layer = "m3" + + + ############################################################ # GDS layout ############################################################ def offset_all_coordinates(self): - """ This function is called after everything is placed to - shift the origin in the lowest left corner """ + """ + This function is called after everything is placed to + shift the origin in the lowest left corner + """ offset = self.find_lowest_coords() self.translate_all(offset) return offset def get_gate_offset(self, x_offset, height, inv_num): - """Gets the base offset and y orientation of stacked rows of gates + """ + Gets the base offset and y orientation of stacked rows of gates assuming a minwidth metal1 vdd/gnd rail. Input is which gate in the stack from 0..n """ if (inv_num % 2 == 0): - base_offset=vector(x_offset, inv_num * height) + base_offset = vector(x_offset, inv_num * height) y_dir = 1 else: - # we lose a rail after every 2 gates - base_offset=vector(x_offset, (inv_num+1) * height - (inv_num%2)*drc["minwidth_metal1"]) + # we lose a rail after every 2 gates + base_offset = vector(x_offset, + (inv_num + 1) * height - \ + (inv_num % 2) * drc["minwidth_m1"]) y_dir = -1 - - return (base_offset,y_dir) + return (base_offset, y_dir) def find_lowest_coords(self): - """Finds the lowest set of 2d cartesian coordinates within - this layout""" + """ + Finds the lowest set of 2d cartesian coordinates within + this layout + """ - if len(self.objs)>0: - lowestx1 = min(obj.lx() for obj in self.objs if obj.name!="label") - lowesty1 = min(obj.by() for obj in self.objs if obj.name!="label") + if len(self.objs) > 0: + lowestx1 = min(obj.lx() for obj in self.objs if obj.name != "label") + lowesty1 = min(obj.by() for obj in self.objs if obj.name != "label") else: - lowestx1=lowesty1=None - if len(self.insts)>0: + lowestx1 = lowesty1 = None + if len(self.insts) > 0: lowestx2 = min(inst.lx() for inst in self.insts) lowesty2 = min(inst.by() for inst in self.insts) else: - lowestx2=lowesty2=None - if lowestx1==None: - return vector(lowestx2,lowesty2) - elif lowestx2==None: - return vector(lowestx1,lowesty1) + lowestx2 = lowesty2 = None + + if lowestx1 == None and lowestx2 == None: + return None + elif lowestx1 == None: + return vector(lowestx2, lowesty2) + elif lowestx2 == None: + return vector(lowestx1, lowesty1) else: return vector(min(lowestx1, lowestx2), min(lowesty1, lowesty2)) def find_highest_coords(self): - """Finds the highest set of 2d cartesian coordinates within - this layout""" - - if len(self.objs)>0: - highestx1 = max(obj.rx() for obj in self.objs if obj.name!="label") - highesty1 = max(obj.uy() for obj in self.objs if obj.name!="label") + """ + Finds the highest set of 2d cartesian coordinates within + this layout + """ + if len(self.objs) > 0: + highestx1 = max(obj.rx() for obj in self.objs if obj.name != "label") + highesty1 = max(obj.uy() for obj in self.objs if obj.name != "label") else: - highestx1=highesty1=None - if len(self.insts)>0: + highestx1 = highesty1 = None + if len(self.insts) > 0: highestx2 = max(inst.rx() for inst in self.insts) highesty2 = max(inst.uy() for inst in self.insts) else: - highestx2=highesty2=None - if highestx1==None: - return vector(highestx2,highesty2) - elif highestx2==None: - return vector(highestx1,highesty1) - else: - return vector(max(highestx1, highestx2), max(highesty1, highesty2)) + highestx2 = highesty2 = None + if highestx1 == None and highestx2 == None: + return None + elif highestx1 == None: + return vector(highestx2, highesty2) + elif highestx2 == None: + return vector(highestx1, highesty1) + else: + return vector(max(highestx1, highestx2), + max(highesty1, highesty2)) + + def find_highest_layer_coords(self, layer): + """ + Finds the highest set of 2d cartesian coordinates within + this layout on a layer + """ + # Only consider the layer not the purpose for now + layerNumber = techlayer[layer][0] + try: + highestx = max(obj.rx() for obj in self.objs if obj.layerNumber == layerNumber) + except ValueError: + highestx =0 + try: + highesty = max(obj.uy() for obj in self.objs if obj.layerNumber == layerNumber) + except ValueError: + highesty = 0 + + for inst in self.insts: + # This really should be rotated/mirrored etc... + subcoord = inst.mod.find_highest_layer_coords(layer) + inst.offset + highestx = max(highestx, subcoord.x) + highesty = max(highesty, subcoord.y) + + return vector(highestx, highesty) + + def find_lowest_layer_coords(self, layer): + """ + Finds the highest set of 2d cartesian coordinates within + this layout on a layer + """ + # Only consider the layer not the purpose for now + layerNumber = techlayer[layer][0] + try: + lowestx = min(obj.lx() for obj in self.objs if obj.layerNumber == layerNumber) + except ValueError: + lowestx = 0 + try: + lowesty = min(obj.by() for obj in self.objs if obj.layerNumber == layerNumber) + except ValueError: + lowesty = 0 + + for inst in self.insts: + # This really should be rotated/mirrored etc... + subcoord = inst.mod.find_lowest_layer_coords(layer) + inst.offset + lowestx = min(lowestx, subcoord.x) + lowesty = min(lowesty, subcoord.y) + + return vector(lowestx, lowesty) def translate_all(self, offset): """ @@ -119,97 +192,118 @@ class layout(): inst.offset = vector(inst.offset - offset) # The instances have a precomputed boundary that we need to update. if inst.__class__.__name__ == "instance": - inst.compute_boundary(inst.offset) + inst.compute_boundary(inst.offset, inst.mirror, inst.rotate) for pin_name in self.pin_map.keys(): # All the pins are absolute coordinates that need to be updated. pin_list = self.pin_map[pin_name] for pin in pin_list: pin.rect = [pin.ll() - offset, pin.ur() - offset] - - def add_inst(self, name, mod, offset=[0,0], mirror="R0",rotate=0): - """Adds an instance of a mod to this module""" + + def add_inst(self, name, mod, offset=[0, 0], mirror="R0", rotate=0): + """ Adds an instance of a mod to this module """ self.insts.append(geometry.instance(name, mod, offset, mirror, rotate)) debug.info(3, "adding instance {}".format(self.insts[-1])) # This is commented out for runtime reasons - #debug.info(4, "instance list: " + ",".join(x.name for x in self.insts)) + # debug.info(4, "instance list: " + ",".join(x.name for x in self.insts)) return self.insts[-1] def get_inst(self, name): - """Retrieve an instance by name""" + """ Retrieve an instance by name """ for inst in self.insts: if inst.name == name: return inst return None - + def add_rect(self, layer, offset, width=None, height=None): """ Adds a rectangle on a given layer,offset with width and height """ if not width: - width=drc["minwidth_{}".format(layer)] + width = drc["minwidth_{}".format(layer)] if not height: - height=drc["minwidth_{}".format(layer)] - # negative layers indicate "unused" layers in a given technology - layer_num = techlayer[layer] - if layer_num >= 0: - self.objs.append(geometry.rectangle(layer_num, offset, width, height)) - return self.objs[-1] - return None + height = drc["minwidth_{}".format(layer)] + lpp = techlayer[layer] + self.objs.append(geometry.rectangle(lpp, + offset, + width, + height)) + return self.objs[-1] def add_rect_center(self, layer, offset, width=None, height=None): """ - Adds a rectangle on a given layer at the center point with width and height + Adds a rectangle on a given layer at the center + point with width and height """ if not width: - width=drc["minwidth_{}".format(layer)] + width = drc["minwidth_{}".format(layer)] if not height: - height=drc["minwidth_{}".format(layer)] - # negative layers indicate "unused" layers in a given technology - layer_num = techlayer[layer] - corrected_offset = offset - vector(0.5*width,0.5*height) - if layer_num >= 0: - self.objs.append(geometry.rectangle(layer_num, corrected_offset, width, height)) - return self.objs[-1] - return None + height = drc["minwidth_{}".format(layer)] + lpp = techlayer[layer] + corrected_offset = offset - vector(0.5 * width, 0.5 * height) + self.objs.append(geometry.rectangle(lpp, + corrected_offset, + width, + height)) + return self.objs[-1] - - def add_segment_center(self, layer, start, end): - """ - Add a min-width rectanglular segment using center line on the start to end point + def add_segment_center(self, layer, start, end, width=None): """ - minwidth_layer = drc["minwidth_{}".format(layer)] - if start.x!=end.x and start.y!=end.y: - debug.error("Nonrectilinear center rect!",-1) - elif start.x!=end.x: - offset = vector(0,0.5*minwidth_layer) - return self.add_rect(layer,start-offset,end.x-start.x,minwidth_layer) + Add a min-width rectanglular segment using center + line on the start to end point + """ + if not width: + width = drc["minwidth_{}".format(layer)] + + if start.x != end.x and start.y != end.y: + debug.error("Nonrectilinear center rect!", -1) + elif start.x != end.x: + offset = vector(0, 0.5 * width) + return self.add_rect(layer, + start - offset, + end.x - start.x, + width) else: - offset = vector(0.5*minwidth_layer,0) - return self.add_rect(layer,start-offset,minwidth_layer,end.y-start.y) + offset = vector(0.5 * width, 0) + return self.add_rect(layer, + start - offset, + width, + end.y - start.y) - - + def get_tx_insts(self, tx_type=None): + """ + Return a list of the instances of given tx type. + """ + tx_list = [] + for i in self.insts: + try: + if tx_type and i.mod.tx_type == tx_type: + tx_list.append(i) + elif not tx_type: + if i.mod.tx_type == "nmos" or i.mod.tx_type == "pmos": + tx_list.append(i) + except AttributeError: + pass + + return tx_list + def get_pin(self, text): - """ - Return the pin or list of pins + """ + Return the pin or list of pins """ try: - if len(self.pin_map[text])>1: - debug.error("Should use a pin iterator since more than one pin {}".format(text),-1) + if len(self.pin_map[text]) > 1: + debug.error("Should use a pin iterator since more than one pin {}".format(text), -1) # If we have one pin, return it and not the list. # Otherwise, should use get_pins() any_pin = next(iter(self.pin_map[text])) return any_pin - except Exception as e: - #print e + except Exception: self.gds_write("missing_pin.gds") - debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(text,self.name),-1) - - + debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(text, self.name), -1) def get_pins(self, text): - """ - Return a pin list (instead of a single pin) + """ + Return a pin list (instead of a single pin) """ if text in self.pin_map.keys(): return self.pin_map[text] @@ -217,87 +311,98 @@ class layout(): return set() def get_pin_names(self): - """ + """ Return a pin list of all pins """ return self.pin_map.keys() - + def copy_layout_pin(self, instance, pin_name, new_name=""): - """ - Create a copied version of the layout pin at the current level. - You can optionally rename the pin to a new name. """ - pins=instance.get_pins(pin_name) - - debug.check(len(pins)>0,"Could not find pin {}".format(pin_name)) - + Create a copied version of the layout pin at the current level. + You can optionally rename the pin to a new name. + """ + pins = instance.get_pins(pin_name) + + debug.check(len(pins) > 0, + "Could not find pin {}".format(pin_name)) + for pin in pins: - if new_name=="": + if new_name == "": new_name = pin.name - self.add_layout_pin(new_name, pin.layer, pin.ll(), pin.width(), pin.height()) + self.add_layout_pin(new_name, + pin.layer, + pin.ll(), + pin.width(), + pin.height()) def copy_layout_pins(self, instance, prefix=""): - """ + """ Create a copied version of the layout pin at the current level. - You can optionally rename the pin to a new name. + You can optionally rename the pin to a new name. """ for pin_name in self.pin_map.keys(): - self.copy_layout_pin(instance, pin_name, prefix+pin_name) - - def add_layout_pin_segment_center(self, text, layer, start, end): - """ - Creates a path like pin with center-line convention - """ + self.copy_layout_pin(instance, pin_name, prefix + pin_name) + + def add_layout_pin_segment_center(self, text, layer, start, end, width=None): + """ + Creates a path like pin with center-line convention + """ + 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) + + if not width: + layer_width = drc["minwidth_{}".format(layer)] + else: + layer_width = width - debug.check(start.x==end.x or start.y==end.y,"Cannot have a non-manhatten layout pin.") - - minwidth_layer = drc["minwidth_{}".format(layer)] - # one of these will be zero - width = max(start.x,end.x) - min(start.x,end.x) - height = max(start.y,end.y) - min(start.y,end.y) - ll_offset = vector(min(start.x,end.x),min(start.y,end.y)) + bbox_width = max(start.x, end.x) - min(start.x, end.x) + bbox_height = max(start.y, end.y) - min(start.y, end.y) + ll_offset = vector(min(start.x, end.x), min(start.y, end.y)) # Shift it down 1/2 a width in the 0 dimension - if height==0: - ll_offset -= vector(0,0.5*minwidth_layer) - if width==0: - ll_offset -= vector(0.5*minwidth_layer,0) - # This makes sure it is long enough, but also it is not 0 width! - height = max(minwidth_layer,height) - width = max(minwidth_layer,width) - - - return self.add_layout_pin(text, layer, ll_offset, width, height) + if bbox_height == 0: + ll_offset -= vector(0, 0.5 * layer_width) + if bbox_width == 0: + ll_offset -= vector(0.5 * layer_width, 0) + + return self.add_layout_pin(text=text, + layer=layer, + offset=ll_offset, + width=bbox_width, + height=bbox_height) def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None): """ Creates a path like pin with center-line convention """ if not width: - width=drc["minwidth_{0}".format(layer)] + width = drc["minwidth_{0}".format(layer)] if not height: - height=drc["minwidth_{0}".format(layer)] + height = drc["minwidth_{0}".format(layer)] - ll_offset = offset - vector(0.5*width,0.5*height) + ll_offset = offset - vector(0.5 * width, 0.5 * height) return self.add_layout_pin(text, layer, ll_offset, width, height) - def remove_layout_pin(self, text): """ Delete a labeled pin (or all pins of the same name) """ - self.pin_map[text]=set() - + self.pin_map[text] = set() + def add_layout_pin(self, text, layer, offset, width=None, height=None): """ - Create a labeled pin + Create a labeled pin """ if not width: - width=drc["minwidth_{0}".format(layer)] + width = drc["minwidth_{0}".format(layer)] if not height: - height=drc["minwidth_{0}".format(layer)] + height = drc["minwidth_{0}".format(layer)] - new_pin = pin_layout(text, [offset,offset+vector(width,height)], layer) + new_pin = pin_layout(text, + [offset, offset + vector(width, height)], + layer) try: # Check if there's a duplicate! @@ -318,42 +423,36 @@ class layout(): in LVS. """ if not width: - width=drc["minwidth_{0}".format(layer)] + width = drc["minwidth_{0}".format(layer)] if not height: - height=drc["minwidth_{0}".format(layer)] + height = drc["minwidth_{0}".format(layer)] self.add_rect(layer=layer, offset=offset, width=width, height=height) self.add_label(text=text, layer=layer, - offset=offset+vector(0.5*width,0.5*height)) - + offset=offset + vector(0.5 * width, + 0.5 * height)) - def add_label(self, text, layer, offset=[0,0],zoom=-1): + def add_label(self, text, layer, offset=[0, 0], zoom=-1): """Adds a text label on the given layer,offset, and zoom level""" - # negative layers indicate "unused" layers in a given technology - debug.info(5,"add label " + str(text) + " " + layer + " " + str(offset)) - layer_num = techlayer[layer] - if layer_num >= 0: - self.objs.append(geometry.label(text, layer_num, offset, zoom)) - return self.objs[-1] - return None - + debug.info(5, "add label " + str(text) + " " + layer + " " + str(offset)) + lpp = techlayer[layer] + self.objs.append(geometry.label(text, lpp, offset, zoom)) + return self.objs[-1] def add_path(self, layer, coordinates, width=None): """Connects a routing path on given layer,coordinates,width.""" - debug.info(4,"add path " + str(layer) + " " + str(coordinates)) + debug.info(4, "add path " + str(layer) + " " + str(coordinates)) import wire_path # NOTE: (UNTESTED) add_path(...) is currently not used - # negative layers indicate "unused" layers in a given technology - #layer_num = techlayer[layer] - #if layer_num >= 0: - # self.objs.append(geometry.path(layer_num, coordinates, width)) + # lpp = techlayer[layer] + # self.objs.append(geometry.path(lpp, coordinates, width)) wire_path.wire_path(obj=self, - layer=layer, - position_list=coordinates, + layer=layer, + position_list=coordinates, width=width) def add_route(self, layers, coordinates, layer_widths): @@ -363,39 +462,70 @@ class layout(): the coordinates. """ import route - debug.info(4,"add route " + str(layers) + " " + str(coordinates)) + debug.info(4, "add route " + str(layers) + " " + str(coordinates)) # add an instance of our path that breaks down into rectangles and contacts route.route(obj=self, - layer_stack=layers, + layer_stack=layers, path=coordinates, layer_widths=layer_widths) - - def add_wire(self, layers, coordinates): + def add_zjog(self, layer, start, end, first_direction="H", var_offset=0.5, fixed_offset=None): + """ + Add a simple jog at the halfway point. + If layer is a single value, it is a path. + If layer is a tuple, it is a wire with preferred directions. + """ + + neg_offset = 1.0 - var_offset + # vertical first + if first_direction == "V": + if fixed_offset: + mid1 = vector(start.x, fixed_offset) + else: + mid1 = vector(start.x, neg_offset * start.y + var_offset * end.y) + mid2 = vector(end.x, mid1.y) + # horizontal first + elif first_direction == "H": + if fixed_offset: + mid1 = vector(fixed_offset, start.y) + else: + mid1 = vector(neg_offset * start.x + var_offset * end.x, start.y) + mid2 = vector(mid1, end.y) + else: + debug.error("Invalid direction for jog -- must be H or V.") + + if layer in layer_stacks: + self.add_wire(layer, [start, mid1, mid2, end]) + elif layer in techlayer: + self.add_path(layer, [start, mid1, mid2, end]) + else: + debug.error("Could not find layer {}".format(layer)) + + def add_horizontal_zjog_path(self, layer, start, end): + """ Add a simple jog at the halfway point """ + + # horizontal first + mid1 = vector(0.5 * start.x + 0.5 * end.x, start.y) + mid2 = vector(mid1, end.y) + self.add_path(layer, [start, mid1, mid2, end]) + + def add_wire(self, layers, coordinates, widen_short_wires=True): """Connects a routing path on given layer,coordinates,width. The layers are the (horizontal, via, vertical). """ import wire - # add an instance of our path that breaks down into rectangles and contacts + # add an instance of our path that breaks down + # into rectangles and contacts wire.wire(obj=self, - layer_stack=layers, - position_list=coordinates) + layer_stack=layers, + position_list=coordinates, + widen_short_wires=widen_short_wires) def get_preferred_direction(self, layer): """ Return the preferred routing directions """ - if layer in ["metal1", "metal3", "metal5"]: - return "H" - elif layer in ["active", "poly", "metal2", "metal4"]: - return "V" - else: - return "N" + return preferred_directions[layer] - - def add_via(self, layers, offset, size=[1,1], directions=None, implant_type=None, well_type=None): + def add_via(self, layers, offset, size=[1, 1], directions=None, implant_type=None, well_type=None): """ Add a three layer via structure. """ - - if directions==None: - directions = (self.get_preferred_direction(layers[0]), self.get_preferred_direction(layers[2])) - from sram_factory import factory via = factory.create(module_type="contact", layer_stack=layers, @@ -404,19 +534,18 @@ class layout(): implant_type=implant_type, well_type=well_type) self.add_mod(via) - inst=self.add_inst(name=via.name, - mod=via, - offset=offset) + inst = self.add_inst(name=via.name, + mod=via, + offset=offset) # We don't model the logical connectivity of wires/paths self.connect_inst([]) return inst - def add_via_center(self, layers, offset, directions=None, size=[1,1], implant_type=None, well_type=None): - """ Add a three layer via structure by the center coordinate accounting for mirroring and rotation. """ - - if directions==None: - directions = (self.get_preferred_direction(layers[0]), self.get_preferred_direction(layers[2])) - + def add_via_center(self, layers, offset, directions=None, size=[1, 1], implant_type=None, well_type=None): + """ + Add a three layer via structure by the center coordinate + accounting for mirroring and rotation. + """ from sram_factory import factory via = factory.create(module_type="contact", layer_stack=layers, @@ -426,17 +555,95 @@ class layout(): well_type=well_type) height = via.height width = via.width - - corrected_offset = offset + vector(-0.5*width,-0.5*height) + + corrected_offset = offset + vector(-0.5 * width, + -0.5 * height) self.add_mod(via) - inst=self.add_inst(name=via.name, - mod=via, - offset=corrected_offset) + inst = self.add_inst(name=via.name, + mod=via, + offset=corrected_offset) # We don't model the logical connectivity of wires/paths self.connect_inst([]) return inst - + + def add_via_stack_center(self, + offset, + from_layer, + to_layer, + directions=None, + size=[1, 1], + implant_type=None, + well_type=None): + """ + Punch a stack of vias from a start layer to a target layer by the center. + """ + + if from_layer == to_layer: + # In the case where we have no vias added, make sure that there is at least + # a metal enclosure. This helps with center-line path routing. + self.add_rect_center(layer=from_layer, + offset=offset) + return None + + via = None + cur_layer = from_layer + while cur_layer != to_layer: + from_id = layer_indices[cur_layer] + to_id = layer_indices[to_layer] + + if from_id < to_id: # grow the stack up + search_id = 0 + next_id = 2 + else: # grow the stack down + search_id = 2 + next_id = 0 + + curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, layer_stacks), None) + + via = self.add_via_center(layers=curr_stack, + size=size, + offset=offset, + directions=directions, + implant_type=implant_type, + well_type=well_type) + + if cur_layer != from_layer: + self.add_min_area_rect_center(cur_layer, + offset, + via.mod.first_layer_width, + via.mod.first_layer_height) + + cur_layer = curr_stack[next_id] + + return via + + def add_min_area_rect_center(self, + layer, + offset, + width=None, + height=None): + """ + Add a minimum area retcangle at the given point. + Either width or height should be fixed. + """ + + min_area = drc("minarea_{}".format(layer)) + if min_area == 0: + return + + min_width = drc("minwidth_{}".format(layer)) + + if preferred_directions[layer] == "V": + height = max(min_area / width, min_width) + else: + width = max(min_area / height, min_width) + + self.add_rect_center(layer=layer, + offset=offset, + width=width, + height=height) + def add_ptx(self, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"): """Adds a ptx module to the design.""" import ptx @@ -444,27 +651,25 @@ class layout(): mults=mults, tx_type=tx_type) self.add_mod(mos) - inst=self.add_inst(name=mos.name, - mod=mos, - offset=offset, - mirror=mirror, - rotate=rotate) + inst = self.add_inst(name=mos.name, + mod=mos, + offset=offset, + mirror=mirror, + rotate=rotate) return inst - - def gds_read(self): """Reads a GDSII file in the library and checks if it exists Otherwise, start a new layout for dynamic generation.""" # This must be done for netlist only mode too if os.path.isfile(self.gds_file): - self.is_library_cell=True - + self.is_library_cell = True + if OPTS.netlist_only: self.gds = None 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)) @@ -477,7 +682,7 @@ class layout(): def print_gds(self, gds_file=None): """Print the gds file (not the vlsi class) to the terminal """ - if gds_file == None: + if not gds_file: gds_file = self.gds_file debug.info(4, "Printing {}".format(gds_file)) arrayCellLayout = gdsMill.VlsiLayout(units=GDS["unit"]) @@ -500,6 +705,34 @@ class layout(): for pin_name in self.pin_map.keys(): for pin in self.pin_map[pin_name]: pin.gds_write_file(gds_layout) + + # If it's not a premade cell + # and we didn't add our own boundary, + # we should add a boundary just for DRC in some technologies + if not self.is_library_cell and not self.bounding_box: + # If there is a boundary layer, and we didn't create one, add one. + boundary_layers = [] + if "boundary" in techlayer.keys(): + boundary_layers.append("boundary") + if "stdc" in techlayer.keys(): + boundary_layers.append("stdc") + boundary = [self.find_lowest_coords(), + self.find_highest_coords()] + debug.check(boundary[0] and boundary[1], "No shapes to make a boundary.") + + height = boundary[1][1] - boundary[0][1] + width = boundary[1][0] - boundary[0][0] + + for boundary_layer in boundary_layers: + (layer_number, layer_purpose) = techlayer[boundary_layer] + gds_layout.addBox(layerNumber=layer_number, + purposeNumber=layer_purpose, + offsetInMicrons=boundary[0], + width=width, + height=height, + center=False) + debug.info(4, "Adding {0} boundary {1}".format(self.name, boundary)) + self.visited.append(self.name) def gds_write(self, gds_name): @@ -518,144 +751,181 @@ 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) - debug.info(3, "Done writing to {}".format(gds_name)) + debug.info(3, "Done writing to {}".format(gds_name)) def get_boundary(self): """ Return the lower-left and upper-right coordinates of boundary """ # This assumes nothing spans outside of the width and height! - return [vector(0,0), vector(self.width, self.height)] + return [vector(0, 0), vector(self.width, self.height)] #return [self.find_lowest_coords(), self.find_highest_coords()] def get_blockages(self, layer, top_level=False): - """ - Write all of the obstacles in the current (and children) modules to the lef file + """ + Write all of the obstacles in the current (and children) + modules to the lef file. Do not write the pins since they aren't obstructions. """ - if type(layer)==str: - layer_num = techlayer[layer] + if type(layer) == str: + lpp = techlayer[layer] else: - layer_num = layer - + lpp = layer + blockages = [] for i in self.objs: - blockages += i.get_blockages(layer_num) + blockages += i.get_blockages(lpp) for i in self.insts: - blockages += i.get_blockages(layer_num) + blockages += i.get_blockages(lpp) # Must add pin blockages to non-top cells if not top_level: - blockages += self.get_pin_blockages(layer_num) + blockages += self.get_pin_blockages(lpp) return blockages - def get_pin_blockages(self, layer_num): + def get_pin_blockages(self, lpp): """ Return the pin shapes as blockages for non-top-level blocks. """ # FIXME: We don't have a body contact in ptx, so just ignore it for now import copy 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) for pin in pin_list: - if pin.layer_num==layer_num: + if pin.same_lpp(pin.lpp, lpp): blockages += [pin.rect] return blockages - def create_horizontal_pin_bus(self, layer, pitch, offset, names, length): + def create_horizontal_pin_bus(self, layer, offset, names, length, pitch=None): """ Create a horizontal bus of pins. """ - return self.create_bus(layer,pitch,offset,names,length,vertical=False,make_pins=True) + return self.create_bus(layer, + offset, + names, + length, + vertical=False, + make_pins=True, + pitch=pitch) - def create_vertical_pin_bus(self, layer, pitch, offset, names, length): + def create_vertical_pin_bus(self, layer, offset, names, length, pitch=None): """ Create a horizontal bus of pins. """ - return self.create_bus(layer,pitch,offset,names,length,vertical=True,make_pins=True) + return self.create_bus(layer, + offset, + names, + length, + vertical=True, + make_pins=True, + pitch=pitch) - def create_vertical_bus(self, layer, pitch, offset, names, length): + def create_vertical_bus(self, layer, offset, names, length, pitch=None): """ Create a horizontal bus. """ - return self.create_bus(layer,pitch,offset,names,length,vertical=True,make_pins=False) + return self.create_bus(layer, + offset, + names, + length, + vertical=True, + make_pins=False, + pitch=pitch) - def create_horizontal_bus(self, layer, pitch, offset, names, length): + def create_horizontal_bus(self, layer, offset, names, length, pitch=None): """ Create a horizontal bus. """ - return self.create_bus(layer,pitch,offset,names,length,vertical=False,make_pins=False) + return self.create_bus(layer, + offset, + names, + length, + vertical=False, + make_pins=False, + pitch=pitch) - - def create_bus(self, layer, pitch, offset, names, length, vertical, make_pins): - """ + def create_bus(self, layer, offset, names, length, vertical, make_pins, pitch=None): + """ Create a horizontal or vertical bus. It can be either just rectangles, or actual - layout pins. It returns an map of line center line positions indexed by name. - The other coordinate is a 0 since the bus provides a range. + layout pins. It returns an map of line center line positions indexed by name. + The other coordinate is a 0 since the bus provides a range. TODO: combine with channel router. """ # half minwidth so we can return the center line offsets - half_minwidth = 0.5*drc["minwidth_{}".format(layer)] - - line_positions = {} + half_minwidth = 0.5 * drc["minwidth_{}".format(layer)] + if not pitch: + pitch = getattr(self, "{}_pitch".format(layer)) + + pins = {} if vertical: for i in range(len(names)): - line_offset = offset + vector(i*pitch,0) + line_offset = offset + vector(i * pitch, + 0) if make_pins: - self.add_layout_pin(text=names[i], - layer=layer, - offset=line_offset, - height=length) + new_pin = self.add_layout_pin(text=names[i], + layer=layer, + offset=line_offset, + height=length) else: - self.add_rect(layer=layer, - offset=line_offset, - height=length) - # Make this the center of the rail - line_positions[names[i]]=line_offset+vector(half_minwidth,0.5*length) + rect = self.add_rect(layer=layer, + offset=line_offset, + height=length) + new_pin = pin_layout(names[i], + [rect.ll(), rect.ur()], + layer) + + pins[names[i]] = new_pin else: for i in range(len(names)): - line_offset = offset + vector(0,i*pitch + half_minwidth) + line_offset = offset + vector(0, + i * pitch + half_minwidth) if make_pins: - self.add_layout_pin(text=names[i], - layer=layer, - offset=line_offset, - width=length) + new_pin = self.add_layout_pin(text=names[i], + layer=layer, + offset=line_offset, + width=length) else: - self.add_rect(layer=layer, - offset=line_offset, - width=length) - # Make this the center of the rail - line_positions[names[i]]=line_offset+vector(0.5*length,half_minwidth) + rect = self.add_rect(layer=layer, + offset=line_offset, + width=length) + new_pin = pin_layout(names[i], + [rect.ll(), rect.ur()], + layer) + + pins[names[i]] = new_pin - return line_positions + return pins - def connect_horizontal_bus(self, mapping, inst, bus_offsets, - layer_stack=("metal1","via1","metal2")): + def connect_horizontal_bus(self, mapping, inst, bus_pins, + layer_stack=("m1", "via1", "m2")): """ Horizontal version of connect_bus. """ - self.connect_bus(mapping, inst, bus_offsets, layer_stack, True) + self.connect_bus(mapping, inst, bus_pins, layer_stack, True) - def connect_vertical_bus(self, mapping, inst, bus_offsets, - layer_stack=("metal1","via1","metal2")): + def connect_vertical_bus(self, mapping, inst, bus_pins, + layer_stack=("m1", "via1", "m2")): """ Vertical version of connect_bus. """ - self.connect_bus(mapping, inst, bus_offsets, layer_stack, False) - - def connect_bus(self, mapping, inst, bus_offsets, layer_stack, horizontal): - """ - Connect a mapping of pin -> name for a bus. This could be - replaced with a channel router in the future. - NOTE: This has only really been tested with point-to-point connections (not multiple pins on a net). + self.connect_bus(mapping, inst, bus_pins, layer_stack, False) + + def connect_bus(self, mapping, inst, bus_pins, layer_stack, horizontal): """ - (horizontal_layer, via_layer, vertical_layer)=layer_stack + Connect a mapping of pin -> name for a bus. This could be + replaced with a channel router in the future. + NOTE: This has only really been tested with point-to-point + connections (not multiple pins on a net). + """ + (horizontal_layer, via_layer, vertical_layer) = layer_stack if horizontal: route_layer = vertical_layer + bys_layer = horizontal_layer else: route_layer = horizontal_layer - + bus_layer = vertical_layer + for (pin_name, bus_name) in mapping: pin = inst.get_pin(pin_name) pin_pos = pin.center() - bus_pos = bus_offsets[bus_name] + bus_pos = bus_pins[bus_name].center() if horizontal: # up/down then left/right @@ -663,405 +933,334 @@ 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]) + + # Don't widen short wires because pin_pos and mid_pos could be really close + self.add_wire(layer_stack, + [bus_pos, mid_pos, pin_pos], + widen_short_wires=False) # Connect to the pin on the instances with a via if it is # not on the right layer if pin.layer != route_layer: - self.add_via_center(layers=layer_stack, - offset=pin_pos) - # FIXME: output pins tend to not be rotate, but supply pins are. Make consistent? - - + self.add_via_stack_center(from_layer=pin.layer, + to_layer=route_layer, + offset=pin_pos) + # FIXME: output pins tend to not be rotate, + # but supply pins are. Make consistent? # We only need a via if they happened to align perfectly # so the add_wire didn't add a via if (horizontal and bus_pos.y == pin_pos.y) or (not horizontal and bus_pos.x == pin_pos.x): - self.add_via_center(layers=layer_stack, - offset=bus_pos, - rotate=90) - def get_layer_pitch(self, layer): - """ Return the track pitch on a given layer """ - if layer=="metal1": - return (self.m1_pitch,self.m1_pitch-self.m1_space,self.m1_space) - elif layer=="metal2": - return (self.m2_pitch,self.m2_pitch-self.m2_space,self.m2_space) - elif layer=="metal3": - return (self.m3_pitch,self.m3_pitch-self.m3_space,self.m3_space) - elif layer=="metal4": - from tech import layer as tech_layer - if "metal4" in tech_layer: - return (self.m3_pitch,self.m3_pitch-self.m4_space,self.m4_space) - else: - return (self.m3_pitch,self.m3_pitch-self.m3_space,self.m3_space) + self.add_via_stack_center(from_layer=route_layer, + to_layer=bus_layer, + offset=bus_pos) + + def connect_vbus(self, src_pin, dest_pin, hlayer="m3", vlayer="m2"): + """ + Helper routine to connect an instance to a vertical bus. + Routes horizontal then vertical L shape. + """ + + if src_pin.cx()slower freq.->less power - proc_div = max(self.get_process_delay_factor(proc)) + # A pply process and temperature factors. + # Roughly, process and Vdd affect the delay which affects the power. + # No other estimations are currently used. Increased delay->slower freq.->less power + proc_div = max(self.get_process_delay_factor(proc)) temp_div = self.get_temp_delay_factor(temp) - power_dyn = power_dyn/(proc_div*temp_div) + power_dyn = power_dyn / (proc_div * temp_div) - return power_dyn + return power_dyn def return_power(self, dynamic=0.0, leakage=0.0): return power_data(dynamic, leakage) diff --git a/compiler/base/pin_layout.py b/compiler/base/pin_layout.py index 2d389119..dac4e525 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -8,72 +8,117 @@ import debug from tech import GDS, drc from vector import vector -from tech import layer +from tech import layer, layer_indices import math + class pin_layout: """ A class to represent a rectangular design pin. It is limited to a single shape. """ - def __init__(self, name, rect, layer_name_num): + def __init__(self, name, rect, layer_name_pp): self.name = name # repack the rect as a vector, just in case - if type(rect[0])==vector: - self.rect = rect + if type(rect[0]) == vector: + self._rect = rect else: - self.rect = [vector(rect[0]),vector(rect[1])] + self._rect = [vector(rect[0]), vector(rect[1])] # snap the rect to the grid - self.rect = [x.snap_to_grid() for x in self.rect] + self._rect = [x.snap_to_grid() for x in self.rect] - debug.check(self.width()>0,"Zero width pin.") - debug.check(self.height()>0,"Zero height pin.") + debug.check(self.width() > 0, "Zero width pin.") + debug.check(self.height() > 0, "Zero height pin.") + + # These are the valid pin layers + valid_layers = { x: layer[x] for x in layer_indices.keys()} - # if it's a layer number look up the layer name. this assumes a unique layer number. - if type(layer_name_num)==int: - self.layer = list(layer.keys())[list(layer.values()).index(layer_name_num)] + # if it's a string, use the name + if type(layer_name_pp) == str: + self._layer = layer_name_pp + # else it is required to be a lpp else: - self.layer=layer_name_num - self.layer_num = layer[self.layer] + for (layer_name, lpp) in valid_layers.items(): + if not lpp: + continue + if self.same_lpp(layer_name_pp, lpp): + self._layer = layer_name + break + else: + debug.error("Couldn't find layer {}".format(layer_name_pp), -1) + + self.lpp = layer[self.layer] + self._recompute_hash() + + @property + def layer(self): + return self._layer + + @layer.setter + def layer(self, l): + self._layer = l + self._recompute_hash() + + @property + def rect(self): + return self._rect + + @rect.setter + def rect(self, r): + self._rect = r + self._recompute_hash() + + def _recompute_hash(self): + """ Recompute the hash for our hash cache """ + self._hash = hash(repr(self)) def __str__(self): """ override print function output """ - return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1]) + return "({} layer={} ll={} ur={})".format(self.name, + self.layer, + self.rect[0], + self.rect[1]) def __repr__(self): - """ - override repr function output (don't include + """ + override repr function output (don't include name since pin shapes could have same shape but diff name e.g. blockage vs A) """ - return "(layer={} ll={} ur={})".format(self.layer,self.rect[0],self.rect[1]) + return "(layer={} ll={} ur={})".format(self.layer, + self.rect[0], + self.rect[1]) def __hash__(self): - """ Implement the hash function for sets etc. """ - return hash(repr(self)) - + """ + Implement the hash function for sets etc. We only return a cached + value, that is updated when either 'rect' or 'layer' are changed. This + is a major speedup, if pin_layout is used as a key for dicts. + """ + return self._hash + def __lt__(self, other): """ Provide a function for ordering items by the ll point """ (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__): - return (self.layer==other.layer and self.rect == other.rect) + return (self.lpp == other.lpp and self.rect == other.rect) else: - return False + return False def bbox(self, pin_list): """ Given a list of layout pins, create a bounding box layout. """ - (ll, ur) = self.rect + (ll, ur) = self.rect min_x = ll.x max_x = ur.x min_y = ll.y @@ -85,39 +130,46 @@ class pin_layout: min_y = min(min_y, pin.ll().y) max_y = max(max_y, pin.ur().y) - self.rect = [vector(min_x,min_y),vector(max_x,max_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) - and return the new rectangle. + """ + Inflate the rectangle by the spacing (or other rule) + and return the new rectangle. """ if not spacing: spacing = 0.5*drc("{0}_to_{0}".format(self.layer)) - - (ll,ur) = self.rect + + (ll, ur) = self.rect spacing = vector(spacing, spacing) newll = ll - spacing newur = ur + spacing - + return (newll, newur) def intersection(self, other): """ Check if a shape overlaps with a rectangle """ - (ll,ur) = self.rect - (oll,our) = other.rect + (ll, ur) = self.rect + (oll, our) = other.rect min_x = max(ll.x, oll.x) max_x = min(ll.x, oll.x) min_y = max(ll.y, oll.y) max_y = min(ll.y, oll.y) - return [vector(min_x,min_y),vector(max_x,max_y)] + return [vector(min_x, min_y), vector(max_x, max_y)] def xoverlaps(self, other): """ Check if shape has x overlap """ - (ll,ur) = self.rect - (oll,our) = other.rect + (ll, ur) = self.rect + (oll, our) = other.rect x_overlaps = False # check if self is within other x range if (ll.x >= oll.x and ll.x <= our.x) or (ur.x >= oll.x and ur.x <= our.x): @@ -130,8 +182,8 @@ class pin_layout: def yoverlaps(self, other): """ Check if shape has x overlap """ - (ll,ur) = self.rect - (oll,our) = other.rect + (ll, ur) = self.rect + (oll, our) = other.rect y_overlaps = False # check if self is within other y range @@ -142,29 +194,29 @@ class pin_layout: y_overlaps = True return y_overlaps - + def xcontains(self, other): """ Check if shape contains the x overlap """ - (ll,ur) = self.rect - (oll,our) = other.rect + (ll, ur) = self.rect + (oll, our) = other.rect return (oll.x >= ll.x and our.x <= ur.x) def ycontains(self, other): """ Check if shape contains the y overlap """ - (ll,ur) = self.rect - (oll,our) = other.rect + (ll, ur) = self.rect + (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 self.layer != other.layer: + if not self.same_lpp(self.lpp, other.lpp): return False if not self.xcontains(other): @@ -181,14 +233,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 self.layer != other.layer: + if not self.same_lpp(self.lpp, other.lpp): return False - + x_overlaps = self.xoverlaps(other) y_overlaps = self.yoverlaps(other) @@ -197,51 +248,56 @@ 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) def normalize(self): """ Re-find the LL and UR points after a transform """ - (first,second)=self.rect - 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 to get the absolute pin location. - We must then re-find the ll and ur. The master is the cell instance. """ - (ll,ur) = self.rect - if mirror=="MX": - ll=ll.scale(1,-1) - ur=ur.scale(1,-1) - elif mirror=="MY": - ll=ll.scale(-1,1) - ur=ur.scale(-1,1) - 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) - elif rotate==180: - ll=ll.scale(-1,-1) - ur=ur.scale(-1,-1) - elif rotate==270: - ll=ll.rotate_scale(1,-1) - ur=ur.rotate_scale(1,-1) + (first, second) = self.rect + 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] - self.rect=[offset+ll,offset+ur] + def transform(self, offset, mirror, rotate): + """ + 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. + """ + (ll, ur) = self.rect + if mirror == "MX": + ll = ll.scale(1, -1) + ur = ur.scale(1, -1) + elif mirror == "MY": + ll = ll.scale(-1, 1) + ur = ur.scale(-1, 1) + 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) + elif rotate == 180: + ll = ll.scale(-1, -1) + ur = ur.scale(-1, -1) + elif rotate == 270: + ll = ll.rotate_scale(1, -1) + ur = ur.rotate_scale(1, -1) + + self.rect = [offset + ll, offset + ur] self.normalize() def center(self): - return vector(0.5*(self.rect[0].x+self.rect[1].x),0.5*(self.rect[0].y+self.rect[1].y)) + return vector(0.5*(self.rect[0].x+self.rect[1].x), + 0.5*(self.rect[0].y+self.rect[1].y)) def cx(self): """ Center x """ @@ -250,7 +306,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 """ @@ -258,17 +314,17 @@ class pin_layout: def ul(self): """ Upper left point """ - return vector(self.rect[0].x,self.rect[1].y) + return vector(self.rect[0].x, self.rect[1].y) def lr(self): """ Lower right point """ - return vector(self.rect[1].x,self.rect[0].y) + return vector(self.rect[1].x, self.rect[0].y) def ur(self): """ Upper right point """ return self.rect[1] - - # The possible y edge values + + # The possible y edge values def uy(self): """ Upper y value """ return self.rect[1].y @@ -278,80 +334,93 @@ 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 """ - return vector(self.rect[1].x,0.5*(self.rect[0].y+self.rect[1].y)) + return vector(self.rect[1].x, + 0.5*(self.rect[0].y+self.rect[1].y)) def lc(self): """ Left center point """ - return vector(self.rect[0].x,0.5*(self.rect[0].y+self.rect[1].y)) - + 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),self.rect[1].y) + return vector(0.5*(self.rect[0].x+self.rect[1].x), + self.rect[1].y) def bc(self): """ Bottom center point """ - return vector(0.5*(self.rect[0].x+self.rect[1].x),self.rect[0].y) - + return vector(0.5*(self.rect[0].x+self.rect[1].x), + self.rect[0].y) def gds_write_file(self, newLayout): """Writes the pin shape and label to GDS""" - debug.info(4, "writing pin (" + str(self.layer) + "):" - + str(self.width()) + "x" + str(self.height()) + " @ " + str(self.ll())) - newLayout.addBox(layerNumber=layer[self.layer], - purposeNumber=0, + debug.info(4, "writing pin (" + str(self.layer) + "):" + + str(self.width()) + "x" + + str(self.height()) + " @ " + str(self.ll())) + (layer_num, purpose) = layer[self.layer] + try: + from tech import pin_purpose + except ImportError: + pin_purpose = purpose + try: + from tech import label_purpose + except ImportError: + label_purpose = purpose + + newLayout.addBox(layerNumber=layer_num, + purposeNumber=pin_purpose, offsetInMicrons=self.ll(), width=self.width(), height=self.height(), center=False) # Add the tet in the middle of the pin. - # This fixes some pin label offsetting when GDS gets imported into Magic. + # This fixes some pin label offsetting when GDS gets + # imported into Magic. newLayout.addText(text=self.name, - layerNumber=layer[self.layer], - purposeNumber=0, + layerNumber=layer_num, + purposeNumber=label_purpose, offsetInMicrons=self.center(), magnification=GDS["zoom"], rotate=None) - def compute_overlap(self, other): """ Calculate the rectangular overlap of two rectangles. """ - (r1_ll,r1_ur) = self.rect - (r2_ll,r2_ur) = other.rect + (r1_ll, r1_ur) = self.rect + (r2_ll, r2_ur) = other.rect - #ov_ur = vector(min(r1_ur.x,r2_ur.x),min(r1_ur.y,r2_ur.y)) - #ov_ll = vector(max(r1_ll.x,r2_ll.x),max(r1_ll.y,r2_ll.y)) + # ov_ur = vector(min(r1_ur.x,r2_ur.x),min(r1_ur.y,r2_ur.y)) + # ov_ll = vector(max(r1_ll.x,r2_ll.x),max(r1_ll.y,r2_ll.y)) - 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] + 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: - return [0,0] + return [0, 0] def distance(self, other): - """ + """ Calculate the distance to another pin layout. """ - (r1_ll,r1_ur) = self.rect - (r2_ll,r2_ur) = other.rect + (r1_ll, r1_ur) = self.rect + (r2_ll, r2_ur) = other.rect 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 @@ -368,7 +437,7 @@ class pin_layout: elif left: return r1_ll.x - r2_ur.x elif right: - return r2_ll.x - r1.ur.x + return r2_ll.x - r1_ur.x elif bottom: return r1_ll.y - r2_ur.y elif top: @@ -376,10 +445,9 @@ class pin_layout: else: # rectangles intersect return 0 - - + def overlap_length(self, other): - """ + """ Calculate the intersection segment and determine its length """ @@ -391,21 +459,21 @@ class pin_layout: intersections = self.compute_overlap_segment(other) # This is the common case where two pairs of edges overlap # at two points, so just find the distance between those two points - if len(intersections)==2: - (p1,p2) = intersections - return math.sqrt(pow(p1[0]-p2[0],2) + pow(p1[1]-p2[1],2)) + if len(intersections) == 2: + (p1, p2) = intersections + return math.sqrt(pow(p1[0]-p2[0], 2) + pow(p1[1]-p2[1], 2)) else: # 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 + """ + Calculate the intersection segment of two rectangles (if any) """ - (r1_ll,r1_ur) = self.rect - (r2_ll,r2_ur) = other.rect + (r1_ll, r1_ur) = self.rect + (r2_ll, r2_ur) = other.rect # The other corners besides ll and ur r1_ul = vector(r1_ll.x, r1_ur.y) @@ -414,23 +482,24 @@ 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]) - + 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 = [] - for (p,q) in pairwise(r2_cw_points): - r2_edges.append([p,q]) + for (p, q) in pairwise(r2_cw_points): + r2_edges.append([p, q]) # There are 4 edges on each rectangle # so just brute force check intersection of each @@ -452,36 +521,46 @@ class pin_layout: q.x >= min(p.x, r.x) and \ q.y <= max(p.y, r.y) and \ q.y >= min(p.y, r.y): - return True - + return True + return False - + def segment_intersection(self, s1, s2): - """ + """ Determine the intersection point of two segments Return the a segment if they overlap. Return None if they don't. """ - (a,b) = s1 - (c,d) = s2 + (a, b) = s1 + (c, d) = s2 # Line AB represented as a1x + b1y = c1 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: + if determinant != 0: x = (b2*c1 - b1*c2)/determinant y = (a1*c2 - a2*c1)/determinant - - r = vector(x,y).snap_to_grid() + + 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): + """ + Check if the layers and purposes are the same. + Ignore if purpose is a None. + """ + 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/base/utils.py b/compiler/base/utils.py index 61e12096..b6a62788 100644 --- a/compiler/base/utils.py +++ b/compiler/base/utils.py @@ -5,7 +5,6 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import os import gdsMill import tech import math @@ -16,6 +15,7 @@ from pin_layout import pin_layout OPTS = globals.OPTS + def ceil(decimal): """ Performs a ceiling function on the decimal place specified by the DRC grid. @@ -23,29 +23,35 @@ def ceil(decimal): grid = tech.drc["grid"] return math.ceil(decimal * 1 / grid) / (1 / grid) + def round_to_grid(number): """ Rounds an arbitrary number to the grid. """ - grid = tech.drc["grid"] + grid = tech.drc["grid"] # this gets the nearest integer value number_grid = int(round(round((number / grid), 2), 0)) number_off = number_grid * grid return number_off + def snap_to_grid(offset): """ Changes the coodrinate to match the grid settings """ - return [round_to_grid(offset[0]),round_to_grid(offset[1])] + return [round_to_grid(offset[0]), + round_to_grid(offset[1])] + def pin_center(boundary): """ This returns the center of a pin shape in the vlsiLayout border format. """ - return [0.5 * (boundary[0] + boundary[2]), 0.5 * (boundary[1] + boundary[3])] + return [0.5 * (boundary[0] + boundary[2]), + 0.5 * (boundary[1] + boundary[3])] -def auto_measure_libcell(pin_list, name, units, layer): + +def auto_measure_libcell(pin_list, name, units, lpp): """ Open a GDS file and find the pins in pin_list as text on a given layer. Return these as a set of properties including the cell width/height too. @@ -56,43 +62,44 @@ def auto_measure_libcell(pin_list, name, units, layer): reader.loadFromFile(cell_gds) cell = {} - measure_result = cell_vlsi.getLayoutBorder(layer) - if measure_result == None: + measure_result = cell_vlsi.getLayoutBorder(lpp[0]) + if measure_result: measure_result = cell_vlsi.measureSize(name) [cell["width"], cell["height"]] = measure_result for pin in pin_list: - (name,layer,boundary)=cell_vlsi.getPinShapeByLabel(str(pin)) + (name, lpp, boundary) = cell_vlsi.getPinShapeByLabel(str(pin)) cell[str(pin)] = pin_center(boundary) return cell - -def get_gds_size(name, gds_filename, units, layer): +def get_gds_size(name, gds_filename, units, lpp): """ Open a GDS file and return the size from either the bounding box or a border layer. """ - debug.info(4,"Creating VLSI layout for {}".format(name)) + debug.info(4, "Creating VLSI layout for {}".format(name)) cell_vlsi = gdsMill.VlsiLayout(units=units) reader = gdsMill.Gds2reader(cell_vlsi) reader.loadFromFile(gds_filename) - cell = {} - measure_result = cell_vlsi.getLayoutBorder(layer) - if measure_result == None: - debug.info(2,"Layout border failed. Trying to measure size for {}".format(name)) + measure_result = cell_vlsi.getLayoutBorder(lpp) + if not measure_result: + debug.info(2, "Layout border failed. Trying to measure size for {}".format(name)) measure_result = cell_vlsi.measureSize(name) # returns width,height return measure_result -def get_libcell_size(name, units, layer): + +def get_libcell_size(name, units, lpp): """ Open a GDS file and return the library cell size from either the bounding box or a border layer. """ + cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds" - return(get_gds_size(name, cell_gds, units, layer)) + return(get_gds_size(name, cell_gds, units, lpp)) + def get_gds_pins(pin_names, name, gds_filename, units): @@ -106,20 +113,24 @@ def get_gds_pins(pin_names, name, gds_filename, units): cell = {} for pin_name in pin_names: - cell[str(pin_name)]=[] - pin_list=cell_vlsi.getPinShape(str(pin_name)) + cell[str(pin_name)] = [] + pin_list = cell_vlsi.getPinShape(str(pin_name)) for pin_shape in pin_list: - (layer,boundary)=pin_shape - rect=[vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])] - # this is a list because other cells/designs may have must-connect pins - cell[str(pin_name)].append(pin_layout(pin_name, rect, layer)) + (lpp, boundary) = pin_shape + rect = [vector(boundary[0], boundary[1]), + vector(boundary[2], boundary[3])] + # this is a list because other cells/designs + # may have must-connect pins + cell[str(pin_name)].append(pin_layout(pin_name, rect, lpp)) return cell + def get_libcell_pins(pin_list, name, units): """ Open a GDS file and find the pins in pin_list as text on a given layer. Return these as a rectangle layer pair for each pin. """ + cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds" return(get_gds_pins(pin_list, name, cell_gds, units)) diff --git a/compiler/base/vector.py b/compiler/base/vector.py index 8bf09f7d..582b9391 100644 --- a/compiler/base/vector.py +++ b/compiler/base/vector.py @@ -28,6 +28,7 @@ class vector(): else: self.x = float(x) self.y = float(y) + self._hash = hash((self.x,self.y)) def __str__(self): """ override print function output """ @@ -49,7 +50,7 @@ class vector(): else: self.x=float(value[0]) self.y=float(value[1]) - + def __getitem__(self, index): """ override getitem function @@ -97,7 +98,7 @@ class vector(): Note: This assumes that you DON'T CHANGE THE VECTOR or it will break things. """ - return hash((self.x,self.y)) + return self._hash def snap_to_grid(self): self.x = self.snap_offset_to_grid(self.x) diff --git a/compiler/base/wire.py b/compiler/base/wire.py index bf1daa6a..9c8dc4e6 100644 --- a/compiler/base/wire.py +++ b/compiler/base/wire.py @@ -6,23 +6,27 @@ # All rights reserved. # from tech import drc -import debug +import contact from wire_path import wire_path from sram_factory import factory + class wire(wire_path): - """ + """ Object metal wire; given the layer type - Add a wire of minimium metal width between a set of points. + Add a wire of minimium metal width between a set of points. The points should be rectilinear to control the bend points. If not, it will always go down first. The points are the center of the wire. - The layer stack is the vertical, contact/via, and horizontal layers, respectively. + The layer stack is the vertical, contact/via, and horizontal layers, respectively. + The widen option will avoid via-to-via spacing problems for really short segments + (added as an option so we can disable it in bus connections) """ - def __init__(self, obj, layer_stack, position_list): + def __init__(self, obj, layer_stack, position_list, widen_short_wires=True): self.obj = obj self.layer_stack = layer_stack self.position_list = position_list + self.widen_short_wires = widen_short_wires self.pins = [] # used for matching parm lengths self.switch_pos_list = [] @@ -36,6 +40,7 @@ class wire(wire_path): # wires and wire_paths should not be offset to (0,0) def setup_layers(self): + (horiz_layer, via_layer, vert_layer) = self.layer_stack self.via_layer_name = via_layer @@ -47,21 +52,49 @@ class wire(wire_path): via_connect = factory.create(module_type="contact", layer_stack=self.layer_stack, dimensions=(1, 1)) + + # This is used for short connections to avoid via-to-via spacing errors + self.vert_layer_contact_width = max(via_connect.second_layer_width, + via_connect.first_layer_width) + self.horiz_layer_contact_width = max(via_connect.second_layer_height, + via_connect.first_layer_height) + self.node_to_node = [drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.width, drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.height] + self.pitch = self.compute_pitch(self.layer_stack) + def compute_pitch(self, layer_stack): + + """ + This is contact direction independent pitch, + i.e. we take the maximum contact dimension + """ + (layer1, via, layer2) = layer_stack + + if layer1 == "poly" or layer1 == "active": + contact1 = getattr(contact, layer1 + "_contact") + else: + try: + contact1 = getattr(contact, layer1 + "_via") + except AttributeError: + contact1 = getattr(contact, layer2 + "_via") + max_contact = max(contact1.width, contact1.height) + + layer1_space = drc("{0}_to_{0}".format(layer1)) + layer2_space = drc("{0}_to_{0}".format(layer2)) + pitch = max_contact + max(layer1_space, layer2_space) + + return pitch + # create a 1x1 contact def create_vias(self): """ Add a via and corner square at every corner of the path.""" self.c=factory.create(module_type="contact", layer_stack=self.layer_stack, dimensions=(1, 1)) - c_width = self.c.width - c_height = self.c.height - - from itertools import tee,islice - nwise = lambda g,n=2: zip(*(islice(g,i,None) for i,g in enumerate(tee(g,n)))) - threewise=nwise(self.position_list,3) + from itertools import tee, islice + nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n)))) + threewise = nwise(self.position_list, 3) for (a, offset, c) in list(threewise): # add a exceptions to prevent a via when we don't change directions @@ -72,18 +105,25 @@ class wire(wire_path): self.obj.add_via_center(layers=self.layer_stack, offset=offset) - def create_rectangles(self): - """ + """ Create the actual rectangles on the appropriate layers - using the position list of the corners. + using the position list of the corners. """ pl = self.position_list # position list for index in range(len(pl) - 1): + # Horizontal wire segment if pl[index][0] != pl[index + 1][0]: line_length = pl[index + 1][0] - pl[index][0] + # Make the wire wider to avoid via-to-via spacing problems + # But don't make it wider if it is shorter than one via + if self.widen_short_wires and abs(line_length) < self.pitch and abs(line_length) > self.horiz_layer_contact_width: + width = self.horiz_layer_contact_width + else: + width = self.horiz_layer_width temp_offset = [pl[index][0], - pl[index][1] - 0.5*self.horiz_layer_width] + pl[index][1] - 0.5 * width] + # If we go in the negative direction, move the offset if line_length < 0: temp_offset = [temp_offset[0] + line_length, temp_offset[1]] @@ -91,10 +131,17 @@ class wire(wire_path): length=abs(line_length), offset=temp_offset, orientation="horizontal", - layer_width=self.horiz_layer_width) + layer_width=width) + # Vertical wire segment elif pl[index][1] != pl[index + 1][1]: line_length = pl[index + 1][1] - pl[index][1] - temp_offset = [pl[index][0] - 0.5 * self.vert_layer_width, + # Make the wire wider to avoid via-to-via spacing problems + # But don't make it wider if it is shorter than one via + if self.widen_short_wires and abs(line_length) < self.pitch and abs(line_length) > self.vert_layer_contact_width: + width = self.vert_layer_contact_width + else: + width = self.vert_layer_width + temp_offset = [pl[index][0] - 0.5 * width, pl[index][1]] if line_length < 0: temp_offset = [temp_offset[0], @@ -103,11 +150,13 @@ class wire(wire_path): length=abs(line_length), offset=temp_offset, orientation="vertical", - layer_width=self.vert_layer_width) + layer_width=width) def assert_node(self, A, B): - """ Check if the node movements are not big enough for the - technology sizes.""" + """ + Check if the node movements are not big enough for the + technology sizes. + """ X_diff = abs(A[0] - B[0]) Y_diff = abs(A[1] - B[1]) [minX, minY] = self.node_to_node diff --git a/compiler/base/wire_path.py b/compiler/base/wire_path.py index ebfb8a0a..31d0ae78 100644 --- a/compiler/base/wire_path.py +++ b/compiler/base/wire_path.py @@ -24,11 +24,12 @@ def create_rectilinear_route(my_list): my_list.append(vector(pl[index][0], pl[index + 1][1])) my_list.append(vector(pl[-1])) return my_list - + + class wire_path(): """ Object metal wire_path; given the layer type - Add a wire_path of minimium metal width between a set of points. + Add a wire_path of minimium metal width between a set of points. The points should be rectilinear to control the bend points. If not, it will always go down first. The points are the center of the wire_path. If width is not given, it uses minimum layer width. @@ -37,7 +38,7 @@ class wire_path(): self.obj = obj self.layer_name = layer self.layer_id = techlayer[layer] - if width==None: + if width == None: self.layer_width = drc["minwidth_{0}".format(layer)] else: self.layer_width = width @@ -46,7 +47,6 @@ class wire_path(): self.switch_pos_list = [] self.create_layout() - def create_layout(self): self.create_rectilinear() self.connect_corner() @@ -60,9 +60,9 @@ class wire_path(): def connect_corner(self): """ Add a corner square at every corner of the wire_path.""" - from itertools import tee,islice - nwise = lambda g,n=2: zip(*(islice(g,i,None) for i,g in enumerate(tee(g,n)))) - threewise=nwise(self.position_list,3) + from itertools import tee, islice + nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n)))) + threewise=nwise(self.position_list, 3) for (a, offset, c) in list(threewise): # add a exceptions to prevent a corner when we retrace back in the same direction @@ -74,7 +74,6 @@ class wire_path(): offset[1] - 0.5 * self.layer_width] self.draw_corner_wire(corner_offset) - def draw_corner_wire(self, offset): """ This function adds the corner squares since the center line convention only draws to the center of the corner.""" @@ -117,7 +116,7 @@ class wire_path(): def add_line(self, layer_name, length, offset, orientation, layer_width): """ - straight line object with layer_minwidth + straight line object with layer_minwidth (orientation: "vertical" or "horizontal") default is vertical """ diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index 52552413..e91d8c2f 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -8,8 +8,9 @@ import debug import utils from tech import GDS, layer +from tech import cell_properties as props import bitcell_base - +from globals import OPTS class bitcell(bitcell_base.bitcell_base): """ @@ -18,10 +19,22 @@ class bitcell(bitcell_base.bitcell_base): the layout and netlist should be available in the technology library. """ - - pin_names = ["bl", "br", "wl", "vdd", "gnd"] - storage_nets = ['Q', 'Q_bar'] - type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] + + # If we have a split WL bitcell, if not be backwards + # compatible in the tech file + + if props.bitcell.split_wl: + pin_names = ["bl", "br", "wl0", "wl1", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] + else: + pin_names = [props.bitcell.cell_6t.pin.bl, + props.bitcell.cell_6t.pin.br, + props.bitcell.cell_6t.pin.wl, + props.bitcell.cell_6t.pin.vdd, + props.bitcell.cell_6t.pin.gnd] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] + storage_nets = ['Q', 'Qbar'] + (width, height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"]) @@ -37,42 +50,49 @@ class bitcell(bitcell_base.bitcell_base): self.pin_map = bitcell.pin_map self.add_pin_types(self.type_list) self.nets_match = self.do_nets_exist(self.storage_nets) - + + debug.check(OPTS.tech_name != "sky130", "sky130 does not yet support single port cells") + def get_all_wl_names(self): """ Creates a list of all wordline pin names """ - row_pins = ["wl"] + if props.bitcell.split_wl: + row_pins = ["wl0", "wl1"] + else: + row_pins = [props.bitcell.cell_6t.pin.wl] return row_pins - + def get_all_bitline_names(self): """ Creates a list of all bitline pin names (both bl and br) """ - column_pins = ["bl", "br"] + pin = props.bitcell.cell_6t.pin + column_pins = [pin.bl, pin.br] return column_pins - + def get_all_bl_names(self): """ Creates a list of all bl pins names """ - column_pins = ["bl"] - return column_pins - + return [props.bitcell.cell_6t.pin.bl] + def get_all_br_names(self): """ Creates a list of all br pins names """ - column_pins = ["br"] - return column_pins - + return [props.bitcell.cell_6t.pin.br] + def get_bl_name(self, port=0): """Get bl name""" debug.check(port == 0, "One port for bitcell only.") - return "bl" - + return props.bitcell.cell_6t.pin.bl + def get_br_name(self, port=0): """Get bl name""" debug.check(port == 0, "One port for bitcell only.") - return "br" + return props.bitcell.cell_6t.pin.br def get_wl_name(self, port=0): """Get wl name""" - debug.check(port == 0, "One port for bitcell only.") - return "wl" - + if props.bitcell.split_wl: + return "wl{}".format(port) + else: + debug.check(port == 0, "One port for bitcell only.") + return props.bitcell.cell_6t.pin.wl + def build_graph(self, graph, inst_name, port_nets): """ Adds edges based on inputs/outputs. diff --git a/compiler/bitcells/bitcell_1rw_1r.py b/compiler/bitcells/bitcell_1rw_1r.py index 0280f3cb..e5eb3043 100644 --- a/compiler/bitcells/bitcell_1rw_1r.py +++ b/compiler/bitcells/bitcell_1rw_1r.py @@ -8,6 +8,7 @@ import debug import utils from tech import GDS, layer, parameter, drc +from tech import cell_properties as props import logical_effort import bitcell_base @@ -20,7 +21,15 @@ class bitcell_1rw_1r(bitcell_base.bitcell_base): library. """ - pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + pin_names = [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.bl1, + props.bitcell.cell_1rw1r.pin.br1, + props.bitcell.cell_1rw1r.pin.wl0, + props.bitcell.cell_1rw1r.pin.wl1, + props.bitcell.cell_1rw1r.pin.vdd, + props.bitcell.cell_1rw1r.pin.gnd] + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] storage_nets = ['Q', 'Q_bar'] @@ -39,85 +48,92 @@ class bitcell_1rw_1r(bitcell_base.bitcell_base): self.pin_map = bitcell_1rw_1r.pin_map self.add_pin_types(self.type_list) self.nets_match = self.do_nets_exist(self.storage_nets) - + + pin_names = bitcell_1rw_1r.pin_names + self.bl_names = [pin_names[0], pin_names[2]] + self.br_names = [pin_names[1], pin_names[3]] + self.wl_names = [pin_names[4], pin_names[5]] + def get_bitcell_pins(self, col, row): """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ - bitcell_pins = ["bl0_{0}".format(col), - "br0_{0}".format(col), - "bl1_{0}".format(col), - "br1_{0}".format(col), - "wl0_{0}".format(row), - "wl1_{0}".format(row), + pin_name = props.bitcell.cell_1rw1r.pin + bitcell_pins = ["{0}_{1}".format(pin_name.bl0, col), + "{0}_{1}".format(pin_name.br0, col), + "{0}_{1}".format(pin_name.bl1, col), + "{0}_{1}".format(pin_name.br1, col), + "{0}_{1}".format(pin_name.wl0, row), + "{0}_{1}".format(pin_name.wl1, row), "vdd", "gnd"] return bitcell_pins - + def get_all_wl_names(self): """ Creates a list of all wordline pin names """ - row_pins = ["wl0", "wl1"] - return row_pins - + return [props.bitcell.cell_1rw1r.pin.wl0, + props.bitcell.cell_1rw1r.pin.wl1] + def get_all_bitline_names(self): """ Creates a list of all bitline pin names (both bl and br) """ - column_pins = ["bl0", "br0", "bl1", "br1"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.bl1, + props.bitcell.cell_1rw1r.pin.br1] + def get_all_bl_names(self): """ Creates a list of all bl pins names """ - column_pins = ["bl0", "bl1"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.bl1] + def get_all_br_names(self): """ Creates a list of all br pins names """ - column_pins = ["br0", "br1"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.br1] + def get_read_bl_names(self): """ Creates a list of bl pin names associated with read ports """ - column_pins = ["bl0", "bl1"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.bl1] + def get_read_br_names(self): """ Creates a list of br pin names associated with read ports """ - column_pins = ["br0", "br1"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.br1] + def get_write_bl_names(self): """ Creates a list of bl pin names associated with write ports """ - column_pins = ["bl0"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.bl0] + def get_write_br_names(self): """ Creates a list of br pin names asscociated with write ports""" - column_pins = ["br0"] - return column_pins - + return [props.bitcell.cell_1rw1r.pin.br1] + def get_bl_name(self, port=0): """Get bl name by port""" debug.check(port < 2, "Two ports for bitcell_1rw_1r only.") - return "bl{}".format(port) - + return self.bl_names[port] + def get_br_name(self, port=0): """Get bl name by port""" debug.check(port < 2, "Two ports for bitcell_1rw_1r only.") - return "br{}".format(port) + return self.br_names[port] def get_wl_name(self, port=0): """Get wl name by port""" debug.check(port < 2, "Two ports for bitcell_1rw_1r only.") - return "wl{}".format(port) - + return self.wl_names[port] + def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph. Multiport bitcell timing graph is too complex to use the add_graph_edges function.""" pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)} # Edges hardcoded here. Essentially wl->bl/br for both ports. # Port 0 edges - graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self) - graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self) + pins = props.bitcell.cell_1rw1r.pin + graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.bl0], self) + graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.br0], self) # Port 1 edges - graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) - graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self) + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self) diff --git a/compiler/bitcells/bitcell_1w_1r.py b/compiler/bitcells/bitcell_1w_1r.py index a92dc75d..56bd0b45 100644 --- a/compiler/bitcells/bitcell_1w_1r.py +++ b/compiler/bitcells/bitcell_1w_1r.py @@ -8,6 +8,7 @@ import debug import utils from tech import GDS, layer +from tech import cell_properties as props import bitcell_base @@ -19,7 +20,14 @@ class bitcell_1w_1r(bitcell_base.bitcell_base): library. """ - pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + pin_names = [props.bitcell.cell_1w1r.pin.bl0, + props.bitcell.cell_1w1r.pin.br0, + props.bitcell.cell_1w1r.pin.bl1, + props.bitcell.cell_1w1r.pin.br1, + props.bitcell.cell_1w1r.pin.wl0, + props.bitcell.cell_1w1r.pin.wl1, + props.bitcell.cell_1w1r.pin.vdd, + props.bitcell.cell_1w1r.pin.gnd] type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] storage_nets = ['Q', 'Q_bar'] @@ -39,80 +47,88 @@ class bitcell_1w_1r(bitcell_base.bitcell_base): self.add_pin_types(self.type_list) self.nets_match = self.do_nets_exist(self.storage_nets) + pin_names = bitcell_1w_1r.pin_names + self.bl_names = [pin_names[0], pin_names[2]] + self.br_names = [pin_names[1], pin_names[3]] + self.wl_names = [pin_names[4], pin_names[5]] + + def get_bitcell_pins(self, col, row): """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ - bitcell_pins = ["bl0_{0}".format(col), - "br0_{0}".format(col), - "bl1_{0}".format(col), - "br1_{0}".format(col), - "wl0_{0}".format(row), - "wl1_{0}".format(row), + pin_name = props.bitcell.cell_1w1r.pin + bitcell_pins = ["{0}_{1}".format(pin_name.bl0, col), + "{0}_{1}".format(pin_name.br0, col), + "{0}_{1}".format(pin_name.bl1, col), + "{0}_{1}".format(pin_name.br1, col), + "{0}_{1}".format(pin_name.wl0, row), + "{0}_{1}".format(pin_name.wl1, row), "vdd", "gnd"] return bitcell_pins - + def get_all_wl_names(self): """ Creates a list of all wordline pin names """ - row_pins = ["wl0", "wl1"] - return row_pins - + return [props.bitcell.cell_1w1r.pin.wl0, + props.bitcell.cell_1w1r.pin.wl1] + def get_all_bitline_names(self): """ Creates a list of all bitline pin names (both bl and br) """ - column_pins = ["bl0", "br0", "bl1", "br1"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.bl0, + props.bitcell.cell_1w1r.pin.br0, + props.bitcell.cell_1w1r.pin.bl1, + props.bitcell.cell_1w1r.pin.br1] + def get_all_bl_names(self): """ Creates a list of all bl pins names """ - column_pins = ["bl0", "bl1"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.bl0, + props.bitcell.cell_1w1r.pin.bl1] + def get_all_br_names(self): """ Creates a list of all br pins names """ - column_pins = ["br0", "br1"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.br0, + props.bitcell.cell_1w1r.pin.br1] + def get_read_bl_names(self): """ Creates a list of bl pin names associated with read ports """ - column_pins = ["bl0", "bl1"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.bl0, + props.bitcell.cell_1w1r.pin.bl1] + def get_read_br_names(self): """ Creates a list of br pin names associated with read ports """ - column_pins = ["br0", "br1"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.br0, + props.bitcell.cell_1w1r.pin.br1] + def get_write_bl_names(self): """ Creates a list of bl pin names associated with write ports """ - column_pins = ["bl0"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.bl0] + def get_write_br_names(self): """ Creates a list of br pin names asscociated with write ports""" - column_pins = ["br0"] - return column_pins - + return [props.bitcell.cell_1w1r.pin.br1] + def get_bl_name(self, port=0): """Get bl name by port""" - return "bl{}".format(port) - + return self.bl_names[port] + def get_br_name(self, port=0): """Get bl name by port""" - return "br{}".format(port) - + return self.br_names[port] + def get_wl_name(self, port=0): """Get wl name by port""" debug.check(port < 2, "Two ports for bitcell_1rw_1r only.") - return "wl{}".format(port) - + return self.wl_names[port] + def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph. Multiport bitcell timing graph is too complex to use the add_graph_edges function.""" pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)} + pins = props.bitcell.cell_1w1r.pin # Edges hardcoded here. Essentially wl->bl/br for both ports. # Port 0 edges - graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) - graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self) + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self) # Port 1 is a write port, so its timing is not considered here. diff --git a/compiler/bitcells/col_cap_bitcell_1rw_1r.py b/compiler/bitcells/col_cap_bitcell_1rw_1r.py new file mode 100644 index 00000000..01818a12 --- /dev/null +++ b/compiler/bitcells/col_cap_bitcell_1rw_1r.py @@ -0,0 +1,44 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +import utils +from tech import GDS, layer +from tech import cell_properties as props +import bitcell_base + + +class col_cap_bitcell_1rw_1r(bitcell_base.bitcell_base): + """ + todo""" + + pin_names = [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.bl1, + props.bitcell.cell_1rw1r.pin.br1, + props.bitcell.cell_1rw1r.pin.vdd] + + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", + "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("col_cap_cell_1rw_1r", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, + "col_cap_cell_1rw_1r", + GDS["unit"]) + + def __init__(self, name=""): + # Ignore the name argument + bitcell_base.bitcell_base.__init__(self, "col_cap_cell_1rw_1r") + debug.info(2, "Create col_cap bitcell 1rw+1r object") + + self.width = col_cap_bitcell_1rw_1r.width + self.height = col_cap_bitcell_1rw_1r.height + self.pin_map = col_cap_bitcell_1rw_1r.pin_map + self.add_pin_types(self.type_list) + self.no_instances = True diff --git a/compiler/bitcells/dummy_bitcell.py b/compiler/bitcells/dummy_bitcell.py index 98da96e2..116ea3ed 100644 --- a/compiler/bitcells/dummy_bitcell.py +++ b/compiler/bitcells/dummy_bitcell.py @@ -8,6 +8,7 @@ import debug import utils from tech import GDS, layer +from tech import cell_properties as props import bitcell_base @@ -18,8 +19,12 @@ class dummy_bitcell(bitcell_base.bitcell_base): the layout and netlist should be available in the technology library. """ + pin_names = [props.bitcell.cell_6t.pin.bl, + props.bitcell.cell_6t.pin.br, + props.bitcell.cell_6t.pin.wl, + props.bitcell.cell_6t.pin.vdd, + props.bitcell.cell_6t.pin.gnd] - pin_names = ["bl", "br", "wl", "vdd", "gnd"] (width, height) = utils.get_libcell_size("dummy_cell_6t", GDS["unit"], layer["boundary"]) diff --git a/compiler/bitcells/dummy_bitcell_1rw_1r.py b/compiler/bitcells/dummy_bitcell_1rw_1r.py index 401e9f85..d29c804f 100644 --- a/compiler/bitcells/dummy_bitcell_1rw_1r.py +++ b/compiler/bitcells/dummy_bitcell_1rw_1r.py @@ -8,6 +8,7 @@ import debug import utils from tech import GDS, layer +from tech import cell_properties as props import bitcell_base @@ -18,7 +19,15 @@ class dummy_bitcell_1rw_1r(bitcell_base.bitcell_base): is a hand-made cell, so the layout and netlist should be available in the technology library. """ - pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + pin_names = [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.bl1, + props.bitcell.cell_1rw1r.pin.br1, + props.bitcell.cell_1rw1r.pin.wl0, + props.bitcell.cell_1rw1r.pin.wl1, + props.bitcell.cell_1rw1r.pin.vdd, + props.bitcell.cell_1rw1r.pin.gnd] + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width, height) = utils.get_libcell_size("dummy_cell_1rw_1r", diff --git a/compiler/bitcells/dummy_bitcell_1w_1r.py b/compiler/bitcells/dummy_bitcell_1w_1r.py index 54192f71..758a5b16 100644 --- a/compiler/bitcells/dummy_bitcell_1w_1r.py +++ b/compiler/bitcells/dummy_bitcell_1w_1r.py @@ -8,6 +8,7 @@ import debug import utils from tech import GDS, layer +from tech import cell_properties as props import bitcell_base @@ -18,7 +19,14 @@ class dummy_bitcell_1w_1r(bitcell_base.bitcell_base): is a hand-made cell, so the layout and netlist should be available in the technology library. """ - pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + pin_names = [props.bitcell.cell_1w1r.pin.bl0, + props.bitcell.cell_1w1r.pin.br0, + props.bitcell.cell_1w1r.pin.bl1, + props.bitcell.cell_1w1r.pin.br1, + props.bitcell.cell_1w1r.pin.wl0, + props.bitcell.cell_1w1r.pin.wl1, + props.bitcell.cell_1w1r.pin.vdd, + props.bitcell.cell_1w1r.pin.gnd] type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width, height) = utils.get_libcell_size("dummy_cell_1w_1r", diff --git a/compiler/bitcells/dummy_pbitcell.py b/compiler/bitcells/dummy_pbitcell.py index ee15e03c..ead3d7f3 100644 --- a/compiler/bitcells/dummy_pbitcell.py +++ b/compiler/bitcells/dummy_pbitcell.py @@ -30,6 +30,7 @@ class dummy_pbitcell(design.design): self.create_netlist() self.create_layout() + self.add_boundary() def create_netlist(self): self.add_pins() diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index d14a36ed..98e477d8 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -7,7 +7,7 @@ # import contact import debug -from tech import drc, parameter +from tech import drc, parameter, layer from vector import vector from ptx import ptx from globals import OPTS @@ -199,7 +199,6 @@ class pbitcell(bitcell_base.bitcell_base): def calculate_spacing(self): """ Calculate transistor spacings """ - # calculate metal contact extensions over transistor active readwrite_nmos_contact_extension = 0.5 * \ (self.readwrite_nmos.active_contact.height - self.readwrite_nmos.active_height) @@ -213,20 +212,20 @@ class pbitcell(bitcell_base.bitcell_base): # y-offset for the access transistor's gate contact self.gate_contact_yoffset = max_contact_extension + self.m2_space \ - + 0.5 * max(contact.poly.height, contact.m1m2.height) + + 0.5 * max(contact.poly_contact.height, contact.m1_via.height) # y-position of access transistors - self.port_ypos = self.m1_space + 0.5 * contact.m1m2.height + self.gate_contact_yoffset + self.port_ypos = self.m1_space + 0.5 * contact.m1_via.height + self.gate_contact_yoffset # y-position of inverter nmos self.inverter_nmos_ypos = self.port_ypos # spacing between ports (same for read/write and write ports) self.bitline_offset = -0.5 * self.readwrite_nmos.active_width \ - + 0.5 * contact.m1m2.height \ + + 0.5 * contact.m1_via.height \ + self.m2_space + self.m2_width m2_constraint = self.bitline_offset + self.m2_space \ - + 0.5 * contact.m1m2.height \ + + 0.5 * contact.m1_via.height \ - 0.5 * self.readwrite_nmos.active_width self.write_port_spacing = max(self.active_space, self.m1_space, @@ -234,7 +233,7 @@ class pbitcell(bitcell_base.bitcell_base): self.read_port_spacing = self.bitline_offset + self.m2_space # spacing between cross coupled inverters - self.inverter_to_inverter_spacing = contact.poly.width + self.m1_space + self.inverter_to_inverter_spacing = contact.poly_contact.width + self.m1_space # calculations related to inverter connections inverter_pmos_contact_extension = 0.5 * \ @@ -243,19 +242,19 @@ class pbitcell(bitcell_base.bitcell_base): (self.inverter_nmos.active_contact.height - self.inverter_nmos.active_height) self.inverter_gap = max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \ - + self.poly_to_polycontact + 2 * contact.poly.width \ + + self.poly_to_contact + 2 * contact.poly_contact.width \ + self.m1_space + inverter_pmos_contact_extension self.cross_couple_lower_ypos = self.inverter_nmos_ypos \ + self.inverter_nmos.active_height \ + max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \ - + 0.5 * contact.poly.width + + 0.5 * contact.poly_contact.width self.cross_couple_upper_ypos = self.inverter_nmos_ypos \ + self.inverter_nmos.active_height \ + max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \ - + self.poly_to_polycontact \ - + 1.5 * contact.poly.width + + self.poly_to_contact \ + + 1.5 * contact.poly_contact.width # spacing between wordlines (and gnd) self.m1_offset = -0.5 * self.m1_width @@ -263,9 +262,9 @@ class pbitcell(bitcell_base.bitcell_base): # spacing for vdd implant_constraint = max(inverter_pmos_contact_extension, 0) \ + 2 * self.implant_enclose_active \ - + 0.5 * (contact.well.width - self.m1_width) + + 0.5*(self.inverter_pmos.active_contact.height - self.m1_width) metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space - self.vdd_offset = max(implant_constraint, metal1_constraint) + 0.5*self.m1_width + self.vdd_offset = max(implant_constraint, metal1_constraint) + self.m1_width # read port dimensions width_reduction = self.read_nmos.active_width - self.read_nmos.get_pin("D").cx() @@ -276,7 +275,7 @@ class pbitcell(bitcell_base.bitcell_base): Calculate positions that describe the edges and dimensions of the cell """ - self.botmost_ypos = self.m1_offset - self.total_ports * self.m1_pitch + self.botmost_ypos = self.m1_offset - self.total_ports * self.m1_nonpref_pitch self.topmost_ypos = self.inverter_nmos_ypos \ + self.inverter_nmos.active_height \ + self.inverter_gap \ @@ -291,7 +290,7 @@ class pbitcell(bitcell_base.bitcell_base): (self.write_nmos.active_width + self.write_port_spacing) \ - self.num_r_ports * \ (self.read_port_width + self.read_port_spacing) \ - - self.bitline_offset - 0.5 * contact.m1m2.width + - self.bitline_offset - 0.5 * contact.m1_via.width self.width = -2 * self.leftmost_xpos self.height = self.topmost_ypos - self.botmost_ypos @@ -369,29 +368,28 @@ class pbitcell(bitcell_base.bitcell_base): self.inverter_pmos_right.get_pin("G").bc()]) # connect output (drain/source) of inverters - self.add_path("metal1", + self.add_path("m1", [self.inverter_nmos_left.get_pin("D").uc(), self.inverter_pmos_left.get_pin("D").bc()], - width=contact.active.second_layer_width) - self.add_path("metal1", + width=self.inverter_nmos_left.get_pin("D").width()) + self.add_path("m1", [self.inverter_nmos_right.get_pin("S").uc(), self.inverter_pmos_right.get_pin("S").bc()], - width=contact.active.second_layer_width) + width=self.inverter_nmos_right.get_pin("S").width()) # add contacts to connect gate poly to drain/source # metal1 (to connect Q to Q_bar) contact_offset_left = vector(self.inverter_nmos_left.get_pin("D").rc().x \ - + 0.5 * contact.poly.height, + + 0.5 * contact.poly_contact.height, self.cross_couple_upper_ypos) - self.add_via_center(layers=("poly", "contact", "metal1"), + self.add_via_center(layers=self.poly_stack, offset=contact_offset_left, directions=("H", "H")) - contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x \ - - 0.5*contact.poly.height, + - 0.5*contact.poly_contact.height, self.cross_couple_lower_ypos) - self.add_via_center(layers=("poly", "contact", "metal1"), + self.add_via_center(layers=self.poly_stack, offset=contact_offset_right, directions=("H", "H")) @@ -417,12 +415,12 @@ class pbitcell(bitcell_base.bitcell_base): def route_rails(self): """ Adds gnd and vdd rails and connects them to the inverters """ # Add rails for vdd and gnd - gnd_ypos = self.m1_offset - self.total_ports * self.m1_pitch + gnd_ypos = self.m1_offset - self.total_ports * self.m1_nonpref_pitch self.gnd_position = vector(0, gnd_ypos) - self.add_rect_center(layer="metal1", + self.add_rect_center(layer="m1", offset=self.gnd_position, width=self.width) - self.add_power_pin("gnd", vector(0, gnd_ypos)) + self.add_power_pin("gnd", vector(0, gnd_ypos), directions=("H", "H")) vdd_ypos = self.inverter_nmos_ypos \ @@ -431,10 +429,10 @@ class pbitcell(bitcell_base.bitcell_base): + self.inverter_pmos.active_height \ + self.vdd_offset self.vdd_position = vector(0, vdd_ypos) - self.add_rect_center(layer="metal1", + self.add_rect_center(layer="m1", offset=self.vdd_position, width=self.width) - self.add_power_pin("vdd", vector(0, vdd_ypos)) + self.add_power_pin("vdd", vector(0, vdd_ypos), directions=("H", "H")) def create_readwrite_ports(self): """ @@ -501,10 +499,10 @@ class pbitcell(bitcell_base.bitcell_base): self.port_ypos]) # add pin for RWWL - rwwl_ypos = self.m1_offset - k * self.m1_pitch + rwwl_ypos = self.m1_offset - k * self.m1_nonpref_pitch self.rwwl_positions[k] = vector(0, rwwl_ypos) self.add_layout_pin_rect_center(text=self.rw_wl_names[k], - layer="metal1", + layer="m1", offset=self.rwwl_positions[k], width=self.width) @@ -514,7 +512,7 @@ class pbitcell(bitcell_base.bitcell_base): + 0.5 * self.m2_width self.rwbl_positions[k] = vector(rwbl_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.rw_bl_names[k], - layer="metal2", + layer="m2", offset=self.rwbl_positions[k], height=self.height) @@ -524,7 +522,7 @@ class pbitcell(bitcell_base.bitcell_base): - 0.5 * self.m2_width self.rwbr_positions[k] = vector(rwbr_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.rw_br_names[k], - layer="metal2", + layer="m2", offset=self.rwbr_positions[k], height=self.height) @@ -597,11 +595,11 @@ class pbitcell(bitcell_base.bitcell_base): # add pin for WWL wwl_ypos = rwwl_ypos = self.m1_offset \ - - self.num_rw_ports * self.m1_pitch \ - - k * self.m1_pitch + - self.num_rw_ports * self.m1_nonpref_pitch \ + - k * self.m1_nonpref_pitch self.wwl_positions[k] = vector(0, wwl_ypos) self.add_layout_pin_rect_center(text=self.w_wl_names[k], - layer="metal1", + layer="m1", offset=self.wwl_positions[k], width=self.width) @@ -611,7 +609,7 @@ class pbitcell(bitcell_base.bitcell_base): + 0.5 * self.m2_width self.wbl_positions[k] = vector(wbl_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.w_bl_names[k], - layer="metal2", + layer="m2", offset=self.wbl_positions[k], height=self.height) @@ -621,7 +619,7 @@ class pbitcell(bitcell_base.bitcell_base): - 0.5 * self.m2_width self.wbr_positions[k] = vector(wbr_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.w_br_names[k], - layer="metal2", + layer="m2", offset=self.wbr_positions[k], height=self.height) @@ -723,12 +721,12 @@ class pbitcell(bitcell_base.bitcell_base): # add pin for RWL rwl_ypos = rwwl_ypos = self.m1_offset \ - - self.num_rw_ports * self.m1_pitch \ - - self.num_w_ports * self.m1_pitch \ - - k * self.m1_pitch + - self.num_rw_ports * self.m1_nonpref_pitch \ + - self.num_w_ports * self.m1_nonpref_pitch \ + - k * self.m1_nonpref_pitch self.rwl_positions[k] = vector(0, rwl_ypos) self.add_layout_pin_rect_center(text=self.r_wl_names[k], - layer="metal1", + layer="m1", offset=self.rwl_positions[k], width=self.width) @@ -738,7 +736,7 @@ class pbitcell(bitcell_base.bitcell_base): + 0.5 * self.m2_width self.rbl_positions[k] = vector(rbl_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.r_bl_names[k], - layer="metal2", + layer="m2", offset=self.rbl_positions[k], height=self.height) @@ -748,7 +746,7 @@ class pbitcell(bitcell_base.bitcell_base): - 0.5 * self.m2_width self.rbr_positions[k] = vector(rbr_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.r_br_names[k], - layer="metal2", + layer="m2", offset=self.rbr_positions[k], height=self.height) @@ -786,25 +784,25 @@ class pbitcell(bitcell_base.bitcell_base): # first transistor on either side of the cross coupled inverters # does not need to route to wordline on metal2 if (k == 0) or (k == 1): - self.add_via_center(layers=("poly", "contact", "metal1"), + self.add_via_center(layers=self.poly_stack, offset=port_contact_offset) + self.add_path("poly", [gate_offset, port_contact_offset]) - self.add_path("metal1", + self.add_path("m1", [port_contact_offset, wl_contact_offset]) else: - self.add_via_center(layers=("poly", "contact", "metal1"), + self.add_via_center(layers=self.poly_stack, offset=port_contact_offset) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=port_contact_offset) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=wl_contact_offset, - directions=("H", "H")) + self.add_via_center(layers=self.m1_stack, + offset=wl_contact_offset) self.add_path("poly", [gate_offset, port_contact_offset]) - self.add_path("metal2", + self.add_path("m2", [port_contact_offset, wl_contact_offset]) def route_bitlines(self): @@ -839,11 +837,12 @@ class pbitcell(bitcell_base.bitcell_base): # Leave bitline disconnected if a dummy cell if not self.dummy_bitcell: - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=port_contact_offest) + self.add_via_center(layers=self.m1_stack, + offset=port_contact_offest, + directions="nonpref") - self.add_path("metal2", - [port_contact_offest, bl_offset], width=contact.m1m2.height) + self.add_path("m2", + [port_contact_offest, bl_offset], width=contact.m1_via.height) for k in range(self.total_ports): port_contact_offest = right_port_transistors[k].get_pin("D").center() @@ -851,11 +850,12 @@ class pbitcell(bitcell_base.bitcell_base): # Leave bitline disconnected if a dummy cell if not self.dummy_bitcell: - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=port_contact_offest) + self.add_via_center(layers=self.m1_stack, + offset=port_contact_offest, + directions="nonpref") - self.add_path("metal2", - [port_contact_offest, br_offset], width=contact.m1m2.height) + self.add_path("m2", + [port_contact_offest, br_offset], width=contact.m1_via.height) def route_supply(self): """ Route inverter nmos and read-access nmos to gnd. Route inverter pmos to vdd. """ @@ -868,30 +868,32 @@ class pbitcell(bitcell_base.bitcell_base): nmos_contact_positions.append(self.read_access_nmos_right[k].get_pin("S").center()) for position in nmos_contact_positions: - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=position) + self.add_via_center(layers=self.m1_stack, + offset=position, + directions=("V", "V")) + if position.x > 0: - contact_correct = 0.5 * contact.m1m2.height + contact_correct = 0.5 * contact.m1_via.height else: - contact_correct = -0.5 * contact.m1m2.height + contact_correct = -0.5 * contact.m1_via.height supply_offset = vector(position.x + contact_correct, self.gnd_position.y) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=supply_offset, directions=("H", "H")) - self.add_path("metal2", [position, supply_offset]) + self.add_path("m2", [position, supply_offset]) # route inverter pmos to vdd vdd_pos_left = vector(self.inverter_nmos_left.get_pin("S").uc().x, self.vdd_position.y) - self.add_path("metal1", + self.add_path("m1", [self.inverter_pmos_left.get_pin("S").uc(), vdd_pos_left]) vdd_pos_right = vector(self.inverter_nmos_right.get_pin("D").uc().x, self.vdd_position.y) - self.add_path("metal1", + self.add_path("m1", [self.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right]) def route_readwrite_access(self): @@ -904,14 +906,14 @@ class pbitcell(bitcell_base.bitcell_base): self.cross_couple_lower_ypos) Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos) - self.add_path("metal1", + self.add_path("m1", [self.readwrite_nmos_left[k].get_pin("D").uc(), mid, Q_pos]) mid = vector(self.readwrite_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos) Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos) - self.add_path("metal1", + self.add_path("m1", [self.readwrite_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos]) def route_write_access(self): @@ -924,14 +926,14 @@ class pbitcell(bitcell_base.bitcell_base): self.cross_couple_lower_ypos) Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos) - self.add_path("metal1", + self.add_path("m1", [self.write_nmos_left[k].get_pin("D").uc(), mid, Q_pos]) mid = vector(self.write_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos) Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos) - self.add_path("metal1", + self.add_path("m1", [self.write_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos]) def route_read_access(self): @@ -941,16 +943,17 @@ class pbitcell(bitcell_base.bitcell_base): """ # add poly to metal1 contacts for gates of the inverters left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x \ - - self.poly_to_polycontact - 0.5*contact.poly.width, + - self.poly_to_contact - 0.5*contact.poly_contact.width, self.cross_couple_upper_ypos) - self.add_via_center(layers=("poly", "contact", "metal1"), + self.add_via_center(layers=self.poly_stack, offset=left_storage_contact, directions=("H", "H")) + right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x \ - + self.poly_to_polycontact + 0.5*contact.poly.width, + + self.poly_to_contact + 0.5*contact.poly_contact.width, self.cross_couple_upper_ypos) - self.add_via_center(layers=("poly", "contact", "metal1"), + self.add_via_center(layers=self.poly_stack, offset=right_storage_contact, directions=("H", "H")) @@ -967,7 +970,7 @@ class pbitcell(bitcell_base.bitcell_base): + vector(0, self.gate_contact_yoffset - self.poly_extend_active) - self.add_via_center(layers=("poly", "contact", "metal1"), + self.add_via_center(layers=self.poly_stack, offset=port_contact_offset) self.add_path("poly", @@ -975,14 +978,14 @@ class pbitcell(bitcell_base.bitcell_base): mid = vector(self.read_access_nmos_left[k].get_pin("G").uc().x, self.cross_couple_upper_ypos) - self.add_path("metal1", + self.add_path("m1", [port_contact_offset, mid, left_storage_contact]) port_contact_offset = self.read_access_nmos_right[k].get_pin("G").uc() \ + vector(0, self.gate_contact_yoffset - self.poly_extend_active) - self.add_via_center(layers=("poly", "contact", "metal1"), + self.add_via_center(layers=self.poly_stack, offset=port_contact_offset) self.add_path("poly", @@ -990,51 +993,55 @@ class pbitcell(bitcell_base.bitcell_base): mid = vector(self.read_access_nmos_right[k].get_pin("G").uc().x, self.cross_couple_upper_ypos) - self.add_path("metal1", + self.add_path("m1", [port_contact_offset, mid, right_storage_contact]) def extend_well(self): """ Connects wells between ptx modules and places well contacts """ - # extend pwell to encompass entire nmos region of the cell up to the - # height of the tallest nmos transistor - max_nmos_well_height = max(self.inverter_nmos.cell_well_height, - self.readwrite_nmos.cell_well_height, - self.write_nmos.cell_well_height, - self.read_nmos.cell_well_height) - well_height = max_nmos_well_height + self.port_ypos \ - - self.well_enclose_active - self.gnd_position.y - offset = vector(self.leftmost_xpos, self.botmost_ypos) - self.add_rect(layer="pwell", - offset=offset, - width=self.width, - height=well_height) - + if "pwell" in layer: + # extend pwell to encompass entire nmos region of the cell up to the + # height of the tallest nmos transistor + max_nmos_well_height = max(self.inverter_nmos.well_height, + self.readwrite_nmos.well_height, + self.write_nmos.well_height, + self.read_nmos.well_height) + well_height = max_nmos_well_height + self.port_ypos \ + - self.nwell_enclose_active - self.gnd_position.y + # FIXME fudge factor xpos + well_width = self.width + 2*self.nwell_enclose_active + offset = vector(self.leftmost_xpos - self.nwell_enclose_active, self.botmost_ypos) + self.add_rect(layer="pwell", + offset=offset, + width=well_width, + height=well_height) + # extend nwell to encompass inverter_pmos # calculate offset of the left pmos well - inverter_well_xpos = -(self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \ - - self.well_enclose_active - inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \ - + self.inverter_gap - self.well_enclose_active + if "nwell" in layer: + inverter_well_xpos = -(self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \ + - self.nwell_enclose_active + inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \ + + self.inverter_gap - self.nwell_enclose_active + + # calculate width of the two combined nwells + # calculate height to encompass nimplant connected to vdd + well_width = 2 * (self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \ + + 2 * self.nwell_enclose_active + well_height = self.vdd_position.y - inverter_well_ypos \ + + self.nwell_enclose_active + drc["minwidth_tx"] - # calculate width of the two combined nwells - # calculate height to encompass nimplant connected to vdd - well_width = 2 * (self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \ - + 2 * self.well_enclose_active - well_height = self.vdd_position.y - inverter_well_ypos \ - + self.well_enclose_active + drc["minwidth_tx"] - - offset = [inverter_well_xpos, inverter_well_ypos] - self.add_rect(layer="nwell", - offset=offset, - width=well_width, - height=well_height) + offset = [inverter_well_xpos, inverter_well_ypos] + self.add_rect(layer="nwell", + offset=offset, + width=well_width, + height=well_height) # add well contacts # connect pimplants to gnd offset = vector(0, self.gnd_position.y) - self.add_via_center(layers=("active", "contact", "metal1"), + self.add_via_center(layers=self.active_stack, offset=offset, directions=("H", "H"), implant_type="p", @@ -1042,7 +1049,7 @@ class pbitcell(bitcell_base.bitcell_base): # connect nimplants to vdd offset = vector(0, self.vdd_position.y) - self.add_via_center(layers=("active", "contact", "metal1"), + self.add_via_center(layers=self.active_stack, offset=offset, directions=("H", "H"), implant_type="n", @@ -1091,7 +1098,7 @@ class pbitcell(bitcell_base.bitcell_base): """ Q_bar_pos = self.inverter_pmos_right.get_pin("S").center() vdd_pos = self.inverter_pmos_right.get_pin("D").center() - self.add_path("metal1", [Q_bar_pos, vdd_pos]) + self.add_path("m1", [Q_bar_pos, vdd_pos]) def get_storage_net_names(self): """ diff --git a/compiler/bitcells/replica_bitcell.py b/compiler/bitcells/replica_bitcell.py index 2f804bf0..479883d9 100644 --- a/compiler/bitcells/replica_bitcell.py +++ b/compiler/bitcells/replica_bitcell.py @@ -8,7 +8,10 @@ import design import debug import utils -from tech import GDS,layer,drc,parameter +from tech import GDS,layer,drc,parameter,cell_properties +from tech import cell_properties as props + +from globals import OPTS class replica_bitcell(design.design): """ @@ -17,10 +20,24 @@ class replica_bitcell(design.design): is a hand-made cell, so the layout and netlist should be available in the technology library. """ - pin_names = ["bl", "br", "wl", "vdd", "gnd"] - type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] - (width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"]) - pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"]) + if cell_properties.bitcell.split_wl: + pin_names = ["bl", "br", "wl0", "wl1", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT" , "POWER", "GROUND"] + else: + pin_names = [props.bitcell.cell_6t.pin.bl, + props.bitcell.cell_6t.pin.br, + props.bitcell.cell_6t.pin.wl, + props.bitcell.cell_6t.pin.vdd, + props.bitcell.cell_6t.pin.gnd] + + type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] + + if not OPTS.netlist_only: + (width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"]) + else: + (width,height) = (0,0) + pin_map = [] def __init__(self, name=""): # Ignore the name argument @@ -56,4 +73,4 @@ class replica_bitcell(design.design): def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) \ No newline at end of file + self.add_graph_edges(graph, port_nets) diff --git a/compiler/bitcells/replica_bitcell_1rw_1r.py b/compiler/bitcells/replica_bitcell_1rw_1r.py index 0f56319e..79f16a47 100644 --- a/compiler/bitcells/replica_bitcell_1rw_1r.py +++ b/compiler/bitcells/replica_bitcell_1rw_1r.py @@ -9,6 +9,7 @@ import design import debug import utils from tech import GDS,layer,drc,parameter +from tech import cell_properties as props class replica_bitcell_1rw_1r(design.design): """ @@ -17,7 +18,15 @@ class replica_bitcell_1rw_1r(design.design): is a hand-made cell, so the layout and netlist should be available in the technology library. """ - pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + pin_names = [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.bl1, + props.bitcell.cell_1rw1r.pin.br1, + props.bitcell.cell_1rw1r.pin.wl0, + props.bitcell.cell_1rw1r.pin.wl1, + props.bitcell.cell_1rw1r.pin.vdd, + props.bitcell.cell_1rw1r.pin.gnd] + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("replica_cell_1rw_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1rw_1r", GDS["unit"]) @@ -47,14 +56,15 @@ class replica_bitcell_1rw_1r(design.design): access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin - def build_graph(self, graph, inst_name, port_nets): + def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph. Multiport bitcell timing graph is too complex to use the add_graph_edges function.""" - pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + pins = props.bitcell.cell_1rw1r.pin #Edges hardcoded here. Essentially wl->bl/br for both ports. # Port 0 edges - graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self) - graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self) + graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.bl0], self) + graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.br0], self) # Port 1 edges - graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) - graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) \ No newline at end of file + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self) + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self) diff --git a/compiler/bitcells/replica_bitcell_1w_1r.py b/compiler/bitcells/replica_bitcell_1w_1r.py index b903e0ad..52bea519 100644 --- a/compiler/bitcells/replica_bitcell_1w_1r.py +++ b/compiler/bitcells/replica_bitcell_1w_1r.py @@ -9,6 +9,7 @@ import design import debug import utils from tech import GDS,layer,drc,parameter +from tech import cell_properties as props class replica_bitcell_1w_1r(design.design): """ @@ -17,7 +18,15 @@ class replica_bitcell_1w_1r(design.design): is a hand-made cell, so the layout and netlist should be available in the technology library. """ - pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + pin_names = [props.bitcell.cell_1w1r.pin.bl0, + props.bitcell.cell_1w1r.pin.br0, + props.bitcell.cell_1w1r.pin.bl1, + props.bitcell.cell_1w1r.pin.br1, + props.bitcell.cell_1w1r.pin.wl0, + props.bitcell.cell_1w1r.pin.wl1, + props.bitcell.cell_1w1r.pin.vdd, + props.bitcell.cell_1w1r.pin.gnd] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("replica_cell_1w_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1w_1r", GDS["unit"]) @@ -47,13 +56,14 @@ class replica_bitcell_1w_1r(design.design): access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin - def build_graph(self, graph, inst_name, port_nets): + def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph. Multiport bitcell timing graph is too complex to use the add_graph_edges function.""" debug.info(1,'Adding edges for {}'.format(inst_name)) - pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + pins = props.bitcell.cell_1w1r.pin #Edges hardcoded here. Essentially wl->bl/br for the read port. # Port 1 edges - graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) - graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) - # Port 0 is a write port, so its timing is not considered here. \ No newline at end of file + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self) + graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self) + # Port 0 is a write port, so its timing is not considered here. diff --git a/compiler/bitcells/replica_pbitcell.py b/compiler/bitcells/replica_pbitcell.py index 4fcfb4c5..5a588c1e 100644 --- a/compiler/bitcells/replica_pbitcell.py +++ b/compiler/bitcells/replica_pbitcell.py @@ -30,6 +30,7 @@ class replica_pbitcell(design.design): self.create_netlist() self.create_layout() + self.add_boundary() def create_netlist(self): self.add_pins() @@ -84,4 +85,4 @@ class replica_pbitcell(design.design): self.copy_layout_pin(self.prbc_inst, "wl{}".format(port)) self.copy_layout_pin(self.prbc_inst, "vdd") self.copy_layout_pin(self.prbc_inst, "gnd") - \ No newline at end of file + diff --git a/compiler/bitcells/row_cap_bitcell_1rw_1r.py b/compiler/bitcells/row_cap_bitcell_1rw_1r.py new file mode 100644 index 00000000..f7a3a687 --- /dev/null +++ b/compiler/bitcells/row_cap_bitcell_1rw_1r.py @@ -0,0 +1,44 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +import utils +from tech import GDS, layer +from tech import cell_properties as props +import bitcell_base + + +class row_cap_bitcell_1rw_1r(bitcell_base.bitcell_base): + """ + A single bit cell which is forced to store a 0. + This module implements the single memory cell used in the design. It + is a hand-made cell, so the layout and netlist should be available in + the technology library. """ + + pin_names = [props.bitcell.cell_1rw1r.pin.wl0, + props.bitcell.cell_1rw1r.pin.wl1, + props.bitcell.cell_1rw1r.pin.gnd] + + type_list = ["INPUT", "INPUT", "GROUND"] + + (width, height) = utils.get_libcell_size("row_cap_cell_1rw_1r", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, + "row_cap_cell_1rw_1r", + GDS["unit"]) + + def __init__(self, name=""): + # Ignore the name argument + bitcell_base.bitcell_base.__init__(self, "row_cap_cell_1rw_1r") + debug.info(2, "Create row_cap bitcell 1rw+1r object") + + self.width = row_cap_bitcell_1rw_1r.width + self.height = row_cap_bitcell_1rw_1r.height + self.pin_map = row_cap_bitcell_1rw_1r.pin_map + self.add_pin_types(self.type_list) + self.no_instances = True diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 36965f4d..21355b0e 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -59,7 +59,8 @@ class delay(simulation): """ Create measurement names. The names themselves currently define the type of measurement """ self.delay_meas_names = ["delay_lh", "delay_hl", "slew_lh", "slew_hl"] - self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power"] + self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power", + "disabled_read0_power", "disabled_read1_power", "disabled_write0_power", "disabled_write1_power"] # self.voltage_when_names = ["volt_bl", "volt_br"] # self.bitline_delay_names = ["delay_bl", "delay_br"] @@ -108,6 +109,11 @@ class delay(simulation): self.read_lib_meas.append(power_measure("read0_power", "FALL", measure_scale=1e3)) self.read_lib_meas[-1].meta_str = sram_op.READ_ZERO + self.read_lib_meas.append(power_measure("disabled_read1_power", "RISE", measure_scale=1e3)) + self.read_lib_meas[-1].meta_str = "disabled_read1" + self.read_lib_meas.append(power_measure("disabled_read0_power", "FALL", measure_scale=1e3)) + self.read_lib_meas[-1].meta_str = "disabled_read0" + # This will later add a half-period to the spice time delay. Only for reading 0. for obj in self.read_lib_meas: if obj.meta_str is sram_op.READ_ZERO: @@ -155,6 +161,11 @@ class delay(simulation): self.write_lib_meas.append(power_measure("write0_power", "FALL", measure_scale=1e3)) self.write_lib_meas[-1].meta_str = sram_op.WRITE_ZERO + self.write_lib_meas.append(power_measure("disabled_write1_power", "RISE", measure_scale=1e3)) + self.write_lib_meas[-1].meta_str = "disabled_write1" + self.write_lib_meas.append(power_measure("disabled_write0_power", "FALL", measure_scale=1e3)) + self.write_lib_meas[-1].meta_str = "disabled_write0" + write_measures = [] write_measures.append(self.write_lib_meas) write_measures.append(self.create_write_bit_measures()) @@ -170,39 +181,40 @@ class delay(simulation): meas.targ_name_no_port)) self.dout_volt_meas[-1].meta_str = meas.meta_str - self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name, "FALL", "RISE", measure_scale=1e9) + self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name+"{}", "FALL", "RISE", measure_scale=1e9) self.sen_meas.meta_str = sram_op.READ_ZERO self.sen_meas.meta_add_delay = True + self.dout_volt_meas.append(self.sen_meas) - return self.dout_volt_meas+[self.sen_meas] + return self.dout_volt_meas def create_read_bit_measures(self): """ Adds bit measurements for read0 and read1 cycles """ - self.bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]} + self.read_bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]} meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE) for cycle in meas_cycles: meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name) single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data) for polarity,meas in single_bit_meas.items(): meas.meta_str = cycle - self.bit_meas[polarity].append(meas) + self.read_bit_meas[polarity].append(meas) # Dictionary values are lists, reduce to a single list of measurements - return [meas for meas_list in self.bit_meas.values() for meas in meas_list] + return [meas for meas_list in self.read_bit_meas.values() for meas in meas_list] def create_write_bit_measures(self): """ Adds bit measurements for write0 and write1 cycles """ - self.bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]} + self.write_bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]} meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE) for cycle in meas_cycles: meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name) single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data) for polarity,meas in single_bit_meas.items(): meas.meta_str = cycle - self.bit_meas[polarity].append(meas) + self.write_bit_meas[polarity].append(meas) # Dictionary values are lists, reduce to a single list of measurements - return [meas for meas_list in self.bit_meas.values() for meas in meas_list] + return [meas for meas_list in self.write_bit_meas.values() for meas in meas_list] def get_bit_measures(self, meas_tag, probe_address, probe_data): """ @@ -225,9 +237,10 @@ class delay(simulation): qbar_name = "bitcell_Q_bar_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col) # Bit measures, measurements times to be defined later. The measurement names must be unique - # but they is enforced externally - q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name, has_port=False) - qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name, has_port=False) + # but they is enforced externally. {} added to names to differentiate between ports allow the + # measurements are independent of the ports + q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name) + qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name) return {bit_polarity.NONINVERTING:q_meas, bit_polarity.INVERTING:qbar_meas} @@ -267,10 +280,33 @@ class delay(simulation): self.graph.get_all_paths('{}{}'.format("clk", port), '{}{}_{}'.format(self.dout_name, port, self.probe_data)) - self.sen_name = self.get_sen_name(self.graph.all_paths) + sen_with_port = self.get_sen_name(self.graph.all_paths) + if sen_with_port.endswith(str(port)): + self.sen_name = sen_with_port[:-len(str(port))] + else: + self.sen_name = sen_with_port + debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.") + debug.info(2,"s_en name = {}".format(self.sen_name)) - self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths, port) + bl_name_port, br_name_port = self.get_bl_name(self.graph.all_paths, port) + port_pos = -1-len(str(self.probe_data))-len(str(port)) + + if bl_name_port.endswith(str(port)+"_"+str(self.probe_data)): + self.bl_name = bl_name_port[:port_pos] +"{}"+ bl_name_port[port_pos+len(str(port)):] + elif not bl_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0 + self.bl_name = bl_name_port + else: + self.bl_name = bl_name_port + debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.") + + if br_name_port.endswith(str(port)+"_"+str(self.probe_data)): + self.br_name = br_name_port[:port_pos] +"{}"+ br_name_port[port_pos+len(str(port)):] + elif not br_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0 + self.br_name = br_name_port + else: + self.br_name = br_name_port + debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.") debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name)) else: self.graph.get_all_paths('{}{}'.format("clk", port), @@ -283,8 +319,9 @@ class delay(simulation): self.bl_name = "bl{0}_{1}".format(port, OPTS.word_size-1) self.br_name = "br{0}_{1}".format(port, OPTS.word_size-1) debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name)) + - def get_sen_name(self, paths): + def get_sen_name(self, paths, assumed_port=None): """ Gets the signal name associated with the sense amp enable from input paths. Only expects a single path to contain the sen signal name. @@ -674,8 +711,9 @@ class delay(simulation): if (time_out <= 0): debug.error("Timed out, could not find a feasible period.",2) - # Clear any write target ports and set read port - self.targ_write_ports = [port] + # Write ports are assumed non-critical to timing, so the first available is used + self.targ_write_ports = [self.write_ports[0]] + # Set target read port for simulation self.targ_read_ports = [port] debug.info(1, "Trying feasible period: {0}ns on Port {1}".format(feasible_period, port)) @@ -689,7 +727,7 @@ class delay(simulation): if not success: feasible_period = 2 * feasible_period continue - + # Positions of measurements currently hardcoded. First 2 are delays, next 2 are slews feasible_delays = [results[port][mname] for mname in self.delay_meas_names if "delay" in mname] feasible_slews = [results[port][mname] for mname in self.delay_meas_names if "slew" in mname] @@ -756,12 +794,10 @@ class delay(simulation): # Loop through all targeted ports and collect delays and powers. result = [{} for i in self.all_ports] - - # First, check that the memory has the right values at the right times - if not self.check_bit_measures(): - return(False,{}) - for port in self.targ_write_ports: + if not self.check_bit_measures(self.write_bit_meas, port): + return(False,{}) + debug.info(2, "Checking write values for port {}".format(port)) write_port_dict = {} for measure in self.write_lib_meas: @@ -773,6 +809,10 @@ class delay(simulation): for port in self.targ_read_ports: + # First, check that the memory has the right values at the right times + if not self.check_bit_measures(self.read_bit_meas, port): + return(False,{}) + debug.info(2, "Checking read delay values for port {}".format(port)) # Check sen timing, then bitlines, then general measurements. if not self.check_sen_measure(port): @@ -849,15 +889,15 @@ class delay(simulation): return dout_success - def check_bit_measures(self): + def check_bit_measures(self, bit_measures, port): """ Checks the measurements which represent the internal storage voltages at the end of the read cycle. """ success = False - for polarity, meas_list in self.bit_meas.items(): + for polarity, meas_list in bit_measures.items(): for meas in meas_list: - val = meas.retrieve_measure() + val = meas.retrieve_measure(port=port) debug.info(2,"{}={}".format(meas.name, val)) if type(val) != float: continue @@ -990,7 +1030,8 @@ class delay(simulation): # Binary search algorithm to find the min period (max frequency) of input port time_out = 25 - self.targ_write_ports = [port] + # Write ports are assumed non-critical to timing, so the first available is used + self.targ_write_ports = [self.write_ports[0]] self.targ_read_ports = [port] while True: time_out -= 1 @@ -1088,7 +1129,8 @@ class delay(simulation): self.trimsp.set_configuration(self.num_banks, self.num_rows, self.num_cols, - self.word_size) + self.word_size, + self.num_spare_rows) self.trimsp.trim(self.probe_address,self.probe_data) else: # The non-reduced netlist file when it is disabled @@ -1220,6 +1262,9 @@ class delay(simulation): write_port) self.measure_cycles[write_port][sram_op.WRITE_ZERO] = len(self.cycle_times)-1 + self.add_noop_clock_one_port(write_port) + self.measure_cycles[write_port]["disabled_write0"] = len(self.cycle_times)-1 + # This also ensures we will have a H->L transition on the next read self.add_read("R data 1 address {} to set dout caps".format(inverse_address), inverse_address, @@ -1230,6 +1275,10 @@ class delay(simulation): read_port) self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times)-1 + self.add_noop_clock_one_port(read_port) + self.measure_cycles[read_port]["disabled_read0"] = len(self.cycle_times) - 1 + + self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)") self.add_write("W data 1 address {} to write value".format(self.probe_address), @@ -1239,12 +1288,19 @@ class delay(simulation): write_port) self.measure_cycles[write_port][sram_op.WRITE_ONE] = len(self.cycle_times)-1 + self.add_noop_clock_one_port(write_port) + self.measure_cycles[write_port]["disabled_write1"] = len(self.cycle_times)-1 + self.add_write("W data 0 address {} to clear din caps".format(inverse_address), inverse_address, data_zeros, wmask_ones, write_port) + self.add_noop_clock_one_port(read_port) + self.measure_cycles[read_port]["disabled_read1"] = len(self.cycle_times) - 1 + + # This also ensures we will have a L->H transition on the next read self.add_read("R data 0 address {} to clear dout caps".format(inverse_address), inverse_address, @@ -1278,8 +1334,8 @@ class delay(simulation): """ # Using this requires setting at least one port to target for simulation. - if len(self.targ_write_ports) == 0 and len(self.targ_read_ports) == 0: - debug.error("No port selected for characterization.",1) + if len(self.targ_write_ports) == 0 or len(self.targ_read_ports) == 0: + debug.error("Write and read port must be specified for characterization.",1) self.set_stimulus_variables() # Get any available read/write port in case only a single write or read ports is being characterized. diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 6d827aa3..2c391e38 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -5,23 +5,18 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys,re,shutil -import copy import collections -from design import design import debug -import math -import tech import random from .stimuli import * from .charutils import * -import utils from globals import OPTS from .simulation import simulation -from .delay import delay +# from .delay import delay import graph_util from sram_factory import factory + class functional(simulation): """ Functions to write random data values to a random address then read them back and check @@ -40,6 +35,9 @@ class functional(simulation): else: self.num_wmasks = 0 + if not self.num_spare_cols: + self.num_spare_cols = 0 + self.set_corner(corner) self.set_spice_constants() self.set_stimulus_variables() @@ -57,7 +55,6 @@ class functional(simulation): self.read_check = [] self.read_results = [] - def run(self, feasible_period=None): if feasible_period: #period defaults to tech.py feasible period otherwise. self.period = feasible_period @@ -82,10 +79,11 @@ class functional(simulation): for port in self.all_ports: checks = [] if port in self.read_ports: - checks.append((self.addr_value[port],"addr")) + checks.append((self.addr_value[port], "addr")) if port in self.write_ports: - checks.append((self.data_value[port],"data")) - checks.append((self.wmask_value[port],"wmask")) + checks.append((self.data_value[port], "data")) + checks.append((self.wmask_value[port], "wmask")) + checks.append((self.spare_wen_value[port], "spare_wen")) for (val, name) in checks: debug.check(len(self.cycle_times)==len(val), @@ -104,15 +102,15 @@ class functional(simulation): r_ops = ["noop", "read"] # First cycle idle is always an idle cycle - comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, "0"*self.num_wmasks, 0, self.t_current) + comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current) self.add_noop_all_ports(comment) # 1. Write all the write ports first to seed a bunch of locations. for port in self.write_ports: addr = self.gen_addr() word = self.gen_data() - comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current) - self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port) + comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current) + self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port) self.stored_words[addr] = word # All other read-only ports are noops. @@ -131,7 +129,7 @@ class functional(simulation): if port in self.write_ports: self.add_noop_one_port(port) else: - comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current) + comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current) self.add_read_one_port(comment, addr, port) self.add_read_check(word, port) self.cycle_times.append(self.t_current) @@ -160,13 +158,13 @@ class functional(simulation): self.add_noop_one_port(port) else: word = self.gen_data() - comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current) - self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port) + comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current) + self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port) self.stored_words[addr] = word w_addrs.append(addr) elif op == "partial_write": # write only to a word that's been written to - (addr,old_word) = self.get_data() + (addr, old_word) = self.get_data() # two ports cannot write to the same address if addr in w_addrs: self.add_noop_one_port(port) @@ -179,7 +177,7 @@ class functional(simulation): self.stored_words[addr] = new_word w_addrs.append(addr) else: - (addr,word) = random.choice(list(self.stored_words.items())) + (addr, word) = random.choice(list(self.stored_words.items())) # The write driver is not sized sufficiently to drive through the two # bitcell access transistors to the read port. So, for now, we do not allow # a simultaneous write and read to the same address on different ports. This @@ -187,7 +185,7 @@ class functional(simulation): if addr in w_addrs: self.add_noop_one_port(port) else: - comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current) + comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current) self.add_read_one_port(comment, addr, port) self.add_read_check(word, port) @@ -195,7 +193,7 @@ class functional(simulation): self.t_current += self.period # Last cycle idle needed to correctly measure the value on the second to last clock edge - comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, "0"*self.num_wmasks, 0, self.t_current) + comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current) self.add_noop_all_ports(comment) def gen_masked_data(self, old_word, word, wmask): @@ -209,7 +207,7 @@ class functional(simulation): if wmask[bit] == "0": lower = bit * self.write_size upper = lower + self.write_size - 1 - new_word = new_word[:lower] + old_word[lower:upper+1] + new_word[upper + 1:] + new_word = new_word[:lower] + old_word[lower:upper + 1] + new_word[upper + 1:] return new_word @@ -219,15 +217,15 @@ class functional(simulation): self.check except: self.check = 0 - self.read_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, self.check]) + self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check]) self.check += 1 def read_stim_results(self): # Extract dout values from spice timing.lis for (word, dout_port, eo_period, check) in self.read_check: sp_read_value = "" - for bit in range(self.word_size): - value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(),bit,check)) + for bit in range(self.word_size + self.num_spare_cols): + value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(), bit, check)) if value > self.v_high: sp_read_value = "1" + sp_read_value elif value < self.v_low: @@ -278,17 +276,24 @@ class functional(simulation): # wmask must be reversed since a python list goes right to left and sram bits go left to right. return wmask[::-1] - def gen_data(self): """ Generates a random word to write. """ - random_value = random.randint(0,(2**self.word_size)-1) - data_bits = self.convert_to_bin(random_value,False) + if not self.num_spare_cols: + random_value = random.randint(0, (2 ** self.word_size) - 1) + else: + random_value1 = random.randint(0, (2 ** self.word_size) - 1) + random_value2 = random.randint(0, (2 ** self.num_spare_cols) - 1) + random_value = random_value1 + random_value2 + data_bits = self.convert_to_bin(random_value, False) return data_bits def gen_addr(self): """ Generates a random address value to write to. """ - random_value = random.randint(0,(2**self.addr_size)-1) - addr_bits = self.convert_to_bin(random_value,True) + if self.num_spare_rows==0: + random_value = random.randint(0, (2 ** self.addr_size) - 1) + else: + random_value = random.randint(0, ((2 ** (self.addr_size - 1) - 1)) + (self.num_spare_rows * self.words_per_row)) + addr_bits = self.convert_to_bin(random_value, True) return addr_bits def get_data(self): @@ -296,37 +301,36 @@ class functional(simulation): # Used for write masks since they should be writing to previously written addresses addr = random.choice(list(self.stored_words.keys())) word = self.stored_words[addr] - return (addr,word) + return (addr, word) - def convert_to_bin(self,value,is_addr): + def convert_to_bin(self, value, is_addr): """ Converts addr & word to usable binary values. """ - new_value = str.replace(bin(value),"0b","") + new_value = str.replace(bin(value), "0b", "") if(is_addr): expected_value = self.addr_size else: - - expected_value = self.word_size - for i in range (expected_value - len(new_value)): + expected_value = self.word_size + self.num_spare_cols + for i in range(expected_value - len(new_value)): new_value = "0" + new_value - #print("Binary Conversion: {} to {}".format(value, new_value)) - return new_value + # print("Binary Conversion: {} to {}".format(value, new_value)) + return new_value def write_functional_stimulus(self): """ Writes SPICE stimulus. """ temp_stim = "{0}/stim.sp".format(OPTS.openram_temp) - self.sf = open(temp_stim,"w") + self.sf = open(temp_stim, "w") self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period)) - self.stim = stimuli(self.sf,self.corner) + self.stim = stimuli(self.sf, self.corner) - #Write include statements + # Write include statements self.stim.write_include(self.sp_file) - #Write Vdd/Gnd statements + # Write Vdd/Gnd statements self.sf.write("\n* Global Power Supplies\n") self.stim.write_supply() - #Instantiate the SRAM + # Instantiate the SRAM self.sf.write("\n* Instantiation of the SRAM\n") self.stim.inst_model(pins=self.pins, model_name=self.sram.name) @@ -334,7 +338,7 @@ class functional(simulation): # Add load capacitance to each of the read ports self.sf.write("\n* SRAM output loads\n") for port in self.read_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): sig_name="{0}{1}_{2} ".format(self.dout_name, port, bit) self.sf.write("CD{0}{1} {2} 0 {3}f\n".format(port, bit, sig_name, self.load)) @@ -351,10 +355,10 @@ class functional(simulation): for comment in self.fn_cycle_comments: self.sf.write("*{}\n".format(comment)) - # Generate data input bits + # Generate data input bits self.sf.write("\n* Generation of data and address signals\n") for port in self.write_ports: - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): sig_name="{0}{1}_{2} ".format(self.din_name, port, bit) self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[port][bit], self.period, self.slew, 0.05) @@ -367,10 +371,10 @@ class functional(simulation): # Generate control signals self.sf.write("\n * Generation of control signals\n") for port in self.all_ports: - self.stim.gen_pwl("CSB{}".format(port), self.cycle_times , self.csb_values[port], self.period, self.slew, 0.05) + self.stim.gen_pwl("CSB{}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05) for port in self.readwrite_ports: - self.stim.gen_pwl("WEB{}".format(port), self.cycle_times , self.web_values[port], self.period, self.slew, 0.05) + self.stim.gen_pwl("WEB{}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05) # Generate wmask bits for port in self.write_ports: @@ -383,6 +387,15 @@ class functional(simulation): self.stim.gen_pwl(sig_name, self.cycle_times, self.wmask_values[port][bit], self.period, self.slew, 0.05) + # Generate spare enable bits (for spare cols) + for port in self.write_ports: + if self.num_spare_cols: + self.sf.write("\n* Generation of spare enable signals\n") + for bit in range(self.num_spare_cols): + sig_name = "SPARE_WEN{0}_{1} ".format(port, bit) + self.stim.gen_pwl(sig_name, self.cycle_times, self.spare_wen_values[port][bit], self.period, + self.slew, 0.05) + # Generate CLK signals for port in self.all_ports: self.stim.gen_pulse(sig_name="{0}{1}".format("clk", port), @@ -396,11 +409,11 @@ class functional(simulation): # Generate dout value measurements self.sf.write("\n * Generation of dout measurements\n") for (word, dout_port, eo_period, check) in self.read_check: - t_intital = eo_period - 0.01*self.period - t_final = eo_period + 0.01*self.period - for bit in range(self.word_size): - self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port,bit,check), - dout="{0}_{1}".format(dout_port,bit), + t_intital = eo_period - 0.01 * self.period + t_final = eo_period + 0.01 * self.period + for bit in range(self.word_size + self.num_spare_cols): + self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port, bit, check), + dout="{0}_{1}".format(dout_port, bit), t_intital=t_intital, t_final=t_final) @@ -430,7 +443,7 @@ class functional(simulation): # Generate new graph every analysis as edges might change depending on test bit self.graph = graph_util.timing_graph() self.sram_spc_name = "X{}".format(self.sram.name) - self.sram.build_graph(self.graph,self.sram_spc_name,self.pins) + self.sram.build_graph(self.graph, self.sram_spc_name, self.pins) # FIXME: refactor to share with delay.py def set_internal_spice_names(self): @@ -438,17 +451,17 @@ class functional(simulation): # For now, only testing these using first read port. port = self.read_ports[0] - self.graph.get_all_paths('{}{}'.format("clk", port), + self.graph.get_all_paths('{}{}'.format("clk", port), '{}{}_{}'.format(self.dout_name, port, 0).lower()) - self.sen_name = self.get_sen_name(self.graph.all_paths) - debug.info(2,"s_en name = {}".format(self.sen_name)) + self.sen_name = self.get_sen_name(self.graph.all_paths) + debug.info(2, "s_en name = {}".format(self.sen_name)) - self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths, port) - debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name)) + self.bl_name, self.br_name = self.get_bl_name(self.graph.all_paths, port) + debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name)) - self.q_name,self.qbar_name = self.get_bit_name() - debug.info(2,"q name={}\nqbar name={}".format(self.q_name,self.qbar_name)) + self.q_name, self.qbar_name = self.get_bit_name() + debug.info(2, "q name={}\nqbar name={}".format(self.q_name, self.qbar_name)) def get_bit_name(self): """ Get a bit cell name """ @@ -456,10 +469,10 @@ class functional(simulation): storage_names = cell_inst.mod.get_storage_net_names() debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" "supported for characterization. Storage nets={}").format(storage_names)) - q_name = cell_name+'.'+str(storage_names[0]) - qbar_name = cell_name+'.'+str(storage_names[1]) + q_name = cell_name + '.' + str(storage_names[0]) + qbar_name = cell_name + '.' + str(storage_names[1]) - return (q_name,qbar_name) + return (q_name, qbar_name) # FIXME: refactor to share with delay.py def get_sen_name(self, paths): @@ -469,29 +482,28 @@ class functional(simulation): """ sa_mods = factory.get_mods(OPTS.sense_amp) - # Any sense amp instantiated should be identical, any change to that + # Any sense amp instantiated should be identical, any change to that # will require some identification to determine the mod desired. debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.") enable_name = sa_mods[0].get_enable_name() sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0]) - return sen_name + return sen_name # FIXME: refactor to share with delay.py def get_bl_name(self, paths, port): """Gets the signal name associated with the bitlines in the bank.""" - cell_mod = factory.create(module_type=OPTS.bitcell) + cell_mod = factory.create(module_type=OPTS.bitcell) cell_bl = cell_mod.get_bl_name(port) cell_br = cell_mod.get_br_name(port) - bl_found = False # Only a single path should contain a single s_en name. Anything else is an error. bl_names = [] exclude_set = self.get_bl_name_search_exclusions() for int_net in [cell_bl, cell_br]: bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set)) - return bl_names[0], bl_names[1] + return bl_names[0], bl_names[1] def get_bl_name_search_exclusions(self): """Gets the mods as a set which should be excluded while searching for name.""" @@ -500,9 +512,9 @@ class functional(simulation): # so it makes the search awkward return set(factory.get_mods(OPTS.replica_bitline)) - def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None): + def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None): """ - Finds a single alias for the int_net in given paths. + Finds a single alias for the int_net in given paths. More or less hits cause an error """ @@ -510,14 +522,14 @@ class functional(simulation): for path in paths: aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set) if net_found and len(aliases) >= 1: - debug.error('Found multiple paths with {} net.'.format(int_net),1) + debug.error('Found multiple paths with {} net.'.format(int_net), 1) elif len(aliases) > 1: - debug.error('Found multiple {} nets in single path.'.format(int_net),1) + debug.error('Found multiple {} nets in single path.'.format(int_net), 1) elif not net_found and len(aliases) == 1: path_net_name = aliases[0] net_found = True if not net_found: - debug.error("Could not find {} net in timing paths.".format(int_net),1) + debug.error("Could not find {} net in timing paths.".format(int_net), 1) - return path_net_name + return path_net_name diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 07a753a7..525f2180 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -45,7 +45,7 @@ class lib: """ Determine the load/slews if they aren't specified in the config file. """ # These are the parameters to determine the table sizes #self.load_scales = np.array([0.1, 0.25, 0.5, 1, 2, 4, 8]) - self.load_scales = np.array([0.25, 1, 8]) + self.load_scales = np.array([0.25, 1, 4]) #self.load_scales = np.array([0.25, 1]) self.load = tech.spice["dff_in_cap"] self.loads = self.load_scales*self.load @@ -66,25 +66,57 @@ class lib: self.supply_voltages = OPTS.supply_voltages self.process_corners = OPTS.process_corners - # Enumerate all possible corners + # Corner values + min_temperature = min(self.temperatures) + nom_temperature = tech.spice["nom_temperature"] + max_temperature = max(self.temperatures) + min_supply = min(self.supply_voltages) + nom_supply = tech.spice["nom_supply_voltage"] + max_supply = max(self.supply_voltages) + min_process = "FF" + nom_process = "TT" + max_process = "SS" + self.corners = [] self.lib_files = [] - for proc in self.process_corners: - for temp in self.temperatures: - for volt in self.supply_voltages: - self.corner_name = "{0}_{1}_{2}V_{3}C".format(self.sram.name, - proc, - volt, - temp) - self.corner_name = self.corner_name.replace(".","p") # Remove decimals - lib_name = self.out_dir+"{}.lib".format(self.corner_name) + + # Nominal corner + corner_set = set() + nom_corner = (nom_process, nom_supply, nom_temperature) + corner_set.add(nom_corner) + if not OPTS.nominal_corner_only: + # Temperature corners + corner_set.add((nom_process, nom_supply, min_temperature)) + corner_set.add((nom_process, nom_supply, max_temperature)) + # Supply corners + corner_set.add((nom_process, min_supply, nom_temperature)) + corner_set.add((nom_process, max_supply, nom_temperature)) + # Process corners + corner_set.add((min_process, nom_supply, nom_temperature)) + corner_set.add((max_process, nom_supply, nom_temperature)) + + # Enforce that nominal corner is the first to be characterized + self.add_corner(*nom_corner) + corner_set.remove(nom_corner) + for corner_tuple in corner_set: + self.add_corner(*corner_tuple) + + def add_corner(self, proc, volt, temp): + self.corner_name = "{0}_{1}_{2}V_{3}C".format(self.sram.name, + proc, + volt, + temp) + self.corner_name = self.corner_name.replace(".","p") # Remove decimals + lib_name = self.out_dir+"{}.lib".format(self.corner_name) + + # A corner is a tuple of PVT + self.corners.append((proc, volt, temp)) + self.lib_files.append(lib_name) - # A corner is a tuple of PVT - self.corners.append((proc, volt, temp)) - self.lib_files.append(lib_name) def characterize_corners(self): """ Characterize the list of corners. """ + debug.info(1,"Characterizing corners: " + str(self.corners)) for (self.corner,lib_name) in zip(self.corners,self.lib_files): debug.info(1,"Corner: " + str(self.corner)) (self.process, self.voltage, self.temperature) = self.corner @@ -149,17 +181,20 @@ class lib: self.lib.write(" dont_touch : true;\n") self.lib.write(" area : {};\n\n".format(self.sram.width * self.sram.height)) - #Build string of all control signals. + self.write_pg_pin() + + #Build string of all control signals. control_str = 'csb0' #assume at least 1 port for i in range(1, self.total_port_num): control_str += ' & csb{0}'.format(i) # Leakage is included in dynamic when macro is enabled self.lib.write(" leakage_power () {\n") - self.lib.write(" when : \"{0}\";\n".format(control_str)) + # 'when' condition unnecessary when cs pin does not turn power to devices + # self.lib.write(" when : \"{0}\";\n".format(control_str)) self.lib.write(" value : {};\n".format(self.char_sram_results["leakage_power"])) self.lib.write(" }\n") - self.lib.write(" cell_leakage_power : {};\n".format(0)) + self.lib.write(" cell_leakage_power : {};\n".format(self.char_sram_results["leakage_power"])) def write_units(self): @@ -208,6 +243,9 @@ class lib: self.lib.write(" default_max_fanout : 4.0 ;\n") self.lib.write(" default_connection_class : universal ;\n\n") + self.lib.write(" voltage_map ( VDD, {} );\n".format(tech.spice["nom_supply_voltage"])) + self.lib.write(" voltage_map ( GND, 0 );\n\n") + def create_list(self,values): """ Helper function to create quoted, line wrapped list """ list_values = ", ".join(str(v) for v in values) @@ -484,42 +522,69 @@ class lib: if port in self.write_ports: if port in self.read_ports: web_name = " & !web{0}".format(port) - avg_write_power = np.mean(self.char_port_results[port]["write1_power"] + self.char_port_results[port]["write0_power"]) + write1_power = np.mean(self.char_port_results[port]["write1_power"]) + write0_power = np.mean(self.char_port_results[port]["write0_power"]) self.lib.write(" internal_power(){\n") - self.lib.write(" when : \"!csb{0} & clk{0}{1}\"; \n".format(port, web_name)) + self.lib.write(" when : \"!csb{0}{1}\"; \n".format(port, web_name)) self.lib.write(" rise_power(scalar){\n") - self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0)) + self.lib.write(" values(\"{0:.6e}\");\n".format(write1_power)) self.lib.write(" }\n") self.lib.write(" fall_power(scalar){\n") - self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0)) + self.lib.write(" values(\"{0:.6e}\");\n".format(write0_power)) + self.lib.write(" }\n") + self.lib.write(" }\n") + + # Disabled power. + disabled_write1_power = np.mean(self.char_port_results[port]["disabled_write1_power"]) + disabled_write0_power = np.mean(self.char_port_results[port]["disabled_write0_power"]) + self.lib.write(" internal_power(){\n") + self.lib.write(" when : \"csb{0}{1}\"; \n".format(port, web_name)) + self.lib.write(" rise_power(scalar){\n") + self.lib.write(" values(\"{0:.6e}\");\n".format(disabled_write1_power)) + self.lib.write(" }\n") + self.lib.write(" fall_power(scalar){\n") + self.lib.write(" values(\"{0:.6e}\");\n".format(disabled_write0_power)) self.lib.write(" }\n") self.lib.write(" }\n") if port in self.read_ports: if port in self.write_ports: web_name = " & web{0}".format(port) - avg_read_power = np.mean(self.char_port_results[port]["read1_power"] + self.char_port_results[port]["read0_power"]) + read1_power = np.mean(self.char_port_results[port]["read1_power"]) + read0_power = np.mean(self.char_port_results[port]["read0_power"]) self.lib.write(" internal_power(){\n") - self.lib.write(" when : \"!csb{0} & !clk{0}{1}\"; \n".format(port, web_name)) + self.lib.write(" when : \"!csb{0}{1}\"; \n".format(port, web_name)) self.lib.write(" rise_power(scalar){\n") - self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0)) + self.lib.write(" values(\"{0:.6e}\");\n".format(read1_power)) self.lib.write(" }\n") self.lib.write(" fall_power(scalar){\n") - self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0)) + self.lib.write(" values(\"{0:.6e}\");\n".format(read0_power)) self.lib.write(" }\n") self.lib.write(" }\n") - # Have 0 internal power when disabled, this will be represented as leakage power. - self.lib.write(" internal_power(){\n") - self.lib.write(" when : \"csb{0}\"; \n".format(port)) - self.lib.write(" rise_power(scalar){\n") - self.lib.write(" values(\"0\");\n") - self.lib.write(" }\n") - self.lib.write(" fall_power(scalar){\n") - self.lib.write(" values(\"0\");\n") - self.lib.write(" }\n") - self.lib.write(" }\n") - + # Disabled power. + disabled_read1_power = np.mean(self.char_port_results[port]["disabled_read1_power"]) + disabled_read0_power = np.mean(self.char_port_results[port]["disabled_read0_power"]) + self.lib.write(" internal_power(){\n") + self.lib.write(" when : \"csb{0}{1}\"; \n".format(port, web_name)) + self.lib.write(" rise_power(scalar){\n") + self.lib.write(" values(\"{0:.6e}\");\n".format(disabled_read1_power)) + self.lib.write(" }\n") + self.lib.write(" fall_power(scalar){\n") + self.lib.write(" values(\"{0:.6e}\");\n".format(disabled_read0_power)) + self.lib.write(" }\n") + self.lib.write(" }\n") + + def write_pg_pin(self): + self.lib.write(" pg_pin(vdd) {\n") + self.lib.write(" voltage_name : VDD;\n") + self.lib.write(" pg_type : primary_power;\n") + self.lib.write(" }\n\n") + self.lib.write(" pg_pin(gnd) {\n") + self.lib.write(" voltage_name : GND;\n") + self.lib.write(" pg_type : primary_ground;\n") + self.lib.write(" }\n\n") + def compute_delay(self): """Compute SRAM delays for current corner""" self.d = delay(self.sram, self.sp_file, self.corner) @@ -527,7 +592,10 @@ class lib: char_results = self.d.analytical_delay(self.slews,self.loads) self.char_sram_results, self.char_port_results = char_results else: - probe_address = "1" * self.sram.addr_size + if (self.sram.num_spare_rows == 0): + probe_address = "1" * self.sram.addr_size + else: + probe_address = "0" + "1" * (self.sram.addr_size - 1) probe_data = self.sram.word_size - 1 char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads) self.char_sram_results, self.char_port_results = char_results @@ -589,17 +657,12 @@ class lib: )) # information of checks - from hierarchy_design import total_drc_errors - from hierarchy_design import total_lvs_errors - DRC = 'skipped' - LVS = 'skipped' - if OPTS.check_lvsdrc: - DRC = str(total_drc_errors) - LVS = str(total_lvs_errors) - - datasheet.write("{0},{1},".format(DRC, LVS)) + # run it only the first time + datasheet.write("{0},{1},".format(self.sram.drc_errors, self.sram.lvs_errors)) + # write area - datasheet.write(str(self.sram.width * self.sram.height)+',') + datasheet.write(str(self.sram.width * self.sram.height) + ',') + # write timing information for all ports for port in self.all_ports: #din timings diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index adbe5f5f..064eb2c5 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -25,18 +25,23 @@ class simulation(): self.word_size = self.sram.word_size self.addr_size = self.sram.addr_size self.write_size = self.sram.write_size + self.num_spare_rows = self.sram.num_spare_rows + if not self.sram.num_spare_cols: + self.num_spare_cols = 0 + else: + self.num_spare_cols = self.sram.num_spare_cols self.sp_file = spfile self.all_ports = self.sram.all_ports self.readwrite_ports = self.sram.readwrite_ports self.read_ports = self.sram.read_ports self.write_ports = self.sram.write_ports + self.words_per_row = self.sram.words_per_row if self.write_size: self.num_wmasks = int(self.word_size/self.write_size) else: self.num_wmasks = 0 - def set_corner(self,corner): """ Set the corner values """ self.corner = corner @@ -59,10 +64,10 @@ class simulation(): self.pins = self.gen_pin_names(port_signal_names=(self.addr_name,self.din_name,self.dout_name), port_info=(len(self.all_ports),self.write_ports,self.read_ports), abits=self.addr_size, - dbits=self.word_size) + dbits=self.word_size + self.num_spare_cols) debug.check(len(self.sram.pins) == len(self.pins), "Number of pins generated for characterization \ - do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins, + do not match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins, self.pins)) #This is TODO once multiport control has been finalized. #self.control_name = "CSB" @@ -80,11 +85,13 @@ class simulation(): self.addr_value = {port:[] for port in self.all_ports} self.data_value = {port:[] for port in self.write_ports} self.wmask_value = {port:[] for port in self.write_ports} + self.spare_wen_value = {port:[] for port in self.write_ports} # Three dimensional list to handle each addr and data bits for each port over the number of checks self.addr_values = {port:[[] for bit in range(self.addr_size)] for port in self.all_ports} - self.data_values = {port:[[] for bit in range(self.word_size)] for port in self.write_ports} + self.data_values = {port:[[] for bit in range(self.word_size + self.num_spare_cols)] for port in self.write_ports} self.wmask_values = {port:[[] for bit in range(self.num_wmasks)] for port in self.write_ports} + self.spare_wen_values = {port:[[] for bit in range(self.num_spare_cols)] for port in self.write_ports} # For generating comments in SPICE stimulus self.cycle_comments = [] @@ -111,10 +118,10 @@ class simulation(): def add_data(self, data, port): """ Add the array of data values """ - debug.check(len(data)==self.word_size, "Invalid data word size.") + debug.check(len(data)==(self.word_size + self.num_spare_cols), "Invalid data word size.") self.data_value[port].append(data) - bit = self.word_size - 1 + bit = self.word_size + self.num_spare_cols - 1 for c in data: if c=="0": self.data_values[port][bit].append(0) @@ -124,7 +131,6 @@ class simulation(): debug.error("Non-binary data string",1) bit -= 1 - def add_address(self, address, port): """ Add the array of address values """ debug.check(len(address)==self.addr_size, "Invalid address size.") @@ -135,7 +141,7 @@ class simulation(): if c=="0": self.addr_values[port][bit].append(0) elif c=="1": - self.addr_values[port][bit].append(1) + self.addr_values[port][bit].append(1) else: debug.error("Non-binary address string",1) bit -= 1 @@ -156,7 +162,21 @@ class simulation(): debug.error("Non-binary wmask string", 1) bit -= 1 - + def add_spare_wen(self, spare_wen, port): + """ Add the array of spare write enable values (for spare cols) """ + debug.check(len(spare_wen) == self.num_spare_cols, "Invalid spare enable size.") + + self.spare_wen_value[port].append(spare_wen) + bit = self.num_spare_cols - 1 + for c in spare_wen: + if c == "0": + self.spare_wen_values[port][bit].append(0) + elif c == "1": + self.spare_wen_values[port][bit].append(1) + else: + debug.error("Non-binary spare enable signal string", 1) + bit -= 1 + def add_write(self, comment, address, data, wmask, port): """ Add the control values for a write cycle. """ debug.check(port in self.write_ports, @@ -172,7 +192,8 @@ class simulation(): self.add_control_one_port(port, "write") self.add_data(data,port) self.add_address(address,port) - self.add_wmask(wmask,port) + self.add_wmask(wmask,port) + self.add_spare_wen("1" * self.num_spare_cols, port) #Add noops to all other ports. for unselected_port in self.all_ports: @@ -191,19 +212,20 @@ class simulation(): self.cycle_times.append(self.t_current) self.t_current += self.period self.add_control_one_port(port, "read") - self.add_address(address, port) - + self.add_address(address, port) + # If the port is also a readwrite then add # the same value as previous cycle if port in self.write_ports: try: self.add_data(self.data_value[port][-1], port) except: - self.add_data("0"*self.word_size, port) + self.add_data("0"*(self.word_size + self.num_spare_cols), port) try: self.add_wmask(self.wmask_value[port][-1], port) except: self.add_wmask("0"*self.num_wmasks, port) + self.add_spare_wen("0" * self.num_spare_cols, port) #Add noops to all other ports. for unselected_port in self.all_ports: @@ -234,6 +256,7 @@ class simulation(): self.add_data(data, port) self.add_address(address, port) self.add_wmask(wmask, port) + self.add_spare_wen("1" * self.num_spare_cols, port) def add_read_one_port(self, comment, address, port): """ Add the control values for a read cycle. Does not increment the period. """ @@ -245,23 +268,24 @@ class simulation(): self.add_control_one_port(port, "read") self.add_address(address, port) + # If the port is also a readwrite then add # the same value as previous cycle if port in self.write_ports: try: self.add_data(self.data_value[port][-1], port) except: - self.add_data("0"*self.word_size, port) + self.add_data("0"*(self.word_size + self.num_spare_cols), port) try: self.add_wmask(self.wmask_value[port][-1], port) except: - self.add_wmask("0"*self.num_wmasks, port) - + self.add_wmask("0"*self.num_wmasks, port) + self.add_spare_wen("0" * self.num_spare_cols, port) def add_noop_one_port(self, port): """ Add the control values for a noop to a single port. Does not increment the period. """ self.add_control_one_port(port, "noop") - + try: self.add_address(self.addr_value[port][-1], port) except: @@ -273,11 +297,29 @@ class simulation(): try: self.add_data(self.data_value[port][-1], port) except: - self.add_data("0"*self.word_size, port) + self.add_data("0"*(self.word_size + self.num_spare_cols), port) try: self.add_wmask(self.wmask_value[port][-1], port) except: self.add_wmask("0"*self.num_wmasks, port) + self.add_spare_wen("0" * self.num_spare_cols, port) + + def add_noop_clock_one_port(self, port): + """ Add the control values for a noop to a single port. Increments the period. """ + debug.info(2, 'Clock only on port {}'.format(port)) + self.fn_cycle_comments.append('Clock only on port {}'.format(port)) + self.append_cycle_comment(port, 'Clock only on port {}'.format(port)) + + self.cycle_times.append(self.t_current) + self.t_current += self.period + + self.add_noop_one_port(port) + + #Add noops to all other ports. + for unselected_port in self.all_ports: + if unselected_port != port: + self.add_noop_one_port(unselected_port) + def append_cycle_comment(self, port, comment): """Add comment to list to be printed in stimulus file""" @@ -352,6 +394,11 @@ class simulation(): for port in write_index: for bit in range(self.num_wmasks): pin_names.append("WMASK{0}_{1}".format(port,bit)) + + if self.num_spare_cols: + for port in write_index: + for bit in range(self.num_spare_cols): + pin_names.append("SPARE_WEN{0}_{1}".format(port,bit)) for read_output in read_index: for i in range(dbits): diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index fb2e261d..90fd6213 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -34,6 +34,10 @@ class stimuli(): self.sf = stim_file (self.process, self.voltage, self.temperature) = corner + try: + self.device_libraries = tech.spice["fet_libraries"][self.process] + except: + debug.info(2, "Not using spice library") self.device_models = tech.spice["fet_models"][self.process] self.sram_name = "Xsram" @@ -270,8 +274,17 @@ class stimuli(): def write_include(self, circuit): """Writes include statements, inputs are lists of model files""" + includes = self.device_models + [circuit] self.sf.write("* {} process corner\n".format(self.process)) + if OPTS.tech_name == "sky130": + libraries = self.device_libraries + for item in list(libraries): + if os.path.isfile(item[0]): + self.sf.write(".lib \"{0}\" {1}\n".format(item[0], item[1])) + else: + debug.error("Could not find spice library: {0}\nSet SPICE_MODEL_DIR to over-ride path.\n".format(item[0])) + for item in list(includes): if os.path.isfile(item): self.sf.write(".include \"{0}\"\n".format(item)) @@ -312,7 +325,9 @@ class stimuli(): OPTS.openram_temp) valid_retcode=0 else: - # ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit + # ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit + # Measurements can't be made with a raw file set in ngspice + # -r {2}timing.raw cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe, temp_stim, OPTS.openram_temp) diff --git a/compiler/characterizer/trim_spice.py b/compiler/characterizer/trim_spice.py index ffc45a9c..d20dfe42 100644 --- a/compiler/characterizer/trim_spice.py +++ b/compiler/characterizer/trim_spice.py @@ -6,7 +6,7 @@ # All rights reserved. # import debug -from math import log +from math import log,ceil import re class trim_spice(): @@ -42,7 +42,7 @@ class trim_spice(): self.word_size = word_size self.words_per_row = self.num_columns / self.word_size - self.row_addr_size = int(log(self.num_rows, 2)) + self.row_addr_size = ceil(log(self.num_rows, 2)) self.col_addr_size = int(log(self.words_per_row, 2)) self.bank_addr_size = self.col_addr_size + self.row_addr_size self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2)) diff --git a/compiler/custom/and2_dec.py b/compiler/custom/and2_dec.py new file mode 100644 index 00000000..e6f314c4 --- /dev/null +++ b/compiler/custom/and2_dec.py @@ -0,0 +1,147 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +from vector import vector +import design +from sram_factory import factory +from globals import OPTS +from tech import layer + + +class and2_dec(design.design): + """ + This is an AND with configurable drive strength. + """ + def __init__(self, name, size=1, height=None, add_wells=True): + + design.design.__init__(self, name) + + debug.info(1, "Creating and2_dec {}".format(name)) + self.add_comment("size: {}".format(size)) + self.size = size + self.height = height + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.add_pins() + self.create_modules() + self.create_insts() + + def create_modules(self): + self.nand = factory.create(module_type="nand2_dec", + height=self.height) + + self.inv = factory.create(module_type="inv_dec", + height=self.height, + size=self.size) + + self.add_mod(self.nand) + self.add_mod(self.inv) + + def create_layout(self): + + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + self.width = self.nand.width + self.inv.width + self.height = self.nand.height + + self.place_insts() + self.add_wires() + self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + self.add_pin("A", "INPUT") + self.add_pin("B", "INPUT") + self.add_pin("Z", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def create_insts(self): + self.nand_inst = self.add_inst(name="pand2_dec_nand", + mod=self.nand) + self.connect_inst(["A", "B", "zb_int", "vdd", "gnd"]) + + self.inv_inst = self.add_inst(name="pand2_dec_inv", + mod=self.inv) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + + def place_insts(self): + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) + + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + if OPTS.tech_name == "sky130": + for name in ["vdd", "gnd"]: + for inst in [self.nand_inst, self.inv_inst]: + self.copy_layout_pin(inst, name) + else: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + + def add_wires(self): + # nand Z to inv A + z1_pin = self.nand_inst.get_pin("Z") + a2_pin = self.inv_inst.get_pin("A") + if OPTS.tech_name == "sky130": + mid1_point = vector(a2_pin.cx(), z1_pin.cy()) + else: + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(self.route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + + def add_layout_pins(self): + pin = self.inv_inst.get_pin("Z") + self.add_layout_pin_rect_center(text="Z", + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + for pin_name in ["A", "B"]: + pin = self.nand_inst.get_pin(pin_name) + self.add_layout_pin_rect_center(text=pin_name, + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + def get_stage_efforts(self, external_cout, inp_is_rise=False): + """Get the stage efforts of the A or B -> Z path""" + stage_effort_list = [] + stage1_cout = self.inv.get_cin() + stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) + stage_effort_list.append(stage1) + last_stage_is_rise = stage1.is_rise + + stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise) + stage_effort_list.append(stage2) + + return stage_effort_list + + def get_cin(self): + """Return the relative input capacitance of a single input""" + return self.nand.get_cin() + diff --git a/compiler/custom/and3_dec.py b/compiler/custom/and3_dec.py new file mode 100644 index 00000000..207d545b --- /dev/null +++ b/compiler/custom/and3_dec.py @@ -0,0 +1,156 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +from vector import vector +import design +from sram_factory import factory +from globals import OPTS +from tech import layer + + +class and3_dec(design.design): + """ + This is an AND with configurable drive strength. + """ + def __init__(self, name, size=1, height=None, add_wells=True): + design.design.__init__(self, name) + debug.info(1, "Creating and3_dec {}".format(name)) + self.add_comment("size: {}".format(size)) + self.size = size + self.height = height + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.add_pins() + self.create_modules() + self.create_insts() + + def create_modules(self): + self.nand = factory.create(module_type="nand3_dec", + height=self.height) + + self.inv = factory.create(module_type="inv_dec", + height=self.height, + size=self.size) + + self.add_mod(self.nand) + self.add_mod(self.inv) + + def create_layout(self): + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + + self.width = self.nand.width + self.inv.width + self.height = self.nand.height + + self.place_insts() + self.add_wires() + self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + self.add_pin("A", "INPUT") + self.add_pin("B", "INPUT") + self.add_pin("C", "INPUT") + self.add_pin("Z", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def create_insts(self): + self.nand_inst = self.add_inst(name="pand3_dec_nand", + mod=self.nand) + self.connect_inst(["A", "B", "C", "zb_int", "vdd", "gnd"]) + + self.inv_inst = self.add_inst(name="pand3_dec_inv", + mod=self.inv) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + + def place_insts(self): + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) + + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + if OPTS.tech_name == "sky130": + for name in ["vdd", "gnd"]: + for inst in [self.nand_inst, self.inv_inst]: + self.copy_layout_pin(inst, name) + else: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + + def add_wires(self): + # nand Z to inv A + z1_pin = self.nand_inst.get_pin("Z") + a2_pin = self.inv_inst.get_pin("A") + if OPTS.tech_name == "sky130": + mid1_point = vector(a2_pin.cx(), z1_pin.cy()) + else: + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(self.route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + + def add_layout_pins(self): + pin = self.inv_inst.get_pin("Z") + self.add_layout_pin_rect_center(text="Z", + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + for pin_name in ["A", "B", "C"]: + pin = self.nand_inst.get_pin(pin_name) + self.add_layout_pin_rect_center(text=pin_name, + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + def analytical_delay(self, corner, slew, load=0.0): + """ Calculate the analytical delay of DFF-> INV -> INV """ + nand_delay = self.nand.analytical_delay(corner, + slew=slew, + load=self.inv.input_load()) + inv_delay = self.inv.analytical_delay(corner, + slew=nand_delay.slew, + load=load) + return nand_delay + inv_delay + + def get_stage_efforts(self, external_cout, inp_is_rise=False): + """Get the stage efforts of the A or B -> Z path""" + stage_effort_list = [] + stage1_cout = self.inv.get_cin() + stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) + stage_effort_list.append(stage1) + last_stage_is_rise = stage1.is_rise + + stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise) + stage_effort_list.append(stage2) + + return stage_effort_list + + def get_cin(self): + """Return the relative input capacitance of a single input""" + return self.nand.get_cin() + diff --git a/compiler/custom/and4_dec.py b/compiler/custom/and4_dec.py new file mode 100644 index 00000000..9c68f78b --- /dev/null +++ b/compiler/custom/and4_dec.py @@ -0,0 +1,159 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +from vector import vector +import design +from sram_factory import factory +from globals import OPTS +from tech import layer + + +class and4_dec(design.design): + """ + This is an AND with configurable drive strength. + """ + def __init__(self, name, size=1, height=None, add_wells=True): + + design.design.__init__(self, name) + + debug.info(1, "Creating and4_dec {}".format(name)) + self.add_comment("size: {}".format(size)) + self.size = size + self.height = height + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.add_pins() + self.create_modules() + self.create_insts() + + def create_modules(self): + self.nand = factory.create(module_type="nand4_dec", + height=self.height) + + self.inv = factory.create(module_type="inv_dec", + height=self.height, + size=self.size) + + self.add_mod(self.nand) + self.add_mod(self.inv) + + def create_layout(self): + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + + self.width = self.nand.width + self.inv.width + self.height = self.nand.height + + self.place_insts() + self.add_wires() + self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + self.add_pin("A", "INPUT") + self.add_pin("B", "INPUT") + self.add_pin("C", "INPUT") + self.add_pin("D", "INPUT") + self.add_pin("Z", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def create_insts(self): + self.nand_inst = self.add_inst(name="pand4_dec_nand", + mod=self.nand) + self.connect_inst(["A", "B", "C", "D", "zb_int", "vdd", "gnd"]) + + self.inv_inst = self.add_inst(name="pand4_dec_inv", + mod=self.inv) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + + def place_insts(self): + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) + + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + if OPTS.tech_name == "sky130": + for name in ["vdd", "gnd"]: + for inst in [self.nand_inst, self.inv_inst]: + self.copy_layout_pin(inst, name) + else: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + + def add_wires(self): + # nand Z to inv A + z1_pin = self.nand_inst.get_pin("Z") + a2_pin = self.inv_inst.get_pin("A") + if OPTS.tech_name == "sky130": + mid1_point = vector(a2_pin.cx(), z1_pin.cy()) + else: + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(self.route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + + def add_layout_pins(self): + pin = self.inv_inst.get_pin("Z") + self.add_layout_pin_rect_center(text="Z", + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + for pin_name in ["A", "B", "C"]: + pin = self.nand_inst.get_pin(pin_name) + self.add_layout_pin_rect_center(text=pin_name, + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + def analytical_delay(self, corner, slew, load=0.0): + """ Calculate the analytical delay of DFF-> INV -> INV """ + nand_delay = self.nand.analytical_delay(corner, + slew=slew, + load=self.inv.input_load()) + inv_delay = self.inv.analytical_delay(corner, + slew=nand_delay.slew, + load=load) + return nand_delay + inv_delay + + def get_stage_efforts(self, external_cout, inp_is_rise=False): + """Get the stage efforts of the A or B -> Z path""" + stage_effort_list = [] + stage1_cout = self.inv.get_cin() + stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) + stage_effort_list.append(stage1) + last_stage_is_rise = stage1.is_rise + + stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise) + stage_effort_list.append(stage2) + + return stage_effort_list + + def get_cin(self): + """Return the relative input capacitance of a single input""" + return self.nand.get_cin() + diff --git a/compiler/modules/dff.py b/compiler/custom/dff.py similarity index 72% rename from compiler/modules/dff.py rename to compiler/custom/dff.py index 3cb1bcf1..c8fdb4b0 100644 --- a/compiler/modules/dff.py +++ b/compiler/custom/dff.py @@ -5,21 +5,28 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import globals import design -from math import log -import design -from tech import GDS,layer,spice,parameter +from tech import GDS, layer, spice, parameter +from tech import cell_properties as props import utils + class dff(design.design): """ Memory address flip-flop """ + if not props.dff.use_custom_ports: + pin_names = ["D", "Q", "clk", "vdd", "gnd"] + type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] + clk_pin = "clk" + else: + pin_names = props.dff.custom_port_list + type_list = props.dff.custom_type_list + clk_pin = props.dff.clk_pin - pin_names = ["D", "Q", "clk", "vdd", "gnd"] - type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] - (width,height) = utils.get_libcell_size("dff", GDS["unit"], layer["boundary"]) + (width, height) = utils.get_libcell_size("dff", + GDS["unit"], + layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"]) def __init__(self, name="dff"): @@ -54,7 +61,7 @@ class dff(design.design): #Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width. return parameter["dff_clk_cin"] - def build_graph(self, graph, inst_name, port_nets): + def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) + self.add_graph_edges(graph, port_nets) diff --git a/compiler/custom/inv_dec.py b/compiler/custom/inv_dec.py new file mode 100644 index 00000000..80fdb74e --- /dev/null +++ b/compiler/custom/inv_dec.py @@ -0,0 +1,80 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import design +from tech import GDS, layer, spice, parameter +import logical_effort +import utils +import debug + + +class inv_dec(design.design): + """ + INV for address decoders. + """ + + pin_names = ["A", "Z", "vdd", "gnd"] + type_list = ["INPUT", "OUTPUT", "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("inv_dec", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "inv_dec", GDS["unit"]) + + def __init__(self, name="inv_dec", height=None): + design.design.__init__(self, name) + + self.width = inv_dec.width + self.height = inv_dec.height + self.pin_map = inv_dec.pin_map + self.add_pin_types(self.type_list) + + def analytical_power(self, corner, load): + """Returns dynamic and leakage power. Results in nW""" + c_eff = self.calculate_effective_capacitance(load) + freq = spice["default_event_frequency"] + power_dyn = self.calc_dynamic_power(corner, c_eff, freq) + power_leak = spice["inv_leakage"] + + total_power = self.return_power(power_dyn, power_leak) + return total_power + + def calculate_effective_capacitance(self, load): + """Computes effective capacitance. Results in fF""" + c_load = load + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) + + return transition_prob * (c_load + c_para) + + def input_load(self): + """ + Return the capacitance of the gate connection in generic capacitive + units relative to the minimum width of a transistor + """ + return self.nmos_size + self.pmos_size + + def get_stage_effort(self, cout, inp_is_rise=True): + """ + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 1 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) + + def build_graph(self, graph, inst_name, port_nets): + """ + Adds edges based on inputs/outputs. + Overrides base class function. + """ + self.add_graph_edges(graph, port_nets) diff --git a/compiler/custom/nand2_dec.py b/compiler/custom/nand2_dec.py new file mode 100644 index 00000000..c806bf5a --- /dev/null +++ b/compiler/custom/nand2_dec.py @@ -0,0 +1,85 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import design +from tech import GDS, layer, spice, parameter, drc +import logical_effort +import utils + + +class nand2_dec(design.design): + """ + 2-input NAND decoder for address decoders. + """ + + pin_names = ["A", "B", "Z", "vdd", "gnd"] + type_list = ["INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("nand2_dec", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "nand2_dec", GDS["unit"]) + + def __init__(self, name="nand2_dec", height=None): + design.design.__init__(self, name) + + self.width = nand2_dec.width + self.height = nand2_dec.height + self.pin_map = nand2_dec.pin_map + self.add_pin_types(self.type_list) + + # FIXME: For now... + size = 1 + self.size = size + self.nmos_size = 2 * size + self.pmos_size = parameter["beta"] * size + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + + def analytical_power(self, corner, load): + """Returns dynamic and leakage power. Results in nW""" + c_eff = self.calculate_effective_capacitance(load) + freq = spice["default_event_frequency"] + power_dyn = self.calc_dynamic_power(corner, c_eff, freq) + power_leak = spice["nand2_leakage"] + + total_power = self.return_power(power_dyn, power_leak) + return total_power + + def calculate_effective_capacitance(self, load): + """Computes effective capacitance. Results in fF""" + c_load = load + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) + transition_prob = 0.1875 + return transition_prob * (c_load + c_para) + + def input_load(self): + """Return the relative input capacitance of a single input""" + return self.nmos_size + self.pmos_size + + def get_stage_effort(self, cout, inp_is_rise=True): + """ + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 2 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) + + def build_graph(self, graph, inst_name, port_nets): + """ + Adds edges based on inputs/outputs. + Overrides base class function. + """ + self.add_graph_edges(graph, port_nets) + diff --git a/compiler/custom/nand3_dec.py b/compiler/custom/nand3_dec.py new file mode 100644 index 00000000..5eea68de --- /dev/null +++ b/compiler/custom/nand3_dec.py @@ -0,0 +1,85 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import design +from tech import GDS, layer, spice, parameter, drc +import logical_effort +import utils + + +class nand3_dec(design.design): + """ + 3-input NAND decoder for address decoders. + """ + + pin_names = ["A", "B", "C", "Z", "vdd", "gnd"] + type_list = ["INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("nand3_dec", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "nand3_dec", GDS["unit"]) + + def __init__(self, name="nand3_dec", height=None): + design.design.__init__(self, name) + + self.width = nand3_dec.width + self.height = nand3_dec.height + self.pin_map = nand3_dec.pin_map + self.add_pin_types(self.type_list) + + # FIXME: For now... + size = 1 + self.size = size + self.nmos_size = 2 * size + self.pmos_size = parameter["beta"] * size + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + + def analytical_power(self, corner, load): + """Returns dynamic and leakage power. Results in nW""" + c_eff = self.calculate_effective_capacitance(load) + freq = spice["default_event_frequency"] + power_dyn = self.calc_dynamic_power(corner, c_eff, freq) + power_leak = spice["nand3_leakage"] + + total_power = self.return_power(power_dyn, power_leak) + return total_power + + def calculate_effective_capacitance(self, load): + """Computes effective capacitance. Results in fF""" + c_load = load + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) + transition_prob = 0.1875 + return transition_prob * (c_load + c_para) + + def input_load(self): + """Return the relative input capacitance of a single input""" + return self.nmos_size + self.pmos_size + + def get_stage_effort(self, cout, inp_is_rise=True): + """ + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 2 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) + + def build_graph(self, graph, inst_name, port_nets): + """ + Adds edges based on inputs/outputs. + Overrides base class function. + """ + self.add_graph_edges(graph, port_nets) + diff --git a/compiler/custom/nand4_dec.py b/compiler/custom/nand4_dec.py new file mode 100644 index 00000000..df3eee14 --- /dev/null +++ b/compiler/custom/nand4_dec.py @@ -0,0 +1,85 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import design +from tech import GDS, layer, spice, parameter, drc +import logical_effort +import utils + + +class nand4_dec(design.design): + """ + 2-input NAND decoder for address decoders. + """ + + pin_names = ["A", "B", "C", "D", "Z", "vdd", "gnd"] + type_list = ["INPUT", "INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("nand4_dec", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "nand4_dec", GDS["unit"]) + + def __init__(self, name="nand4_dec", height=None): + design.design.__init__(self, name) + + self.width = nand4_dec.width + self.height = nand4_dec.height + self.pin_map = nand4_dec.pin_map + self.add_pin_types(self.type_list) + + # FIXME: For now... + size = 1 + self.size = size + self.nmos_size = 2 * size + self.pmos_size = parameter["beta"] * size + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + + def analytical_power(self, corner, load): + """Returns dynamic and leakage power. Results in nW""" + c_eff = self.calculate_effective_capacitance(load) + freq = spice["default_event_frequency"] + power_dyn = self.calc_dynamic_power(corner, c_eff, freq) + power_leak = spice["nand4_leakage"] + + total_power = self.return_power(power_dyn, power_leak) + return total_power + + def calculate_effective_capacitance(self, load): + """Computes effective capacitance. Results in fF""" + c_load = load + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) + transition_prob = 0.1875 + return transition_prob * (c_load + c_para) + + def input_load(self): + """Return the relative input capacitance of a single input""" + return self.nmos_size + self.pmos_size + + def get_stage_effort(self, cout, inp_is_rise=True): + """ + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 2 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) + + def build_graph(self, graph, inst_name, port_nets): + """ + Adds edges based on inputs/outputs. + Overrides base class function. + """ + self.add_graph_edges(graph, port_nets) + diff --git a/compiler/modules/tri_gate.py b/compiler/custom/tri_gate.py similarity index 100% rename from compiler/modules/tri_gate.py rename to compiler/custom/tri_gate.py diff --git a/compiler/modules/write_driver.py b/compiler/custom/write_driver.py similarity index 58% rename from compiler/modules/write_driver.py rename to compiler/custom/write_driver.py index 85a58fd5..9afac81b 100644 --- a/compiler/modules/write_driver.py +++ b/compiler/custom/write_driver.py @@ -8,7 +8,9 @@ import debug 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): """ @@ -18,10 +20,20 @@ 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"] - (width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"]) - pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"]) + if not OPTS.netlist_only: + (width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"]) + else: + (width,height) = (0,0) + pin_map = [] def __init__(self, name): design.design.__init__(self, name) @@ -32,6 +44,20 @@ class write_driver(design.design): self.pin_map = write_driver.pin_map self.add_pin_types(self.type_list) + def get_bl_names(self): + return props.write_driver.pin.bl + + def get_br_names(self): + 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""" # This is approximated from SCMOS. It has roughly 5 3x transistor gates. diff --git a/compiler/debug.py b/compiler/debug.py index 15876f22..f07471cc 100644 --- a/compiler/debug.py +++ b/compiler/debug.py @@ -26,6 +26,9 @@ def check(check, str): log("ERROR: file {0}: line {1}: {2}\n".format( os.path.basename(filename), line_number, str)) + if globals.OPTS.debug_level > 0: + import pdb + pdb.set_trace() assert 0 @@ -37,6 +40,9 @@ def error(str, return_value=0): log("ERROR: file {0}: line {1}: {2}\n".format( os.path.basename(filename), line_number, str)) + if globals.OPTS.debug_level > 0 and return_value != 0: + import pdb + pdb.set_trace() assert return_value == 0 diff --git a/compiler/drc/design_rules.py b/compiler/drc/design_rules.py index 1017aca6..b194d082 100644 --- a/compiler/drc/design_rules.py +++ b/compiler/drc/design_rules.py @@ -9,9 +9,10 @@ import debug from drc_value import * from drc_lut import * -class design_rules(): - """ - This is a class that implements the design rules structures. + +class design_rules(dict): + """ + This is a class that implements the design rules structures. """ def __init__(self, name): self.tech_name = name @@ -43,6 +44,22 @@ class design_rules(): else: debug.error("Must call complex DRC rule {} with arguments.".format(b),-1) - - + def keys(self): + return self.rules.keys() + + def add_layer(self, name, width, spacing, area=0): + # Minimum width + self.add("minwidth_{}".format(name), width) + # Minimum spacing (could be a table too) + self.add("{0}_to_{0}".format(name), spacing) + # Minimum area + self.add("minarea_{}".format(name), area) + + def add_enclosure(self, name, layer, enclosure, extension=None): + self.add("{0}_enclose_{1}".format(name, layer), enclosure) + # Reserved for asymmetric enclosures + if extension: + self.add("{0}_extend_{1}".format(name, layer), extension) + else: + self.add("{0}_extend_{1}".format(name, layer), enclosure) diff --git a/compiler/drc/drc_lut.py b/compiler/drc/drc_lut.py index 0ad0fde4..8a7b49d2 100644 --- a/compiler/drc/drc_lut.py +++ b/compiler/drc/drc_lut.py @@ -7,9 +7,10 @@ # import debug + class drc_lut(): - """ - Implement a lookup table of rules. + """ + Implement a lookup table of rules. Each element is a tuple with the last value being the rule. It searches through backwards until all of the key values are met and returns the rule value. @@ -31,7 +32,6 @@ class drc_lut(): for table_key in sorted(self.table.keys(), reverse=True): if self.match(key, table_key): return self.table[table_key] - def match(self, key1, key2): """ @@ -39,8 +39,8 @@ class drc_lut(): (i.e. return false if key1>(63-index))&0x1,eol='') print("\n") - + def stripNonASCII(self,bytestring): string = bytestring.decode('utf-8') return string @@ -29,20 +29,20 @@ class Gds2reader: #(1)sign (7)exponent (56)mantissa #exponent is excess 64, mantissa has no implied 1 #a normal IEEE double is like this: - #(1)sign (11)exponent (52)mantissa + #(1)sign (11)exponent (52)mantissa data = struct.unpack('>q',ibmData)[0] sign = (data >> 63)&0x01 exponent = (data >> 56) & 0x7f mantissa = data<<8 #chop off sign and exponent - + if mantissa == 0: newFloat = 0.0 - else: + else: exponent = ((exponent-64)*4)+1023 #convert to double exponent #re normalize - while mantissa & 0x8000000000000000 == 0: + while mantissa & 0x8000000000000000 == 0: mantissa<<=1 - exponent-=1 + exponent-=1 mantissa<<=1 #remove the assumed high bit exponent-=1 #check for underflow error -- should handle these properly! @@ -56,7 +56,7 @@ class Gds2reader: #convert back to double newFloat = struct.unpack('>d',asciiDouble)[0] return newFloat - + def ieeeFloatCheck(self,aFloat): asciiDouble = struct.pack('>d',aFloat) data = struct.unpack('>q',asciiDouble)[0] @@ -70,12 +70,12 @@ class Gds2reader: asciiDouble = struct.pack('>q',(sign<<63)|(exponent+1023<<52)|(mantissa>>12)) newFloat = struct.unpack('>d',asciiDouble)[0] print("Check:"+str(newFloat)) - + def readNextRecord(self): global offset recordLengthAscii = self.fileHandle.read(2) #first 2 bytes tell us the length of the record if len(recordLengthAscii)==0: - return + return recordLength = struct.unpack(">h",recordLengthAscii) #gives us a tuple with a short int inside offset_int = int(recordLength[0]) # extract length offset += offset_int # count offset @@ -96,20 +96,20 @@ class Gds2reader: else: print("Invalid GDSII Header") return -1 - + #read records until we hit the UNITS section... this is the last part of the header while 1: record = self.readNextRecord() idBits = record[0:2] ## Modified Date if idBits==b'\x01\x02' and len(record)==26: - modYear = struct.unpack(">h",record[2:4])[0] + modYear = struct.unpack(">h",record[2:4])[0] modMonth = struct.unpack(">h",record[4:6])[0] modDay = struct.unpack(">h",record[6:8])[0] modHour = struct.unpack(">h",record[8:10])[0] modMinute = struct.unpack(">h",record[10:12])[0] modSecond = struct.unpack(">h",record[12:14])[0] - lastAccessYear = struct.unpack(">h",record[14:16])[0] + lastAccessYear = struct.unpack(">h",record[14:16])[0] lastAccessMonth = struct.unpack(">h",record[16:18])[0] lastAccessDay = struct.unpack(">h",record[18:20])[0] lastAccessHour = struct.unpack(">h",record[20:22])[0] @@ -164,17 +164,19 @@ class Gds2reader: print("Mask: "+mask) elif(idBits==b'\x03\x05'): #this is also wrong b/c python doesn't natively have an 8 byte float userUnits=self.ieeeDoubleFromIbmData(record[2:10]) - dbUnits=self.ieeeDoubleFromIbmData + dbUnits=self.ieeeDoubleFromIbmData(record[10:18]) self.layoutObject.info["units"] = (userUnits,dbUnits) if(self.debugToTerminal==1): print("Units: 1 user unit="+str(userUnits)+" database units, 1 database unit="+str(dbUnits)+" meters.") break; - if(self.debugToTerminal==1): + if(self.debugToTerminal==1): print("End of GDSII Header Found") return 1 - + def readBoundary(self): ##reads in a boundary type structure = a filled polygon + if(self.debugToTerminal==1): + print("\t\t\tBeginBoundary") thisBoundary=GdsBoundary() while 1: record = self.readNextRecord() @@ -196,16 +198,11 @@ class Gds2reader: self.layoutObject.layerNumbersInUse += [drawingLayer] if(self.debugToTerminal==1): print("\t\tDrawing Layer: "+str(drawingLayer)) - elif(idBits==b'\x16\x02'): #Purpose + elif(idBits==b'\x0E\x02'): #Purpose DATATYPE purposeLayer = struct.unpack(">h",record[2:4])[0] - thisBoundary.purposeLayer=purposeLayer + thisBoundary.purposeLayer=purposeLayer if(self.debugToTerminal==1): print("\t\tPurpose Layer: "+str(purposeLayer)) - elif(idBits==b'\x0E\x02'): #DataType - dataType = struct.unpack(">h",record[2:4])[0] - thisBoundary.dataType=dataType - if(self.debugToTerminal==1): - print("\t\t\tData Type: "+str(dataType)) elif(idBits==b'\x10\x03'): #XY Data Points numDataPoints = len(record)-2 #packed as XY coordinates 4 bytes each thisBoundary.coordinates=[] @@ -216,10 +213,15 @@ class Gds2reader: if(self.debugToTerminal==1): print("\t\t\tXY Point: "+str(x)+","+str(y)) elif(idBits==b'\x11\x00'): #End Of Element + if(self.debugToTerminal==1): + print("\t\t\tEndBoundary") break; return thisBoundary - + def readPath(self): #reads in a path structure + if(self.debugToTerminal==1): + print("\t\t\tBeginPath") + thisPath=GdsPath() while 1: record = self.readNextRecord() @@ -243,7 +245,7 @@ class Gds2reader: print("\t\t\tDrawing Layer: "+str(drawingLayer)) elif(idBits==b'\x16\x02'): #Purpose purposeLayer = struct.unpack(">h",record[2:4])[0] - thisPath.purposeLayer=purposeLayer + thisPath.purposeLayer=purposeLayer if(self.debugToTerminal==1): print("\t\tPurpose Layer: "+str(purposeLayer)) elif(idBits==b'\x21\x02'): #Path type @@ -251,6 +253,11 @@ class Gds2reader: thisPath.pathType=pathType if(self.debugToTerminal==1): print("\t\t\tPath Type: "+str(pathType)) + elif(idBits==b'\x0E\x02'): #Data type + dataType = struct.unpack(">h",record[2:4])[0] + thisPath.dataType=dataType + if(self.debugToTerminal==1): + print("\t\t\tData Type: "+str(dataType)) elif(idBits==b'\x0F\x03'): #Path width pathWidth = struct.unpack(">i",record[2:6])[0] thisPath.pathWidth=pathWidth @@ -266,10 +273,15 @@ class Gds2reader: if(self.debugToTerminal==1): print("\t\t\tXY Point: "+str(x)+","+str(y)) elif(idBits==b'\x11\x00'): #End Of Element + if(self.debugToTerminal==1): + print("\t\t\tEndPath") break; return thisPath - + def readSref(self): #reads in a reference to another structure + if(self.debugToTerminal==1): + print("\t\t\tBeginSref") + thisSref=GdsSref() while 1: record = self.readNextRecord() @@ -306,7 +318,7 @@ class Gds2reader: print("\t\t\tMagnification:"+str(magFactor)) elif(idBits==b'\x1C\x05'): #Rotate Angle rotateAngle=self.ieeeDoubleFromIbmData(record[2:10]) - thisSref.rotateAngle=rotateAngle + thisSref.rotateAngle=rotateAngle if(self.debugToTerminal==1): print("\t\t\tRotate Angle (CCW):"+str(rotateAngle)) elif(idBits==b'\x10\x03'): #XY Data Points @@ -317,10 +329,15 @@ class Gds2reader: if(self.debugToTerminal==1): print("\t\t\tXY Point: "+str(x)+","+str(y)) elif(idBits==b'\x11\x00'): #End Of Element + if(self.debugToTerminal==1): + print("\t\t\tEndSref") break; return thisSref - + def readAref(self): #an array of references + if(self.debugToTerminal==1): + print("\t\t\tBeginAref") + thisAref = GdsAref() while 1: record = self.readNextRecord() @@ -357,7 +374,7 @@ class Gds2reader: print("\t\t\tMagnification:"+str(magFactor)) elif(idBits==b'\x1C\x05'): #Rotate Angle rotateAngle=self.ieeeDoubleFromIbmData(record[2:10]) - thisAref.rotateAngle=rotateAngle + thisAref.rotateAngle=rotateAngle if(self.debugToTerminal==1): print("\t\t\tRotate Angle (CCW):"+str(rotateAngle)) elif(idBits==b'\x10\x03'): #XY Data Points @@ -372,11 +389,15 @@ class Gds2reader: print("\t\t\t\tArray Width: "+str(rightMostX-topLeftX)) print("\t\t\t\tArray Height: "+str(topLeftY-bottomMostY)) elif(idBits==b'\x11\x00'): #End Of Element + if(self.debugToTerminal==1): + print("\t\t\tEndAref") break; return thisAref - + def readText(self): - ##reads in a text structure + if(self.debugToTerminal==1): + print("\t\t\tBeginText") + thisText=GdsText() while 1: record = self.readNextRecord() @@ -398,9 +419,9 @@ class Gds2reader: self.layoutObject.layerNumbersInUse += [drawingLayer] if(self.debugToTerminal==1): print("\t\tDrawing Layer: "+str(drawingLayer)) - elif(idBits==b'\x16\x02'): #Purpose + elif(idBits==b'\x16\x02'): #Purpose TEXTTYPE purposeLayer = struct.unpack(">h",record[2:4])[0] - thisText.purposeLayer=purposeLayer + thisText.purposeLayer=purposeLayer if(self.debugToTerminal==1): print("\t\tPurpose Layer: "+str(purposeLayer)) elif(idBits==b'\x1A\x01'): #Transformation @@ -472,10 +493,15 @@ class Gds2reader: if(self.debugToTerminal==1): print("\t\t\tText String: "+textString) elif(idBits==b'\x11\x00'): #End Of Element + if(self.debugToTerminal==1): + print("\t\t\tEndText") break; return thisText - + def readNode(self): + if(self.debugToTerminal==1): + print("\t\t\tBeginNode") + ##reads in a node type structure = an electrical net thisNode = GdsNode() while 1: @@ -513,10 +539,15 @@ class Gds2reader: if(self.debugToTerminal==1): print("\t\t\tXY Point: "+str(x)+","+str(y)) elif(idBits==b'\x11\x00'): #End Of Element + if(self.debugToTerminal==1): + print("\t\t\tEndNode") break; return thisNode - + def readBox(self): + if(self.debugToTerminal==1): + print("\t\t\tBeginBox") + ##reads in a gds BOX structure thisBox = GdsBox() while 1: @@ -539,9 +570,9 @@ class Gds2reader: self.layoutObject.layerNumbersInUse += [drawingLayer] if(self.debugToTerminal==1): print("\t\tDrawing Layer: "+str(drawingLayer)) - elif(idBits==b'\x16\x02'): #Purpose + elif(idBits==b'\x16\x02'): #Purpose TEXTYPE purposeLayer = struct.unpack(">h",record[2:4])[0] - thisBox.purposeLayer=purposeLayer + thisBox.purposeLayer=purposeLayer if(self.debugToTerminal==1): print("\t\tPurpose Layer: "+str(purposeLayer)) elif(idBits==b'\x2D\x00'): #Box @@ -559,15 +590,18 @@ class Gds2reader: if(self.debugToTerminal==1): print("\t\t\tXY Point: "+str(x)+","+str(y)) elif(idBits==b'\x11\x00'): #End Of Element + if(self.debugToTerminal==1): + print("\t\t\tEndBox") break; return thisBox - + def readNextStructure(self): - thisStructure = GdsStructure() + thisStructure = GdsStructure() record = self.readNextRecord() idBits = record[0:2] + # Begin structure if(idBits==b'\x05\x02' and len(record)==26): - createYear = struct.unpack(">h",record[2:4])[0] + createYear = struct.unpack(">h",record[2:4])[0] createMonth = struct.unpack(">h",record[4:6])[0] createDay = struct.unpack(">h",record[6:8])[0] createHour = struct.unpack(">h",record[8:10])[0] @@ -581,6 +615,10 @@ class Gds2reader: modSecond = struct.unpack(">h",record[24:26])[0] thisStructure.createDate=(createYear,createMonth,createDay,createHour,createMinute,createSecond) thisStructure.modDate=(modYear,modMonth,modDay,modHour,modMinute,modSecond) + if(self.debugToTerminal==1): + print("Date Created:"+str(createYear)+","+str(createMonth)+","+str(createDay)+\ + ","+str(createHour)+","+str(createMinute)+","+str(createSecond)) + print("Date Modified:"+str(modYear)+","+str(modMonth)+","+str(modDay)+","+str(modHour)+","+str(modMinute)+","+str(modSecond)) else: #means we have hit the last structure, so return the record #to whoever called us to do something with it @@ -590,7 +628,7 @@ class Gds2reader: idBits = record[0:2] if idBits==b'\x07\x00': break; #we've reached the end of the structure elif(idBits==b'\x06\x06'): - structName = self.stripNonASCII(record[2::]) + structName = self.stripNonASCII(record[2::]) thisStructure.name = structName if(self.debugToTerminal==1): print("\tStructure Name: "+structName) @@ -608,11 +646,11 @@ class Gds2reader: thisStructure.nodes+=[self.readNode()] elif(idBits==b'\x2E\x02'): thisStructure.boxes+=[self.readBox()] - if(self.debugToTerminal==1): + if(self.debugToTerminal==1): print("\tEnd of Structure.") self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object return 1 - + def readGds2(self): if(self.readHeader()): #did the header read ok? record = self.readNextStructure() @@ -629,7 +667,7 @@ class Gds2reader: print("There was an error reading the structure list.") else: print("There was an error parsing the GDS header. Aborting...") - + def loadFromFile(self, fileName): self.fileHandle = open(fileName,"rb") self.readGds2() @@ -657,11 +695,11 @@ class Gds2reader: def findStruct_readNextStruct(self,findStructName): self.debugToTerminal=0 - thisStructure = GdsStructure() + thisStructure = GdsStructure() record = self.readNextRecord() idBits = record[0:2] if(idBits==('\x05','\x02') and len(record)==26): - createYear = struct.unpack(">h",record[2]+record[3])[0] + createYear = struct.unpack(">h",record[2]+record[3])[0] createMonth = struct.unpack(">h",record[4]+record[5])[0] createDay = struct.unpack(">h",record[6]+record[7])[0] createHour = struct.unpack(">h",record[8]+record[9])[0] @@ -686,10 +724,6 @@ class Gds2reader: if idBits==('\x07','\x00'): break; #we've reached the end of the structure elif(idBits==('\x06','\x06')): structName = self.stripNonASCII(record[2::]) #(record[2:1] + record[1::]).rstrip() -# print(''.[x for x in structName if ord(x) < 128]) -# stripped = (c for c in structName if 0 < ord(c) < 127) -# structName = "".join(stripped) -# print(self.stripNonASCII(structName)) ##FIXME: trimming by Tom g. ##could be an issue here with string trimming! thisStructure.name = structName if(findStructName==thisStructure.name): wantedStruct=1 @@ -709,7 +743,7 @@ class Gds2reader: thisStructure.nodes+=[self.readNode()] elif(idBits==('\x2E','\x02')): thisStructure.boxes+=[self.readBox()] - if(self.debugToTerminal==1): + if(self.debugToTerminal==1): print("\tEnd of Structure.") self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object if(wantedStruct == 0): @@ -737,11 +771,11 @@ class Gds2reader: def findLabel_readNextStruct(self,findLabelName): self.debugToTerminal=0 - thisStructure = GdsStructure() + thisStructure = GdsStructure() record = self.readNextRecord() idBits = record[0:2] if(idBits==('\x05','\x02') and len(record)==26): - createYear = struct.unpack(">h",record[2]+record[3])[0] + createYear = struct.unpack(">h",record[2]+record[3])[0] createMonth = struct.unpack(">h",record[4]+record[5])[0] createDay = struct.unpack(">h",record[6]+record[7])[0] createHour = struct.unpack(">h",record[8]+record[9])[0] @@ -767,10 +801,6 @@ class Gds2reader: if idBits==('\x07','\x00'): break; #we've reached the end of the structure elif(idBits==('\x06','\x06')): structName = self.stripNonASCII(record[2::]) #(record[2:1] + record[1::]).rstrip() -# print(''.[x for x in structName if ord(x) < 128]) -# stripped = (c for c in structName if 0 < ord(c) < 127) -# structName = "".join(stripped) -# print(self.stripNonASCIIx(structName)) ##FIXME: trimming by Tom g. ##could be an issue here with string trimming! thisStructure.name = structName if(self.debugToTerminal==1): print("\tStructure Name: "+structName) @@ -795,7 +825,7 @@ class Gds2reader: thisStructure.nodes+=[self.readNode()] elif(idBits==('\x2E','\x02')): thisStructure.boxes+=[self.readBox()] - if(self.debugToTerminal==1): + if(self.debugToTerminal==1): print("\tEnd of Structure.") self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object if(wantedLabel == 0): @@ -803,4 +833,3 @@ class Gds2reader: else: #print("\tDone with collectting bound. Return") return [0,wantedtexts] - diff --git a/compiler/gdsMill/gdsMill/gds2writer.py b/compiler/gdsMill/gdsMill/gds2writer.py index 402416cd..875de831 100644 --- a/compiler/gdsMill/gdsMill/gds2writer.py +++ b/compiler/gdsMill/gdsMill/gds2writer.py @@ -5,37 +5,37 @@ from .gdsPrimitives import * class Gds2writer: """Class to take a populated layout class and write it to a file in GDSII format""" ## Based on info from http://www.rulabinsky.com/cavd/text/chapc.html - + def __init__(self,layoutObject): self.fileHandle = 0 self.layoutObject = layoutObject self.debugToTerminal=0 #do we dump debug data to the screen - + def print64AsBinary(self,number): #debugging method for binary inspection for index in range(0,64): print((number>>(63-index))&0x1,eol='') print("\n") - + def ieeeDoubleFromIbmData(self,ibmData): #the GDS double is in IBM 370 format like this: #(1)sign (7)exponent (56)mantissa #exponent is excess 64, mantissa has no implied 1 #a normal IEEE double is like this: - #(1)sign (11)exponent (52)mantissa + #(1)sign (11)exponent (52)mantissa data = struct.unpack('>q',ibmData)[0] sign = (data >> 63)&0x01 exponent = (data >> 56) & 0x7f mantissa = data<<8 #chop off sign and exponent - + if mantissa == 0: newFloat = 0.0 else: exponent = ((exponent-64)*4)+1023 #convert to double exponent #re normalize - while mantissa & 0x8000000000000000 == 0: + while mantissa & 0x8000000000000000 == 0: mantissa<<=1 - exponent-=1 + exponent-=1 mantissa<<=1 #remove the assumed high bit exponent-=1 #check for underflow error -- should handle these properly! @@ -49,12 +49,12 @@ class Gds2writer: #convert back to double newFloat = struct.unpack('>d',asciiDouble)[0] return newFloat - + def ibmDataFromIeeeDouble(self,ieeeDouble): asciiDouble = struct.pack('>d',ieeeDouble) data = struct.unpack('>q',asciiDouble)[0] sign = (data >> 63) & 0x01 - exponent = ((data >> 52) & 0x7ff)-1023 + exponent = ((data >> 52) & 0x7ff)-1023 mantissa = data << 12 #chop off sign and exponent if(ieeeDouble == 0): mantissa = 0 @@ -70,14 +70,14 @@ class Gds2writer: for index in range (0,-exponent&3): mantissa >>= 1 mantissa = mantissa & 0x7fffffffffffffff - - exponent = (exponent+3) >> 2 + + exponent = (exponent+3) >> 2 exponent+=64 - - newFloat =(sign<<63)|(exponent<<56)|((mantissa>>8)&0xffffffffffffff) - asciiDouble = struct.pack('>q',newFloat) + + newFloat =(sign<<63)|(exponent<<56)|((mantissa>>8)&0xffffffffffffff) + asciiDouble = struct.pack('>q',newFloat) return asciiDouble - + def ieeeFloatCheck(self,aFloat): #debugging method for float construction asciiDouble = struct.pack('>d',aFloat) @@ -90,7 +90,7 @@ class Gds2writer: asciiDouble = struct.pack('>q',(sign<<63)|(exponent+1023<<52)|(mantissa>>12)) newFloat = struct.unpack('>d',asciiDouble)[0] print("Check:"+str(newFloat)) - + def writeRecord(self,record): recordLength = len(record)+2 #make sure to include this in the length recordLengthAscii=struct.pack(">h",recordLength) @@ -127,7 +127,7 @@ class Gds2writer: libraryName = self.layoutObject.info["libraryName"].encode() + "\0" else: libraryName = self.layoutObject.info["libraryName"].encode() - self.writeRecord(idBits+libraryName) + self.writeRecord(idBits+libraryName) ## reference libraries if("referenceLibraries" in self.layoutObject.info): idBits=b'\x1F\x06' @@ -158,11 +158,11 @@ class Gds2writer: mask = self.layoutObject.info["mask"] self.writeRecord(idBits+mask) if("units" in self.layoutObject.info): - idBits=b'\x03\x05' + idBits=b'\x03\x05' userUnits=self.ibmDataFromIeeeDouble(self.layoutObject.info["units"][0]) dbUnits=self.ibmDataFromIeeeDouble((self.layoutObject.info["units"][0]*1e-6/self.layoutObject.info["units"][1])*self.layoutObject.info["units"][1]) - #User Units are hardcoded, since the floating point implementation of gdsMill is not adequate, + #User Units are hardcoded, since the floating point implementation of gdsMill is not adequate, #resulting in a different value being written in output stream. Hardcoded to sram compiler's outputed gds units. #db="39225c17d04dad2a" #uu="3e20c49ba5e353f8" @@ -172,42 +172,40 @@ class Gds2writer: #dbUnits="39225c17d04dad2a".decode("hex") #db=39225c17d04dad2a - - + + self.writeRecord(idBits+userUnits+dbUnits) - if(self.debugToTerminal==1): + if(self.debugToTerminal==1): print("writer: userUnits %s"%(userUnits.encode("hex"))) print("writer: dbUnits %s"%(dbUnits.encode("hex"))) #self.ieeeFloatCheck(1.3e-6) - + print("End of GDSII Header Written") return 1 - + def writeBoundary(self,thisBoundary): idBits=b'\x08\x00' #record Type self.writeRecord(idBits) if(thisBoundary.elementFlags!=""): - idBits=b'\x26\x01' #ELFLAGS + idBits=b'\x26\x01' # ELFLAGS elementFlags = struct.pack(">h",thisBoundary.elementFlags) self.writeRecord(idBits+elementFlags) if(thisBoundary.plex!=""): - idBits=b'\x2F\x03' #PLEX + idBits=b'\x2F\x03' # PLEX plex = struct.pack(">i",thisBoundary.plex) self.writeRecord(idBits+plex) if(thisBoundary.drawingLayer!=""): - idBits=b'\x0D\x02' #drawig layer + idBits=b'\x0D\x02' # drawing layer drawingLayer = struct.pack(">h",thisBoundary.drawingLayer) self.writeRecord(idBits+drawingLayer) - if(thisBoundary.purposeLayer): - idBits=b'\x16\x02' #purpose layer - purposeLayer = struct.pack(">h",thisBoundary.purposeLayer) - self.writeRecord(idBits+purposeLayer) - if(thisBoundary.dataType!=""): - idBits=b'\x0E\x02'#DataType - dataType = struct.pack(">h",thisBoundary.dataType) + if(thisBoundary.purposeLayer!=""): + idBits=b'\x0E\x02' # DataType + if type(thisBoundary.purposeLayer)!=int: + import pdb; pdb.set_trace() + dataType = struct.pack(">h",thisBoundary.purposeLayer) self.writeRecord(idBits+dataType) if(thisBoundary.coordinates!=""): - idBits=b'\x10\x03' #XY Data Points + idBits=b'\x10\x03' # XY Data Points coordinateRecord = idBits for coordinate in thisBoundary.coordinates: x=struct.pack(">i",int(coordinate[0])) @@ -218,7 +216,7 @@ class Gds2writer: idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writePath(self,thisPath): #writes out a path structure idBits=b'\x09\x00' #record Type self.writeRecord(idBits) @@ -238,6 +236,10 @@ class Gds2writer: idBits=b'\x16\x02' #purpose layer purposeLayer = struct.pack(">h",thisPath.purposeLayer) self.writeRecord(idBits+purposeLayer) + if(thisPath.dataType is not None): + idBits=b'\x0E\x02' #Data type + dataType = struct.pack(">h",thisPath.dataType) + self.writeRecord(idBits+dataType) if(thisPath.pathType): idBits=b'\x21\x02' #Path type pathType = struct.pack(">h",thisPath.pathType) @@ -258,7 +260,7 @@ class Gds2writer: idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writeSref(self,thisSref): #reads in a reference to another structure idBits=b'\x0A\x00' #record Type self.writeRecord(idBits) @@ -294,7 +296,7 @@ class Gds2writer: magFactor=self.ibmDataFromIeeeDouble(thisSref.magFactor) self.writeRecord(idBits+magFactor) if(thisSref.rotateAngle!=""): - idBits=b'\x1C\x05' + idBits=b'\x1C\x05' rotateAngle=self.ibmDataFromIeeeDouble(thisSref.rotateAngle) self.writeRecord(idBits+rotateAngle) if(thisSref.coordinates!=""): @@ -310,7 +312,7 @@ class Gds2writer: idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writeAref(self,thisAref): #an array of references idBits=b'\x0B\x00' #record Type self.writeRecord(idBits) @@ -346,7 +348,7 @@ class Gds2writer: magFactor=self.ibmDataFromIeeeDouble(thisAref.magFactor) self.writeRecord(idBits+magFactor) if(thisAref.rotateAngle!=""): - idBits=b'\x1C\x05' + idBits=b'\x1C\x05' rotateAngle=self.ibmDataFromIeeeDouble(thisAref.rotateAngle) self.writeRecord(idBits+rotateAngle) if(thisAref.coordinates): @@ -361,7 +363,7 @@ class Gds2writer: idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writeText(self,thisText): idBits=b'\x0C\x00' #record Type self.writeRecord(idBits) @@ -377,8 +379,7 @@ class Gds2writer: idBits=b'\x0D\x02' #drawing layer drawingLayer = struct.pack(">h",thisText.drawingLayer) self.writeRecord(idBits+drawingLayer) - #if(thisText.purposeLayer): - idBits=b'\x16\x02' #purpose layer + idBits=b'\x16\x02' #purpose layer TEXTTYPE purposeLayer = struct.pack(">h",thisText.purposeLayer) self.writeRecord(idBits+purposeLayer) if(thisText.transFlags != ""): @@ -398,7 +399,7 @@ class Gds2writer: magFactor=self.ibmDataFromIeeeDouble(thisText.magFactor) self.writeRecord(idBits+magFactor) if(thisText.rotateAngle!=""): - idBits=b'\x1C\x05' + idBits=b'\x1C\x05' rotateAngle=self.ibmDataFromIeeeDouble(thisText.rotateAngle) self.writeRecord(idBits+rotateAngle) if(thisText.pathType !=""): @@ -410,12 +411,12 @@ class Gds2writer: pathWidth = struct.pack(">i",thisText.pathWidth) self.writeRecord(idBits+pathWidth) if(thisText.presentationFlags!=""): - idBits=b'\x1A\x01' + idBits=b'\x1A\x01' font = thisText.presentationFlags[0]<<4 verticalFlags = int(thisText.presentationFlags[1])<<2 horizontalFlags = int(thisText.presentationFlags[2]) presentationFlags = struct.pack(">H",font|verticalFlags|horizontalFlags) - self.writeRecord(idBits+transFlags) + self.writeRecord(idBits+transFlags) if(thisText.coordinates!=""): idBits=b'\x10\x03' #XY Data Points coordinateRecord = idBits @@ -429,11 +430,11 @@ class Gds2writer: idBits=b'\x19\x06' textString = thisText.textString self.writeRecord(idBits+textString.encode()) - + idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writeNode(self,thisNode): idBits=b'\x15\x00' #record Type self.writeRecord(idBits) @@ -448,11 +449,11 @@ class Gds2writer: if(thisNode.drawingLayer!=""): idBits=b'\x0D\x02' #drawig layer drawingLayer = struct.pack(">h",thisNode.drawingLayer) - self.writeRecord(idBits+drawingLayer) + self.writeRecord(idBits+drawingLayer) if(thisNode.nodeType!=""): idBits=b'\x2A\x02' nodeType = struct.pack(">h",thisNode.nodeType) - self.writeRecord(idBits+nodeType) + self.writeRecord(idBits+nodeType) if(thisText.coordinates!=""): idBits=b'\x10\x03' #XY Data Points coordinateRecord = idBits @@ -462,11 +463,11 @@ class Gds2writer: coordinateRecord+=x coordinateRecord+=y self.writeRecord(coordinateRecord) - + idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writeBox(self,thisBox): idBits=b'\x2E\x02' #record Type self.writeRecord(idBits) @@ -489,7 +490,7 @@ class Gds2writer: if(thisBox.boxValue!=""): idBits=b'\x2D\x00' boxValue = struct.pack(">h",thisBox.boxValue) - self.writeRecord(idBits+boxValue) + self.writeRecord(idBits+boxValue) if(thisBox.coordinates!=""): idBits=b'\x10\x03' #XY Data Points coordinateRecord = idBits @@ -499,11 +500,11 @@ class Gds2writer: coordinateRecord+=x coordinateRecord+=y self.writeRecord(coordinateRecord) - + idBits=b'\x11\x00' #End Of Element coordinateRecord = idBits self.writeRecord(coordinateRecord) - + def writeNextStructure(self,structureName): #first put in the structure head thisStructure = self.layoutObject.structures[structureName] @@ -530,7 +531,7 @@ class Gds2writer: structureName = structureName + '\x00' self.writeRecord(idBits+structureName.encode()) #now go through all the structure elements and write them in - + for boundary in thisStructure.boundaries: self.writeBoundary(boundary) for path in thisStructure.paths: @@ -548,7 +549,7 @@ class Gds2writer: #put in the structure tail idBits=b'\x07\x00' self.writeRecord(idBits) - + def writeGds2(self): self.writeHeader(); #first, put the header in #go through each structure in the layout and write it to the file @@ -557,7 +558,7 @@ class Gds2writer: #at the end, put in the END LIB record idBits=b'\x04\x00' self.writeRecord(idBits) - + def writeToFile(self,fileName): self.fileHandle = open(fileName,"wb") self.writeGds2() diff --git a/compiler/gdsMill/gdsMill/gdsPrimitives.py b/compiler/gdsMill/gdsMill/gdsPrimitives.py index dc43fea6..8e07524c 100644 --- a/compiler/gdsMill/gdsMill/gdsPrimitives.py +++ b/compiler/gdsMill/gdsMill/gdsPrimitives.py @@ -9,7 +9,7 @@ class GdsStructure: #these are the primitives defined in GDS2, and we will maintain lists of them all self.boundaries=[] self.paths=[] - self.srefs=[] + self.srefs=[] self.arefs=[] self.texts=[] self.nodes=[] @@ -21,21 +21,21 @@ class GdsBoundary: self.elementFlags="" self.plex="" self.drawingLayer="" - self.purposeLayer = None - self.dataType="" + self.purposeLayer=0 self.coordinates="" - + class GdsPath: """Class represent a GDS Path Object""" def __init__(self): self.elementFlags="" self.plex="" self.drawingLayer="" - self.purposeLayer = None + self.purposeLayer=0 self.pathType="" + self.dataType=None self.pathWidth="" self.coordinates="" - + def equivalentBoundaryCoordinates(self): """Convert the path to a set of boundary coordinates that define it""" halfWidth = (self.pathWidth/2) @@ -62,7 +62,7 @@ class GdsPath: nextX = None; nextY = None; if lastX==None: #start of the path - if nextX>x:#moving right + if nextX>x:#moving right boundaryEquivalent+=[(x,y+halfWidth)] if nextX nextX): boundaryEquivalent+=[(x+halfWidth,y-halfWidth)] - - if nextX == None: #end of path, put in the last 2 points - if lastXx:#moving left boundaryEquivalent+=[(x,y-halfWidth)] @@ -140,7 +140,7 @@ class GdsText: self.elementFlags="" self.plex="" self.drawingLayer="" - self.purposeLayer = None + self.purposeLayer=0 self.transFlags=[0,0,0] self.magFactor="" self.rotateAngle="" @@ -149,7 +149,7 @@ class GdsText: self.presentationFlags="" self.coordinates="" self.textString = "" - + class GdsNode: """Class represent a GDS Node Object""" def __init__(self): @@ -158,13 +158,13 @@ class GdsNode: self.drawingLayer="" self.nodeType="" self.coordinates="" - + class GdsBox: """Class represent a GDS Box Object""" def __init__(self): self.elementFlags="" self.plex="" self.drawingLayer="" - self.purposeLayer = None + self.purposeLayer=0 self.boxValue="" self.coordinates="" diff --git a/compiler/gdsMill/gdsMill/vlsiLayout.py b/compiler/gdsMill/gdsMill/vlsiLayout.py index f4248ebd..979180cd 100644 --- a/compiler/gdsMill/gdsMill/vlsiLayout.py +++ b/compiler/gdsMill/gdsMill/vlsiLayout.py @@ -2,7 +2,6 @@ from .gdsPrimitives import * from datetime import * #from mpmath import matrix #from numpy import matrix -from vector import vector import numpy as np #import gdsPrimitives import debug @@ -19,7 +18,8 @@ class VlsiLayout: self.layerNumbersInUse = [] self.debug = False if name: - self.rootStructureName=name + #take the root structure and copy it to a new structure with the new name + self.rootStructureName=self.padText(name) #create the ROOT structure self.structures[self.rootStructureName] = GdsStructure() self.structures[self.rootStructureName].name = name @@ -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,22 +73,18 @@ 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): - #make sure the newName is a multiple of 2 characters - if(len(newName)%2 == 1): - #pad with a zero - newName = newName + '\x00' #take the root structure and copy it to a new structure with the new name self.structures[newName] = self.structures[self.rootStructureName] - self.structures[newName].name = newName + self.structures[newName].name = self.padText(newName) #and delete the old root del self.structures[self.rootStructureName] self.rootStructureName = newName @@ -133,8 +129,8 @@ class VlsiLayout: modDate.hour, modDate.minute, modDate.second) - - + + #repopulate the 2d map so drawing occurs correctly self.prepareForWrite() @@ -159,14 +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 - - #set up the rotation matrix + startingStructureName = self.rootStructureName + + #set up the rotation matrix if(rotateAngle == None or rotateAngle == ""): angle = 0 else: @@ -193,40 +190,44 @@ class VlsiLayout: delegateFunction(startingStructureName, transformPath) #starting with a particular structure, we will recursively traverse the tree #********might have to set the recursion level deeper for big layouts! - 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) - 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, - delegateFunction = delegateFunction, - transformPath = transformPath, - rotateAngle = sref.rotateAngle, - transFlags = sref.transFlags, - coordinates = sref.coordinates) + 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) + 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, + delegateFunction = delegateFunction, + transformPath = transformPath, + rotateAngle = sref.rotateAngle, + transFlags = sref.transFlags, + 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() + # self.traverseTheHierarchy() self.populateCoordinateMap() for layerNumber in self.layerNumbersInUse: - self.processLabelPins(layerNumber) - - + 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 @@ -236,28 +237,31 @@ 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): + + def microns(self, userUnits): """Utility function to convert user units to microns""" userUnit = self.units[1]/self.units[0] - userUnitsPerMicron = userUnit / (userunit) + userUnitsPerMicron = userUnit / userunit layoutUnitsPerMicron = userUnitsPerMicron / self.units[0] return userUnits / layoutUnitsPerMicron - - def userUnits(self,microns): + + def userUnits(self, microns): """Utility function to convert microns to user units""" userUnit = self.units[1]/self.units[0] - #userUnitsPerMicron = userUnit / 1e-6 + # userUnitsPerMicron = userUnit / 1e-6 userUnitsPerMicron = userUnit / (userUnit) layoutUnitsPerMicron = userUnitsPerMicron / self.units[0] - #print("userUnit:",userUnit,"userUnitsPerMicron",userUnitsPerMicron,"layoutUnitsPerMicron",layoutUnitsPerMicron,[microns,microns*layoutUnitsPerMicron]) - return round(microns*layoutUnitsPerMicron,0) + # print("userUnit:",userUnit, + # "userUnitsPerMicron",userUnitsPerMicron, + # "layoutUnitsPerMicron",layoutUnitsPerMicron, + # [microns,microns*layoutUnitsPerMicron]) + return round(microns*layoutUnitsPerMicron, 0) def changeRoot(self,newRoot, create=False): """ @@ -266,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)): @@ -278,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 @@ -299,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 @@ -307,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: @@ -326,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 @@ -352,8 +356,8 @@ class VlsiLayout: #add the sref to the root structure self.structures[self.rootStructureName].srefs.append(layoutToAddSref) - - def addBox(self,layerNumber=0, purposeNumber=None, offsetInMicrons=(0,0), width=1.0, height=1.0,center=False): + + 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 """ @@ -369,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), @@ -378,13 +382,12 @@ class VlsiLayout: boundaryToAdd = GdsBoundary() boundaryToAdd.drawingLayer = layerNumber - boundaryToAdd.dataType = 0 boundaryToAdd.coordinates = coordinates boundaryToAdd.purposeLayer = purposeNumber #add the sref to the root structure self.structures[self.rootStructureName].boundaries.append(boundaryToAdd) - - def addPath(self, layerNumber=0, purposeNumber = None, coordinates=[(0,0)], width=1.0): + + def addPath(self, layerNumber=0, purposeNumber=0, coordinates=[(0,0)], width=1.0): """ Method to add a path to a layout """ @@ -396,24 +399,21 @@ class VlsiLayout: cY = self.userUnits(coordinate[1]) layoutUnitCoordinates.append((cX,cY)) pathToAdd = GdsPath() - pathToAdd.drawingLayer=layerNumber + pathToAdd.drawingLayer = layerNumber pathToAdd.purposeLayer = purposeNumber - pathToAdd.pathWidth=widthInLayoutUnits - pathToAdd.coordinates=layoutUnitCoordinates + pathToAdd.pathWidth = widthInLayoutUnits + pathToAdd.coordinates = layoutUnitCoordinates #add the sref to the root structure self.structures[self.rootStructureName].paths.append(pathToAdd) - - def addText(self, text, layerNumber=0, purposeNumber = None, 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.dataType = 0 textToAdd.coordinates = [offsetInLayoutUnits] - textToAdd.transFlags = [0,0,0] - if(len(text)%2 == 1): - text = text + '\x00' - textToAdd.textString = text + textToAdd.transFlags = [0,0,0] + textToAdd.textString = self.padText(text) #textToAdd.transFlags[1] = 1 textToAdd.magFactor = magnification if rotate: @@ -421,7 +421,13 @@ class VlsiLayout: textToAdd.rotateAngle = rotate #add the sref to the root structure self.structures[self.rootStructureName].texts.append(textToAdd) - + + def padText(self, text): + if(len(text)%2 == 1): + return text + '\x00' + else: + return text + def isBounded(self,testPoint,startPoint,endPoint): #these arguments are touples of (x,y) coordinates if testPoint == None: @@ -433,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]) @@ -453,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): @@ -462,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. @@ -486,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 @@ -511,7 +517,7 @@ class VlsiLayout: pointCoordinates[1]max_area: + if not max_pin or new_area > max_area: max_pin = pin max_area = new_area max_pins.append(max_pin) return max_pins - + def getAllPinShapes(self, pin_name): """ @@ -690,32 +705,33 @@ class VlsiLayout: pin_map = self.pins[pin_name] for pin_list in pin_map: for pin in pin_list: - (pin_layer, boundary) = pin + (pin_layer, boundary) = pin shape_list.append(pin) return shape_list - - def processLabelPins(self, layer): + def processLabelPins(self, lpp): """ Find all text labels and create a map to a list of shapes that they enclose on the given layer. """ # Get the labels on a layer in the root level - labels = self.getTexts(layer) + labels = self.getTexts(lpp) + # Get all of the shapes on the layer at all levels # and transform them to the current level - shapes = self.getAllShapes(layer) + shapes = self.getAllShapes(lpp) for label in labels: label_coordinate = label.coordinates[0] user_coordinate = [x*self.units[0] for x in label_coordinate] pin_shapes = [] for boundary in shapes: - if self.labelInRectangle(user_coordinate,boundary): - pin_shapes.append((layer, boundary)) + if self.labelInRectangle(user_coordinate, boundary): + pin_shapes.append((lpp, boundary)) label_text = label.textString + # Remove the padding if it exists if label_text[-1] == "\x00": label_text = label_text[0:-1] @@ -725,81 +741,97 @@ class VlsiLayout: except KeyError: self.pins[label_text] = [] self.pins[label_text].append(pin_shapes) - - - def getBlockages(self,layer): + + def getBlockages(self, lpp): """ - Return all blockages on a given layer in [coordinate 1, coordinate 2,...] format and + Return all blockages on a given layer in + [coordinate 1, coordinate 2,...] format and user units. """ blockages = [] - shapes = self.getAllShapes(layer) + shapes = self.getAllShapes(lpp) for boundary in shapes: vectors = [] - for i in range(0,len(boundary),2): - vectors.append(vector(boundary[i],boundary[i+1])) + for i in range(0, len(boundary), 2): + vectors.append((boundary[i], boundary[i+1])) blockages.append(vectors) - return blockages - def getAllShapes(self,layer): + return blockages + + def getAllShapes(self, lpp): """ - Return all shapes on a given layer in [llx, lly, urx, ury] format and user units for rectangles - and [coordinate 1, coordinate 2,...] format and user units for polygons. + Return all shapes on a given layer in [llx, lly, urx, ury] + format and user units for rectangles + and [coordinate 1, coordinate 2,...] format and user + units for polygons. """ boundaries = set() for TreeUnit in self.xyTree: - #print(TreeUnit[0]) - boundaries.update(self.getShapesInStructure(layer,TreeUnit)) + # print(TreeUnit[0]) + boundaries.update(self.getShapesInStructure(lpp, TreeUnit)) # Convert to user units user_boundaries = [] for boundary in boundaries: boundaries_list = [] - for i in range(0,len(boundary)): + for i in range(0, len(boundary)): boundaries_list.append(boundary[i]*self.units[0]) user_boundaries.append(boundaries_list) return user_boundaries - - def getShapesInStructure(self,layer,structure): - """ - Go through all the shapes in a structure and return the list of shapes in - the form [llx, lly, urx, ury] for rectangles and [coordinate 1, coordinate 2,...] for polygons. + def getShapesInStructure(self, lpp, structure): """ - (structureName,structureOrigin,structureuVector,structurevVector)=structure - #print(structureName,"u",structureuVector.transpose(),"v",structurevVector.transpose(),"o",structureOrigin.transpose()) + Go through all the shapes in a structure and + return the list of shapes in + the form [llx, lly, urx, ury] for rectangles + and [coordinate 1, coordinate 2,...] for polygons. + """ + (structureName, structureOrigin, + structureuVector, structurevVector) = structure + # print(structureName, + # "u", structureuVector.transpose(), + # "v",structurevVector.transpose(), + # "o",structureOrigin.transpose()) boundaries = [] for boundary in self.structures[str(structureName)].boundaries: - if layer==boundary.drawingLayer: - if len(boundary.coordinates)!=5: + if sameLPP((boundary.drawingLayer, boundary.purposeLayer), + lpp): + if len(boundary.coordinates) != 5: # if shape is a polygon (used in DFF) boundaryPolygon = [] # Polygon is a list of coordinates going ccw - for coord in range(0,len(boundary.coordinates)): + for coord in range(0, len(boundary.coordinates)): boundaryPolygon.append(boundary.coordinates[coord][0]) boundaryPolygon.append(boundary.coordinates[coord][1]) # perform the rotation - boundaryPolygon=self.transformPolygon(boundaryPolygon,structureuVector,structurevVector) - # add the offset + boundaryPolygon = self.transformPolygon(boundaryPolygon, + structureuVector, + structurevVector) + # add the offset polygon = [] - for i in range(0,len(boundaryPolygon),2): - polygon.append(boundaryPolygon[i]+structureOrigin[0].item()) - polygon.append(boundaryPolygon[i+1]+structureOrigin[1].item()) + for i in range(0, len(boundaryPolygon), 2): + polygon.append(boundaryPolygon[i] + structureOrigin[0].item()) + polygon.append(boundaryPolygon[i+1] + structureOrigin[1].item()) # make it a tuple polygon = tuple(polygon) boundaries.append(polygon) else: # else shape is a rectangle - left_bottom=boundary.coordinates[0] - right_top=boundary.coordinates[2] + left_bottom = boundary.coordinates[0] + right_top = boundary.coordinates[2] # Rectangle is [leftx, bottomy, rightx, topy]. - boundaryRect=[left_bottom[0],left_bottom[1],right_top[0],right_top[1]] + boundaryRect = [left_bottom[0], left_bottom[1], + right_top[0], right_top[1]] # perform the rotation - boundaryRect=self.transformRectangle(boundaryRect,structureuVector,structurevVector) + boundaryRect = self.transformRectangle(boundaryRect, + structureuVector, + structurevVector) # add the offset and make it a tuple - boundaryRect=(boundaryRect[0]+structureOrigin[0].item(),boundaryRect[1]+structureOrigin[1].item(), - boundaryRect[2]+structureOrigin[0].item(),boundaryRect[3]+structureOrigin[1].item()) + boundaryRect = (boundaryRect[0]+structureOrigin[0].item(), + boundaryRect[1]+structureOrigin[1].item(), + boundaryRect[2]+structureOrigin[0].item(), + boundaryRect[3]+structureOrigin[1].item()) boundaries.append(boundaryRect) return boundaries @@ -839,7 +871,7 @@ class VlsiLayout: """ Rotate a coordinate in space. """ - # MRG: 9/3/18 Incorrect matrix multiplication! + # MRG: 9/3/18 Incorrect matrix multiplication! # This is fixed to be: # |u[0] v[0]| |x| |x'| # |u[1] v[1]|x|y|=|y'| @@ -861,11 +893,21 @@ class VlsiLayout: else: return False - + +def sameLPP(lpp1, lpp2): + """ + Check if the layers and purposes are the same. + Ignore if purpose is a None. + """ + if lpp1[1] == None or lpp2[1] == None: + return lpp1[0] == lpp2[0] + + return lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1] + + def boundaryArea(A): """ Returns boundary area for sorting. """ area_A=(A[2]-A[0])*(A[3]-A[1]) return area_A - diff --git a/compiler/gen_stimulus.py b/compiler/gen_stimulus.py index 0c5d988c..cfea17d4 100755 --- a/compiler/gen_stimulus.py +++ b/compiler/gen_stimulus.py @@ -52,11 +52,12 @@ import sram class fake_sram(sram.sram): """ This is an SRAM that doesn't actually create itself, just computes the sizes. """ - def __init__(self, word_size, num_words, num_banks, name): + def __init__(self, word_size, num_words, num_banks, name, num_spare_rows): self.name = name self.word_size = word_size self.num_words = num_words self.num_banks = num_banks + self.num_spare_rows = num_spare_rows c = reload(__import__(OPTS.bitcell)) self.mod_bitcell = getattr(c, OPTS.bitcell) self.bitcell = self.mod_bitcell() @@ -75,7 +76,10 @@ d.period = period # Set the load of outputs and slew of inputs d.set_load_slew(load,slew) # Set the probe address/bit -probe_address = "1" * sram.addr_size +if (self.num_spare_rows == 0): + probe_address = "1" * sram.addr_size +else: + probe_address = "0" + ("1" * sram.addr_size - 1) probe_data = sram.word_size - 1 d.set_probe(probe_address, probe_data) diff --git a/compiler/globals.py b/compiler/globals.py index ab489242..a192ebc9 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -6,8 +6,8 @@ # All rights reserved. # """ -This is called globals.py, but it actually parses all the arguments and performs -the global OpenRAM setup as well. +This is called globals.py, but it actually parses all the arguments +and performs the global OpenRAM setup as well. """ import os import debug @@ -19,12 +19,13 @@ import re import copy import importlib -VERSION = "1.1.2" +VERSION = "1.1.5" NAME = "OpenRAM v{}".format(VERSION) USAGE = "openram.py [options] \nUse -h for help.\n" OPTS = options.options() -CHECKPOINT_OPTS=None +CHECKPOINT_OPTS = None + def parse_args(): """ Parse the optional arguments for OpenRAM """ @@ -32,27 +33,55 @@ def parse_args(): global OPTS option_list = { - optparse.make_option("-b", "--backannotated", action="store_true", dest="use_pex", + optparse.make_option("-b", + "--backannotated", + action="store_true", + dest="use_pex", help="Back annotate simulation"), - optparse.make_option("-o", "--output", dest="output_name", - help="Base output file name(s) prefix", metavar="FILE"), - optparse.make_option("-p", "--outpath", dest="output_path", + optparse.make_option("-o", + "--output", + dest="output_name", + help="Base output file name(s) prefix", + metavar="FILE"), + optparse.make_option("-p", "--outpath", + dest="output_path", help="Output file(s) location"), - optparse.make_option("-i", "--inlinecheck", action="store_true", - help="Enable inline LVS/DRC checks", dest="inline_lvsdrc"), - optparse.make_option("-n", "--nocheck", action="store_false", - help="Disable all LVS/DRC checks", dest="check_lvsdrc"), - optparse.make_option("-v", "--verbose", action="count", dest="debug_level", + optparse.make_option("-i", + "--inlinecheck", + action="store_true", + help="Enable inline LVS/DRC checks", + dest="inline_lvsdrc"), + optparse.make_option("-n", "--nocheck", + action="store_false", + help="Disable all LVS/DRC checks", + dest="check_lvsdrc"), + optparse.make_option("-v", + "--verbose", + action="count", + dest="debug_level", help="Increase the verbosity level"), - optparse.make_option("-t", "--tech", dest="tech_name", + optparse.make_option("-t", + "--tech", + dest="tech_name", help="Technology name"), - optparse.make_option("-s", "--spice", dest="spice_name", + optparse.make_option("-s", + "--spice", + dest="spice_name", help="Spice simulator executable name"), - optparse.make_option("-r", "--remove_netlist_trimming", action="store_false", dest="trim_netlist", + optparse.make_option("-r", + "--remove_netlist_trimming", + action="store_false", + dest="trim_netlist", help="Disable removal of noncritical memory cells during characterization"), - optparse.make_option("-c", "--characterize", action="store_false", dest="analytical_delay", + optparse.make_option("-c", + "--characterize", + action="store_false", + dest="analytical_delay", help="Perform characterization to calculate delays (default is analytical models)"), - optparse.make_option("-d", "--dontpurge", action="store_false", dest="purge_temp", + optparse.make_option("-d", + "--dontpurge", + action="store_false", + dest="purge_temp", help="Don't purge the contents of the temp directory after a successful run") # -h --help is implicit. } @@ -70,9 +99,13 @@ def parse_args(): # Alias SCMOS to 180nm if OPTS.tech_name == "scmos": OPTS.tech_name = "scn4m_subm" + # Alias s8 to sky130 + if OPTS.tech_name == "s8": + OPTS.tech_name = "sky130" return (options, args) + def print_banner(): """ Conditionally print the banner to stdout """ global OPTS @@ -117,13 +150,13 @@ def check_versions(): except: OPTS.coverage = 0 + def init_openram(config_file, is_unit_test=True): - """Initialize the technology, paths, simulators, etc.""" + """ Initialize the technology, paths, simulators, etc. """ - check_versions() - debug.info(1,"Initializing OpenRAM...") + debug.info(1, "Initializing OpenRAM...") setup_paths() @@ -138,18 +171,17 @@ def init_openram(config_file, is_unit_test=True): from sram_factory import factory factory.reset() - setup_bitcell() - global OPTS global CHECKPOINT_OPTS # This is a hack. If we are running a unit test and have checkpointed # the options, load them rather than reading the config file. # This way, the configuration is reloaded at the start of every unit test. - # If a unit test fails, we don't have to worry about restoring the old config values + # If a unit test fails, + # we don't have to worry about restoring the old config values # that may have been tested. if is_unit_test and CHECKPOINT_OPTS: - OPTS.__dict__ = CHECKPOINT_OPTS.__dict__.copy() + OPTS.__dict__ = CHECKPOINT_OPTS.__dict__.copy() return # Import these to find the executables for checkpointing @@ -159,7 +191,8 @@ def init_openram(config_file, is_unit_test=True): # after each unit test if not CHECKPOINT_OPTS: CHECKPOINT_OPTS = copy.copy(OPTS) - + + def setup_bitcell(): """ Determine the correct custom or parameterized bitcell for the design. @@ -169,44 +202,36 @@ def setup_bitcell(): # If we have non-1rw ports, # and the user didn't over-ride the bitcell manually, # figure out the right bitcell to use - if (OPTS.bitcell=="bitcell"): + if (OPTS.bitcell == "bitcell"): - if (OPTS.num_rw_ports==1 and OPTS.num_w_ports==0 and OPTS.num_r_ports==0): + if (OPTS.num_rw_ports == 1 and OPTS.num_w_ports == 0 and OPTS.num_r_ports == 0): OPTS.bitcell = "bitcell" OPTS.replica_bitcell = "replica_bitcell" OPTS.dummy_bitcell = "dummy_bitcell" else: ports = "" - if OPTS.num_rw_ports>0: + if OPTS.num_rw_ports > 0: ports += "{}rw_".format(OPTS.num_rw_ports) - if OPTS.num_w_ports>0: + if OPTS.num_w_ports > 0: ports += "{}w_".format(OPTS.num_w_ports) - if OPTS.num_r_ports>0: + if OPTS.num_r_ports > 0: ports += "{}r".format(OPTS.num_r_ports) - OPTS.bitcell = "bitcell_"+ports - OPTS.replica_bitcell = "replica_bitcell_"+ports - OPTS.dummy_bitcell = "dummy_bitcell_"+ports - else: - OPTS.replica_bitcell = "replica_" + OPTS.bitcell - OPTS.replica_bitcell = "dummy_" + OPTS.bitcell + if ports != "": + OPTS.bitcell_suffix = "_" + ports + OPTS.bitcell = "bitcell" + OPTS.bitcell_suffix # See if bitcell exists - from importlib import find_loader try: __import__(OPTS.bitcell) - __import__(OPTS.replica_bitcell) - __import__(OPTS.dummy_bitcell) except ImportError: # Use the pbitcell if we couldn't find a custom bitcell # or its custom replica bitcell # Use the pbitcell (and give a warning if not in unit test mode) OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell = "replica_pbitcell" - OPTS.replica_bitcell = "dummy_pbitcell" if not OPTS.is_unit_test: debug.warning("Using the parameterized bitcell which may have suboptimal density.") - debug.info(1,"Using bitcell: {}".format(OPTS.bitcell)) + debug.info(1, "Using bitcell: {}".format(OPTS.bitcell)) def get_tool(tool_type, preferences, default_name=None): @@ -215,63 +240,75 @@ def get_tool(tool_type, preferences, default_name=None): one selected and its full path. If default is specified, find that one only and error otherwise. """ - debug.info(2,"Finding {} tool...".format(tool_type)) + debug.info(2, "Finding {} tool...".format(tool_type)) if default_name: - exe_name=find_exe(default_name) + exe_name = find_exe(default_name) if exe_name == None: - debug.error("{0} not found. Cannot find {1} tool.".format(default_name,tool_type),2) + debug.error("{0} not found. Cannot find {1} tool.".format(default_name, + tool_type), + 2) else: - debug.info(1, "Using {0}: {1}".format(tool_type,exe_name)) - return(default_name,exe_name) + debug.info(1, "Using {0}: {1}".format(tool_type, exe_name)) + return(default_name, exe_name) else: for name in preferences: exe_name = find_exe(name) if exe_name != None: - debug.info(1, "Using {0}: {1}".format(tool_type,exe_name)) - return(name,exe_name) + debug.info(1, "Using {0}: {1}".format(tool_type, exe_name)) + return(name, exe_name) else: - debug.info(1, "Could not find {0}, trying next {1} tool.".format(name,tool_type)) + debug.info(1, + "Could not find {0}, trying next {1} tool.".format(name, + tool_type)) else: - return(None,"") + return(None, "") def read_config(config_file, is_unit_test=True): - """ + """ Read the configuration file that defines a few parameters. The config file is just a Python file that defines some config options. This will only actually get read the first time. Subsequent reads will just restore the previous copy (ask mrg) """ global OPTS - - # Create a full path relative to current dir unless it is already an abs path + + # it is already not an abs path, make it one if not os.path.isabs(config_file): config_file = os.getcwd() + "/" + config_file + # Make it a python file if the base name was only given config_file = re.sub(r'\.py$', "", config_file) + + # Expand the user if it is used config_file = os.path.expanduser(config_file) - OPTS.config_file = config_file - # Add the path to the system path so we can import things in the other directory + + OPTS.config_file = config_file + ".py" + # Add the path to the system path + # so we can import things in the other directory dir_name = os.path.dirname(config_file) - file_name = os.path.basename(config_file) + module_name = os.path.basename(config_file) + # Prepend the path to avoid if we are using the example config - sys.path.insert(0,dir_name) + sys.path.insert(0, dir_name) # Import the configuration file of which modules to use debug.info(1, "Configuration file is " + config_file + ".py") try: - config = importlib.import_module(file_name) + config = importlib.import_module(module_name) except: debug.error("Unable to read configuration file: {0}".format(config_file),2) - for k,v in config.__dict__.items(): + OPTS.overridden = {} + for k, v in config.__dict__.items(): # The command line will over-ride the config file # except in the case of the tech name! This is because the tech name # is sometimes used to specify the config file itself (e.g. unit tests) # Note that if we re-read a config file, nothing will get read again! - if not k in OPTS.__dict__ or k=="tech_name": - OPTS.__dict__[k]=v + if k not in OPTS.__dict__ or k == "tech_name": + OPTS.__dict__[k] = v + OPTS.overridden[k] = True # Massage the output path to be an absolute one if not OPTS.output_path.endswith('/'): @@ -281,20 +318,20 @@ def read_config(config_file, is_unit_test=True): debug.info(1, "Output saved in " + OPTS.output_path) # Remember if we are running unit tests to reduce output - OPTS.is_unit_test=is_unit_test + OPTS.is_unit_test = is_unit_test # If we are only generating a netlist, we can't do DRC/LVS if OPTS.netlist_only: - OPTS.check_lvsdrc=False + OPTS.check_lvsdrc = False # If config didn't set output name, make a reasonable default. if (OPTS.output_name == ""): ports = "" - if OPTS.num_rw_ports>0: + if OPTS.num_rw_ports > 0: ports += "{}rw_".format(OPTS.num_rw_ports) - if OPTS.num_w_ports>0: + if OPTS.num_w_ports > 0: ports += "{}w_".format(OPTS.num_w_ports) - if OPTS.num_r_ports>0: + if OPTS.num_r_ports > 0: ports += "{}r_".format(OPTS.num_r_ports) OPTS.output_name = "sram_{0}b_{1}_{2}{3}".format(OPTS.word_size, OPTS.num_words, @@ -302,8 +339,6 @@ def read_config(config_file, is_unit_test=True): OPTS.tech_name) - - def end_openram(): """ Clean up openram for a proper exit """ cleanup_paths() @@ -312,9 +347,7 @@ def end_openram(): import verify verify.print_drc_stats() verify.print_lvs_stats() - verify.print_pex_stats() - - + verify.print_pex_stats() def cleanup_paths(): @@ -323,54 +356,62 @@ def cleanup_paths(): """ global OPTS if not OPTS.purge_temp: - debug.info(0,"Preserving temp directory: {}".format(OPTS.openram_temp)) + debug.info(0, + "Preserving temp directory: {}".format(OPTS.openram_temp)) return elif os.path.exists(OPTS.openram_temp): - debug.info(1,"Purging temp directory: {}".format(OPTS.openram_temp)) - # This annoyingly means you have to re-cd into the directory each debug iteration - #shutil.rmtree(OPTS.openram_temp, ignore_errors=True) + debug.info(1, + "Purging temp directory: {}".format(OPTS.openram_temp)) + # This annoyingly means you have to re-cd into + # the directory each debug iteration + # shutil.rmtree(OPTS.openram_temp, ignore_errors=True) contents = [os.path.join(OPTS.openram_temp, i) for i in os.listdir(OPTS.openram_temp)] for i in contents: if os.path.isfile(i) or os.path.islink(i): os.remove(i) else: shutil.rmtree(i) - def setup_paths(): """ Set up the non-tech related paths. """ - debug.info(2,"Setting up paths...") + debug.info(2, "Setting up paths...") global OPTS try: OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME")) except: - debug.error("$OPENRAM_HOME is not properly defined.",1) - debug.check(os.path.isdir(OPENRAM_HOME),"$OPENRAM_HOME does not exist: {0}".format(OPENRAM_HOME)) + debug.error("$OPENRAM_HOME is not properly defined.", 1) + debug.check(os.path.isdir(OPENRAM_HOME), + "$OPENRAM_HOME does not exist: {0}".format(OPENRAM_HOME)) # Add all of the subdirs to the python path - # These subdirs are modules and don't need to be added: characterizer, verify + # These subdirs are modules and don't need + # to be added: characterizer, verify subdirlist = [ item for item in os.listdir(OPENRAM_HOME) if os.path.isdir(os.path.join(OPENRAM_HOME, item)) ] for subdir in subdirlist: - full_path = "{0}/{1}".format(OPENRAM_HOME,subdir) + full_path = "{0}/{1}".format(OPENRAM_HOME, subdir) debug.check(os.path.isdir(full_path), - "$OPENRAM_HOME/{0} does not exist: {1}".format(subdir,full_path)) - sys.path.append("{0}".format(full_path)) + "$OPENRAM_HOME/{0} does not exist: {1}".format(subdir, full_path)) + if "__pycache__" not in full_path: + sys.path.append("{0}".format(full_path)) if not OPTS.openram_temp.endswith('/'): OPTS.openram_temp += "/" debug.info(1, "Temporary files saved in " + OPTS.openram_temp) - def is_exe(fpath): """ Return true if the given is an executable file that exists. """ return os.path.exists(fpath) and os.access(fpath, os.X_OK) + def find_exe(check_exe): - """ Check if the binary exists in any path dir and return the full path. """ + """ + Check if the binary exists in any path dir + and return the full path. + """ # Check if the preferred spice option exists in the path for path in os.environ["PATH"].split(os.pathsep): exe = os.path.join(path, check_exe) @@ -379,12 +420,14 @@ def find_exe(check_exe): return exe return None + def init_paths(): """ Create the temp and output directory if it doesn't exist """ # make the directory if it doesn't exist try: - debug.info(1,"Creating temp directory: {}".format(OPTS.openram_temp)) + debug.info(1, + "Creating temp directory: {}".format(OPTS.openram_temp)) os.makedirs(OPTS.openram_temp, 0o750) except OSError as e: if e.errno == 17: # errno.EEXIST @@ -398,35 +441,48 @@ def init_paths(): if e.errno == 17: # errno.EEXIST os.chmod(OPTS.output_path, 0o750) except: - debug.error("Unable to make output directory.",-1) + debug.error("Unable to make output directory.", -1) + def set_default_corner(): """ Set the default corner. """ - + + import tech # Set some default options now based on the technology... if (OPTS.process_corners == ""): - OPTS.process_corners = tech.spice["fet_models"].keys() + if OPTS.nominal_corner_only: + OPTS.process_corners = ["TT"] + else: + OPTS.process_corners = tech.spice["fet_models"].keys() if (OPTS.supply_voltages == ""): - OPTS.supply_voltages = tech.spice["supply_voltages"] + if OPTS.nominal_corner_only: + OPTS.supply_voltages = [tech.spice["supply_voltages"][1]] + else: + OPTS.supply_voltages = tech.spice["supply_voltages"] if (OPTS.temperatures == ""): - OPTS.temperatures = tech.spice["temperatures"] + if OPTS.nominal_corner_only: + OPTS.temperatures = [tech.spice["temperatures"][1]] + else: + OPTS.temperatures = tech.spice["temperatures"] def import_tech(): """ Dynamically adds the tech directory to the path and imports it. """ global OPTS - debug.info(2,"Importing technology: " + OPTS.tech_name) + debug.info(2, + "Importing technology: " + OPTS.tech_name) # environment variable should point to the technology dir try: OPENRAM_TECH = os.path.abspath(os.environ.get("OPENRAM_TECH")) except: - debug.error("$OPENRAM_TECH environment variable is not defined.",1) + debug.error("$OPENRAM_TECH environment variable is not defined.", 1) # Add all of the paths for tech_path in OPENRAM_TECH.split(":"): - debug.check(os.path.isdir(tech_path),"$OPENRAM_TECH does not exist: {0}".format(tech_path)) + debug.check(os.path.isdir(tech_path), + "$OPENRAM_TECH does not exist: {0}".format(tech_path)) sys.path.append(tech_path) debug.info(1, "Adding technology path: {}".format(tech_path)) @@ -434,11 +490,10 @@ def import_tech(): try: tech_mod = __import__(OPTS.tech_name) except ImportError: - debug.error("Nonexistent technology_setup_file: {0}.py".format(filename), -1) + debug.error("Nonexistent technology module: {0}".format(OPTS.tech_name), -1) OPTS.openram_tech = os.path.dirname(tech_mod.__file__) + "/" - # Add the tech directory tech_path = OPTS.openram_tech sys.path.append(tech_path) @@ -447,61 +502,68 @@ def import_tech(): except ImportError: debug.error("Could not load tech module.", -1) + # Add custom modules of the technology to the path, if they exist + custom_mod_path = os.path.join(tech_path, "modules/") + if os.path.exists(custom_mod_path): + sys.path.append(custom_mod_path) + def print_time(name, now_time, last_time=None, indentation=2): """ Print a statement about the time delta. """ global OPTS # Don't print during testing - if not OPTS.is_unit_test or OPTS.debug_level>0: + if not OPTS.is_unit_test or OPTS.debug_level > 0: if last_time: time = str(round((now_time-last_time).total_seconds(),1)) + " seconds" else: time = now_time.strftime('%m/%d/%Y %H:%M:%S') - debug.print_raw("{0} {1}: {2}".format("*"*indentation,name,time)) + debug.print_raw("{0} {1}: {2}".format("*"*indentation, name, time)) def report_status(): - """ Check for valid arguments and report the info about the SRAM being generated """ + """ + Check for valid arguments and report the + info about the SRAM being generated + """ global OPTS # Check if all arguments are integers for bits, size, banks - if type(OPTS.word_size)!=int: + if type(OPTS.word_size) != int: debug.error("{0} is not an integer in config file.".format(OPTS.word_size)) - if type(OPTS.num_words)!=int: + if type(OPTS.num_words) != int: debug.error("{0} is not an integer in config file.".format(OPTS.sram_size)) if type(OPTS.write_size) is not int and OPTS.write_size is not None: debug.error("{0} is not an integer in config file.".format(OPTS.write_size)) - + # If a write mask is specified by the user, the mask write size should be the same as # the word size so that an entire word is written at once. if OPTS.write_size is not None: if (OPTS.word_size % OPTS.write_size != 0): debug.error("Write size needs to be an integer multiple of word size.") - # If write size is more than half of the word size, then it doesn't need a write mask. It would be writing + # If write size is more than half of the word size, + # then it doesn't need a write mask. It would be writing # the whole word. if (OPTS.write_size < 1 or OPTS.write_size > OPTS.word_size/2): debug.error("Write size needs to be between 1 bit and {0} bits/2.".format(OPTS.word_size)) - - if not OPTS.tech_name: debug.error("Tech name must be specified in config file.") debug.print_raw("Technology: {0}".format(OPTS.tech_name)) total_size = OPTS.word_size*OPTS.num_words*OPTS.num_banks debug.print_raw("Total size: {} bits".format(total_size)) - if total_size>=2**14: + if total_size >= 2**14: debug.warning("Requesting such a large memory size ({0}) will have a large run-time. ".format(total_size) + "Consider using multiple smaller banks.") debug.print_raw("Word size: {0}\nWords: {1}\nBanks: {2}".format(OPTS.word_size, - OPTS.num_words, - OPTS.num_banks)) + OPTS.num_words, + OPTS.num_banks)) if (OPTS.write_size != OPTS.word_size): debug.print_raw("Write size: {}".format(OPTS.write_size)) debug.print_raw("RW ports: {0}\nR-only ports: {1}\nW-only ports: {2}".format(OPTS.num_rw_ports, - OPTS.num_r_ports, - OPTS.num_w_ports)) + OPTS.num_r_ports, + OPTS.num_w_ports)) if OPTS.netlist_only: debug.print_raw("Netlist only mode (no physical design is being done, netlist_only=False to disable).") @@ -518,9 +580,9 @@ def report_status(): if OPTS.analytical_delay: debug.print_raw("Characterization is disabled (using analytical delay models) (analytical_delay=False to simulate).") else: - if OPTS.spice_name!="": + if OPTS.spice_name != "": debug.print_raw("Performing simulation-based characterization with {}".format(OPTS.spice_name)) if OPTS.trim_netlist: debug.print_raw("Trimming netlist to speed up characterization (trim_netlist=False to disable).") - - + if OPTS.nominal_corner_only: + debug.print_raw("Only characterizing nominal corner.") diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 0fe40ea8..1efc3b13 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -5,25 +5,21 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys -from tech import drc, parameter import debug import design -import math -from math import log,sqrt,ceil -import contact -import pgates from sram_factory import factory +from math import log, ceil +from tech import drc, layer from vector import vector - from globals import OPTS + class bank(design.design): """ Dynamically generated a single bank including bitcell array, - hierarchical_decoder, precharge, (optional column_mux and column decoder), + hierarchical_decoder, precharge, (optional column_mux and column decoder), write driver and sense amplifiers. - This can create up to two ports in any combination: rw, w, r. + This can create up to two ports in any combination: rw, w, r. """ def __init__(self, sram_config, name=""): @@ -31,15 +27,18 @@ class bank(design.design): self.sram_config = sram_config sram_config.set_local_config(self) 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 + if not self.num_spare_cols: + self.num_spare_cols = 0 + if name == "": name = "bank_{0}_{1}".format(self.word_size, self.num_words) design.design.__init__(self, name) - debug.info(2, "create sram of size {0} with {1} words".format(self.word_size,self.num_words)) - + debug.info(2, "create sram of size {0} with {1} words".format(self.word_size, + self.num_words)) # The local control signals are gated when we have bank select logic, # so this prefix will be added to all of the input signals to create @@ -51,17 +50,17 @@ class bank(design.design): 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() - def create_netlist(self): + self.compute_sizes() self.add_modules() self.add_pins() # Must create the replica bitcell array first self.create_instances() - def create_layout(self): self.place_instances() @@ -69,34 +68,33 @@ class bank(design.design): self.route_layout() # Can remove the following, but it helps for debug! - #self.add_lvs_correspondence_points() + # self.add_lvs_correspondence_points() # Remember the bank center for further placement - self.bank_array_ll = self.offset_all_coordinates().scale(-1,-1) + self.bank_array_ll = self.offset_all_coordinates().scale(-1, -1) self.bank_array_ur = self.bitcell_array_inst.ur() self.bank_array_ul = self.bitcell_array_inst.ul() - self.DRC_LVS() def add_pins(self): """ Adding pins for Bank module""" for port in self.read_ports: - for bit in range(self.word_size): - self.add_pin("dout{0}_{1}".format(port,bit),"OUTPUT") + for bit in range(self.word_size + self.num_spare_cols): + self.add_pin("dout{0}_{1}".format(port, bit), "OUTPUT") for port in self.all_ports: - self.add_pin(self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]),"OUTPUT") + self.add_pin(self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]), "OUTPUT") for port in self.write_ports: - for bit in range(self.word_size): - self.add_pin("din{0}_{1}".format(port,bit),"INPUT") + for bit in range(self.word_size + self.num_spare_cols): + self.add_pin("din{0}_{1}".format(port, bit), "INPUT") for port in self.all_ports: for bit in range(self.addr_size): - self.add_pin("addr{0}_{1}".format(port,bit),"INPUT") + self.add_pin("addr{0}_{1}".format(port, bit), "INPUT") # For more than one bank, we have a bank select and name # the signals gated_*. if self.num_banks > 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: @@ -104,12 +102,13 @@ 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 bit in range(self.num_spare_cols): + self.add_pin("bank_spare_wen{0}_{1}".format(port, bit), "INPUT") for port in self.all_ports: self.add_pin("wl_en{0}".format(port), "INPUT") - self.add_pin("vdd","POWER") - self.add_pin("gnd","GROUND") - + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def route_layout(self): """ Create routing amoung the modules """ @@ -126,18 +125,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()) - - + + # Connect the rbl to the port data pin + bl_pin = self.port_data_inst[port].get_pin("rbl_bl") + if port % 2: + pin_pos = bl_pin.uc() + pin_offset = pin_pos + vector(0, self.m3_pitch) + left_right_offset = vector(self.max_x_offset, pin_offset.y) + else: + pin_pos = bl_pin.bc() + pin_offset = pin_pos - vector(0, self.m3_pitch) + left_right_offset = vector(self.min_x_offset, pin_offset.y) + self.add_via_stack_center(from_layer=bl_pin.layer, + to_layer="m3", + offset=pin_offset) + self.add_path(bl_pin.layer, [pin_offset, pin_pos]) + self.add_layout_pin_segment_center(text="rbl_bl{0}".format(port), + layer="m3", + start=left_right_offset, + end=pin_offset) def route_bitlines(self, port): """ Route the bitlines depending on the port type rw, w, or r. """ @@ -148,7 +156,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. """ @@ -163,19 +170,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() @@ -187,7 +193,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): """ @@ -198,29 +203,31 @@ 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 + # by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs + # may be routed in M3 or M4 x_offset = self.central_bus_width[port] + self.port_address.wordline_driver.width if self.col_addr_size > 0: x_offset += self.column_decoder.width + self.col_addr_bus_width - y_offset = 0.5*self.dff.height + self.column_decoder.height + y_offset = 1.25 * self.dff.height + self.column_decoder.height else: y_offset = 0 - self.column_decoder_offsets[port] = vector(-x_offset,-y_offset) + 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: @@ -229,7 +236,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): """ @@ -248,18 +255,23 @@ 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 + # This is also placed so that it's supply rails do not align with the SRAM-level + # control logic to allow control signals to easily pass over in M3 + # by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs + # may be routed in M3 or M4 + x_offset = self.bitcell_array_right + self.central_bus_width[port] + self.port_address.wordline_driver.width if self.col_addr_size > 0: x_offset += self.column_decoder.width + self.col_addr_bus_width - y_offset = self.bitcell_array_top + 0.5*self.dff.height + self.column_decoder.height + y_offset = self.bitcell_array_top + 1.25 * self.dff.height + self.column_decoder.height else: y_offset = self.bitcell_array_top - self.column_decoder_offsets[port] = vector(x_offset,y_offset) + 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: @@ -267,7 +279,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. """ @@ -283,25 +295,27 @@ 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_rows = int(self.num_words / self.words_per_row) + 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 - self.row_addr_size = int(log(self.num_rows, 2)) + self.row_addr_size = ceil(log(self.num_rows, 2)) self.col_addr_size = int(log(self.words_per_row, 2)) self.addr_size = self.col_addr_size + self.row_addr_size - debug.check(self.num_rows*self.num_cols==self.word_size*self.num_words,"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 = [] port_num = 0 for port in range(OPTS.num_rw_ports): - self.input_control_signals.append(["w_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) + self.input_control_signals.append(["s_en{}".format(port_num), "w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) port_num += 1 for port in range(OPTS.num_w_ports): self.input_control_signals.append(["w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)]) @@ -314,33 +328,33 @@ class bank(design.design): self.num_control_lines = [len(x) for x in self.input_control_signals] # The width of this bus is needed to place other modules (e.g. decoder) for each port - self.central_bus_width = [self.m2_pitch*x + self.m2_width for x in self.num_control_lines] + self.central_bus_width = [self.m3_pitch * x + self.m3_width for x in self.num_control_lines] # These will be outputs of the gaters if this is multibank, if not, normal signals. self.control_signals = [] 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]) + # The central bus is the column address (one hot) and row address (binary) 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("well_enclosure_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() @@ -354,40 +368,36 @@ 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, + cols=self.num_cols + self.num_spare_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) self.bitcell_array = factory.create(module_type="replica_bitcell_array", - cols=self.num_cols, + cols=self.num_cols + self.num_spare_cols, rows=self.num_rows, left_rbl=1, right_rbl=1 if len(self.all_ports)>1 else 0, 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 col in range(self.num_cols + self.num_spare_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) @@ -395,23 +405,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]) @@ -421,17 +429,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 + self.num_spare_cols): + temp.append("{0}_{1}".format(self.bl_names[port], col)) + temp.append("{0}_{1}".format(self.br_names[port], col)) if port in self.read_ports: - for bit in range(self.word_size): - temp.append("dout{0}_{1}".format(port,bit)) + for bit in range(self.word_size + self.num_spare_cols): + temp.append("dout{0}_{1}".format(port, bit)) if port in self.write_ports: - for bit in range(self.word_size): - temp.append("din{0}_{1}".format(port,bit)) + for bit in range(self.word_size + self.num_spare_cols): + temp.append("din{0}_{1}".format(port, bit)) # Will be empty if no col addr lines - sel_names = ["sel{0}_{1}".format(port,x) for x in range(self.num_col_addr_lines)] + 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)) @@ -440,17 +448,18 @@ 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"]) + for bit in range(self.num_spare_cols): + temp.append("bank_spare_wen{0}_{1}".format(port, bit)) + temp.extend(["vdd", "gnd"]) self.connect_inst(temp) - 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" @@ -459,91 +468,91 @@ 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. """ - self.dff = factory.create(module_type="dff") + self.dff =factory.create(module_type="dff") if self.col_addr_size == 0: return elif self.col_addr_size == 1: - self.column_decoder = factory.create(module_type="pinvbuf", height=self.dff.height) + self.column_decoder = factory.create(module_type="pinvbuf", + height=self.dff.height) elif self.col_addr_size == 2: - self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", height=self.dff.height) + self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", + height=self.dff.height) + elif self.col_addr_size == 3: - self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", height=self.dff.height) + self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", + height=self.dff.height) else: # No error checking before? - debug.error("Invalid column decoder?",-1) + 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. """ @@ -551,7 +560,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) @@ -563,24 +572,37 @@ 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 """ + # Copy only the power pins already on the power layer + # (this won't add vias to internal bitcell pins, for example) for inst in self.insts: - self.copy_power_pins(inst,"vdd") - self.copy_power_pins(inst,"gnd") + self.copy_power_pins(inst, "vdd", add_vias=False) + self.copy_power_pins(inst, "gnd", add_vias=False) + + # If we use the pinvbuf as the decoder, we need to add power pins. + # Other decoders already have them. + if self.col_addr_size == 1: + for port in self.all_ports: + inst = self.column_decoder_inst[port] + for pin_name in ["vdd", "gnd"]: + pin_list = inst.get_pins(pin_name) + for pin in pin_list: + self.add_power_pin(pin_name, + pin.center(), + start_layer=pin.layer) def route_bank_select(self, port): """ Route the bank select logic. """ @@ -595,7 +617,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]) @@ -603,26 +625,25 @@ class bank(design.design): # Connect the inverter output to the central bus out_pos = self.bank_select_inst[port].get_pin(gated_bank_sel_signals[signal]).rc() name = self.control_signals[port][signal] - bus_pos = vector(self.bus_xoffset[port][name].x, out_pos.y) - self.add_path("metal3",[out_pos, bus_pos]) - self.add_via_center(layers=("metal2", "via2", "metal3"), + bus_pos = vector(self.bus_pins[port][name].cx(), out_pos.y) + self.add_path("m3", [out_pos, bus_pos]) + self.add_via_center(layers=self.m2_stack, offset=bus_pos) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=out_pos) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=out_pos) - 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 @@ -632,7 +653,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. """ @@ -640,36 +660,35 @@ class bank(design.design): # Overall central bus width. It includes all the column mux lines, # and control lines. - self.bus_xoffset = [None]*len(self.all_ports) + self.bus_pins = [None] * len(self.all_ports) # Port 0 # The bank is at (0,0), so this is to the left of the y-axis. - # 2 pitches on the right for vias/jogs to access the inputs - control_bus_offset = vector(-self.m2_pitch * self.num_control_lines[0] - self.m2_pitch, self.min_y_offset) + # 2 pitches on the right for vias/jogs to access the inputs + control_bus_offset = vector(-self.m3_pitch * self.num_control_lines[0] - self.m3_pitch, self.min_y_offset) # The control bus is routed up to two pitches below the bitcell array - control_bus_length = self.main_bitcell_array_bottom - self.min_y_offset - 2*self.m1_pitch - self.bus_xoffset[0] = self.create_bus(layer="metal2", - pitch=self.m2_pitch, - offset=control_bus_offset, - names=self.control_signals[0], - length=control_bus_length, - vertical=True, - make_pins=(self.num_banks==1)) + control_bus_length = self.main_bitcell_array_bottom - self.min_y_offset - 2 * self.m1_pitch + self.bus_pins[0] = self.create_bus(layer="m2", + offset=control_bus_offset, + names=self.control_signals[0], + length=control_bus_length, + vertical=True, + make_pins=(self.num_banks==1), + pitch=self.m3_pitch) # Port 1 if len(self.all_ports)==2: # 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_offset = vector(self.bitcell_array_right + self.m2_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.m3_pitch, self.max_y_offset - control_bus_length) # The bus for the right port is reversed so that the rbl_wl is closest to the array - self.bus_xoffset[1] = self.create_bus(layer="metal2", - pitch=self.m2_pitch, - offset=control_bus_offset, - names=list(reversed(self.control_signals[1])), - length=control_bus_length, - vertical=True, - make_pins=(self.num_banks==1)) - + self.bus_pins[1] = self.create_bus(layer="m2", + offset=control_bus_offset, + names=list(reversed(self.control_signals[1])), + length=control_bus_length, + vertical=True, + make_pins=(self.num_banks==1), + pitch=self.m3_pitch) def route_port_data_to_bitcell_array(self, port): """ Routing of BL and BR between port data and bitcell array """ @@ -677,11 +696,24 @@ 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() + "_{}" - self.connect_bitlines(inst1=inst1, inst2=inst2, num_bits=self.num_cols, - inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_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 spare bitlines + for i in range(self.num_spare_cols): + self.connect_bitline(inst1, inst2, inst1_bl_name.format(self.num_cols+i), "spare" + inst2_bl_name.format(i)) + self.connect_bitline(inst1, inst2, inst1_br_name.format(self.num_cols+i), "spare" + inst2_br_name.format(i)) # Connect the replica bitlines rbl_bl_name=self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]) @@ -689,20 +721,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): + for bit in range(self.word_size + self.num_spare_cols): data_pin = self.port_data_inst[port].get_pin("dout_{0}".format(bit)) - self.add_layout_pin_rect_center(text="dout{0}_{1}".format(port,bit), - layer=data_pin.layer, + 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 """ @@ -710,26 +739,27 @@ 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): + for row in range(self.word_size + self.num_spare_cols): 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: + if self.write_size: for row in range(self.num_wmasks): wmask_name = "bank_wmask_{}".format(row) bank_wmask_name = "bank_wmask{0}_{1}".format(port, row) self.copy_layout_pin(self.port_data_inst[port], wmask_name, bank_wmask_name) - - + + for col in range(self.num_spare_cols): + sparecol_name = "bank_spare_wen{}".format(col) + bank_sparecol_name = "bank_spare_wen{0}_{1}".format(port, col) + self.copy_layout_pin(self.port_data_inst[port], sparecol_name, bank_sparecol_name) def channel_route_bitlines(self, inst1, inst2, num_bits, inst1_bl_name="bl_{}", inst1_br_name="br_{}", @@ -747,19 +777,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.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). """ @@ -780,14 +809,16 @@ 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="bl_{}", inst1_br_name="br_{}", - inst2_bl_name="bl_{}", inst2_br_name="br_{}"): + inst1_bl_name, inst1_br_name, + inst2_bl_name, inst2_br_name): """ Connect the bl and br of two modules. """ @@ -795,14 +826,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) @@ -812,36 +842,54 @@ 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) - self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) - + driver_wl_pin = self.port_address_inst[port].get_pin("wl_{}".format(row)) + driver_wl_pos = driver_wl_pin.rc() + bitcell_wl_pin = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row)) + bitcell_wl_pos = bitcell_wl_pin.lc() + mid1 = driver_wl_pos.scale(0, 1) + vector(0.5 * self.port_address_inst[port].rx() + 0.5 * self.bitcell_array_inst.lx(), 0) + mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0.5, 1) + self.add_path(driver_wl_pin.layer, [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + self.add_via_stack_center(from_layer=driver_wl_pin.layer, + to_layer=bitcell_wl_pin.layer, + offset=bitcell_wl_pos, + directions=("H", "H")) def route_port_address_right(self, port): """ Connecting Wordline driver output to Bitcell WL connection """ for row in range(self.num_rows): # The mid guarantees we exit the input cell to the right. - driver_wl_pos = self.port_address_inst[port].get_pin("wl_{}".format(row)).lc() - bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.wl_names[port]+"_{}".format(row)).rc() - mid1 = driver_wl_pos.scale(0,1) + vector(0.5*self.port_address_inst[port].lx() + 0.5*self.bitcell_array_inst.rx(),0) - mid2 = mid1.scale(1,0)+bitcell_wl_pos.scale(0,1) - self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + driver_wl_pin = self.port_address_inst[port].get_pin("wl_{}".format(row)) + driver_wl_pos = driver_wl_pin.lc() + bitcell_wl_pin = self.bitcell_array_inst.get_pin(self.wl_names[port] + "_{}".format(row)) + bitcell_wl_pos = bitcell_wl_pin.rc() + mid1 = driver_wl_pos.scale(0, 1) + vector(0.5 * self.port_address_inst[port].lx() + 0.5 * self.bitcell_array_inst.rx(), 0) + mid2 = mid1.scale(1, 0) + bitcell_wl_pos.scale(0, 1) + self.add_path(driver_wl_pin.layer, [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + self.add_via_stack_center(from_layer=driver_wl_pin.layer, + to_layer=bitcell_wl_pin.layer, + offset=bitcell_wl_pos, + directions=("H", "H")) def route_column_address_lines(self, port): """ Connecting the select lines of column mux to the address bus """ if not self.col_addr_size>0: return + if OPTS.tech_name == "sky130": + stack = self.m2_stack + pitch = self.m3_pitch + else: + stack = self.m1_stack + pitch = self.m2_pitch + if self.col_addr_size == 1: # Connect to sel[0] and sel[1] 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 = [] @@ -850,13 +898,13 @@ 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 + 1) * pitch, 0) else: - offset = self.column_decoder_inst[port].lr() + vector(self.m2_pitch, 0) + offset = self.column_decoder_inst[port].lr() + vector(pitch, 0) decode_pins = [self.column_decoder_inst[port].get_pin(x) for x in decode_names] @@ -864,11 +912,13 @@ class bank(design.design): column_mux_pins = [self.port_data_inst[port].get_pin(x) for x in sel_names] route_map = list(zip(decode_pins, column_mux_pins)) - self.create_vertical_channel_route(route_map, offset) - + self.create_vertical_channel_route(route_map, + offset, + 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. """ @@ -877,7 +927,7 @@ class bank(design.design): wl_name = "wl_{}".format(i) wl_pin = self.bitcell_array_inst.get_pin(wl_name) self.add_label(text=wl_name, - layer="metal1", + layer="m1", offset=wl_pin.center()) # Add the bitline names @@ -887,110 +937,114 @@ class bank(design.design): bl_pin = self.bitcell_array_inst.get_pin(bl_name) br_pin = self.bitcell_array_inst.get_pin(br_name) self.add_label(text=bl_name, - layer="metal2", + layer="m2", offset=bl_pin.center()) self.add_label(text=br_name, - layer="metal2", + layer="m2", offset=br_pin.center()) - # # Add the data output names to the sense amp output + # # 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="metal2", + # 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="metal1", + 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 - write_inst = 0 - read_inst = 0 - connection = [] - connection.append((self.prefix+"p_en_bar{}".format(port), self.port_data_inst[port].get_pin("p_en_bar").lc())) + connection.append((self.prefix + "p_en_bar{}".format(port), + self.port_data_inst[port].get_pin("p_en_bar"))) rbl_wl_name = self.bitcell_array.get_rbl_wl_name(self.port_rbl_map[port]) - connection.append((self.prefix+"wl_en{}".format(port), self.bitcell_array_inst.get_pin(rbl_wl_name).lc())) + connection.append((self.prefix + "wl_en{}".format(port), + self.bitcell_array_inst.get_pin(rbl_wl_name))) if port in self.write_ports: - if port % 2: - connection.append((self.prefix+"w_en{}".format(port), self.port_data_inst[port].get_pin("w_en").rc())) - else: - connection.append((self.prefix+"w_en{}".format(port), self.port_data_inst[port].get_pin("w_en").lc())) + connection.append((self.prefix + "w_en{}".format(port), + self.port_data_inst[port].get_pin("w_en"))) if port in self.read_ports: - connection.append((self.prefix+"s_en{}".format(port), self.port_data_inst[port].get_pin("s_en").lc())) + connection.append((self.prefix + "s_en{}".format(port), + self.port_data_inst[port].get_pin("s_en"))) - for (control_signal, pin_pos) in connection: - control_mid_pos = self.bus_xoffset[port][control_signal] - control_pos = vector(self.bus_xoffset[port][control_signal].x ,pin_pos.y) - self.add_wire(("metal1","via1","metal2"), [control_mid_pos, control_pos, pin_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=control_pos) + for (control_signal, pin) in connection: + control_pin = self.bus_pins[port][control_signal] + control_pos = vector(control_pin.cx(), pin.cy()) + # If the y doesn't overlap the bus, add a segment + if pin.cy() < control_pin.by(): + self.add_path("m2", [control_pos, control_pin.bc()]) + elif pin.cy() > control_pin.uy(): + self.add_path("m2", [control_pos, control_pin.uc()]) + self.add_path(pin.layer, [control_pos, pin.center()]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer="m2", + offset=control_pos) - # clk to wordline_driver - control_signal = self.prefix+"wl_en{}".format(port) - 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 - control_x_offset = self.bus_xoffset[port][control_signal].x + mid_pos = pin_pos - vector(0, 2 * self.m2_gap) # to route down to the top of the bus + control_x_offset = self.bus_pins[port][control_signal].cx() control_pos = vector(control_x_offset, mid_pos.y) - self.add_wire(("metal1","via1","metal2"),[pin_pos, mid_pos, control_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_wire(self.m1_stack, [pin_pos, mid_pos, control_pos]) + self.add_via_center(layers=self.m1_stack, offset=control_pos) - def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True): + def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True): """Get the all the stage efforts for each stage in the path within the bank clk_buf to a wordline""" - #Decoder is assumed to have settled before the negative edge of the clock. Delay model relies on this assumption + # 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): @@ -1001,5 +1055,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/bank_select.py b/compiler/modules/bank_select.py index 296cef8b..b6246268 100644 --- a/compiler/modules/bank_select.py +++ b/compiler/modules/bank_select.py @@ -42,10 +42,12 @@ class bank_select(design.design): self.place_instances() self.route_instances() + self.height = max([x.uy() for x in self.inv_inst]) + self.m1_width + self.width = max([x.rx() for x in self.inv_inst]) + self.add_boundary() self.DRC_LVS() - def add_pins(self): # Number of control lines in the bus @@ -61,19 +63,18 @@ class bank_select(design.design): if (self.port == "rw") or (self.port == "r"): self.input_control_signals.append("s_en") # These will be outputs of the gaters if this is multibank - self.control_signals = ["gated_"+str for str in self.input_control_signals] + self.control_signals = ["gated_" + str for str in self.input_control_signals] self.add_pin_list(self.input_control_signals, "INPUT") self.add_pin("bank_sel") self.add_pin_list(self.control_signals, "OUTPUT") - self.add_pin("vdd","POWER") - self.add_pin("gnd","GROUND") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def add_modules(self): """ Create modules for later instantiation """ - self.bitcell = factory.create(module_type="bitcell") - - height = self.bitcell.height + drc("poly_to_active") + self.dff = factory.create(module_type="dff") + height = self.dff.height + drc("poly_to_active") # 1x Inverter self.inv_sel = factory.create(module_type="pinv", height=height) @@ -94,20 +95,15 @@ class bank_select(design.design): def calculate_module_offsets(self): - self.xoffset_nand = self.inv4x.width + 2*self.m2_pitch + drc("pwell_to_nwell") - self.xoffset_nor = self.inv4x.width + 2*self.m2_pitch + drc("pwell_to_nwell") - self.xoffset_inv = max(self.xoffset_nand + self.nand2.width, self.xoffset_nor + self.nor2.width) - self.xoffset_bank_sel_inv = 0 + self.xoffset_nand = self.inv4x.width + 3 * self.m2_pitch + drc("pwell_to_nwell") + self.xoffset_nor = self.inv4x.width + 3 * self.m2_pitch + drc("pwell_to_nwell") + self.xoffset_bank_sel_inv = 0 self.xoffset_inputs = 0 - self.yoffset_maxpoint = self.num_control_lines * self.inv4x.height - # Include the M1 pitches for the supply rails and spacing - self.height = self.yoffset_maxpoint + 2*self.m1_pitch - self.width = self.xoffset_inv + self.inv4x.width def create_instances(self): - self.bank_sel_inv=self.add_inst(name="bank_sel_inv", + self.bank_sel_inv=self.add_inst(name="bank_sel_inv", mod=self.inv_sel) self.connect_inst(["bank_sel", "bank_sel_bar", "vdd", "gnd"]) @@ -124,36 +120,36 @@ class bank_select(design.design): # (writes occur on clk low) if input_name in ("clk_buf"): - self.logic_inst.append(self.add_inst(name=name_nor, - mod=self.nor2)) + self.logic_inst.append(self.add_inst(name=name_nor, + mod=self.nor2)) self.connect_inst([input_name, "bank_sel_bar", - gated_name+"_temp_bar", + gated_name + "_temp_bar", "vdd", "gnd"]) # They all get inverters on the output - self.inv_inst.append(self.add_inst(name=name_inv, + self.inv_inst.append(self.add_inst(name=name_inv, mod=self.inv4x_nor)) - self.connect_inst([gated_name+"_temp_bar", + self.connect_inst([gated_name + "_temp_bar", gated_name, "vdd", "gnd"]) # the rest are AND (nand2+inv) gates else: - self.logic_inst.append(self.add_inst(name=name_nand, + self.logic_inst.append(self.add_inst(name=name_nand, mod=self.nand2)) self.connect_inst([input_name, "bank_sel", - gated_name+"_temp_bar", + gated_name + "_temp_bar", "vdd", "gnd"]) # They all get inverters on the output - self.inv_inst.append(self.add_inst(name=name_inv, + self.inv_inst.append(self.add_inst(name=name_inv, mod=self.inv4x)) - self.connect_inst([gated_name+"_temp_bar", + self.connect_inst([gated_name + "_temp_bar", gated_name, "vdd", "gnd"]) @@ -176,9 +172,9 @@ class bank_select(design.design): if i == 0: y_offset = 0 else: - y_offset = self.inv4x_nor.height + self.inv4x.height * (i-1) + y_offset = self.inv4x_nor.height + self.inv4x.height * (i - 1) - if i%2: + if i % 2: y_offset += self.inv4x.height mirror = "MX" else: @@ -197,9 +193,8 @@ class bank_select(design.design): mirror=mirror) # They all get inverters on the output - inv_inst.place(offset=[self.xoffset_inv, y_offset], + inv_inst.place(offset=[logic_inst.rx(), y_offset], mirror=mirror) - def route_instances(self): @@ -208,72 +203,72 @@ class bank_select(design.design): xoffset_bank_sel = bank_sel_inv_pin.lx() bank_sel_line_pos = vector(xoffset_bank_sel, 0) bank_sel_line_end = vector(xoffset_bank_sel, self.yoffset_maxpoint) - self.add_path("metal2", [bank_sel_line_pos, bank_sel_line_end]) - self.add_via_center(layers=("metal1","via1","metal2"), - offset=bank_sel_inv_pin.lc()) + self.add_path("m2", [bank_sel_line_pos, bank_sel_line_end]) + self.add_via_center(layers=self.m1_stack, + offset=bank_sel_inv_pin.center()) # Route the pin to the left edge as well bank_sel_pin_pos=vector(0, 0) bank_sel_pin_end=vector(bank_sel_line_pos.x, bank_sel_pin_pos.y) self.add_layout_pin_segment_center(text="bank_sel", - layer="metal3", + layer="m3", start=bank_sel_pin_pos, end=bank_sel_pin_end) - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_via_center(layers=self.m2_stack, offset=bank_sel_pin_end, - directions=("H","H")) + directions=("H", "H")) # bank_sel_bar is vertical wire bank_sel_bar_pin = self.bank_sel_inv.get_pin("Z") xoffset_bank_sel_bar = bank_sel_bar_pin.rx() self.add_label_pin(text="bank_sel_bar", - layer="metal2", - offset=vector(xoffset_bank_sel_bar, 0), + layer="m2", + offset=vector(xoffset_bank_sel_bar, 0), height=self.inv4x.height) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=bank_sel_bar_pin.rc()) - for i in range(self.num_control_lines): logic_inst = self.logic_inst[i] inv_inst = self.inv_inst[i] input_name = self.input_control_signals[i] - gated_name = self.control_signals[i] + gated_name = self.control_signals[i] if input_name in ("clk_buf"): xoffset_bank_signal = xoffset_bank_sel_bar else: xoffset_bank_signal = xoffset_bank_sel # Connect the logic output to inverter input - pre = logic_inst.get_pin("Z").lc() - out_position = logic_inst.get_pin("Z").rc() + vector(0.5*self.m1_width,0) - in_position = inv_inst.get_pin("A").lc() + vector(0.5*self.m1_width,0) - post = inv_inst.get_pin("A").rc() - self.add_path("metal1", [pre, out_position, in_position, post]) + out_pin = logic_inst.get_pin("Z") + out_pos = out_pin.center() + in_pin = inv_inst.get_pin("A") + in_pos = in_pin.center() + mid1_pos = vector(0.5 * (out_pos.x + in_pos.x), out_pos.y) + mid2_pos = vector(0.5 * (out_pos.x + in_pos.x), in_pos.y) + self.add_path("m1", [out_pos, mid1_pos, mid2_pos, in_pos]) - - # Connect the logic B input to bank_sel/bank_sel_bar - logic_pos = logic_inst.get_pin("B").lc() - vector(0.5*contact.m1m2.height,0) + # Connect the logic B input to bank_sel / bank_sel_bar + logic_pin = logic_inst.get_pin("B") + logic_pos = logic_pin.center() input_pos = vector(xoffset_bank_signal, logic_pos.y) - self.add_path("metal2",[logic_pos, input_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=logic_pos, - directions=("H","H")) + self.add_path("m3", [logic_pos, input_pos]) + self.add_via_center(self.m2_stack, + input_pos) + self.add_via_stack_center(from_layer=logic_pin.layer, + to_layer="m3", + offset=logic_pos) - # Connect the logic A input to the input pin - logic_pos = logic_inst.get_pin("A").lc() - input_pos = vector(0,logic_pos.y) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=logic_pos, - directions=("H","H")) - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=logic_pos, - directions=("H","H")) + logic_pin = logic_inst.get_pin("A") + logic_pos = logic_pin.center() + input_pos = vector(0, logic_pos.y) + self.add_via_stack_center(from_layer=logic_pin.layer, + to_layer="m3", + offset=logic_pos) self.add_layout_pin_segment_center(text=input_name, - layer="metal3", + layer="m3", start=input_pos, end=logic_pos) @@ -285,7 +280,6 @@ class bank_select(design.design): width=inv_inst.rx() - out_pin.lx(), height=out_pin.height()) - # Find the x offsets for where the vias/pins should be placed a_xoffset = self.logic_inst[0].lx() b_xoffset = self.inv_inst[0].lx() @@ -293,35 +287,35 @@ class bank_select(design.design): # Route both supplies for n in ["vdd", "gnd"]: supply_pin = self.inv_inst[num].get_pin(n) - supply_offset = supply_pin.ll().scale(0,1) - self.add_rect(layer="metal1", + supply_offset = supply_pin.ll().scale(0, 1) + self.add_rect(layer="m1", offset=supply_offset, width=self.width) # Add pins in two locations for xoffset in [a_xoffset, b_xoffset]: pin_pos = vector(xoffset, supply_pin.cy()) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=pin_pos, - directions=("H","H")) - self.add_via_center(layers=("metal2", "via2", "metal3"), + directions=("H", "H")) + self.add_via_center(layers=self.m2_stack, offset=pin_pos, - directions=("H","H")) + directions=("H", "H")) self.add_layout_pin_rect_center(text=n, - layer="metal3", + layer="m3", offset=pin_pos) # Add vdd/gnd supply rails - gnd_pin = inv_inst.get_pin("gnd") + gnd_pin = self.inv_inst[num].get_pin("gnd") left_gnd_pos = vector(0, gnd_pin.cy()) self.add_layout_pin_segment_center(text="gnd", - layer="metal1", + layer="m1", start=left_gnd_pos, end=gnd_pin.rc()) - vdd_pin = inv_inst.get_pin("vdd") + vdd_pin = self.inv_inst[num].get_pin("vdd") left_vdd_pos = vector(0, vdd_pin.cy()) self.add_layout_pin_segment_center(text="vdd", - layer="metal1", + layer="m1", start=left_vdd_pos, end=vdd_pin.rc()) diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index b1b61487..97b681e9 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -5,27 +5,20 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import debug -import design +from bitcell_base_array import bitcell_base_array from tech import drc, spice -from vector import vector from globals import OPTS from sram_factory import factory -import logical_effort -class bitcell_array(design.design): + +class bitcell_array(bitcell_base_array): """ Creates a rows x cols array of memory cells. Assumes bit-lines and word line is connected by abutment. Connects the word lines and bit lines. """ - def __init__(self, cols, rows, name): - design.design.__init__(self, name) - debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) - self.add_comment("rows: {0} cols: {1}".format(rows, cols)) - - self.column_size = cols - self.row_size = rows + def __init__(self, cols, rows, name, column_offset=0): + super().__init__(cols, rows, name, column_offset) self.create_netlist() if not OPTS.netlist_only: @@ -33,8 +26,7 @@ class bitcell_array(design.design): # We don't offset this because we need to align # the replica bitcell in the control logic - #self.offset_all_coordinates() - + # self.offset_all_coordinates() def create_netlist(self): """ Create and connect the netlist """ @@ -44,27 +36,7 @@ class bitcell_array(design.design): def create_layout(self): - # We increase it by a well enclosure so the precharges don't overlap our wells - self.height = self.row_size*self.cell.height - self.width = self.column_size*self.cell.width - - xoffset = 0.0 - for col in range(self.column_size): - yoffset = 0.0 - for row in range(self.row_size): - name = "bit_r{0}_c{1}".format(row, col) - - if row % 2: - tempy = yoffset + self.cell.height - dir_key = "MX" - else: - tempy = yoffset - dir_key = "" - - self.cell_inst[row,col].place(offset=[xoffset, tempy], - mirror=dir_key) - yoffset += self.cell.height - xoffset += self.cell.width + self.place_array("bit_r{0}_c{1}") self.add_layout_pins() @@ -72,98 +44,35 @@ class bitcell_array(design.design): self.DRC_LVS() - def add_pins(self): - row_list = self.cell.get_all_wl_names() - column_list = self.cell.get_all_bitline_names() - for col in range(self.column_size): - for cell_column in column_list: - self.add_pin(cell_column+"_{0}".format(col), "INOUT") - for row in range(self.row_size): - for cell_row in row_list: - self.add_pin(cell_row+"_{0}".format(row), "INPUT") - self.add_pin("vdd", "POWER") - self.add_pin("gnd", "GROUND") - def add_modules(self): """ Add the modules used in this design """ self.cell = factory.create(module_type="bitcell") self.add_mod(self.cell) - def get_bitcell_pins(self, col, row): - """ Creates a list of connections in the bitcell, - indexed by column and row, for instance use in bitcell_array """ - - bitcell_pins = [] - - pin_names = self.cell.get_all_bitline_names() - for pin in pin_names: - bitcell_pins.append(pin+"_{0}".format(col)) - pin_names = self.cell.get_all_wl_names() - for pin in pin_names: - bitcell_pins.append(pin+"_{0}".format(row)) - bitcell_pins.append("vdd") - bitcell_pins.append("gnd") - - return bitcell_pins - - def create_instances(self): """ Create the module instances used in this design """ self.cell_inst = {} for col in range(self.column_size): for row in range(self.row_size): name = "bit_r{0}_c{1}".format(row, col) - self.cell_inst[row,col]=self.add_inst(name=name, - mod=self.cell) + self.cell_inst[row, col]=self.add_inst(name=name, + mod=self.cell) self.connect_inst(self.get_bitcell_pins(col, row)) - def add_layout_pins(self): - """ Add the layout pins """ - - row_list = self.cell.get_all_wl_names() - column_list = self.cell.get_all_bitline_names() - - for col in range(self.column_size): - for cell_column in column_list: - bl_pin = self.cell_inst[0,col].get_pin(cell_column) - self.add_layout_pin(text=cell_column+"_{0}".format(col), - layer=bl_pin.layer, - offset=bl_pin.ll().scale(1,0), - width=bl_pin.width(), - height=self.height) - - for row in range(self.row_size): - for cell_row in row_list: - wl_pin = self.cell_inst[row,0].get_pin(cell_row) - self.add_layout_pin(text=cell_row+"_{0}".format(row), - layer=wl_pin.layer, - offset=wl_pin.ll().scale(0,1), - width=self.width, - height=wl_pin.height()) - - # For every second row and column, add a via for gnd and vdd - for row in range(self.row_size): - for col in range(self.column_size): - inst = self.cell_inst[row,col] - for pin_name in ["vdd", "gnd"]: - for pin in inst.get_pins(pin_name): - self.add_power_pin(name=pin_name, loc=pin.center(), vertical=True, start_layer=pin.layer) - def analytical_power(self, corner, load): """Power of Bitcell array and bitline in nW.""" - from tech import drc, parameter # Dynamic Power from Bitline bl_wire = self.gen_bl_wire() - cell_load = 2 * bl_wire.return_input_cap() + cell_load = 2 * bl_wire.return_input_cap() bl_swing = OPTS.rbl_delay_percentage freq = spice["default_event_frequency"] bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing) - #Calculate the bitcell power which currently only includes leakage + # Calculate the bitcell power which currently only includes leakage cell_power = self.cell.analytical_power(corner, load) - #Leakage power grows with entire array and bitlines. + # Leakage power grows with entire array and bitlines. total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size, cell_power.leakage * self.column_size * self.row_size) return total_power @@ -173,8 +82,8 @@ class bitcell_array(design.design): width = 0 else: width = self.width - wl_wire = self.generate_rc_net(int(self.column_size), width, drc("minwidth_metal1")) - wl_wire.wire_c = 2*spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell + wl_wire = self.generate_rc_net(int(self.column_size), width, drc("minwidth_m1")) + wl_wire.wire_c = 2 * spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell return wl_wire def gen_bl_wire(self): @@ -183,26 +92,26 @@ class bitcell_array(design.design): else: height = self.height bl_pos = 0 - bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), height, drc("minwidth_metal1")) + bl_wire = self.generate_rc_net(int(self.row_size - bl_pos), height, drc("minwidth_m1")) bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell return bl_wire def get_wordline_cin(self): """Get the relative input capacitance from the wordline connections in all the bitcell""" - #A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns + # A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns bitcell_wl_cin = self.cell.get_wl_cin() total_cin = bitcell_wl_cin * self.column_size return total_cin def graph_exclude_bits(self, targ_row, targ_col): """Excludes bits in column from being added to graph except target""" - #Function is not robust with column mux configurations + # Function is not robust with column mux configurations for row in range(self.row_size): for col in range(self.column_size): if row == targ_row and col == targ_col: continue - self.graph_inst_exclude.add(self.cell_inst[row,col]) + self.graph_inst_exclude.add(self.cell_inst[row, col]) def get_cell_name(self, inst_name, row, col): - """Gets the spice name of the target bitcell.""" - return inst_name+'.x'+self.cell_inst[row,col].name, self.cell_inst[row,col] + """Gets the spice name of the target bitcell.""" + return inst_name + '.x' + self.cell_inst[row, col].name, self.cell_inst[row, col] diff --git a/compiler/modules/bitcell_base_array.py b/compiler/modules/bitcell_base_array.py new file mode 100644 index 00000000..7d241b4d --- /dev/null +++ b/compiler/modules/bitcell_base_array.py @@ -0,0 +1,155 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +import design +from tech import cell_properties + + +class bitcell_base_array(design.design): + """ + Abstract base class for bitcell-arrays -- bitcell, dummy + """ + def __init__(self, cols, rows, name, column_offset): + design.design.__init__(self, name) + debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) + self.add_comment("rows: {0} cols: {1}".format(rows, cols)) + + self.column_size = cols + self.row_size = rows + self.column_offset = column_offset + + def get_all_bitline_names(self): + + res = list() + bitline_names = self.cell.get_all_bitline_names() + + # We have to keep the order of self.pins, otherwise we connect + # it wrong in the spice netlist + for pin in self.pins: + for bl_name in bitline_names: + if bl_name in pin: + res.append(pin) + return res + + def get_all_wordline_names(self): + + res = list() + wordline_names = self.cell.get_all_wl_names() + + # We have to keep the order of self.pins, otherwise we connect + # it wrong in the spice netlist + for pin in self.pins: + for wl_name in wordline_names: + if wl_name in pin: + res.append(pin) + return res + + def add_pins(self): + row_list = self.cell.get_all_wl_names() + column_list = self.cell.get_all_bitline_names() + for col in range(self.column_size): + for cell_column in column_list: + self.add_pin(cell_column+"_{0}".format(col), "INOUT") + for row in range(self.row_size): + for cell_row in row_list: + self.add_pin(cell_row+"_{0}".format(row), "INPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def get_bitcell_pins(self, col, row): + """ Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array """ + + bitcell_pins = [] + + pin_names = self.cell.get_all_bitline_names() + for pin in pin_names: + bitcell_pins.append(pin + "_{0}".format(col)) + pin_names = self.cell.get_all_wl_names() + for pin in pin_names: + bitcell_pins.append(pin + "_{0}".format(row)) + bitcell_pins.append("vdd") + bitcell_pins.append("gnd") + + return bitcell_pins + + def add_layout_pins(self): + """ Add the layout pins """ + + row_list = self.cell.get_all_wl_names() + column_list = self.cell.get_all_bitline_names() + + for col in range(self.column_size): + for cell_column in column_list: + bl_pin = self.cell_inst[0, col].get_pin(cell_column) + self.add_layout_pin(text=cell_column + "_{0}".format(col), + layer=bl_pin.layer, + offset=bl_pin.ll().scale(1, 0), + width=bl_pin.width(), + height=self.height) + + for row in range(self.row_size): + for cell_row in row_list: + wl_pin = self.cell_inst[row, 0].get_pin(cell_row) + self.add_layout_pin(text=cell_row + "_{0}".format(row), + layer=wl_pin.layer, + offset=wl_pin.ll().scale(0, 1), + width=self.width, + height=wl_pin.height()) + + # Copy a vdd/gnd layout pin from every cell + for row in range(self.row_size): + for col in range(self.column_size): + inst = self.cell_inst[row, col] + for pin_name in ["vdd", "gnd"]: + self.copy_layout_pin(inst, pin_name) + + def _adjust_x_offset(self, xoffset, col, col_offset): + tempx = xoffset + dir_y = False + # If we mirror the current cell on the y axis adjust the x position + if cell_properties.bitcell.mirror.y and (col + col_offset) % 2: + tempx = xoffset + self.cell.width + dir_y = True + return (tempx, dir_y) + + def _adjust_y_offset(self, yoffset, row, row_offset): + tempy = yoffset + dir_x = False + # If we mirror the current cell on the x axis adjust the y position + if cell_properties.bitcell.mirror.x and (row + row_offset) % 2: + tempy = yoffset + self.cell.height + dir_x = True + return (tempy, dir_x) + + def place_array(self, name_template, row_offset=0): + # We increase it by a well enclosure so the precharges don't overlap our wells + self.height = self.row_size * self.cell.height + self.width = self.column_size * self.cell.width + + xoffset = 0.0 + for col in range(self.column_size): + yoffset = 0.0 + tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset) + + for row in range(self.row_size): + tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset) + + if dir_x and dir_y: + dir_key = "XY" + elif dir_x: + dir_key = "MX" + elif dir_y: + dir_key = "MY" + else: + dir_key = "" + + self.cell_inst[row, col].place(offset=[tempx, tempy], + mirror=dir_key) + yoffset += self.cell.height + xoffset += self.cell.width diff --git a/compiler/modules/col_cap_array.py b/compiler/modules/col_cap_array.py new file mode 100644 index 00000000..ee9302d8 --- /dev/null +++ b/compiler/modules/col_cap_array.py @@ -0,0 +1,93 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +from bitcell_base_array import bitcell_base_array +from sram_factory import factory +from globals import OPTS +from tech import cell_properties + + +class col_cap_array(bitcell_base_array): + """ + Generate a dummy row/column for the replica array. + """ + def __init__(self, cols, rows, column_offset=0, mirror=0, name=""): + super().__init__(cols, rows, name, column_offset) + self.mirror = mirror + + self.no_instances = True + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + """ Create and connect the netlist """ + self.add_modules() + self.add_pins() + self.create_instances() + + def create_layout(self): + + self.place_array("dummy_r{0}_c{1}", self.mirror) + self.add_layout_pins() + self.add_boundary() + self.DRC_LVS() + + def add_modules(self): + """ Add the modules used in this design """ + self.dummy_cell = factory.create(module_type="col_cap_{}".format(OPTS.bitcell)) + self.add_mod(self.dummy_cell) + + self.cell = factory.create(module_type="bitcell") + + def create_instances(self): + """ Create the module instances used in this design """ + self.cell_inst = {} + for col in range(self.column_size): + for row in range(self.row_size): + name = "bit_r{0}_c{1}".format(row, col) + self.cell_inst[row, col]=self.add_inst(name=name, + mod=self.dummy_cell) + self.connect_inst(self.get_bitcell_pins(col, row)) + + def get_bitcell_pins(self, col, row): + """ + Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array + """ + + pin_name = cell_properties.bitcell.cell_1rw1r.pin + bitcell_pins = ["{0}_{1}".format(pin_name.bl0, col), + "{0}_{1}".format(pin_name.br0, col), + "{0}_{1}".format(pin_name.bl1, col), + "{0}_{1}".format(pin_name.br1, col), + "vdd"] + + return bitcell_pins + + def add_layout_pins(self): + """ Add the layout pins """ + + column_list = self.cell.get_all_bitline_names() + + for col in range(self.column_size): + for cell_column in column_list: + bl_pin = self.cell_inst[0, col].get_pin(cell_column) + self.add_layout_pin(text=cell_column + "_{0}".format(col), + layer=bl_pin.layer, + offset=bl_pin.ll().scale(1, 0), + width=bl_pin.width(), + height=self.height) + + # Add vdd/gnd via stacks + for row in range(self.row_size): + for col in range(self.column_size): + inst = self.cell_inst[row, col] + for pin_name in ["vdd", "gnd"]: + for pin in inst.get_pins(pin_name): + self.add_power_pin(name=pin.name, + loc=pin.center(), + start_layer=pin.layer) + diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 354179ca..8c037ef1 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -5,30 +5,29 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from math import log import design -from tech import drc, parameter +from tech import cell_properties as props import debug -import contact from sram_factory import factory import math from vector import vector from globals import OPTS import logical_effort + class control_logic(design.design): """ Dynamically generated Control logic for the total SRAM circuit. """ - def __init__(self, num_rows, words_per_row, word_size, sram=None, port_type="rw", name=""): + def __init__(self, num_rows, words_per_row, word_size, spare_columns=None, sram=None, port_type="rw", name=""): """ Constructor """ name = "control_logic_" + port_type design.design.__init__(self, name) debug.info(1, "Creating {}".format(name)) self.add_comment("num_rows: {0}".format(num_rows)) self.add_comment("words_per_row: {0}".format(words_per_row)) - self.add_comment("word_size {0}".format(word_size)) + self.add_comment("word_size {0}".format(word_size)) self.sram=sram self.num_rows = num_rows @@ -36,14 +35,20 @@ class control_logic(design.design): self.word_size = word_size self.port_type = port_type - self.num_cols = word_size*words_per_row - self.num_words = num_rows*words_per_row + if not spare_columns: + self.num_spare_cols = 0 + else: + self.num_spare_cols = spare_columns + + self.num_cols = word_size * words_per_row + self.num_spare_cols + self.num_words = num_rows * words_per_row self.enable_delay_chain_resizing = False self.inv_parasitic_delay = logical_effort.logical_effort.pinv # Determines how much larger the sen delay should be. Accounts for possible error in model. - self.wl_timing_tolerance = 1 + # FIXME: This should be made a parameter + self.wl_timing_tolerance = 1 self.wl_stage_efforts = None self.sen_stage_efforts = None @@ -66,17 +71,16 @@ class control_logic(design.design): """ Create layout and route between modules """ self.place_instances() self.route_all() - #self.add_lvs_correspondence_points() + # self.add_lvs_correspondence_points() self.add_boundary() self.DRC_LVS() - def add_pins(self): """ Add the pins to the control logic module. """ self.add_pin_list(self.input_list + ["clk"] + self.rbl_list, "INPUT") - self.add_pin_list(self.output_list,"OUTPUT") - self.add_pin("vdd","POWER") - self.add_pin("gnd","GROUND") + self.add_pin_list(self.output_list, "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def add_modules(self): """ Add all the required modules """ @@ -91,7 +95,7 @@ class control_logic(design.design): self.add_mod(self.ctrl_dff_array) self.and2 = factory.create(module_type="pand2", - size=4, + size=12, height=dff_height) self.add_mod(self.and2) @@ -100,14 +104,13 @@ class control_logic(design.design): height=dff_height) self.add_mod(self.rbl_driver) - - # clk_buf drives a flop for every address - addr_flops = math.log(self.num_words,2) + math.log(self.words_per_row,2) + # clk_buf drives a flop for every address + addr_flops = math.log(self.num_words, 2) + math.log(self.words_per_row, 2) # plus data flops and control flops - num_flops = addr_flops + self.word_size + self.num_control_signals + num_flops = addr_flops + self.word_size + self.num_spare_cols + self.num_control_signals # each flop internally has a FO 5 approximately # plus about 5 fanouts for the control logic - clock_fanout = 5*num_flops + 5 + clock_fanout = 5 * num_flops + 5 self.clk_buf_driver = factory.create(module_type="pdriver", fanout=clock_fanout, height=dff_height) @@ -116,7 +119,7 @@ class control_logic(design.design): # We will use the maximum since this same value is used to size the wl_en # and the p_en_bar drivers - max_fanout = max(self.num_rows,self.num_cols) + max_fanout = max(self.num_rows, self.num_cols) # wl_en drives every row in the bank self.wl_en_driver = factory.create(module_type="pdriver", @@ -126,17 +129,17 @@ class control_logic(design.design): # w_en drives every write driver self.wen_and = factory.create(module_type="pand3", - size=self.word_size+8, + size=self.word_size + 8, height=dff_height) self.add_mod(self.wen_and) # s_en drives every sense amp self.sen_and3 = factory.create(module_type="pand3", - size=self.word_size, + size=self.word_size + self.num_spare_cols, height=dff_height) self.add_mod(self.sen_and3) - # used to generate inverted signals with low fanout + # used to generate inverted signals with low fanout self.inv = factory.create(module_type="pinv", size=1, height=dff_height) @@ -150,7 +153,6 @@ class control_logic(design.design): height=dff_height) self.add_mod(self.p_en_bar_driver) - self.nand2 = factory.create(module_type="pnand2", height=dff_height) self.add_mod(self.nand2) @@ -178,14 +180,14 @@ class control_logic(design.design): # delay_fanout_list=[delay_fanout_heuristic]*delay_stages_heuristic, # bitcell_loads=bitcell_loads) # #Resize if necessary, condition depends on resizing method - # if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_rise_fall_timing_match(): + # if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_rise_fall_timing_match(): # #This resizes to match fall and rise delays, can make the delay chain weird sizes. # stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic) # self.replica_bitline = factory.create(module_type="replica_bitline", # delay_fanout_list=stage_list, # bitcell_loads=bitcell_loads) - # #This resizes based on total delay. + # #This resizes based on total delay. # # delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic) # # self.replica_bitline = factory.create(module_type="replica_bitline", # # delay_fanout_list=[delay_fanout]*delay_stages, @@ -194,9 +196,10 @@ class control_logic(design.design): # self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing # self.delay_chain_resized = True - debug.check(OPTS.delay_chain_stages%2, "Must use odd number of delay chain stages for inverting delay chain.") + debug.check(OPTS.delay_chain_stages % 2, + "Must use odd number of delay chain stages for inverting delay chain.") self.delay_chain=factory.create(module_type="delay_chain", - fanout_list = OPTS.delay_chain_stages*[OPTS.delay_chain_fanout_per_stage]) + fanout_list = OPTS.delay_chain_stages * [ OPTS.delay_chain_fanout_per_stage ]) self.add_mod(self.delay_chain) def get_heuristic_delay_chain_size(self): @@ -218,17 +221,17 @@ class control_logic(design.design): def set_sen_wl_delays(self): """Set delays for wordline and sense amp enable""" - self.wl_delay_rise,self.wl_delay_fall = self.get_delays_to_wl() - self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() - self.wl_delay = self.wl_delay_rise+self.wl_delay_fall - self.sen_delay = self.sen_delay_rise+self.sen_delay_fall + self.wl_delay_rise, self.wl_delay_fall = self.get_delays_to_wl() + self.sen_delay_rise, self.sen_delay_fall = self.get_delays_to_sen() + self.wl_delay = self.wl_delay_rise + self.wl_delay_fall + self.sen_delay = self.sen_delay_rise + self.sen_delay_fall def does_sen_rise_fall_timing_match(self): """Compare the relative rise/fall delays of the sense amp enable and wordline""" self.set_sen_wl_delays() # This is not necessarily more reliable than total delay in some cases. - if (self.wl_delay_rise*self.wl_timing_tolerance >= self.sen_delay_rise or - self.wl_delay_fall*self.wl_timing_tolerance >= self.sen_delay_fall): + if (self.wl_delay_rise * self.wl_timing_tolerance >= self.sen_delay_rise or + self.wl_delay_fall * self.wl_timing_tolerance >= self.sen_delay_fall): return False else: return True @@ -239,91 +242,107 @@ class control_logic(design.design): # The sen delay must always be bigger than than the wl # delay. This decides how much larger the sen delay must be # before a re-size is warranted. - if self.wl_delay*self.wl_timing_tolerance >= self.sen_delay: + if self.wl_delay * self.wl_timing_tolerance >= self.sen_delay: return False else: - return True + return True def get_dynamic_delay_chain_size(self, previous_stages, previous_fanout): """Determine the size of the delay chain used for the Sense Amp Enable using path delays""" from math import ceil - previous_delay_chain_delay = (previous_fanout+1+self.inv_parasitic_delay)*previous_stages + previous_delay_chain_delay = (previous_fanout + 1 + self.inv_parasitic_delay) * previous_stages debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) - - delay_fanout = 3 # This can be anything >=2 + + # This can be anything >=2 + delay_fanout = 3 # The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each # inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value - required_delay = self.wl_delay*self.wl_timing_tolerance - (self.sen_delay-previous_delay_chain_delay) + required_delay = self.wl_delay * self.wl_timing_tolerance - (self.sen_delay - previous_delay_chain_delay) debug.check(required_delay > 0, "Cannot size delay chain to have negative delay") - delay_stages = ceil(required_delay/(delay_fanout+1+self.inv_parasitic_delay)) - if delay_stages%2 == 1: #force an even number of stages. - delay_stages+=1 + delay_per_stage = delay_fanout + 1 + self.inv_parasitic_delay + delay_stages = ceil(required_delay / delay_per_stage) + # force an even number of stages. + if delay_stages % 2 == 1: + delay_stages += 1 # Fanout can be varied as well but is a little more complicated but potentially optimal. debug.info(1, "Setting delay chain to {} stages with {} fanout to match {} delay".format(delay_stages, delay_fanout, required_delay)) return (delay_stages, delay_fanout) def get_dynamic_delay_fanout_list(self, previous_stages, previous_fanout): """Determine the size of the delay chain used for the Sense Amp Enable using path delays""" - - previous_delay_chain_delay = (previous_fanout+1+self.inv_parasitic_delay)*previous_stages + + previous_delay_per_stage = previous_fanout + 1 + self.inv_parasitic_delay + previous_delay_chain_delay = previous_delay_per_stage * previous_stages debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) fanout_rise = fanout_fall = 2 # This can be anything >=2 # The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each # inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value - required_delay_fall = self.wl_delay_fall*self.wl_timing_tolerance - (self.sen_delay_fall-previous_delay_chain_delay/2) - required_delay_rise = self.wl_delay_rise*self.wl_timing_tolerance - (self.sen_delay_rise-previous_delay_chain_delay/2) - debug.info(2,"Required delays from chain: fall={}, rise={}".format(required_delay_fall,required_delay_rise)) + required_delay_fall = self.wl_delay_fall * self.wl_timing_tolerance - \ + (self.sen_delay_fall - previous_delay_chain_delay / 2) + required_delay_rise = self.wl_delay_rise * self.wl_timing_tolerance - \ + (self.sen_delay_rise - previous_delay_chain_delay / 2) + debug.info(2, + "Required delays from chain: fall={}, rise={}".format(required_delay_fall, + required_delay_rise)) # If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic. WARNING_FANOUT_DIFF = 5 stages_close = False # The stages need to be equal (or at least a even number of stages with matching rise/fall delays) while True: - stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,fanout_fall) - stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise,fanout_rise) - debug.info(1,"Fall stages={}, rise stages={}".format(stages_fall,stages_rise)) - if abs(stages_fall-stages_rise) == 1 and not stages_close: + stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall, + fanout_fall) + stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise, + fanout_rise) + debug.info(1, + "Fall stages={}, rise stages={}".format(stages_fall, + stages_rise)) + if abs(stages_fall - stages_rise) == 1 and not stages_close: stages_close = True safe_fanout_rise = fanout_rise safe_fanout_fall = fanout_fall - if stages_fall == stages_rise: + if stages_fall == stages_rise: break - elif abs(stages_fall-stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall-fanout_rise): + elif abs(stages_fall - stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall - fanout_rise): debug.info(1, "Delay chain fanouts between stages are large. Making chain size larger for safety.") fanout_rise = safe_fanout_rise fanout_fall = safe_fanout_fall break - # There should also be a condition to make sure the fanout does not get too large. + # There should also be a condition to make sure the fanout does not get too large. # Otherwise, increase the fanout of delay with the most stages, calculate new stages elif stages_fall>stages_rise: fanout_fall+=1 else: fanout_rise+=1 - total_stages = max(stages_fall,stages_rise)*2 + total_stages = max(stages_fall, stages_rise) * 2 debug.info(1, "New Delay chain: stages={}, fanout_rise={}, fanout_fall={}".format(total_stages, fanout_rise, fanout_fall)) # Creates interleaved fanout list of rise/fall delays. Assumes fall is the first stage. - stage_list = [fanout_fall if i%2==0 else fanout_rise for i in range(total_stages)] + stage_list = [fanout_fall if i % 2==0 else fanout_rise for i in range(total_stages)] return stage_list def calculate_stages_with_fixed_fanout(self, required_delay, fanout): from math import ceil # Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay - if required_delay <= 3+self.inv_parasitic_delay: #3 is the minimum delay per stage (with pinv=0). + # 3 is the minimum delay per stage (with pinv=0). + if required_delay <= 3 + self.inv_parasitic_delay: return 1 - delay_stages = ceil(required_delay/(fanout+1+self.inv_parasitic_delay)) + delay_per_stage = fanout + 1 + self.inv_parasitic_delay + delay_stages = ceil(required_delay / delay_per_stage) return delay_stages def calculate_stage_list(self, total_stages, fanout_rise, fanout_fall): - """Produces a list of fanouts which determine the size of the delay chain. List length is the number of stages. - Assumes the first stage is falling. + """ + Produces a list of fanouts which determine the size of the delay chain. + List length is the number of stages. + Assumes the first stage is falling. """ stage_list = [] for i in range(total_stages): - if i%2 == 0: + if i % 2 == 0: stage_list.append() def setup_signal_busses(self): @@ -344,13 +363,13 @@ class control_logic(design.design): # list of output control signals (for making a vertical bus) if self.port_type == "rw": - self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "clk_buf", "we_bar", "cs"] + self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "we", "we_bar", "clk_buf", "cs"] elif self.port_type == "r": self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs_bar", "cs"] else: self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs"] # leave space for the bus plus one extra space - self.internal_bus_width = (len(self.internal_bus_list)+1)*self.m2_pitch + self.internal_bus_width = (len(self.internal_bus_list) + 1) * self.m2_pitch # Outputs to the bank if self.port_type == "rw": @@ -365,15 +384,16 @@ class control_logic(design.design): self.supply_list = ["vdd", "gnd"] - def route_rails(self): """ Add the input signal inverted tracks """ height = self.control_logic_center.y - self.m2_pitch - offset = vector(self.ctrl_dff_array.width,0) + offset = vector(self.ctrl_dff_array.width, 0) - self.rail_offsets = self.create_vertical_bus("metal2", self.m2_pitch, offset, self.internal_bus_list, height) - - + self.input_bus = self.create_vertical_bus("m2", + offset, + self.internal_bus_list, + height) + def create_instances(self): """ Create all the instances """ self.create_dffs() @@ -387,9 +407,7 @@ class control_logic(design.design): if (self.port_type == "rw") or (self.port_type == "r"): self.create_sen_row() self.create_delay() - self.create_pen_row() - - + self.create_pen_row() def place_instances(self): """ Place all the instances """ @@ -405,13 +423,13 @@ class control_logic(design.design): row = 0 # Add the logic on the right of the bus - self.place_clk_buf_row(row) + self.place_clk_buf_row(row) row += 1 - self.place_gated_clk_bar_row(row) + self.place_gated_clk_bar_row(row) row += 1 - self.place_gated_clk_buf_row(row) + self.place_gated_clk_buf_row(row) row += 1 - self.place_wlen_row(row) + self.place_wlen_row(row) row += 1 if (self.port_type == "rw") or (self.port_type == "w"): self.place_wen_row(row) @@ -420,10 +438,10 @@ class control_logic(design.design): row += 1 self.place_pen_row(row) row += 1 - if (self.port_type == "rw") or (self.port_type == "w"): + if (self.port_type == "rw") or (self.port_type == "w"): self.place_rbl_delay_row(row) - row += 1 - if (self.port_type == "rw") or (self.port_type == "r"): + row += 1 + if (self.port_type == "rw") or (self.port_type == "r"): self.place_sen_row(row) row += 1 self.place_delay(row) @@ -434,11 +452,11 @@ class control_logic(design.design): self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y) # Extra pitch on top and right - self.height = height + 2*self.m1_pitch + self.height = height + 2 * self.m1_pitch # Max of modules or logic rows self.width = max([inst.rx() for inst in self.row_end_inst]) if (self.port_type == "rw") or (self.port_type == "r"): - self.width = max(self.delay_inst.rx() , self.width) + self.width = max(self.delay_inst.rx(), self.width) self.width += self.m2_pitch def route_all(self): @@ -458,7 +476,6 @@ class control_logic(design.design): self.route_gated_clk_buf() self.route_supply() - def create_delay(self): """ Create the replica bitline """ self.delay_inst=self.add_inst(name="delay_chain", @@ -466,9 +483,9 @@ class control_logic(design.design): # rbl_bl_delay is asserted (1) when the bitline has been discharged self.connect_inst(["rbl_bl", "rbl_bl_delay", "vdd", "gnd"]) - def place_delay(self,row): + def place_delay(self, row): """ Place the replica bitline """ - y_off = row * self.and2.height + 2*self.m1_pitch + y_off = row * self.and2.height + 2 * self.m1_pitch # Add the RBL above the rows # Add to the right of the control rows and routing channel @@ -481,24 +498,22 @@ class control_logic(design.design): # Connect to the rail level with the vdd rail # Use pen since it is in every type of control logic vdd_ypos = self.p_en_bar_nand_inst.get_pin("vdd").by() - in_pos = vector(self.rail_offsets["rbl_bl_delay"].x,vdd_ypos) - mid1 = vector(out_pos.x,in_pos.y) - self.add_wire(("metal1","via1","metal2"),[out_pos, mid1, in_pos]) - self.add_via_center(layers=("metal1","via1","metal2"), + in_pos = vector(self.input_bus["rbl_bl_delay"].cx(), vdd_ypos) + mid1 = vector(out_pos.x, in_pos.y) + self.add_wire(self.m1_stack, [out_pos, mid1, in_pos]) + self.add_via_center(layers=self.m1_stack, offset=in_pos) - # Input from RBL goes to the delay line for futher delay self.copy_layout_pin(self.delay_inst, "in", "rbl_bl") - def create_clk_buf_row(self): """ Create the multistage and gated clock buffer """ self.clk_buf_inst = self.add_inst(name="clkbuf", mod=self.clk_buf_driver) - self.connect_inst(["clk","clk_buf","vdd","gnd"]) + self.connect_inst(["clk", "clk_buf", "vdd", "gnd"]) - def place_clk_buf_row(self,row): + def place_clk_buf_row(self, row): x_offset = self.control_x_offset x_offset = self.place_util(self.clk_buf_inst, x_offset, row) @@ -508,73 +523,70 @@ class control_logic(design.design): def route_clk_buf(self): clk_pin = self.clk_buf_inst.get_pin("A") clk_pos = clk_pin.center() - self.add_layout_pin_segment_center(text="clk", - layer="metal2", - start=clk_pos, - end=clk_pos.scale(1,0)) - self.add_via_center(layers=("metal1","via1","metal2"), - offset=clk_pos) - - - # Connect this at the bottom of the buffer - out_pos = self.clk_buf_inst.get_pin("Z").center() - mid1 = vector(out_pos.x,2*self.m2_pitch) - mid2 = vector(self.rail_offsets["clk_buf"].x, mid1.y) - bus_pos = self.rail_offsets["clk_buf"] - self.add_wire(("metal3","via2","metal2"),[out_pos, mid1, mid2, bus_pos]) - # The pin is on M1, so we need another via as well - self.add_via_center(layers=("metal1","via1","metal2"), - offset=self.clk_buf_inst.get_pin("Z").center()) + self.add_layout_pin_rect_center(text="clk", + layer="m2", + offset=clk_pos) + self.add_via_stack_center(from_layer=clk_pin.layer, + to_layer="m2", + offset=clk_pos) + self.route_output_to_bus_jogged(self.clk_buf_inst, + "clk_buf") self.connect_output(self.clk_buf_inst, "Z", "clk_buf") def create_gated_clk_bar_row(self): self.clk_bar_inst = self.add_inst(name="inv_clk_bar", mod=self.inv) - self.connect_inst(["clk_buf","clk_bar","vdd","gnd"]) + self.connect_inst(["clk_buf", "clk_bar", "vdd", "gnd"]) self.gated_clk_bar_inst = self.add_inst(name="and2_gated_clk_bar", mod=self.and2) - self.connect_inst(["cs","clk_bar","gated_clk_bar","vdd","gnd"]) + self.connect_inst(["clk_bar", "cs", "gated_clk_bar", "vdd", "gnd"]) - def place_gated_clk_bar_row(self,row): + def place_gated_clk_bar_row(self, row): x_offset = self.control_x_offset x_offset = self.place_util(self.clk_bar_inst, x_offset, row) - x_offset = self.place_util(self.gated_clk_bar_inst, x_offset, row) + x_offset = self.place_util(self.gated_clk_bar_inst, x_offset, row) self.row_end_inst.append(self.gated_clk_bar_inst) def route_gated_clk_bar(self): clkbuf_map = zip(["A"], ["clk_buf"]) - self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.rail_offsets) + self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.input_bus) + + out_pin = self.clk_bar_inst.get_pin("Z") + out_pos = out_pin.center() + in_pin = self.gated_clk_bar_inst.get_pin("A") + in_pos = in_pin.center() + self.add_zjog(out_pin.layer, out_pos, in_pos) + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer=in_pin.layer, + offset=in_pos) - out_pos = self.clk_bar_inst.get_pin("Z").center() - in_pos = self.gated_clk_bar_inst.get_pin("B").center() - mid1 = vector(in_pos.x,out_pos.y) - self.add_path("metal1",[out_pos, mid1, in_pos]) # This is the second gate over, so it needs to be on M3 - clkbuf_map = zip(["A"], ["cs"]) - self.connect_vertical_bus(clkbuf_map, self.gated_clk_bar_inst, self.rail_offsets, ("metal3", "via2", "metal2")) + clkbuf_map = zip(["B"], ["cs"]) + self.connect_vertical_bus(clkbuf_map, + self.gated_clk_bar_inst, + self.input_bus, + self.m2_stack[::-1]) # The pin is on M1, so we need another via as well - self.add_via_center(layers=("metal1","via1","metal2"), - offset=self.gated_clk_bar_inst.get_pin("A").center()) - + b_pin = self.gated_clk_bar_inst.get_pin("B") + self.add_via_stack_center(from_layer=b_pin.layer, + to_layer="m3", + offset=b_pin.center()) # This is the second gate over, so it needs to be on M3 - clkbuf_map = zip(["Z"], ["gated_clk_bar"]) - self.connect_vertical_bus(clkbuf_map, self.gated_clk_bar_inst, self.rail_offsets, ("metal3", "via2", "metal2")) - # The pin is on M1, so we need another via as well - self.add_via_center(layers=("metal1","via1","metal2"), - offset=self.gated_clk_bar_inst.get_pin("Z").center()) + self.route_output_to_bus_jogged(self.gated_clk_bar_inst, + "gated_clk_bar") def create_gated_clk_buf_row(self): self.gated_clk_buf_inst = self.add_inst(name="and2_gated_clk_buf", mod=self.and2) - self.connect_inst(["clk_buf", "cs","gated_clk_buf","vdd","gnd"]) + self.connect_inst(["clk_buf", "cs", "gated_clk_buf", "vdd", "gnd"]) - def place_gated_clk_buf_row(self,row): + def place_gated_clk_buf_row(self, row): x_offset = self.control_x_offset x_offset = self.place_util(self.gated_clk_buf_inst, x_offset, row) @@ -583,14 +595,20 @@ class control_logic(design.design): def route_gated_clk_buf(self): clkbuf_map = zip(["A", "B"], ["clk_buf", "cs"]) - self.connect_vertical_bus(clkbuf_map, self.gated_clk_buf_inst, self.rail_offsets) + self.connect_vertical_bus(clkbuf_map, + self.gated_clk_buf_inst, + self.input_bus) - clkbuf_map = zip(["Z"], ["gated_clk_buf"]) - self.connect_vertical_bus(clkbuf_map, self.gated_clk_buf_inst, self.rail_offsets, ("metal3", "via2", "metal2")) + self.connect_vertical_bus(clkbuf_map, + self.gated_clk_buf_inst, + self.input_bus, + self.m2_stack[::-1]) # The pin is on M1, so we need another via as well - self.add_via_center(layers=("metal1","via1","metal2"), - offset=self.gated_clk_buf_inst.get_pin("Z").center()) + z_pin = self.gated_clk_buf_inst.get_pin("Z") + self.add_via_stack_center(from_layer=z_pin.layer, + to_layer="m2", + offset=z_pin.center()) def create_wlen_row(self): # input pre_p_en, output: wl_en @@ -601,13 +619,13 @@ class control_logic(design.design): def place_wlen_row(self, row): x_offset = self.control_x_offset - x_offset = self.place_util(self.wl_en_inst, x_offset, row) + x_offset = self.place_util(self.wl_en_inst, x_offset, row) self.row_end_inst.append(self.wl_en_inst) def route_wlen(self): wlen_map = zip(["A"], ["gated_clk_bar"]) - self.connect_vertical_bus(wlen_map, self.wl_en_inst, self.rail_offsets) + self.connect_vertical_bus(wlen_map, self.wl_en_inst, self.input_bus) self.connect_output(self.wl_en_inst, "Z", "wl_en") @@ -622,23 +640,28 @@ class control_logic(design.design): mod=self.p_en_bar_driver) self.connect_inst(["p_en_bar_unbuf", "p_en_bar", "vdd", "gnd"]) - def place_pen_row(self,row): + def place_pen_row(self, row): x_offset = self.control_x_offset - x_offset = self.place_util(self.p_en_bar_nand_inst, x_offset, row) - x_offset = self.place_util(self.p_en_bar_driver_inst, x_offset, row) + x_offset = self.place_util(self.p_en_bar_nand_inst, x_offset, row) + x_offset = self.place_util(self.p_en_bar_driver_inst, x_offset, row) self.row_end_inst.append(self.p_en_bar_driver_inst) def route_pen(self): in_map = zip(["A", "B"], ["gated_clk_buf", "rbl_bl_delay"]) - self.connect_vertical_bus(in_map, self.p_en_bar_nand_inst, self.rail_offsets) + self.connect_vertical_bus(in_map, self.p_en_bar_nand_inst, self.input_bus) + + out_pin = self.p_en_bar_nand_inst.get_pin("Z") + out_pos = out_pin.center() + in_pin = self.p_en_bar_driver_inst.get_pin("A") + in_pos = in_pin.center() + mid1 = vector(in_pos.x, out_pos.y) + self.add_path(out_pin.layer, [out_pos, mid1, in_pos]) + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer=in_pin.layer, + offset=in_pin.center()) - out_pos = self.p_en_bar_nand_inst.get_pin("Z").rc() - in_pos = self.p_en_bar_driver_inst.get_pin("A").lc() - mid1 = vector(out_pos.x,in_pos.y) - self.add_wire(("metal1","via1","metal2"),[out_pos, mid1,in_pos]) - self.connect_output(self.p_en_bar_driver_inst, "Z", "p_en_bar") def create_sen_row(self): @@ -646,7 +669,7 @@ class control_logic(design.design): if self.port_type=="rw": input_name = "we_bar" else: - input_name = "cs_bar" + input_name = "cs" # GATE FOR S_EN self.s_en_gate_inst = self.add_inst(name="buf_s_en_and", mod=self.sen_and3) @@ -655,24 +678,22 @@ class control_logic(design.design): # hence we use rbl_bl_delay as well. self.connect_inst(["rbl_bl_delay", "gated_clk_bar", input_name, "s_en", "vdd", "gnd"]) - - def place_sen_row(self,row): + def place_sen_row(self, row): x_offset = self.control_x_offset x_offset = self.place_util(self.s_en_gate_inst, x_offset, row) self.row_end_inst.append(self.s_en_gate_inst) - def route_sen(self): if self.port_type=="rw": input_name = "we_bar" else: - input_name = "cs_bar" + input_name = "cs" sen_map = zip(["A", "B", "C"], ["rbl_bl_delay", "gated_clk_bar", input_name]) - self.connect_vertical_bus(sen_map, self.s_en_gate_inst, self.rail_offsets) + self.connect_vertical_bus(sen_map, self.s_en_gate_inst, self.input_bus) self.connect_output(self.s_en_gate_inst, "Z", "s_en") @@ -682,7 +703,7 @@ class control_logic(design.design): mod=self.inv) self.connect_inst(["rbl_bl_delay", "rbl_bl_delay_bar", "vdd", "gnd"]) - def place_rbl_delay_row(self,row): + def place_rbl_delay_row(self, row): x_offset = self.control_x_offset x_offset = self.place_util(self.rbl_bl_delay_inv_inst, x_offset, row) @@ -693,16 +714,10 @@ class control_logic(design.design): # Connect from delay line # Connect to rail - rbl_map = zip(["Z"], ["rbl_bl_delay_bar"]) - self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets, ("metal3", "via2", "metal2")) - # The pin is on M1, so we need another via as well - self.add_via_center(layers=("metal1","via1","metal2"), - offset=self.rbl_bl_delay_inv_inst.get_pin("Z").center()) - + self.route_output_to_bus_jogged(self.rbl_bl_delay_inv_inst, "rbl_bl_delay_bar") rbl_map = zip(["A"], ["rbl_bl_delay"]) - self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets) - + self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.input_bus) def create_wen_row(self): @@ -719,8 +734,7 @@ class control_logic(design.design): # Only drive the writes in the second half of the clock cycle during a write operation. self.connect_inst([input_name, "rbl_bl_delay_bar", "gated_clk_bar", "w_en", "vdd", "gnd"]) - - def place_wen_row(self,row): + def place_wen_row(self, row): x_offset = self.control_x_offset x_offset = self.place_util(self.w_en_gate_inst, x_offset, row) @@ -735,107 +749,116 @@ class control_logic(design.design): input_name = "cs" wen_map = zip(["A", "B", "C"], [input_name, "rbl_bl_delay_bar", "gated_clk_bar"]) - self.connect_vertical_bus(wen_map, self.w_en_gate_inst, self.rail_offsets) + self.connect_vertical_bus(wen_map, self.w_en_gate_inst, self.input_bus) self.connect_output(self.w_en_gate_inst, "Z", "w_en") def create_dffs(self): self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs", mod=self.ctrl_dff_array) - self.connect_inst(self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list) + inst_pins = self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list + if props.dff_buff_array.add_body_contacts: + inst_pins.append("vpb") + inst_pins.append("vnb") + self.connect_inst(inst_pins) def place_dffs(self): - self.ctrl_dff_inst.place(vector(0,0)) + self.ctrl_dff_inst.place(vector(0, 0)) def route_dffs(self): if self.port_type == "rw": dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_1"], ["cs", "we", "we_bar"]) elif self.port_type == "r": - dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"]) + dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"]) else: dff_out_map = zip(["dout_bar_0"], ["cs"]) - self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets, ("metal3", "via2", "metal2")) + self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.input_bus, self.m2_stack[::-1]) # Connect the clock rail to the other clock rail + # by routing in the supply rail track to avoid channel conflicts in_pos = self.ctrl_dff_inst.get_pin("clk").uc() - mid_pos = in_pos + vector(0,2*self.m2_pitch) - rail_pos = vector(self.rail_offsets["clk_buf"].x, mid_pos.y) - self.add_wire(("metal1","via1","metal2"),[in_pos, mid_pos, rail_pos]) - self.add_via_center(layers=("metal1","via1","metal2"), + mid_pos = in_pos + vector(0, self.and2.height) + rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y) + self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos]) + self.add_via_center(layers=self.m1_stack, offset=rail_pos) self.copy_layout_pin(self.ctrl_dff_inst, "din_0", "csb") if (self.port_type == "rw"): self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web") - def get_offset(self,row): + def get_offset(self, row): """ Compute the y-offset and mirroring """ - y_off = row*self.and2.height + y_off = row * self.and2.height if row % 2: y_off += self.and2.height mirror="MX" else: mirror="R0" - return (y_off,mirror) - + return (y_off, mirror) def connect_output(self, inst, pin_name, out_name): """ Create an output pin on the right side from the pin of a given instance. """ out_pin = inst.get_pin(pin_name) - right_pos=out_pin.center() + vector(self.width-out_pin.cx(),0) - self.add_layout_pin_segment_center(text=out_name, - layer="metal1", - start=out_pin.center(), - end=right_pos) + out_pos = out_pin.center() + right_pos = out_pos + vector(self.width - out_pin.cx(), 0) - + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer="m2", + offset=out_pos) + self.add_layout_pin_segment_center(text=out_name, + layer="m2", + start=out_pos, + end=right_pos) def route_supply(self): """ Add vdd and gnd to the instance cells """ - max_row_x_loc = max([inst.rx() for inst in self.row_end_inst]) + if OPTS.tech_name == "sky130": + supply_layer = "li" + else: + supply_layer = "m1" + max_row_x_loc = max([inst.rx() for inst in self.row_end_inst]) for inst in self.row_end_inst: pins = inst.get_pins("vdd") for pin in pins: - if pin.layer == "metal1": + if pin.layer == supply_layer: row_loc = pin.rc() pin_loc = vector(max_row_x_loc, pin.rc().y) - self.add_power_pin("vdd", pin_loc) - self.add_path("metal1", [row_loc, pin_loc]) + self.add_power_pin("vdd", pin_loc, start_layer=pin.layer) + self.add_path(supply_layer, [row_loc, pin_loc]) pins = inst.get_pins("gnd") for pin in pins: - if pin.layer == "metal1": + if pin.layer == supply_layer: row_loc = pin.rc() pin_loc = vector(max_row_x_loc, pin.rc().y) - self.add_power_pin("gnd", pin_loc) - self.add_path("metal1", [row_loc, pin_loc]) + self.add_power_pin("gnd", pin_loc, start_layer=pin.layer) + self.add_path(supply_layer, [row_loc, pin_loc]) - self.copy_layout_pin(self.delay_inst,"gnd") - self.copy_layout_pin(self.delay_inst,"vdd") + self.copy_layout_pin(self.delay_inst, "gnd") + self.copy_layout_pin(self.delay_inst, "vdd") - self.copy_layout_pin(self.ctrl_dff_inst,"gnd") - self.copy_layout_pin(self.ctrl_dff_inst,"vdd") + self.copy_layout_pin(self.ctrl_dff_inst, "gnd") + self.copy_layout_pin(self.ctrl_dff_inst, "vdd") - - 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. """ # pin=self.clk_inv1.get_pin("Z") # self.add_label_pin(text="clk1_bar", - # layer="metal1", + # layer="m1", # offset=pin.ll(), # height=pin.height(), # width=pin.width()) # pin=self.clk_inv2.get_pin("Z") # self.add_label_pin(text="clk2", - # layer="metal1", + # layer="m1", # offset=pin.ll(), # height=pin.height(), # width=pin.width()) @@ -846,74 +869,78 @@ class control_logic(design.design): offset=pin.ll(), height=pin.height(), width=pin.width()) - - def get_delays_to_wl(self): """Get the delay (in delay units) of the clk to a wordline in the bitcell array""" debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.") self.wl_stage_efforts = self.get_wordline_stage_efforts() - clk_to_wl_rise,clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(self.wl_stage_efforts) - total_delay = clk_to_wl_rise + clk_to_wl_fall - debug.info(1, "Clock to wl delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_wl_rise, clk_to_wl_fall,total_delay)) - return clk_to_wl_rise,clk_to_wl_fall + clk_to_wl_rise, clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(self.wl_stage_efforts) + total_delay = clk_to_wl_rise + clk_to_wl_fall + debug.info(1, + "Clock to wl delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_wl_rise, + clk_to_wl_fall, + total_delay)) + return clk_to_wl_rise, clk_to_wl_fall - def get_wordline_stage_efforts(self): """Follows the gated_clk_bar -> wl_en -> wordline signal for the total path efforts""" stage_effort_list = [] - #Initial direction of gated_clk_bar signal for this path + # Initial direction of gated_clk_bar signal for this path is_clk_bar_rise = True - #Calculate the load on wl_en within the module and add it to external load + # Calculate the load on wl_en within the module and add it to external load external_cout = self.sram.get_wl_en_cin() - #First stage is the clock buffer + # First stage is the clock buffer stage_effort_list += self.clk_buf_driver.get_stage_efforts(external_cout, is_clk_bar_rise) last_stage_is_rise = stage_effort_list[-1].is_rise - #Then ask the sram for the other path delays (from the bank) + # Then ask the sram for the other path delays (from the bank) stage_effort_list += self.sram.get_wordline_stage_efforts(last_stage_is_rise) return stage_effort_list def get_delays_to_sen(self): - """Get the delay (in delay units) of the clk to a sense amp enable. - This does not incorporate the delay of the replica bitline. + """ + Get the delay (in delay units) of the clk to a sense amp enable. + This does not incorporate the delay of the replica bitline. """ debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.") self.sen_stage_efforts = self.get_sa_enable_stage_efforts() clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(self.sen_stage_efforts) - total_delay = clk_to_sen_rise + clk_to_sen_fall - debug.info(1, "Clock to s_en delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_sen_rise, clk_to_sen_fall,total_delay)) - return clk_to_sen_rise, clk_to_sen_fall + total_delay = clk_to_sen_rise + clk_to_sen_fall + debug.info(1, + "Clock to s_en delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_sen_rise, + clk_to_sen_fall, + total_delay)) + return clk_to_sen_rise, clk_to_sen_fall def get_sa_enable_stage_efforts(self): """Follows the gated_clk_bar signal to the sense amp enable signal adding each stages stage effort to a list""" stage_effort_list = [] - #Initial direction of clock signal for this path + # Initial direction of clock signal for this path last_stage_rise = True - #First stage, gated_clk_bar -(and2)-> rbl_in. Only for RW ports. + # First stage, gated_clk_bar -(and2)-> rbl_in. Only for RW ports. if self.port_type == "rw": stage1_cout = self.replica_bitline.get_en_cin() stage_effort_list += self.and2.get_stage_efforts(stage1_cout, last_stage_rise) last_stage_rise = stage_effort_list[-1].is_rise - #Replica bitline stage, rbl_in -(rbl)-> pre_s_en + # Replica bitline stage, rbl_in -(rbl)-> pre_s_en stage2_cout = self.sen_and2.get_cin() stage_effort_list += self.replica_bitline.determine_sen_stage_efforts(stage2_cout, last_stage_rise) last_stage_rise = stage_effort_list[-1].is_rise - #buffer stage, pre_s_en -(buffer)-> s_en + # buffer stage, pre_s_en -(buffer)-> s_en stage3_cout = self.sram.get_sen_cin() stage_effort_list += self.s_en_driver.get_stage_efforts(stage3_cout, last_stage_rise) last_stage_rise = stage_effort_list[-1].is_rise - return stage_effort_list + return stage_effort_list def get_wl_sen_delays(self): - """Gets a list of the stages and delays in order of their path.""" + """ Gets a list of the stages and delays in order of their path. """ if self.sen_stage_efforts == None or self.wl_stage_efforts == None: debug.error("Model delays not calculated for SRAM.", 1) @@ -922,45 +949,45 @@ class control_logic(design.design): return wl_delays, sen_delays def analytical_delay(self, corner, slew, load): - """Gets the analytical delay from clk input to wl_en output""" + """ Gets the analytical delay from clk input to wl_en output """ stage_effort_list = [] - #Calculate the load on clk_buf_bar - ext_clk_buf_cout = self.sram.get_clk_bar_cin() + # Calculate the load on clk_buf_bar + # ext_clk_buf_cout = self.sram.get_clk_bar_cin() - #Operations logic starts on negative edge - last_stage_rise = False + # Operations logic starts on negative edge + last_stage_rise = False - #First stage(s), clk -(pdriver)-> clk_buf. - #clk_buf_cout = self.replica_bitline.get_en_cin() + # First stage(s), clk -(pdriver)-> clk_buf. + # clk_buf_cout = self.replica_bitline.get_en_cin() clk_buf_cout = 0 stage_effort_list += self.clk_buf_driver.get_stage_efforts(clk_buf_cout, last_stage_rise) last_stage_rise = stage_effort_list[-1].is_rise - #Second stage, clk_buf -(inv)-> clk_bar + # Second stage, clk_buf -(inv)-> clk_bar clk_bar_cout = self.and2.get_cin() stage_effort_list += self.and2.get_stage_efforts(clk_bar_cout, last_stage_rise) last_stage_rise = stage_effort_list[-1].is_rise - #Third stage clk_bar -(and)-> gated_clk_bar + # Third stage clk_bar -(and)-> gated_clk_bar gated_clk_bar_cin = self.get_gated_clk_bar_cin() stage_effort_list.append(self.inv.get_stage_effort(gated_clk_bar_cin, last_stage_rise)) last_stage_rise = stage_effort_list[-1].is_rise - #Stages from gated_clk_bar -------> wordline + # Stages from gated_clk_bar -------> wordline stage_effort_list += self.get_wordline_stage_efforts() return stage_effort_list def get_clk_buf_cin(self): """ - Get the loads that are connected to the buffered clock. + Get the loads that are connected to the buffered clock. Includes all the DFFs and some logic. """ - #Control logic internal load + # Control logic internal load int_clk_buf_cap = self.inv.get_cin() + self.ctrl_dff_array.get_clk_cin() + self.and2.get_cin() - #Control logic external load (in the other parts of the SRAM) + # Control logic external load (in the other parts of the SRAM) ext_clk_buf_cap = self.sram.get_clk_bar_cin() return int_clk_buf_cap + ext_clk_buf_cap @@ -971,7 +998,7 @@ class control_logic(design.design): total_cin = 0 total_cin += self.wl_en_driver.get_cin() if self.port_type == 'rw': - total_cin +=self.and2.get_cin() + total_cin += self.and2.get_cin() return total_cin def graph_exclude_dffs(self): @@ -984,7 +1011,20 @@ class control_logic(design.design): def place_util(self, inst, x_offset, row): """ Utility to place a row and compute the next offset """ - (y_offset,mirror)=self.get_offset(row) + (y_offset, mirror) = self.get_offset(row) offset = vector(x_offset, y_offset) inst.place(offset, mirror) - return x_offset+inst.width + return x_offset + inst.width + + def route_output_to_bus_jogged(self, inst, name): + # Connect this at the bottom of the buffer + out_pin = inst.get_pin("Z") + out_pos = out_pin.center() + mid1 = vector(out_pos.x, out_pos.y - 0.4 * inst.mod.height) + mid2 = vector(self.input_bus[name].cx(), mid1.y) + bus_pos = self.input_bus[name].center() + self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos]) + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer="m2", + offset=out_pos) + diff --git a/compiler/modules/delay_chain.py b/compiler/modules/delay_chain.py index bc932a26..c261138a 100644 --- a/compiler/modules/delay_chain.py +++ b/compiler/modules/delay_chain.py @@ -7,12 +7,11 @@ # import debug import design -from tech import drc -from contact import contact from vector import vector from globals import OPTS from sram_factory import factory + class delay_chain(design.design): """ Generate a delay chain with the given number of stages and fanout. @@ -28,7 +27,7 @@ class delay_chain(design.design): # Two fanouts are needed so that we can route the vdd/gnd connections for f in fanout_list: - debug.check(f>=2,"Must have >=2 fanouts for each stage.") + debug.check(f>=2, "Must have >=2 fanouts for each stage.") # number of inverters including any fanout loads. self.fanout_list = fanout_list @@ -36,7 +35,6 @@ class delay_chain(design.design): self.create_netlist() if not OPTS.netlist_only: self.create_layout() - def create_netlist(self): self.add_modules() @@ -45,12 +43,13 @@ class delay_chain(design.design): def create_layout(self): # Each stage is a a row - self.height = len(self.fanout_list)*self.inv.height + self.height = len(self.fanout_list) * self.inv.height # The width is determined by the largest fanout plus the driver - self.width = (max(self.fanout_list)+1) * self.inv.width + self.width = (max(self.fanout_list) + 1) * self.inv.width self.place_inverters() self.route_inverters() + self.route_supplies() self.add_layout_pins() self.add_boundary() self.DRC_LVS() @@ -63,15 +62,14 @@ class delay_chain(design.design): self.add_pin("gnd", "GROUND") def add_modules(self): - self.inv = factory.create(module_type="pinv", route_output=False) + self.inv = factory.create(module_type="pinv") self.add_mod(self.inv) def create_inverters(self): """ Create the inverters and connect them based on the stage list """ self.driver_inst_list = [] - self.rightest_load_inst = {} self.load_inst_map = {} - for stage_num,fanout_size in zip(range(len(self.fanout_list)),self.fanout_list): + for stage_num, fanout_size in zip(range(len(self.fanout_list)), self.fanout_list): # Add the inverter cur_driver=self.add_inst(name="dinv{}".format(stage_num), mod=self.inv) @@ -79,40 +77,37 @@ class delay_chain(design.design): self.driver_inst_list.append(cur_driver) # Hook up the driver - if stage_num+1==len(self.fanout_list): + if stage_num + 1 == len(self.fanout_list): stageout_name = "out" else: - stageout_name = "dout_{}".format(stage_num+1) + stageout_name = "dout_{}".format(stage_num + 1) if stage_num == 0: stagein_name = "in" else: - stagein_name = "dout_{}".format(stage_num) + stagein_name = "dout_{}".format(stage_num) self.connect_inst([stagein_name, stageout_name, "vdd", "gnd"]) # Now add the dummy loads to the right self.load_inst_map[cur_driver]=[] for i in range(fanout_size): - cur_load=self.add_inst(name="dload_{0}_{1}".format(stage_num,i), + cur_load=self.add_inst(name="dload_{0}_{1}".format(stage_num, i), mod=self.inv) # Fanout stage is always driven by driver and output is disconnected - disconnect_name = "n_{0}_{1}".format(stage_num,i) + disconnect_name = "n_{0}_{1}".format(stage_num, i) self.connect_inst([stageout_name, disconnect_name, "vdd", "gnd"]) # Keep track of all the loads to connect their inputs as a load self.load_inst_map[cur_driver].append(cur_load) - else: - # Keep track of the last one so we can add the the wire later - self.rightest_load_inst[cur_driver]=cur_load def place_inverters(self): """ Place the inverters and connect them based on the stage list """ - for stage_num,fanout_size in zip(range(len(self.fanout_list)),self.fanout_list): + for stage_num, fanout_size in zip(range(len(self.fanout_list)), self.fanout_list): if stage_num % 2: inv_mirror = "MX" - inv_offset = vector(0, (stage_num+1)* self.inv.height) + inv_offset = vector(0, (stage_num + 1) * self.inv.height) else: inv_mirror = "R0" - inv_offset = vector(0, stage_num * self.inv.height) + inv_offset = vector(0, stage_num * self.inv.height) # Add the inverter cur_driver=self.driver_inst_list[stage_num] @@ -122,21 +117,20 @@ class delay_chain(design.design): # Now add the dummy loads to the right load_list = self.load_inst_map[cur_driver] for i in range(fanout_size): - inv_offset += vector(self.inv.width,0) + inv_offset += vector(self.inv.width, 0) load_list[i].place(offset=inv_offset, mirror=inv_mirror) - def add_route(self, pin1, pin2): """ This guarantees that we route from the top to bottom row correctly. """ pin1_pos = pin1.center() pin2_pos = pin2.center() if pin1_pos.y == pin2_pos.y: - self.add_path("metal2", [pin1_pos, pin2_pos]) + self.add_path("m2", [pin1_pos, pin2_pos]) else: - mid_point = vector(pin2_pos.x, 0.5*(pin1_pos.y+pin2_pos.y)) + mid_point = vector(pin2_pos.x, 0.5 * (pin1_pos.y + pin2_pos.y)) # Written this way to guarantee it goes right first if we are switching rows - self.add_path("metal2", [pin1_pos, vector(pin1_pos.x,mid_point.y), mid_point, vector(mid_point.x,pin2_pos.y), pin2_pos]) + self.add_path("m2", [pin1_pos, vector(pin1_pos.x, mid_point.y), mid_point, vector(mid_point.x, pin2_pos.y), pin2_pos]) def route_inverters(self): """ Add metal routing for each of the fanout stages """ @@ -145,100 +139,93 @@ class delay_chain(design.design): inv = self.driver_inst_list[i] for load in self.load_inst_map[inv]: # Drop a via on each A pin - a_pin = load.get_pin("A") - self.add_via_center(layers=("metal1","via1","metal2"), - offset=a_pin.center()) - self.add_via_center(layers=("metal2","via2","metal3"), - offset=a_pin.center()) + a_pin = load.get_pin("A") + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m3", + offset=a_pin.center()) # Route an M3 horizontal wire to the furthest z_pin = inv.get_pin("Z") a_pin = inv.get_pin("A") - a_max = self.rightest_load_inst[inv].get_pin("A") - self.add_via_center(layers=("metal1","via1","metal2"), - offset=a_pin.center()) - self.add_via_center(layers=("metal1","via1","metal2"), - offset=z_pin.center()) - self.add_via_center(layers=("metal2","via2","metal3"), - offset=z_pin.center()) - self.add_path("metal3",[z_pin.center(), a_max.center()]) + a_max = self.load_inst_map[inv][-1].get_pin("A") + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m2", + offset=a_pin.center()) + self.add_via_stack_center(from_layer=z_pin.layer, + to_layer="m3", + offset=z_pin.center()) + self.add_path("m3", [z_pin.center(), a_max.center()]) - # Route Z to the A of the next stage - if i+1 < len(self.driver_inst_list): + if i + 1 < len(self.driver_inst_list): z_pin = inv.get_pin("Z") - next_inv = self.driver_inst_list[i+1] + next_inv = self.driver_inst_list[i + 1] next_a_pin = next_inv.get_pin("A") - y_mid = (z_pin.cy() + next_a_pin.cy())/2 + y_mid = (z_pin.cy() + next_a_pin.cy()) / 2 mid1_point = vector(z_pin.cx(), y_mid) mid2_point = vector(next_a_pin.cx(), y_mid) - self.add_path("metal2",[z_pin.center(), mid1_point, mid2_point, next_a_pin.center()]) - - - def add_layout_pins(self): - """ Add vdd and gnd rails and the input/output. Connect the gnd rails internally on - the top end with no input/output to obstruct. """ + self.add_path("m2", [z_pin.center(), mid1_point, mid2_point, next_a_pin.center()]) + def route_supplies(self): # Add power and ground to all the cells except: # the fanout driver, the right-most load # The routing to connect the loads is over the first and last cells # We have an even number of drivers and must only do every other # supply rail - for i in range(0,len(self.driver_inst_list),2): - inv = self.driver_inst_list[i] - for load in self.load_inst_map[inv]: - if load==self.rightest_load_inst[inv]: - continue - for pin_name in ["vdd", "gnd"]: - pin = load.get_pin(pin_name) - self.add_power_pin(pin_name, pin.rc()) - else: - # We have an even number of rows, so need to get the last gnd rail - inv = self.driver_inst_list[-1] - for load in self.load_inst_map[inv]: - if load==self.rightest_load_inst[inv]: - continue - pin_name = "gnd" - pin = load.get_pin(pin_name) - self.add_power_pin(pin_name, pin.rc()) + for inst in self.driver_inst_list: + load_list = self.load_inst_map[inst] + for pin_name in ["vdd", "gnd"]: + pin = load_list[0].get_pin(pin_name) + self.add_power_pin(pin_name, + pin.rc() - vector(self.m1_pitch, 0), + start_layer=pin.layer) + + pin = load_list[-2].get_pin(pin_name) + self.add_power_pin(pin_name, + pin.rc() - vector(self.m1_pitch, 0), + start_layer=pin.layer) + + def add_layout_pins(self): # input is A pin of first inverter a_pin = self.driver_inst_list[0].get_pin("A") - self.add_via_center(layers=("metal1","via1","metal2"), - offset=a_pin.center()) + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m2", + offset=a_pin.center()) self.add_layout_pin(text="in", - layer="metal2", - offset=a_pin.ll().scale(1,0), + layer="m2", + offset=a_pin.ll().scale(1, 0), height=a_pin.cy()) - # output is A pin of last load inverter last_driver_inst = self.driver_inst_list[-1] - a_pin = self.rightest_load_inst[last_driver_inst].get_pin("A") - self.add_via_center(layers=("metal1","via1","metal2"), - offset=a_pin.center()) - mid_point = vector(a_pin.cx()+3*self.m2_width,a_pin.cy()) - self.add_path("metal2",[a_pin.center(), mid_point, mid_point.scale(1,0)]) + a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A") + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m2", + offset=a_pin.center()) + mid_point = vector(a_pin.cx() + 3 * self.m2_width, a_pin.cy()) + self.add_path("m2", [a_pin.center(), mid_point, mid_point.scale(1, 0)]) self.add_layout_pin_segment_center(text="out", - layer="metal2", + layer="m2", start=mid_point, - end=mid_point.scale(1,0)) + end=mid_point.scale(1, 0)) def get_cin(self): """Get the enable input ralative capacitance""" - #Only 1 input to the delay chain which is connected to an inverter. + # Only 1 input to the delay chain which is connected to an inverter. dc_cin = self.inv.get_cin() - return dc_cin + return dc_cin def determine_delayed_en_stage_efforts(self, ext_delayed_en_cout, inp_is_rise=True): """Get the stage efforts from the en to s_en. Does not compute the delay for the bitline load.""" stage_effort_list = [] - #Add a stage to the list for every stage in delay chain. Stages only differ in fanout except the last which has an external cout. + # Add a stage to the list for every stage in delay chain. + # Stages only differ in fanout except the last which has an external cout. last_stage_is_rise = inp_is_rise for stage_fanout in self.fanout_list: - stage_cout = self.inv.get_cin()*(stage_fanout+1) - if len(stage_effort_list) == len(self.fanout_list)-1: #last stage + stage_cout = self.inv.get_cin() * (stage_fanout + 1) + if len(stage_effort_list) == len(self.fanout_list) - 1: stage_cout+=ext_delayed_en_cout stage = self.inv.get_stage_effort(stage_cout, last_stage_is_rise) stage_effort_list.append(stage) diff --git a/compiler/modules/dff_array.py b/compiler/modules/dff_array.py index 89b29476..d3f9b68e 100644 --- a/compiler/modules/dff_array.py +++ b/compiler/modules/dff_array.py @@ -7,12 +7,11 @@ # import debug import design -from tech import drc -from math import log from vector import vector from sram_factory import factory from globals import OPTS + class dff_array(design.design): """ This is a simple row (or multiple rows) of flops. @@ -52,41 +51,41 @@ class dff_array(design.design): self.add_mod(self.dff) def add_pins(self): - for row in range(self.rows): + for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_din_name(row,col), "INPUT") - for row in range(self.rows): + self.add_pin(self.get_din_name(row, col), "INPUT") + for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_dout_name(row,col), "OUTPUT") + self.add_pin(self.get_dout_name(row, col), "OUTPUT") self.add_pin("clk", "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") def create_dff_array(self): self.dff_insts={} - for row in range(self.rows): + for row in range(self.rows): for col in range(self.columns): - name = "dff_r{0}_c{1}".format(row,col) - self.dff_insts[row,col]=self.add_inst(name=name, - mod=self.dff) - self.connect_inst([self.get_din_name(row,col), - self.get_dout_name(row,col), - "clk", - "vdd", - "gnd"]) + name = "dff_r{0}_c{1}".format(row, col) + self.dff_insts[row, col]=self.add_inst(name=name, + mod=self.dff) + instance_ports = [self.get_din_name(row, col), + self.get_dout_name(row, col)] + for port in self.dff.pin_names: + if port != 'D' and port != 'Q': + instance_ports.append(port) + self.connect_inst(instance_ports) def place_dff_array(self): - for row in range(self.rows): + for row in range(self.rows): for col in range(self.columns): - name = "dff_r{0}_c{1}".format(row,col) if (row % 2 == 0): - base = vector(col*self.dff.width,row*self.dff.height) + base = vector(col * self.dff.width, row * self.dff.height) mirror = "R0" else: - base = vector(col*self.dff.width,(row+1)*self.dff.height) + base = vector(col * self.dff.width, (row + 1) * self.dff.height) mirror = "MX" - self.dff_insts[row,col].place(offset=base, - mirror=mirror) + self.dff_insts[row, col].place(offset=base, + mirror=mirror) def get_din_name(self, row, col): if self.columns == 1: @@ -94,7 +93,7 @@ class dff_array(design.design): elif self.rows == 1: din_name = "din_{0}".format(col) else: - din_name = "din_{0}_{1}".format(row,col) + din_name = "din_{0}_{1}".format(row, col) return din_name @@ -104,61 +103,58 @@ class dff_array(design.design): elif self.rows == 1: dout_name = "dout_{0}".format(col) else: - dout_name = "dout_{0}_{1}".format(row,col) + dout_name = "dout_{0}_{1}".format(row, col) return dout_name - def add_layout_pins(self): for row in range(self.rows): - for col in range(self.columns): + for col in range(self.columns): # Continous vdd rail along with label. - vdd_pin=self.dff_insts[row,col].get_pin("vdd") - self.add_power_pin("vdd", vdd_pin.center()) + vdd_pin=self.dff_insts[row, col].get_pin("vdd") + self.add_power_pin("vdd", vdd_pin.center(), start_layer=vdd_pin.layer) # Continous gnd rail along with label. - gnd_pin=self.dff_insts[row,col].get_pin("gnd") - self.add_power_pin("gnd", gnd_pin.center()) + gnd_pin=self.dff_insts[row, col].get_pin("gnd") + self.add_power_pin("gnd", gnd_pin.center(), start_layer=gnd_pin.layer) - - for row in range(self.rows): - for col in range(self.columns): - din_pin = self.dff_insts[row,col].get_pin("D") - debug.check(din_pin.layer=="metal2","DFF D pin not on metal2") - self.add_layout_pin(text=self.get_din_name(row,col), + for row in range(self.rows): + for col in range(self.columns): + din_pin = self.dff_insts[row, col].get_pin("D") + debug.check(din_pin.layer == "m2", "DFF D pin not on metal2") + self.add_layout_pin(text=self.get_din_name(row, col), layer=din_pin.layer, offset=din_pin.ll(), width=din_pin.width(), height=din_pin.height()) - dout_pin = self.dff_insts[row,col].get_pin("Q") - debug.check(dout_pin.layer=="metal2","DFF Q pin not on metal2") - self.add_layout_pin(text=self.get_dout_name(row,col), + dout_pin = self.dff_insts[row, col].get_pin("Q") + debug.check(dout_pin.layer == "m2", "DFF Q pin not on metal2") + self.add_layout_pin(text=self.get_dout_name(row, col), layer=dout_pin.layer, offset=dout_pin.ll(), width=dout_pin.width(), height=dout_pin.height()) - - # Create vertical spines to a single horizontal rail - clk_pin = self.dff_insts[0,0].get_pin("clk") - clk_ypos = 2*self.m3_pitch+self.m3_width - debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2") + clk_pin = self.dff_insts[0, 0].get_pin(self.dff.clk_pin) + clk_ypos = 2 * self.m3_pitch + self.m3_width + debug.check(clk_pin.layer == "m2", "DFF clk pin not on metal2") self.add_layout_pin_segment_center(text="clk", - layer="metal3", - start=vector(0,clk_ypos), - end=vector(self.width,clk_ypos)) + layer="m3", + start=vector(0, clk_ypos), + end=vector(self.width, clk_ypos)) for col in range(self.columns): - clk_pin = self.dff_insts[0,col].get_pin("clk") + clk_pin = self.dff_insts[0, col].get_pin(self.dff.clk_pin) # Make a vertical strip for each column - self.add_rect(layer="metal2", - offset=clk_pin.ll().scale(1,0), + self.add_rect(layer="m2", + offset=clk_pin.ll().scale(1, 0), width=self.m2_width, height=self.height) # Drop a via to the M3 pin - self.add_via_center(layers=("metal2","via2","metal3"), - offset=vector(clk_pin.cx(),clk_ypos)) + self.add_via_stack_center(from_layer=clk_pin.layer, + to_layer="m3", + offset=vector(clk_pin.cx(), clk_ypos)) def get_clk_cin(self): """Return the total capacitance (in relative units) that the clock is loaded by in the dff array""" diff --git a/compiler/modules/dff_buf.py b/compiler/modules/dff_buf.py index f6fc1cf2..a1e54a4d 100644 --- a/compiler/modules/dff_buf.py +++ b/compiler/modules/dff_buf.py @@ -7,12 +7,13 @@ # import debug import design -from tech import drc,parameter -from math import log +from tech import parameter, layer +from tech import cell_properties as props from vector import vector from globals import OPTS from sram_factory import factory + class dff_buf(design.design): """ This is a simple buffered DFF. The output is buffered @@ -34,7 +35,7 @@ class dff_buf(design.design): # This causes a DRC in the pinv which assumes min width rails. This ensures the output # contact does not violate spacing to the rail in the NMOS. debug.check(inv1_size>=2, "Inverter must be greater than two for rail spacing DRC rules.") - debug.check(inv2_size>=2, "Inverter must be greater than two for rail spacing DRC rules.") + debug.check(inv2_size>=2, "Inverter must be greater than two for rail spacing DRC rules.") self.inv1_size=inv1_size self.inv2_size=inv2_size @@ -49,15 +50,14 @@ class dff_buf(design.design): self.create_instances() def create_layout(self): - self.width = self.dff.width + self.inv1.width + self.inv2.width - self.height = self.dff.height - self.place_instances() + self.width = self.inv2_inst.rx() + self.height = self.dff.height self.route_wires() self.add_layout_pins() self.add_boundary() self.DRC_LVS() - + def add_modules(self): self.dff = factory.create(module_type="dff") self.add_mod(self.dff) @@ -71,8 +71,6 @@ class dff_buf(design.design): size=self.inv2_size, height=self.dff.height) self.add_mod(self.inv2) - - def add_pins(self): self.add_pin("D", "INPUT") @@ -82,58 +80,76 @@ class dff_buf(design.design): self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") + if props.dff_buff.add_body_contacts: + self.add_pin("vpb", "INPUT") + self.add_pin("vpn", "INPUT") + def create_instances(self): self.dff_inst=self.add_inst(name="dff_buf_dff", mod=self.dff) + self.connect_inst(["D", "qint", "clk", "vdd", "gnd"]) self.inv1_inst=self.add_inst(name="dff_buf_inv1", mod=self.inv1) - self.connect_inst(["qint", "Qb", "vdd", "gnd"]) + self.connect_inst(["qint", "Qb", "vdd", "gnd"]) self.inv2_inst=self.add_inst(name="dff_buf_inv2", mod=self.inv2) - self.connect_inst(["Qb", "Q", "vdd", "gnd"]) + self.connect_inst(["Qb", "Q", "vdd", "gnd"]) def place_instances(self): # Add the DFF - self.dff_inst.place(vector(0,0)) + self.dff_inst.place(vector(0, 0)) # Add INV1 to the right - self.inv1_inst.place(vector(self.dff_inst.rx(),0)) - + # The INV needs well spacing because the DFF is likely from a library + # with different well construction rules + well_spacing = 0 + try: + well_spacing = max(well_spacing, self.nwell_space) + except AttributeError: + pass + try: + well_spacing = max(well_spacing, self.pwell_space) + except AttributeError: + pass + try: + well_spacing = max(well_spacing, self.pwell_to_nwell) + except AttributeError: + pass + self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing + self.well_extend_active, 0)) + # Add INV2 to the right - self.inv2_inst.place(vector(self.inv1_inst.rx(),0)) + self.inv2_inst.place(vector(self.inv1_inst.rx(), 0)) def route_wires(self): + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + # Route dff q to inv1 a q_pin = self.dff_inst.get_pin("Q") a1_pin = self.inv1_inst.get_pin("A") - mid_x_offset = 0.5*(a1_pin.cx() + q_pin.cx()) - mid1 = vector(mid_x_offset, q_pin.cy()) - mid2 = vector(mid_x_offset, a1_pin.cy()) - self.add_path("metal3", [q_pin.center(), mid1, mid2, a1_pin.center()]) - self.add_via_center(layers=("metal2","via2","metal3"), - offset=q_pin.center()) - self.add_via_center(layers=("metal2","via2","metal3"), - offset=a1_pin.center()) - self.add_via_center(layers=("metal1","via1","metal2"), - offset=a1_pin.center()) + mid1 = vector(a1_pin.cx(), q_pin.cy()) + self.add_path(q_pin.layer, [q_pin.center(), mid1, a1_pin.center()], width=q_pin.height()) + self.add_via_stack_center(from_layer=a1_pin.layer, + to_layer=q_pin.layer, + offset=a1_pin.center()) # Route inv1 z to inv2 a z1_pin = self.inv1_inst.get_pin("Z") a2_pin = self.inv2_inst.get_pin("A") - mid_x_offset = 0.5*(z1_pin.cx() + a2_pin.cx()) - self.mid_qb_pos = vector(mid_x_offset, z1_pin.cy()) - mid2 = vector(mid_x_offset, a2_pin.cy()) - self.add_path("metal1", [z1_pin.center(), self.mid_qb_pos, mid2, a2_pin.center()]) + self.mid_qb_pos = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy()) + self.add_zjog(z1_pin.layer, z1_pin.center(), a2_pin.center()) def add_layout_pins(self): # Continous vdd rail along with label. vdd_pin=self.dff_inst.get_pin("vdd") self.add_layout_pin(text="vdd", - layer="metal1", + layer=vdd_pin.layer, offset=vdd_pin.ll(), width=self.width, height=vdd_pin.height()) @@ -141,7 +157,7 @@ class dff_buf(design.design): # Continous gnd rail along with label. gnd_pin=self.dff_inst.get_pin("gnd") self.add_layout_pin(text="gnd", - layer="metal1", + layer=gnd_pin.layer, offset=gnd_pin.ll(), width=self.width, height=vdd_pin.height()) @@ -161,26 +177,29 @@ class dff_buf(design.design): height=din_pin.height()) dout_pin = self.inv2_inst.get_pin("Z") - mid_pos = dout_pin.center() + vector(self.m1_pitch,0) - q_pos = mid_pos - vector(0,self.m2_pitch) + mid_pos = dout_pin.center() + vector(self.m2_nonpref_pitch, 0) + q_pos = mid_pos - vector(0, 2 * self.m2_nonpref_pitch) self.add_layout_pin_rect_center(text="Q", - layer="metal2", + layer="m2", offset=q_pos) - self.add_path("metal1", [dout_pin.center(), mid_pos, q_pos]) - self.add_via_center(layers=("metal1","via1","metal2"), - offset=q_pos) + self.add_path(self.route_layer, [dout_pin.center(), mid_pos, q_pos]) + self.add_via_stack_center(from_layer=dout_pin.layer, + to_layer="m2", + offset=q_pos) - qb_pos = self.mid_qb_pos + vector(0,self.m2_pitch) + qb_pos = self.mid_qb_pos + vector(0, 2 * self.m2_nonpref_pitch) self.add_layout_pin_rect_center(text="Qb", - layer="metal2", + layer="m2", offset=qb_pos) - self.add_path("metal1", [self.mid_qb_pos, qb_pos]) - self.add_via_center(layers=("metal1","via1","metal2"), - offset=qb_pos) + self.add_path(self.route_layer, [self.mid_qb_pos, qb_pos]) + a2_pin = self.inv2_inst.get_pin("A") + self.add_via_stack_center(from_layer=a2_pin.layer, + to_layer="m2", + offset=qb_pos) def get_clk_cin(self): """Return the total capacitance (in relative units) that the clock is loaded by in the dff""" - #This is a handmade cell so the value must be entered in the tech.py file or estimated. - #Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width. - #FIXME: Dff changed in a past commit. The parameter need to be updated. + # This is a handmade cell so the value must be entered in the tech.py file or estimated. + # Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width. + # FIXME: Dff changed in a past commit. The parameter need to be updated. return parameter["dff_clk_cin"] diff --git a/compiler/modules/dff_buf_array.py b/compiler/modules/dff_buf_array.py index 2fafee76..1cbd9284 100644 --- a/compiler/modules/dff_buf_array.py +++ b/compiler/modules/dff_buf_array.py @@ -7,12 +7,12 @@ # import debug import design -from tech import drc -from math import log +from tech import cell_properties as props from vector import vector from globals import OPTS from sram_factory import factory + class dff_buf_array(design.design): """ This is a simple row (or multiple rows) of flops. @@ -48,22 +48,27 @@ class dff_buf_array(design.design): self.width = self.columns * self.dff.width self.height = self.rows * self.dff.height self.place_dff_array() + self.route_supplies() self.add_layout_pins() self.add_boundary() self.DRC_LVS() def add_pins(self): - for row in range(self.rows): + for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_din_name(row,col), "INPUT") - for row in range(self.rows): + self.add_pin(self.get_din_name(row, col), "INPUT") + for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_dout_name(row,col), "OUTPUT") - self.add_pin(self.get_dout_bar_name(row,col), "OUTPUT") + self.add_pin(self.get_dout_name(row, col), "OUTPUT") + self.add_pin(self.get_dout_bar_name(row, col), "OUTPUT") self.add_pin("clk", "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") + if props.dff_buff_array.add_body_contacts: + self.add_pin("vpb", "INPUT") + self.add_pin("vnb", "INPUT") + def add_modules(self): self.dff = factory.create(module_type="dff_buf", inv1_size=self.inv1_size, @@ -72,30 +77,51 @@ class dff_buf_array(design.design): def create_dff_array(self): self.dff_insts={} - for row in range(self.rows): + for row in range(self.rows): for col in range(self.columns): - name = "dff_r{0}_c{1}".format(row,col) - self.dff_insts[row,col]=self.add_inst(name=name, - mod=self.dff) - self.connect_inst([self.get_din_name(row,col), - self.get_dout_name(row,col), - self.get_dout_bar_name(row,col), + name = "dff_r{0}_c{1}".format(row, col) + self.dff_insts[row, col]=self.add_inst(name=name, + mod=self.dff) + inst_ports = [self.get_din_name(row, col), + self.get_dout_name(row, col), + self.get_dout_bar_name(row, col), "clk", "vdd", - "gnd"]) + "gnd"] + if props.dff_buff_array.add_body_contacts: + inst_ports.append("vpb") + inst_ports.append("vnb") + self.connect_inst(inst_ports) def place_dff_array(self): - for row in range(self.rows): + + well_spacing = 0 + try: + well_spacing = max(self.nwell_space, well_spacing) + except AttributeError: + pass + try: + well_spacing = max(self.pwell_space, well_spacing) + except AttributeError: + pass + try: + well_spacing = max(self.pwell_to_nwell, well_spacing) + except AttributeError: + pass + + dff_pitch = self.dff.width + well_spacing + self.well_extend_active + + for row in range(self.rows): for col in range(self.columns): - name = "Xdff_r{0}_c{1}".format(row,col) + # name = "Xdff_r{0}_c{1}".format(row, col) if (row % 2 == 0): - base = vector(col*self.dff.width,row*self.dff.height) + base = vector(col * dff_pitch, row * self.dff.height) mirror = "R0" else: - base = vector(col*self.dff.width,(row+1)*self.dff.height) + base = vector(col * dff_pitch, (row + 1) * self.dff.height) mirror = "MX" - self.dff_insts[row,col].place(offset=base, - mirror=mirror) + self.dff_insts[row, col].place(offset=base, + mirror=mirror) def get_din_name(self, row, col): if self.columns == 1: @@ -103,7 +129,7 @@ class dff_buf_array(design.design): elif self.rows == 1: din_name = "din_{0}".format(col) else: - din_name = "din_{0}_{1}".format(row,col) + din_name = "din_{0}_{1}".format(row, col) return din_name @@ -113,7 +139,7 @@ class dff_buf_array(design.design): elif self.rows == 1: dout_name = "dout_{0}".format(col) else: - dout_name = "dout_{0}_{1}".format(row,col) + dout_name = "dout_{0}_{1}".format(row, col) return dout_name @@ -123,75 +149,84 @@ class dff_buf_array(design.design): elif self.rows == 1: dout_bar_name = "dout_bar_{0}".format(col) else: - dout_bar_name = "dout_bar_{0}_{1}".format(row,col) + dout_bar_name = "dout_bar_{0}_{1}".format(row, col) return dout_bar_name - - def add_layout_pins(self): + + def route_supplies(self): for row in range(self.rows): - for col in range(self.columns): + vdd0_pin=self.dff_insts[row, 0].get_pin("vdd") + vddn_pin=self.dff_insts[row, self.columns - 1].get_pin("vdd") + self.add_path(vdd0_pin.layer, [vdd0_pin.lc(), vddn_pin.rc()], width=vdd0_pin.height()) + + gnd0_pin=self.dff_insts[row, 0].get_pin("gnd") + gndn_pin=self.dff_insts[row, self.columns - 1].get_pin("gnd") + self.add_path(gnd0_pin.layer, [gnd0_pin.lc(), gndn_pin.rc()], width=gnd0_pin.height()) + + for row in range(self.rows): + for col in range(self.columns): # Continous vdd rail along with label. - vdd_pin=self.dff_insts[row,col].get_pin("vdd") - self.add_power_pin("vdd", vdd_pin.lc()) + vdd_pin=self.dff_insts[row, col].get_pin("vdd") + self.add_power_pin("vdd", vdd_pin.lc(), start_layer=vdd_pin.layer) # Continous gnd rail along with label. - gnd_pin=self.dff_insts[row,col].get_pin("gnd") - self.add_power_pin("gnd", gnd_pin.lc()) + gnd_pin=self.dff_insts[row, col].get_pin("gnd") + self.add_power_pin("gnd", gnd_pin.lc(), start_layer=gnd_pin.layer) + + def add_layout_pins(self): - - for row in range(self.rows): - for col in range(self.columns): - din_pin = self.dff_insts[row,col].get_pin("D") - debug.check(din_pin.layer=="metal2","DFF D pin not on metal2") - self.add_layout_pin(text=self.get_din_name(row,col), + for row in range(self.rows): + for col in range(self.columns): + din_pin = self.dff_insts[row, col].get_pin("D") + debug.check(din_pin.layer=="m2", "DFF D pin not on metal2") + self.add_layout_pin(text=self.get_din_name(row, col), layer=din_pin.layer, offset=din_pin.ll(), width=din_pin.width(), height=din_pin.height()) - dout_pin = self.dff_insts[row,col].get_pin("Q") - debug.check(dout_pin.layer=="metal2","DFF Q pin not on metal2") - self.add_layout_pin(text=self.get_dout_name(row,col), + dout_pin = self.dff_insts[row, col].get_pin("Q") + debug.check(dout_pin.layer=="m2", "DFF Q pin not on metal2") + self.add_layout_pin(text=self.get_dout_name(row, col), layer=dout_pin.layer, offset=dout_pin.ll(), width=dout_pin.width(), height=dout_pin.height()) - dout_bar_pin = self.dff_insts[row,col].get_pin("Qb") - debug.check(dout_bar_pin.layer=="metal2","DFF Qb pin not on metal2") - self.add_layout_pin(text=self.get_dout_bar_name(row,col), + dout_bar_pin = self.dff_insts[row, col].get_pin("Qb") + debug.check(dout_bar_pin.layer=="m2", "DFF Qb pin not on metal2") + self.add_layout_pin(text=self.get_dout_bar_name(row, col), layer=dout_bar_pin.layer, offset=dout_bar_pin.ll(), width=dout_bar_pin.width(), height=dout_bar_pin.height()) - # Create vertical spines to a single horizontal rail - clk_pin = self.dff_insts[0,0].get_pin("clk") - clk_ypos = 2*self.m3_pitch+self.m3_width - debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2") + clk_pin = self.dff_insts[0, 0].get_pin("clk") + clk_ypos = 2 * self.m3_pitch + self.m3_width + debug.check(clk_pin.layer=="m2", "DFF clk pin not on metal2") if self.columns==1: self.add_layout_pin(text="clk", - layer="metal2", - offset=clk_pin.ll().scale(1,0), + layer="m2", + offset=clk_pin.ll().scale(1, 0), width=self.m2_width, height=self.height) else: self.add_layout_pin_segment_center(text="clk", - layer="metal3", - start=vector(0,clk_ypos), - end=vector(self.width,clk_ypos)) + layer="m3", + start=vector(0, clk_ypos), + end=vector(self.width, clk_ypos)) for col in range(self.columns): - clk_pin = self.dff_insts[0,col].get_pin("clk") + clk_pin = self.dff_insts[0, col].get_pin("clk") # Make a vertical strip for each column - self.add_rect(layer="metal2", - offset=clk_pin.ll().scale(1,0), + self.add_rect(layer="m2", + offset=clk_pin.ll().scale(1, 0), width=self.m2_width, height=self.height) # Drop a via to the M3 pin - self.add_via_center(layers=("metal2","via2","metal3"), - offset=vector(clk_pin.cx(),clk_ypos)) + self.add_via_center(layers=self.m2_stack, + offset=vector(clk_pin.cx(), clk_ypos)) def get_clk_cin(self): """Return the total capacitance (in relative units) that the clock is loaded by in the dff array""" diff --git a/compiler/modules/dff_inv.py b/compiler/modules/dff_inv.py index 207a5ad0..9dcb84c5 100644 --- a/compiler/modules/dff_inv.py +++ b/compiler/modules/dff_inv.py @@ -97,13 +97,13 @@ class dff_inv(design.design): mid_x_offset = 0.5*(a1_pin.cx() + q_pin.cx()) mid1 = vector(mid_x_offset, q_pin.cy()) mid2 = vector(mid_x_offset, a1_pin.cy()) - self.add_path("metal3", + self.add_path("m3", [q_pin.center(), mid1, mid2, a1_pin.center()]) - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_via_center(layers=self.m2_stack, offset=q_pin.center()) - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_via_center(layers=self.m2_stack, offset=a1_pin.center()) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=a1_pin.center()) @@ -112,7 +112,7 @@ class dff_inv(design.design): # Continous vdd rail along with label. vdd_pin=self.dff_inst.get_pin("vdd") self.add_layout_pin(text="vdd", - layer="metal1", + layer="m1", offset=vdd_pin.ll(), width=self.width, height=vdd_pin.height()) @@ -120,7 +120,7 @@ class dff_inv(design.design): # Continous gnd rail along with label. gnd_pin=self.dff_inst.get_pin("gnd") self.add_layout_pin(text="gnd", - layer="metal1", + layer="m1", offset=gnd_pin.ll(), width=self.width, height=vdd_pin.height()) @@ -146,9 +146,9 @@ class dff_inv(design.design): dout_pin = self.inv1_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Qb", - layer="metal2", + layer="m2", offset=dout_pin.center()) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=self.m1_stack, offset=dout_pin.center()) def get_clk_cin(self): diff --git a/compiler/modules/dff_inv_array.py b/compiler/modules/dff_inv_array.py index 760a2337..aadb4257 100644 --- a/compiler/modules/dff_inv_array.py +++ b/compiler/modules/dff_inv_array.py @@ -140,7 +140,7 @@ class dff_inv_array(design.design): for row in range(self.rows): for col in range(self.columns): din_pin = self.dff_insts[row,col].get_pin("D") - debug.check(din_pin.layer=="metal2","DFF D pin not on metal2") + debug.check(din_pin.layer=="m2","DFF D pin not on metal2") self.add_layout_pin(text=self.get_din_name(row,col), layer=din_pin.layer, offset=din_pin.ll(), @@ -148,7 +148,7 @@ class dff_inv_array(design.design): height=din_pin.height()) dout_pin = self.dff_insts[row,col].get_pin("Q") - debug.check(dout_pin.layer=="metal2","DFF Q pin not on metal2") + debug.check(dout_pin.layer=="m2","DFF Q pin not on metal2") self.add_layout_pin(text=self.get_dout_name(row,col), layer=dout_pin.layer, offset=dout_pin.ll(), @@ -156,7 +156,7 @@ class dff_inv_array(design.design): height=dout_pin.height()) dout_bar_pin = self.dff_insts[row,col].get_pin("Qb") - debug.check(dout_bar_pin.layer=="metal2","DFF Qb pin not on metal2") + debug.check(dout_bar_pin.layer=="m2","DFF Qb pin not on metal2") self.add_layout_pin(text=self.get_dout_bar_name(row,col), layer=dout_bar_pin.layer, offset=dout_bar_pin.ll(), @@ -167,27 +167,27 @@ class dff_inv_array(design.design): # Create vertical spines to a single horizontal rail clk_pin = self.dff_insts[0,0].get_pin("clk") clk_ypos = 2*self.m3_pitch+self.m3_width - debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2") + debug.check(clk_pin.layer=="m2","DFF clk pin not on metal2") if self.columns==1: self.add_layout_pin(text="clk", - layer="metal2", + layer="m2", offset=clk_pin.ll().scale(1,0), width=self.m2_width, height=self.height) else: self.add_layout_pin_segment_center(text="clk", - layer="metal3", + layer="m3", start=vector(0,clk_ypos), end=vector(self.width,clk_ypos)) for col in range(self.columns): clk_pin = self.dff_insts[0,col].get_pin("clk") # Make a vertical strip for each column - self.add_rect(layer="metal2", + self.add_rect(layer="m2", offset=clk_pin.ll().scale(1,0), width=self.m2_width, height=self.height) # Drop a via to the M3 pin - self.add_via_center(layers=("metal2","via2","metal3"), + self.add_via_center(layers=self.m2_stack, offset=vector(clk_pin.cx(),clk_ypos)) def get_clk_cin(self): diff --git a/compiler/modules/dummy_array.py b/compiler/modules/dummy_array.py index f1f433ce..f4b240da 100644 --- a/compiler/modules/dummy_array.py +++ b/compiler/modules/dummy_array.py @@ -3,31 +3,22 @@ # Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # -import debug -import design -from tech import drc -import contact +from bitcell_base_array import bitcell_base_array from sram_factory import factory -from vector import vector from globals import OPTS -class dummy_array(design.design): + +class dummy_array(bitcell_base_array): """ Generate a dummy row/column for the replica array. """ - def __init__(self, cols, rows, mirror=0, name=""): - design.design.__init__(self, name) - debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) - self.add_comment("rows: {0} cols: {1}".format(rows, cols)) - - self.column_size = cols - self.row_size = rows + def __init__(self, cols, rows, column_offset=0, mirror=0, name=""): + super().__init__(cols, rows, name, column_offset) self.mirror = mirror self.create_netlist() if not OPTS.netlist_only: self.create_layout() - def create_netlist(self): """ Create and connect the netlist """ @@ -37,27 +28,7 @@ class dummy_array(design.design): def create_layout(self): - # We increase it by a well enclosure so the precharges don't overlap our wells - self.height = self.row_size*self.dummy_cell.height - self.width = self.column_size*self.dummy_cell.width - - xoffset = 0.0 - for col in range(self.column_size): - yoffset = 0.0 - for row in range(self.row_size): - name = "dummy_r{0}_c{1}".format(row, col) - - if (row+self.mirror) % 2: - tempy = yoffset + self.dummy_cell.height - dir_key = "MX" - else: - tempy = yoffset - dir_key = "" - - self.cell_inst[row,col].place(offset=[xoffset, tempy], - mirror=dir_key) - yoffset += self.dummy_cell.height - xoffset += self.dummy_cell.width + self.place_array("dummy_r{0}_c{1}", self.mirror) self.add_layout_pins() @@ -65,85 +36,22 @@ class dummy_array(design.design): self.DRC_LVS() - def add_pins(self): - row_list = self.cell.get_all_wl_names() - column_list = self.cell.get_all_bitline_names() - for col in range(self.column_size): - for cell_column in column_list: - self.add_pin(cell_column+"_{0}".format(col), "INOUT") - for row in range(self.row_size): - for cell_row in row_list: - self.add_pin(cell_row+"_{0}".format(row), "INPUT") - self.add_pin("vdd", "POWER") - self.add_pin("gnd", "GROUND") - def add_modules(self): """ Add the modules used in this design """ - self.dummy_cell = factory.create(module_type="dummy_bitcell") + self.dummy_cell = factory.create(module_type="dummy_{}".format(OPTS.bitcell)) self.add_mod(self.dummy_cell) self.cell = factory.create(module_type="bitcell") - def get_bitcell_pins(self, col, row): - """ Creates a list of connections in the bitcell, - indexed by column and row, for instance use in bitcell_array """ - - bitcell_pins = [] - - pin_names = self.cell.get_all_bitline_names() - for pin in pin_names: - bitcell_pins.append(pin+"_{0}".format(col)) - pin_names = self.cell.get_all_wl_names() - for pin in pin_names: - bitcell_pins.append(pin+"_{0}".format(row)) - bitcell_pins.append("vdd") - bitcell_pins.append("gnd") - - return bitcell_pins - - def create_instances(self): """ Create the module instances used in this design """ self.cell_inst = {} for col in range(self.column_size): for row in range(self.row_size): name = "bit_r{0}_c{1}".format(row, col) - self.cell_inst[row,col]=self.add_inst(name=name, - mod=self.dummy_cell) + self.cell_inst[row, col]=self.add_inst(name=name, + mod=self.dummy_cell) self.connect_inst(self.get_bitcell_pins(col, row)) - - def add_layout_pins(self): - """ Add the layout pins """ - - row_list = self.cell.get_all_wl_names() - column_list = self.cell.get_all_bitline_names() - - for col in range(self.column_size): - for cell_column in column_list: - bl_pin = self.cell_inst[0,col].get_pin(cell_column) - self.add_layout_pin(text=cell_column+"_{0}".format(col), - layer="metal2", - offset=bl_pin.ll(), - width=bl_pin.width(), - height=self.height) - - for row in range(self.row_size): - for cell_row in row_list: - wl_pin = self.cell_inst[row,0].get_pin(cell_row) - self.add_layout_pin(text=cell_row+"_{0}".format(row), - layer="metal1", - offset=wl_pin.ll(), - width=self.width, - height=wl_pin.height()) - - # For every second row and column, add a via for gnd and vdd - for row in range(self.row_size): - for col in range(self.column_size): - inst = self.cell_inst[row,col] - for pin_name in ["vdd", "gnd"]: - for pin in inst.get_pins(pin_name): - self.add_power_pin(name=pin_name, loc=pin.center(), vertical=True, start_layer=pin.layer) - def input_load(self): wl_wire = self.gen_wl_wire() @@ -151,7 +59,7 @@ class dummy_array(design.design): def get_wordline_cin(self): """Get the relative input capacitance from the wordline connections in all the bitcell""" - #A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns + # A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns bitcell_wl_cin = self.cell.get_wl_cin() total_cin = bitcell_wl_cin * self.column_size return total_cin diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 0e70db55..3233bdc8 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -5,40 +5,37 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from tech import drc import debug import design -from math import log -from math import sqrt import math -import contact from sram_factory import factory from vector import vector from globals import OPTS + class hierarchical_decoder(design.design): """ Dynamically generated hierarchical decoder. """ - def __init__(self, name, rows, height=None): + def __init__(self, name, num_outputs): 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 = [] - self.cell_height = height - self.rows = rows - self.num_inputs = int(math.log(self.rows, 2)) - (self.no_of_pre2x4,self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs) + b = factory.create(module_type="bitcell") + self.cell_height = b.height + + self.num_outputs = num_outputs + self.num_inputs = math.ceil(math.log(self.num_outputs, 2)) + (self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs) self.create_netlist() if not OPTS.netlist_only: self.create_layout() - - + def create_netlist(self): self.add_modules() self.setup_netlist_constants() @@ -50,23 +47,32 @@ class hierarchical_decoder(design.design): self.setup_layout_constants() self.place_pre_decoder() self.place_row_decoder() - self.route_input_rails() - self.route_predecode_rails() + + self.height = max(self.predecoder_height, self.row_decoder_height) + self.bus_space + + self.route_inputs() + self.route_outputs() + self.route_decoder_bus() self.route_vdd_gnd() + self.offset_all_coordinates() + + self.width = self.and_inst[0].rx() + self.m1_space + self.add_boundary() self.DRC_LVS() def add_modules(self): - self.inv = factory.create(module_type="pinv", - height=self.cell_height) - self.add_mod(self.inv) - self.nand2 = factory.create(module_type="pnand2", - 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.and2 = factory.create(module_type="and2_dec", + height=self.cell_height) + self.add_mod(self.and2) + + self.and3 = factory.create(module_type="and3_dec", + height=self.cell_height) + self.add_mod(self.and3) + # TBD + # self.and4 = factory.create(module_type="and4_dec") + # self.add_mod(self.and4) self.add_decoders() @@ -80,32 +86,32 @@ 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. - # Distributing vertical rails to different groups. One group belongs to one pre-decoder. + # Distributing vertical bus to different groups. One group belongs to one pre-decoder. # For example, for two 2:4 pre-decoder and one 3:8 pre-decoder, we will # have total 16 output lines out of these 3 pre-decoders and they will # be distributed as [ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ] @@ -125,107 +131,121 @@ 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 + # FIXME: Update with 4x16 + if self.no_of_pre3x8 > 0 and self.no_of_pre2x4 > 0: + self.predecoder_width = max(self.pre3_8.width, self.pre2_4.width) + elif self.no_of_pre3x8 > 0: + self.predecoder_width = self.pre3_8.width else: self.predecoder_width = self.pre2_4.width - - self.predecoder_height = self.pre2_4.height*self.no_of_pre2x4 + self.pre3_8.height*self.no_of_pre3x8 - # Calculates height and width of row-decoder - if (self.num_inputs == 4 or self.num_inputs == 5): - nand_width = self.nand2.width + # How much space between each predecoder + self.predecoder_spacing = 2 * self.and2.height + self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 \ + + (self.no_of_pre2x4 + self.no_of_pre3x8 - 1) * self.predecoder_spacing + + # Inputs to cells are on input layer + # Outputs from cells are on output layer + if OPTS.tech_name == "sky130": + self.bus_layer = "m1" + self.bus_directions = "nonpref" + self.bus_pitch = self.m1_pitch + self.bus_space = self.m2_space + self.input_layer = "m2" + self.output_layer = "li" + self.output_layer_pitch = self.li_pitch else: - nand_width = self.nand3.width - self.internal_routing_width = self.m2_pitch*self.total_number_of_predecoder_outputs - self.row_decoder_height = self.inv.height * self.rows + self.bus_layer = "m2" + self.bus_directions = "pref" + self.bus_pitch = self.m2_pitch + self.bus_space = self.m2_space + # These two layers being the same requires a special jog + # to ensure to conflicts with the output layers + self.input_layer = "m1" + self.output_layer = "m3" + self.output_layer_pitch = self.m3_pitch - 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 - - 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 + # Two extra pitches between modules on left and right + self.internal_routing_width = self.total_number_of_predecoder_outputs * self.bus_pitch + self.bus_pitch + self.row_decoder_height = self.and2.height * self.num_outputs + # Extra bus space for supply contacts + self.input_routing_width = self.num_inputs * self.bus_pitch + self.bus_space + + def route_inputs(self): + """ Create input bus for the predecoders """ # Find the left-most predecoder min_x = 0 if self.no_of_pre2x4 > 0: - min_x = min(min_x, -self.pre2_4.width) + min_x = min(min_x, self.pre2x4_inst[0].lx()) 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) + min_x = min(min_x, self.pre3x8_inst[0].lx()) + input_offset=vector(min_x - self.input_routing_width, 0) input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)] - self.input_rails = self.create_vertical_pin_bus(layer="metal2", - pitch=self.m2_pitch, - offset=input_offset, - names=input_bus_names, - length=input_height) + self.input_bus = self.create_vertical_pin_bus(layer=self.bus_layer, + offset=input_offset, + names=input_bus_names, + length=self.predecoder_height) 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): for i in range(2): index = pre_num * 2 + i - input_pos = self.input_rails["addr_{}".format(index)] + input_pos = self.input_bus["addr_{}".format(index)].center() in_name = "in_{}".format(i) decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name) - # To prevent conflicts, we will offset each input connect so - # that it aligns with the vdd/gnd rails - decoder_offset = decoder_pin.bc() + vector(0,(i+1)*self.inv.height) - input_offset = input_pos.scale(1,0) + decoder_offset.scale(0,1) + decoder_offset = decoder_pin.center() + input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) - self.route_input_rail(decoder_offset, input_offset) - + self.route_input_bus(decoder_offset, input_offset) for pre_num in range(self.no_of_pre3x8): for i in range(3): index = pre_num * 3 + i + self.no_of_pre2x4 * 2 - input_pos = self.input_rails["addr_{}".format(index)] + input_pos = self.input_bus["addr_{}".format(index)].center() in_name = "in_{}".format(i) decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name) - # To prevent conflicts, we will offset each input connect so - # that it aligns with the vdd/gnd rails - decoder_offset = decoder_pin.bc() + vector(0,(i+1)*self.inv.height) - input_offset = input_pos.scale(1,0) + decoder_offset.scale(0,1) + decoder_offset = decoder_pin.center() + input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) - self.route_input_rail(decoder_offset, input_offset) - + self.route_input_bus(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 """ + def route_input_bus(self, input_offset, output_offset): + """ + Route a vertical M2 coordinate to another + vertical M2 coordinate to the predecode inputs + """ - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=input_offset) - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=output_offset) - self.add_path(("metal3"), [input_offset, output_offset]) - + self.add_via_stack_center(from_layer=self.bus_layer, + to_layer=self.input_layer, + offset=input_offset) + self.add_via_stack_center(from_layer=self.bus_layer, + to_layer=self.input_layer, + offset=output_offset, + directions=self.bus_directions) + self.add_path(self.input_layer, [input_offset, output_offset]) def add_pins(self): """ Add the module pins """ @@ -233,12 +253,11 @@ class hierarchical_decoder(design.design): for i in range(self.num_inputs): self.add_pin("addr_{0}".format(i), "INPUT") - for j in range(self.rows): + for j in range(self.num_outputs): self.add_pin("decode_{0}".format(j), "OUTPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") - def create_pre_decoder(self): """ Creates pre-decoder and places labels input address [A] """ @@ -248,7 +267,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): @@ -268,8 +287,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 @@ -283,11 +301,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] """ @@ -297,212 +314,151 @@ 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) + base= vector(-self.pre2_4.width, num * (self.pre2_4.height + self.predecoder_spacing)) 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) else: - height = self.no_of_pre2x4*self.pre2_4.height + num*self.pre3_8.height + height = self.no_of_pre2x4 * (self.pre2_4.height + self.predecoder_spacing) + num * (self.pre3_8.height + self.predecoder_spacing) offset = vector(-self.pre3_8.width, height) self.pre3x8_inst[num].place(offset) - 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_and_array(self): + """ Add a column of AND gates for final decode """ - def create_decoder_nand_array(self): - """ Add a column of NAND 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 - name = self.NAND_FORMAT.format(row) - self.nand_inst.append(self.add_inst(name=name, - mod=self.nand2)) - pins =["out_{0}".format(i), - "out_{0}".format(j + len(self.predec_groups[0])), - "Z_{0}".format(row), - "vdd", "gnd"] - self.connect_inst(pins) + output = len(self.predec_groups[0]) * j + i + if (output < self.num_outputs): + name = self.AND_FORMAT.format(output) + 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])), + "decode_{0}".format(output), + "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 + output = (len(self.predec_groups[0]) * len(self.predec_groups[1])) * k \ + + len(self.predec_groups[0]) * j + i - name = self.NAND_FORMAT.format(row) - self.nand_inst.append(self.add_inst(name=name, - mod=self.nand3)) + if (output < self.num_outputs): + name = self.AND_FORMAT.format(output) + 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), - "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) + 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])), + "decode_{0}".format(output), + "vdd", "gnd"] + self.connect_inst(pins) 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.route_decoder() + self.place_decoder_and_array() - - 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. + This may have more than one decoder per row to match the bitcell height. + """ - # 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.""" - - for row in range(self.rows): - name = self.NAND_FORMAT.format(row) + def place_and_array(self, and_mod): + """ + Add a column of AND gates for the decoder above the predecoders. + """ + + for row in range(self.num_outputs): 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) + x_off = self.internal_routing_width + self.and_inst[row].place(offset=vector(x_off, y_off), + mirror=mirror) - + def route_outputs(self): + """ Add the pins. """ - - def route_decoder(self): - """ Route the nand to inverter in the decoder and 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("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos]) - - z_pin = self.inv_inst[row].get_pin("Z") - self.add_layout_pin(text="decode_{0}".format(row), - layer="metal1", - offset=z_pin.ll(), - width=z_pin.width(), - height=z_pin.height()) + for row in range(self.num_outputs): + and_inst = self.and_inst[row] + self.copy_layout_pin(and_inst, "Z", "decode_{0}".format(row)) - - - def route_predecode_rails(self): - """ Creates vertical metal 2 rails to connect predecoder and decoder stages.""" + def route_decoder_bus(self): + """ + Creates vertical metal 2 bus 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) + # This leaves an offset for the predecoder output jogs input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)] - self.predecode_rails = self.create_vertical_pin_bus(layer="metal2", - pitch=self.m2_pitch, - offset=input_offset, - names=input_bus_names, - length=self.height) - + self.predecode_bus = self.create_vertical_pin_bus(layer=self.bus_layer, + pitch=self.bus_pitch, + offset=vector(self.bus_pitch, 0), + names=input_bus_names, + length=self.height) - self.route_rails_to_predecodes() - self.route_rails_to_decoder() - - def route_rails_to_predecodes(self): - """ Iterates through all of the predecodes and connects to the rails including the offsets """ + self.route_predecodes_to_bus() + self.route_bus_to_decoder() + def route_predecodes_to_bus(self): + """ + Iterates through all of the predecodes + and connects to the rails including the offsets + """ # FIXME: convert to connect_bus for pre_num in range(self.no_of_pre2x4): for i in range(4): 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) - + x_offset = self.pre2x4_inst[pre_num].rx() + self.output_layer_pitch + y_offset = self.pre2x4_inst[pre_num].by() + i * self.cell_height + self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset) # FIXME: convert to connect_bus for pre_num in range(self.no_of_pre3x8): @@ -510,91 +466,147 @@ 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) + x_offset = self.pre3x8_inst[pre_num].rx() + self.output_layer_pitch + y_offset = self.pre3x8_inst[pre_num].by() + i * self.cell_height + self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset) - - - def route_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. - 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] + def route_bus_to_decoder(self): """ - row_index = 0 + 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 AND3 inputs are connected to [0,4,8], + second AND3 is connected to [0,4,9], + ... + and the 128th AND3 is connected to [3,7,15] + """ + output_index = 0 + if (self.num_inputs == 4 or self.num_inputs == 5): for index_B in self.predec_groups[1]: for index_A in self.predec_groups[0]: # FIXME: convert to connect_bus? - 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")) - row_index = row_index + 1 + if (output_index < self.num_outputs): + predecode_name = "predecode_{}".format(index_A) + self.route_predecode_bus_outputs(predecode_name, + self.and_inst[output_index].get_pin("A"), + output_index) + predecode_name = "predecode_{}".format(index_B) + self.route_predecode_bus_outputs(predecode_name, + self.and_inst[output_index].get_pin("B"), + output_index) + output_index = output_index + 1 elif (self.num_inputs > 5): for index_C in self.predec_groups[2]: for index_B in self.predec_groups[1]: for index_A in self.predec_groups[0]: # FIXME: convert to connect_bus? - 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")) - row_index = row_index + 1 + if (output_index < self.num_outputs): + predecode_name = "predecode_{}".format(index_A) + self.route_predecode_bus_outputs(predecode_name, + self.and_inst[output_index].get_pin("A"), + output_index) + predecode_name = "predecode_{}".format(index_B) + self.route_predecode_bus_outputs(predecode_name, + self.and_inst[output_index].get_pin("B"), + output_index) + predecode_name = "predecode_{}".format(index_C) + self.route_predecode_bus_outputs(predecode_name, + self.and_inst[output_index].get_pin("C"), + output_index) + output_index = output_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].cx() - 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) - 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="metal1", - offset=mid, - width=end.x-start.x) + """ + Add a pin for each row of vdd/gnd which are + must-connects next level up. + """ - - # 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") + if OPTS.tech_name == "sky130": + for n in ["vdd", "gnd"]: + pins = self.and_inst[0].get_pins(n) + for pin in pins: + self.add_rect(layer=pin.layer, + offset=pin.ll() + vector(0, self.bus_space), + width=pin.width(), + height=self.height - 2 * self.bus_space) + + # This adds power vias at the top of each cell + # (except the last to keep them inside the boundary) + for i in self.and_inst[:-1]: + pins = i.get_pins(n) + for pin in pins: + self.add_power_pin(name=n, + loc=pin.uc(), + start_layer=pin.layer) + self.add_power_pin(name=n, + loc=pin.uc(), + start_layer=pin.layer) + + for i in self.pre2x4_inst + self.pre3x8_inst: + self.copy_layout_pin(i, n) + else: + # The vias will be placed at the right of the cells. + xoffset = max(x.rx() for x in self.and_inst) + 0.5 * self.m1_space + for row in range(0, self.num_outputs): + for pin_name in ["vdd", "gnd"]: + # The nand and inv are the same height rows... + supply_pin = self.and_inst[row].get_pin(pin_name) + pin_pos = vector(xoffset, supply_pin.cy()) + self.add_power_pin(name=pin_name, + loc=pin_pos, + start_layer=supply_pin.layer) + + # Copy the pins from the predecoders + for pre in self.pre2x4_inst + self.pre3x8_inst: + for pin_name in ["vdd", "gnd"]: + self.copy_layout_pin(pre, pin_name) + def route_predecode_bus_outputs(self, rail_name, pin, row): + """ + Connect the routing rail to the given metal1 pin + using a routing track at the given y_offset + """ - def route_predecode_rail(self, rail_name, pin): - """ Connect the routing rail to the given metal1 pin """ - rail_pos = vector(self.predecode_rails[rail_name].x,pin.lc().y) - self.add_path("metal1", [rail_pos, pin.lc()]) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=rail_pos) - - - def route_predecode_rail_m3(self, rail_name, pin): - """ Connect the routing rail to the given metal1 pin """ + pin_pos = pin.center() + rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y) + self.add_path(self.input_layer, [rail_pos, pin_pos]) + + self.add_via_stack_center(from_layer=self.bus_layer, + to_layer=self.input_layer, + offset=rail_pos, + directions=self.bus_directions) + + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.input_layer, + offset=pin_pos, + directions=("H", "H")) + + def route_predecode_bus_inputs(self, rail_name, pin, x_offset, y_offset): + """ + Connect the routing rail to the given metal1 pin using a jog + to the right of the cell at the given x_offset. + """ # This routes the pin up to the rail, basically, to avoid conflicts. # It would be fixed with a channel router. - mid_point = vector(pin.cx(), pin.cy()+self.inv.height/2) - rail_pos = vector(self.predecode_rails[rail_name].x,mid_point.y) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=pin.center()) - self.add_wire(("metal3","via2","metal2"), [rail_pos, mid_point, pin.uc()]) - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=rail_pos) + pin_pos = pin.rc() + mid_point1 = vector(x_offset, pin_pos.y) + mid_point2 = vector(x_offset, y_offset) + rail_pos = vector(self.predecode_bus[rail_name].cx(), mid_point2.y) + self.add_path(self.output_layer, [pin_pos, mid_point1, mid_point2, rail_pos]) + + # pin_pos = pin.center() + # rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y) + # self.add_path(self.output_layer, [pin_pos, rail_pos]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.output_layer, + offset=pin_pos) + self.add_via_stack_center(from_layer=self.bus_layer, + to_layer=self.output_layer, + offset=rail_pos, + directions=self.bus_directions) def input_load(self): if self.determine_predecodes(self.num_inputs)[1]==0: diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index bec0ce06..a89f5fa6 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -8,19 +8,28 @@ import debug import design import math -from tech import drc -import contact from vector import vector -from globals import OPTS from sram_factory import factory +from globals import OPTS + class hierarchical_predecode(design.design): """ - Pre 2x4 and 3x8 decoder shared code. + Pre 2x4 and 3x8 and TBD 4x16 decoder shared code. """ def __init__(self, name, input_number, height=None): self.number_of_inputs = input_number - self.cell_height = height + + b = factory.create(module_type="bitcell") + if not height: + self.cell_height = b.height + self.column_decoder = False + else: + self.cell_height = height + # If we are pitch matched to the bitcell, it's a predecoder + # otherwise it's a column decoder (out of pgates) + self.column_decoder = (height != b.height) + self.number_of_outputs = int(math.pow(2, self.number_of_inputs)) design.design.__init__(self, name) @@ -33,70 +42,103 @@ class hierarchical_predecode(design.design): self.add_pin("gnd", "GROUND") def add_modules(self): - """ Add the INV and NAND 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) + """ Add the INV and AND gate modules """ - def add_nand(self,inputs): - """ Create the NAND for the predecode input stage """ - if inputs==2: - self.nand = factory.create(module_type="pnand2", - height=self.cell_height) - elif inputs==3: - self.nand = factory.create(module_type="pnand3", - height=self.cell_height) - else: - debug.error("Invalid number of predecode inputs: {}".format(inputs),-1) + debug.check(self.number_of_inputs < 4, + "Invalid number of predecode inputs: {}".format(self.number_of_inputs)) + if self.column_decoder: + and_type = "pand{}".format(self.number_of_inputs) + inv_type = "pinv" + else: + and_type = "and{}_dec".format(self.number_of_inputs) + inv_type = "inv_dec" + self.and_mod = factory.create(module_type=and_type, + height=self.cell_height) + self.add_mod(self.and_mod) + + # This uses the pinv_dec parameterized cell + self.inv = factory.create(module_type=inv_type, + height=self.cell_height, + size=1) + self.add_mod(self.inv) + + def create_layout(self): + """ The general organization is from left to right: + 1) a set of M2 rails for input signals + 2) a set of inverters to invert input signals + 3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs + 4) a set of AND gates for inversion + """ + self.setup_layout_constraints() + self.route_rails() + self.place_input_inverters() + self.place_and_array() + self.route() + self.add_boundary() + self.DRC_LVS() + def setup_layout_constraints(self): - self.height = self.number_of_outputs * self.nand.height + # Inputs to cells are on input layer + # Outputs from cells are on output layer + if OPTS.tech_name == "sky130": + self.bus_layer = "m1" + self.bus_directions = "nonpref" + self.bus_pitch = self.m1_pitch + self.bus_space = 1.5 * self.m1_space + self.input_layer = "m2" + self.output_layer = "li" + self.output_layer_pitch = self.li_pitch + else: + self.bus_layer = "m2" + self.bus_directions = "pref" + self.bus_pitch = self.m2_pitch + self.bus_space = self.m2_space + # This requires a special jog to ensure to conflicts with the output layers + self.input_layer = "m1" + self.output_layer = "m1" + self.output_layer_pitch = self.m1_pitch + + self.height = self.number_of_outputs * self.and_mod.height # x offset for input inverters - self.x_off_inv_1 = self.number_of_inputs*self.m2_pitch + # +1 input for spacing for supply rail contacts + self.x_off_inv_1 = (self.number_of_inputs + 1) * self.bus_pitch + self.bus_pitch - # x offset to 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 bus pitches + self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.bus_pitch # x offset to output inverters - self.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) - self.input_rails = self.create_vertical_pin_bus(layer="metal2", - pitch=self.m2_pitch, - offset=offset, - names=input_names, - length=self.height - 2*self.m1_width) + # Offsets for the perimeter spacing to other modules + # This uses m3 pitch to leave space for power routes + offset = vector(self.bus_pitch, self.bus_pitch) + self.input_rails = self.create_vertical_bus(layer=self.bus_layer, + offset=offset, + names=input_names, + length=self.height - 2 * self.bus_pitch) invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)] non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)] decode_names = invert_names + non_invert_names - offset = vector(self.x_off_inv_1 + self.inv.width + 2*self.m2_pitch, 2*self.m1_width) - self.decode_rails = self.create_vertical_bus(layer="metal2", - pitch=self.m2_pitch, + offset = vector(self.x_off_inv_1 + self.inv.width + self.bus_pitch, self.bus_pitch) + self.decode_rails = self.create_vertical_bus(layer=self.bus_layer, offset=offset, names=decode_names, - length=self.height - 2*self.m1_width) + length=self.height - 2 * self.bus_pitch) - def create_input_inverters(self): """ Create the input inverters to invert input signals for the decode stage. """ - self.in_inst = [] + self.inv_inst = [] for inv_num in range(self.number_of_inputs): name = "pre_inv_{0}".format(inv_num) - self.in_inst.append(self.add_inst(name=name, - mod=self.inv)) + self.inv_inst.append(self.add_inst(name=name, + mod=self.inv)) self.connect_inst(["in_{0}".format(inv_num), "inbar_{0}".format(inv_num), "vdd", "gnd"]) @@ -104,6 +146,7 @@ class hierarchical_predecode(design.design): def place_input_inverters(self): """ Place the input inverters to invert input signals for the decode stage. """ for inv_num in range(self.number_of_inputs): + if (inv_num % 2 == 0): y_off = inv_num * (self.inv.height) mirror = "R0" @@ -111,178 +154,204 @@ class hierarchical_predecode(design.design): y_off = (inv_num + 1) * (self.inv.height) mirror="MX" offset = vector(self.x_off_inv_1, y_off) - self.in_inst[inv_num].place(offset=offset, - mirror=mirror) - - 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 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 - mirror = "R0" - else: - y_off =(inv_num + 1)*self.inv.height - mirror = "MX" - offset = vector(self.x_off_inv_2, y_off) self.inv_inst[inv_num].place(offset=offset, mirror=mirror) + + 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 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 + 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 = (nand_input + 1) * self.inv.height + y_off = (and_input + 1) * self.and_mod.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): """ Route the uninverted inputs to the second set of rails """ - 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.m1m2.width + self.m1_space - in_pin = "in_{}".format(num) - a_pin = "A_{}".format(num) - in_pos = vector(self.input_rails[in_pin].x,y_offset) - a_pos = vector(self.decode_rails[a_pin].x,y_offset) - self.add_path("metal1",[in_pos, a_pos]) - self.add_via_center(layers = ("metal1", "via1", "metal2"), - offset=[self.input_rails[in_pin].x, y_offset]) - self.add_via_center(layers = ("metal1", "via1", "metal2"), - offset=[self.decode_rails[a_pin].x, y_offset]) - def route_output_inverters(self): + top_and_gate = self.and_inst[-1] + for num in range(self.number_of_inputs): + if num == 0: + pin = top_and_gate.get_pin("A") + elif num == 1: + pin = top_and_gate.get_pin("B") + elif num == 2: + pin = top_and_gate.get_pin("C") + elif num == 3: + pin = top_and_gate.get_pin("D") + else: + debug.error("Too many inputs for predecoder.", -1) + y_offset = pin.cy() + in_pin = "in_{}".format(num) + a_pin = "A_{}".format(num) + in_pos = vector(self.input_rails[in_pin].cx(), y_offset) + a_pos = vector(self.decode_rails[a_pin].cx(), y_offset) + self.add_path(self.input_layer, [in_pos, a_pos]) + self.add_via_stack_center(from_layer=self.input_layer, + to_layer=self.bus_layer, + offset=[self.input_rails[in_pin].cx(), y_offset]) + self.add_via_stack_center(from_layer=self.input_layer, + to_layer=self.bus_layer, + offset=[self.decode_rails[a_pin].cx(), y_offset]) + + def route_output_and(self): """ - 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("metal1", [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="metal1", + layer=z_pin.layer, offset=z_pin.ll(), height=z_pin.height(), width=z_pin.width()) - def route_input_inverters(self): """ - Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd] + Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd] """ for inv_num in range(self.number_of_inputs): out_pin = "Abar_{}".format(inv_num) in_pin = "in_{}".format(inv_num) + + inv_out_pin = self.inv_inst[inv_num].get_pin("Z") - #add output so that it is just below the vdd or gnd rail + # 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. - 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) - rail_pos = vector(self.decode_rails[out_pin].x,y_offset) - self.add_path("metal1", [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos]) - self.add_via_center(layers = ("metal1", "via1", "metal2"), - offset=rail_pos) - + # pins in the and gates. + inv_out_pos = inv_out_pin.rc() + y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch + right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0) + rail_pos = vector(self.decode_rails[out_pin].cx(), y_offset) + self.add_path(self.output_layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos]) + + self.add_via_stack_center(from_layer=inv_out_pin.layer, + to_layer=self.output_layer, + offset=inv_out_pos) + self.add_via_stack_center(from_layer=self.output_layer, + to_layer=self.bus_layer, + offset=rail_pos, + directions=self.bus_directions) - #route input - inv_in_pos = self.in_inst[inv_num].get_pin("A").lc() - in_pos = vector(self.input_rails[in_pin].x,inv_in_pos.y) - self.add_path("metal1", [in_pos, inv_in_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=in_pos) + # route input + pin = self.inv_inst[inv_num].get_pin("A") + inv_in_pos = pin.center() + in_pos = vector(self.input_rails[in_pin].cx(), inv_in_pos.y) + self.add_path(self.input_layer, [in_pos, inv_in_pos]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.input_layer, + offset=inv_in_pos) + via=self.add_via_stack_center(from_layer=self.input_layer, + to_layer=self.bus_layer, + offset=in_pos) + # Create the input pin at this location on the rail + self.add_layout_pin_rect_center(text=in_pin, + layer=self.bus_layer, + offset=in_pos, + height=via.mod.second_layer_height, + width=via.mod.second_layer_width) - - def route_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"] + gate_lst = ["A", "B"] else: - gate_lst = ["A","B","C"] + gate_lst = ["A", "B", "C"] # 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() - rail_pos = vector(self.decode_rails[rail_pin].x, pin_pos.y) - self.add_path("metal1", [rail_pos, pin_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=rail_pos) - - - + for rail_pin, gate_pin in zip(index_lst, gate_lst): + pin = self.and_inst[k].get_pin(gate_pin) + pin_pos = pin.center() + rail_pos = vector(self.decode_rails[rail_pin].cx(), pin_pos.y) + self.add_path(self.input_layer, [rail_pos, pin_pos]) + self.add_via_stack_center(from_layer=self.input_layer, + to_layer=self.bus_layer, + offset=rail_pos, + directions=self.bus_directions) + if gate_pin == "A": + direction = None + else: + direction = ("H", "H") + + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.input_layer, + offset=pin_pos, + directions=direction) def route_vdd_gnd(self): """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ - # Find the x offsets for where the vias/pins should be placed - in_xoffset = self.in_inst[0].rx() - out_xoffset = self.inv_inst[0].lx() - self.m1_space - for num in range(0,self.number_of_outputs): - # this will result in duplicate polygons for rails, but who cares - - # Route both supplies + # In sky130, we use hand-made decoder cells with vertical power + if OPTS.tech_name == "sky130" and not self.column_decoder: for n in ["vdd", "gnd"]: - nand_pin = self.nand_inst[num].get_pin(n) - supply_offset = nand_pin.ll().scale(0,1) - self.add_rect(layer="metal1", - offset=supply_offset, - width=self.inv_inst[num].rx()) + # This makes a wire from top to bottom for both inv and and gates + for i in [self.inv_inst, self.and_inst]: + bot_pins = i[0].get_pins(n) + top_pins = i[-1].get_pins(n) + for (bot_pin, top_pin) in zip(bot_pins, top_pins): + self.add_rect(layer=bot_pin.layer, + offset=vector(bot_pin.lx(), self.bus_pitch), + width=bot_pin.width(), + height=top_pin.uy() - self.bus_pitch) + # This adds power vias at the top of each cell + # (except the last to keep them inside the boundary) + for i in self.inv_inst[:-1:2] + self.and_inst[:-1:2]: + pins = i.get_pins(n) + for pin in pins: + self.add_power_pin(name=n, + loc=pin.uc(), + start_layer=pin.layer) + self.add_power_pin(name=n, + loc=pin.uc(), + start_layer=pin.layer) + + # In other techs, we are using standard cell decoder cells with horizontal power + else: + for num in range(0, self.number_of_outputs): - # Add pins in two locations - for xoffset in [in_xoffset, out_xoffset]: - pin_pos = vector(xoffset, nand_pin.cy()) - self.add_power_pin(n, pin_pos) + # Route both supplies + for n in ["vdd", "gnd"]: + and_pins = self.and_inst[num].get_pins(n) + for and_pin in and_pins: + self.add_segment_center(layer=and_pin.layer, + start=vector(0, and_pin.cy()), + end=vector(self.width, and_pin.cy())) + + # Add pins in two locations + for xoffset in [self.inv_inst[0].lx() - self.bus_space, + self.and_inst[0].lx() - self.bus_space]: + pin_pos = vector(xoffset, and_pin.cy()) + self.add_power_pin(name=n, + loc=pin_pos, + start_layer=and_pin.layer) diff --git a/compiler/modules/hierarchical_predecode2x4.py b/compiler/modules/hierarchical_predecode2x4.py index f05f54b0..9c7ddfa3 100644 --- a/compiler/modules/hierarchical_predecode2x4.py +++ b/compiler/modules/hierarchical_predecode2x4.py @@ -5,13 +5,10 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from tech import drc -import debug -import design -from vector import vector from hierarchical_predecode import hierarchical_predecode from globals import OPTS + class hierarchical_predecode2x4(hierarchical_predecode): """ Pre 2x4 decoder used in hierarchical_decoder. @@ -27,33 +24,16 @@ 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 - """ - self.setup_layout_constraints() - self.route_rails() - self.place_input_inverters() - self.place_output_inverters() - self.place_nand_array() - self.route() - self.add_boundary() - 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..e8c44e48 100644 --- a/compiler/modules/hierarchical_predecode3x8.py +++ b/compiler/modules/hierarchical_predecode3x8.py @@ -5,13 +5,10 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from tech import drc -import debug -import design -from vector import vector from hierarchical_predecode import hierarchical_predecode from globals import OPTS + class hierarchical_predecode3x8(hierarchical_predecode): """ Pre 3x8 decoder used in hierarchical_decoder. @@ -20,49 +17,31 @@ 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 - 3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs - 4) a set of NAND gates for inversion - """ - self.setup_layout_constraints() - self.route_rails() - self.place_input_inverters() - self.place_output_inverters() - self.place_nand_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/hierarchical_predecode4x16.py b/compiler/modules/hierarchical_predecode4x16.py new file mode 100644 index 00000000..4a258bfb --- /dev/null +++ b/compiler/modules/hierarchical_predecode4x16.py @@ -0,0 +1,64 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +from hierarchical_predecode import hierarchical_predecode +from globals import OPTS + + +class hierarchical_predecode4x16(hierarchical_predecode): + """ + Pre 4x16 decoder used in hierarchical_decoder. + """ + def __init__(self, name, height=None): + hierarchical_predecode.__init__(self, name, 4, height) + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.add_pins() + self.add_modules() + self.create_input_inverters() + connections=[["inbar_0", "inbar_1", "inbar_2", "inbar_3", "out_0", "vdd", "gnd"], + ["in_0", "inbar_1", "inbar_2", "inbar_3", "out_1", "vdd", "gnd"], + ["inbar_0", "in_1", "inbar_2", "inbar_3", "out_2", "vdd", "gnd"], + ["in_0", "in_1", "inbar_2", "inbar_3", "out_3", "vdd", "gnd"], + ["inbar_0", "inbar_1", "in_2", "inbar_3", "out_4", "vdd", "gnd"], + ["in_0", "inbar_1", "in_2", "inbar_3", "out_5", "vdd", "gnd"], + ["inbar_0", "in_1", "in_2", "inbar_3", "out_6", "vdd", "gnd"], + ["in_0", "in_1", "in_2", "inbar_3", "out_7", "vdd", "gnd"], + ["inbar_0", "inbar_1", "inbar_2", "in_3", "out_0", "vdd", "gnd"], + ["in_0", "inbar_1", "inbar_2", "in_3", "out_1", "vdd", "gnd"], + ["inbar_0", "in_1", "inbar_2", "in_3", "out_2", "vdd", "gnd"], + ["in_0", "in_1", "inbar_2", "in_3", "out_3", "vdd", "gnd"], + ["inbar_0", "inbar_1", "in_2", "in_3", "out_4", "vdd", "gnd"], + ["in_0", "inbar_1", "in_2", "in_3", "out_5", "vdd", "gnd"], + ["inbar_0", "in_1", "in_2", "in_3", "out_6", "vdd", "gnd"], + ["in_0", "in_1", "in_2", "in_3", "out_7", "vdd", "gnd"] ] + + self.create_and_array(connections) + + def get_and_input_line_combination(self): + """ These are the decoder connections of the AND gates to the A,B pins """ + combination = [["Abar_0", "Abar_1", "Abar_2", "Abar_3"], + ["A_0", "Abar_1", "Abar_2", "Abar_3"], + ["Abar_0", "A_1", "Abar_2", "Abar_3"], + ["A_0", "A_1", "Abar_2", "Abar_3"], + ["Abar_0", "Abar_1", "A_2" , "Abar_3"], + ["A_0", "Abar_1", "A_2" , "Abar_3"], + ["Abar_0", "A_1", "A_2" , "Abar_3"], + ["A_0", "A_1", "A_2" , "Abar_3"], + ["Abar_0", "Abar_1", "Abar_2", "A_3"], + ["A_0", "Abar_1", "Abar_2", "A_3"], + ["Abar_0", "A_1", "Abar_2", "A_3"], + ["A_0", "A_1", "Abar_2", "A_3"], + ["Abar_0", "Abar_1", "A_2", "A_3"], + ["A_0", "Abar_1", "A_2", "A_3"], + ["Abar_0", "A_1", "A_2", "A_3"], + ["A_0", "A_1", "A_2", "A_3"]] + return combination diff --git a/compiler/modules/module_type.py b/compiler/modules/module_type.py new file mode 100644 index 00000000..419e6041 --- /dev/null +++ b/compiler/modules/module_type.py @@ -0,0 +1,26 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# + +class module_type(): + """ + This is a class that maps cell names to python classes implementing them. + """ + def __init__(self): + self.names = {} + + def __setitem__(self, b, c): + self.names[b] = c + + def is_overridden(self, b): + return (b in self.names.keys()) + + def __getitem__(self, b): + if b not in self.names.keys(): + raise KeyError + + return self.names[b] diff --git a/compiler/modules/multibank.py b/compiler/modules/multibank.py index 2f933a2d..bf19954b 100644 --- a/compiler/modules/multibank.py +++ b/compiler/modules/multibank.py @@ -160,7 +160,7 @@ class multibank(design.design): self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width # A space for wells or jogging m2 - self.m2_gap = max(2*drc("pwell_to_nwell"] + drc["well_enclosure_active"), + self.m2_gap = max(2*drc("pwell_to_nwell"] + drc["well_enclose_active"), 2*self.m2_pitch) @@ -451,14 +451,14 @@ class multibank(design.design): # Connect the inverter output to the central bus out_pos = self.bank_select_inst.get_pin(gated_name).rc() bus_pos = vector(self.bus_xoffset[gated_name], out_pos.y) - self.add_path("metal3",[out_pos, bus_pos]) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_path("m3",[out_pos, bus_pos]) + self.add_via_center(layers=self.m2_stack, offset=bus_pos, rotate=90) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=out_pos, rotate=90) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=out_pos, rotate=90) @@ -512,7 +512,7 @@ class multibank(design.design): # 2 pitches on the right for vias/jogs to access the inputs control_bus_offset = vector(-self.m2_pitch * self.num_control_lines - self.m2_width, 0) control_bus_length = self.bitcell_array_inst.uy() - self.bus_xoffset = self.create_vertical_bus(layer="metal2", + self.bus_xoffset = self.create_vertical_bus(layer="m2", pitch=self.m2_pitch, offset=control_bus_offset, names=self.control_signals, @@ -530,9 +530,9 @@ class multibank(design.design): bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).uc() yoffset = 0.5*(precharge_bl.y+bitcell_bl.y) - self.add_path("metal2",[precharge_bl, vector(precharge_bl.x,yoffset), + self.add_path("m2",[precharge_bl, vector(precharge_bl.x,yoffset), vector(bitcell_bl.x,yoffset), bitcell_bl]) - self.add_path("metal2",[precharge_br, vector(precharge_br.x,yoffset), + self.add_path("m2",[precharge_br, vector(precharge_br.x,yoffset), vector(bitcell_br.x,yoffset), bitcell_br]) @@ -550,9 +550,9 @@ class multibank(design.design): bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).bc() yoffset = 0.5*(col_mux_bl.y+bitcell_bl.y) - self.add_path("metal2",[col_mux_bl, vector(col_mux_bl.x,yoffset), + self.add_path("m2",[col_mux_bl, vector(col_mux_bl.x,yoffset), vector(bitcell_bl.x,yoffset), bitcell_bl]) - self.add_path("metal2",[col_mux_br, vector(col_mux_br.x,yoffset), + self.add_path("m2",[col_mux_br, vector(col_mux_br.x,yoffset), vector(bitcell_br.x,yoffset), bitcell_br]) def route_sense_amp_to_col_mux_or_bitcell_array(self): @@ -573,9 +573,9 @@ class multibank(design.design): yoffset = 0.5*(sense_amp_bl.y+connect_bl.y) - self.add_path("metal2",[sense_amp_bl, vector(sense_amp_bl.x,yoffset), + self.add_path("m2",[sense_amp_bl, vector(sense_amp_bl.x,yoffset), vector(connect_bl.x,yoffset), connect_bl]) - self.add_path("metal2",[sense_amp_br, vector(sense_amp_br.x,yoffset), + self.add_path("m2",[sense_amp_br, vector(sense_amp_br.x,yoffset), vector(connect_br.x,yoffset), connect_br]) def route_sense_amp_to_trigate(self): @@ -586,11 +586,11 @@ class multibank(design.design): tri_gate_in = self.tri_gate_array_inst.get_pin("in_{}".format(i)).lc() sa_data_out = self.sense_amp_array_inst.get_pin("data_{}".format(i)).bc() - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=tri_gate_in) - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=sa_data_out) - self.add_path("metal3",[sa_data_out,tri_gate_in]) + self.add_path("m3",[sa_data_out,tri_gate_in]) def route_sense_amp_out(self): """ Add pins for the sense amp output """ @@ -644,14 +644,14 @@ class multibank(design.design): driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(i)).lc() mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0) mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1) - self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos]) + self.add_path("m1", [decoder_out_pos, mid1, mid2, driver_in_pos]) # The mid guarantees we exit the input cell to the right. driver_wl_pos = self.wordline_driver_inst.get_pin("wl_{}".format(i)).rc() bitcell_wl_pos = self.bitcell_array_inst.get_pin("wl_{}".format(i)).lc() mid1 = driver_wl_pos.scale(0.5,1)+bitcell_wl_pos.scale(0.5,0) mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1) - self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) + self.add_path("m1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos]) @@ -698,8 +698,8 @@ class multibank(design.design): else: mid1_pos = vector(decode_out_pos.x + delta_offset + (self.num_col_addr_lines-i)*self.m2_pitch,decode_out_pos.y) mid2_pos = vector(mid1_pos.x,mux_addr_pos.y) - #self.add_wire(("metal1","via1","metal2"),[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos]) - self.add_path("metal1",[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos]) + #self.add_wire(self.m1_stack,[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos]) + self.add_path("m1",[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos]) @@ -715,7 +715,7 @@ class multibank(design.design): wl_name = "wl_{}".format(i) wl_pin = self.bitcell_array_inst.get_pin(wl_name) self.add_label(text=wl_name, - layer="metal1", + layer="m1", offset=wl_pin.center()) # Add the bitline names @@ -725,10 +725,10 @@ class multibank(design.design): bl_pin = self.bitcell_array_inst.get_pin(bl_name) br_pin = self.bitcell_array_inst.get_pin(br_name) self.add_label(text=bl_name, - layer="metal2", + layer="m2", offset=bl_pin.center()) self.add_label(text=br_name, - layer="metal2", + layer="m2", offset=br_pin.center()) # # Add the data output names to the sense amp output @@ -736,7 +736,7 @@ class multibank(design.design): # data_name = "data_{}".format(i) # data_pin = self.sense_amp_array_inst.get_pin(data_name) # self.add_label(text="sa_out_{}".format(i), - # layer="metal2", + # layer="m2", # offset=data_pin.center()) # Add labels on the decoder @@ -745,7 +745,7 @@ class multibank(design.design): pin_name = "in_{}".format(i) data_pin = self.wordline_driver_inst.get_pin(pin_name) self.add_label(text=data_name, - layer="metal1", + layer="m1", offset=data_pin.center()) @@ -765,8 +765,8 @@ class multibank(design.design): for (control_signal, pin_pos) in connection: control_pos = vector(self.bus_xoffset[control_signal].x ,pin_pos.y) - self.add_path("metal1", [control_pos, pin_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_path("m1", [control_pos, pin_pos]) + self.add_via_center(layers=self.m1_stack, offset=control_pos, rotate=90) @@ -776,9 +776,9 @@ class multibank(design.design): mid_pos = pin_pos + vector(0,self.m1_pitch) control_x_offset = self.bus_xoffset[control_signal].x control_pos = vector(control_x_offset + self.m1_width, mid_pos.y) - self.add_wire(("metal1","via1","metal2"),[pin_pos, mid_pos, control_pos]) + self.add_wire(self.m1_stack,[pin_pos, mid_pos, control_pos]) control_via_pos = vector(control_x_offset, mid_pos.y) - self.add_via_center(layers=("metal1", "via1", "metal2"), + self.add_via_center(layers=self.m1_stack, offset=control_via_pos, rotate=90) @@ -791,13 +791,13 @@ class multibank(design.design): if self.num_banks > 1: # it's not an input pin if we have multiple banks self.add_label_pin(text=ctrl, - layer="metal2", + layer="m2", offset=vector(x_offset, self.min_y_offset), width=self.m2_width, height=self.max_y_offset-self.min_y_offset) else: self.add_layout_pin(text=ctrl, - layer="metal2", + layer="m2", offset=vector(x_offset, self.min_y_offset), width=self.m2_width, height=self.max_y_offset-self.min_y_offset) @@ -807,12 +807,12 @@ class multibank(design.design): """ Helper routine to connect an unrotated/mirrored oriented instance to the rails """ in_pin = inst.get_pin(pin).lc() rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y) - self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)]) + self.add_wire(("m3","via2","m2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)]) # Bring it up to M2 for M2/M3 routing - self.add_via(layers=("metal1","via1","metal2"), - offset=in_pin + contact.m1m2.offset, + self.add_via(layers=self.m1_stack, + offset=in_pin + contact.m1_via.offset, rotate=90) - self.add_via(layers=("metal2","via2","metal3"), + self.add_via(layers=self.m2_stack, offset=in_pin + self.m2m3_via_offset, rotate=90) @@ -821,10 +821,10 @@ class multibank(design.design): """ Helper routine to connect an unrotated/mirrored oriented instance to the rails """ in_pin = inst.get_pin(pin).rc() rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y) - self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)]) - self.add_via(layers=("metal1","via1","metal2"), - offset=in_pin + contact.m1m2.offset, + self.add_wire(("m3","via2","m2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)]) + self.add_via(layers=self.m1_stack, + offset=in_pin + contact.m1_via.offset, rotate=90) - self.add_via(layers=("metal2","via2","metal3"), + self.add_via(layers=self.m2_stack, offset=in_pin + self.m2m3_via_offset, rotate=90) diff --git a/compiler/modules/port_address.py b/compiler/modules/port_address.py index 0a624c60..a73e536c 100644 --- a/compiler/modules/port_address.py +++ b/compiler/modules/port_address.py @@ -3,16 +3,15 @@ # Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # -import sys -from tech import drc, parameter -from math import log +from math import log, ceil import debug import design from sram_factory import factory from vector import vector - +from tech import layer from globals import OPTS + class port_address(design.design): """ Create the address port (row decoder and wordline driver).. @@ -22,20 +21,19 @@ class port_address(design.design): self.num_cols = cols self.num_rows = rows - self.addr_size = int(log(self.num_rows, 2)) + self.addr_size = ceil(log(self.num_rows, 2)) if name == "": - name = "port_address_{0}_{1}".format(cols,rows) + name = "port_address_{0}_{1}".format(cols, rows) design.design.__init__(self, name) - debug.info(2, "create data port of cols {0} rows {1}".format(cols,rows)) + debug.info(2, "create data port of cols {0} rows {1}".format(cols, rows)) 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() - def create_netlist(self): self.add_pins() self.add_modules() @@ -43,6 +41,10 @@ class port_address(design.design): self.create_wordline_driver() def create_layout(self): + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" self.place_instances() self.route_layout() self.DRC_LVS() @@ -51,16 +53,15 @@ class port_address(design.design): """ Adding pins for port address module""" for bit in range(self.addr_size): - self.add_pin("addr_{0}".format(bit),"INPUT") + self.add_pin("addr_{0}".format(bit), "INPUT") self.add_pin("wl_en", "INPUT") for bit in range(self.num_rows): - self.add_pin("wl_{0}".format(bit),"OUTPUT") + self.add_pin("wl_{0}".format(bit), "OUTPUT") - 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 """ @@ -71,8 +72,8 @@ class port_address(design.design): 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_pins(self): for row in range(self.addr_size): @@ -88,31 +89,35 @@ class port_address(design.design): def route_internal(self): for row in range(self.num_rows): # The pre/post is to access the pin from "outside" the cell to avoid DRCs - decoder_out_pos = self.row_decoder_inst.get_pin("decode_{}".format(row)).rc() - driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(row)).lc() - mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0) - mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1) - self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos]) + decoder_out_pin = self.row_decoder_inst.get_pin("decode_{}".format(row)) + decoder_out_pos = decoder_out_pin.rc() + driver_in_pin = self.wordline_driver_inst.get_pin("in_{}".format(row)) + driver_in_pos = driver_in_pin.lc() + self.add_zjog(self.route_layer, decoder_out_pos, driver_in_pos, var_offset=0.3) + self.add_via_stack_center(from_layer=decoder_out_pin.layer, + to_layer=self.route_layer, + offset=decoder_out_pos) - + self.add_via_stack_center(from_layer=driver_in_pin.layer, + to_layer=self.route_layer, + offset=driver_in_pos) def add_modules(self): self.row_decoder = factory.create(module_type="decoder", - rows=self.num_rows) + num_outputs=self.num_rows) self.add_mod(self.row_decoder) - self.wordline_driver = factory.create(module_type="wordline_driver", + self.wordline_driver = factory.create(module_type="wordline_driver_array", rows=self.num_rows, cols=self.num_cols) self.add_mod(self.wordline_driver) - def create_row_decoder(self): """ Create the hierarchical row decoder """ - self.row_decoder_inst = self.add_inst(name="row_decoder", + self.row_decoder_inst = self.add_inst(name="row_decoder", mod=self.row_decoder) temp = [] @@ -123,12 +128,10 @@ class port_address(design.design): temp.extend(["vdd", "gnd"]) self.connect_inst(temp) - - def create_wordline_driver(self): """ Create the Wordline Driver """ - self.wordline_driver_inst = self.add_inst(name="wordline_driver", + self.wordline_driver_inst = self.add_inst(name="wordline_driver", mod=self.wordline_driver) temp = [] @@ -141,20 +144,13 @@ class port_address(design.design): temp.append("gnd") self.connect_inst(temp) - - def place_instances(self): """ Compute the offsets and place the instances. """ - # A space for wells or jogging m2 - self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclosure_active"), - 3*self.m2_pitch) - - row_decoder_offset = vector(0,0) - wordline_driver_offset = vector(self.row_decoder.width + self.m2_gap,0) - + row_decoder_offset = vector(0, 0) + wordline_driver_offset = vector(self.row_decoder.width, 0) self.wordline_driver_inst.place(wordline_driver_offset) self.row_decoder_inst.place(row_decoder_offset) diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index c3473803..bd6b39e2 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. @@ -19,25 +19,52 @@ class port_data(design.design): """ def __init__(self, sram_config, port, name=""): - + sram_config.set_local_config(self) self.port = port if self.write_size is not None: - 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 self.num_spare_cols is None: + self.num_spare_cols = 0 + if name == "": name = "port_data_{0}".format(self.port) design.design.__init__(self, name) - 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() + def get_bl_names(self): + # bl lines are connect from the precharger + return self.precharge.get_bl_names() + + def get_br_names(self): + # br lines are connect from the precharger + return self.precharge.get_br_names() + + def 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() @@ -71,8 +98,6 @@ class port_data(design.design): else: self.column_mux_array_inst = None - - def create_layout(self): self.compute_instance_offsets() self.place_instances() @@ -80,33 +105,42 @@ class port_data(design.design): self.DRC_LVS() def add_pins(self): - """ Adding pins for port address module""" + """ Adding pins for port data module""" - self.add_pin("rbl_bl","INOUT") - self.add_pin("rbl_br","INOUT") + self.add_pin("rbl_bl", "INOUT") + self.add_pin("rbl_br", "INOUT") for bit in range(self.num_cols): - self.add_pin("{0}_{1}".format(self.bl_names[self.port], bit),"INOUT") - self.add_pin("{0}_{1}".format(self.br_names[self.port], bit),"INOUT") + bl_name = self.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") + for bit in range(self.num_spare_cols): + bl_name = self.get_bl_name(self.port) + br_name = self.get_br_name(self.port) + self.add_pin("spare{0}_{1}".format(bl_name, bit), "INOUT") + self.add_pin("spare{0}_{1}".format(br_name, bit), "INOUT") + if self.port in self.read_ports: - for bit in range(self.word_size): - self.add_pin("dout_{}".format(bit),"OUTPUT") + for bit in range(self.word_size + self.num_spare_cols): + self.add_pin("dout_{}".format(bit), "OUTPUT") if self.port in self.write_ports: - for bit in range(self.word_size): - self.add_pin("din_{}".format(bit),"INPUT") + for bit in range(self.word_size + self.num_spare_cols): + self.add_pin("din_{}".format(bit), "INPUT") # Will be empty if no col addr lines sel_names = ["sel_{}".format(x) for x in range(self.num_col_addr_lines)] 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") + for bit in range(self.num_spare_cols): + self.add_pin("bank_spare_wen{}".format(bit), "INPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def route_layout(self): """ Create routing among the modules """ @@ -148,29 +182,34 @@ 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): # Extra column +1 is for RBL # Precharge will be shifted left if needed + # Column offset is set to port so extra column can be on left or right + # and mirroring happens correctly self.precharge_array = factory.create(module_type="precharge_array", - columns=self.num_cols + 1, + columns=self.num_cols + self.num_spare_cols + 1, bitcell_bl=self.bl_names[self.port], - bitcell_br=self.br_names[self.port]) + bitcell_br=self.br_names[self.port], + column_offset=self.port - 1) self.add_mod(self.precharge_array) if self.port in self.read_ports: + # RBLs don't get a sense amp self.sense_amp_array = factory.create(module_type="sense_amp_array", word_size=self.word_size, - words_per_row=self.words_per_row) + words_per_row=self.words_per_row, + num_spare_cols=self.num_spare_cols) self.add_mod(self.sense_amp_array) else: self.sense_amp_array = None - if self.col_addr_size > 0: + # RBLs dont get a col mux self.column_mux_array = factory.create(module_type="column_mux_array", columns=self.num_cols, word_size=self.word_size, @@ -180,19 +219,20 @@ class port_data(design.design): else: self.column_mux_array = None - if self.port in self.write_ports: + # RBLs dont get a write driver self.write_driver_array = factory.create(module_type="write_driver_array", columns=self.num_cols, word_size=self.word_size, - write_size=self.write_size) + write_size=self.write_size, + num_spare_cols=self.num_spare_cols) self.add_mod(self.write_driver_array) if self.write_size is not None: + # RBLs don't get a write mask self.write_mask_and_array = factory.create(module_type="write_mask_and_array", columns=self.num_cols, word_size=self.word_size, - write_size=self.write_size, - port = self.port) + write_size=self.write_size) self.add_mod(self.write_mask_and_array) else: self.write_mask_and_array = None @@ -210,17 +250,21 @@ 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("well_enclosure_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]) def create_precharge_array(self): """ Creating Precharge """ @@ -230,6 +274,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.get_bl_name(self.port) + br_name = self.get_br_name(self.port) temp = [] # Use left BLs for RBL @@ -237,8 +283,13 @@ class port_data(design.design): temp.append("rbl_bl") temp.append("rbl_br") for bit in range(self.num_cols): - temp.append(self.bl_names[self.port]+"_{0}".format(bit)) - temp.append(self.br_names[self.port]+"_{0}".format(bit)) + temp.append("{0}_{1}".format(bl_name, bit)) + temp.append("{0}_{1}".format(br_name, bit)) + + for bit in range(self.num_spare_cols): + temp.append("spare{0}_{1}".format(bl_name, bit)) + temp.append("spare{0}_{1}".format(br_name, bit)) + # Use right BLs for RBL if self.port==1: temp.append("rbl_bl") @@ -246,32 +297,32 @@ 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.get_bl_name(self.port) + br_name = self.get_br_name(self.port) temp = [] for col in range(self.num_cols): - temp.append(self.bl_names[self.port]+"_{0}".format(col)) - temp.append(self.br_names[self.port]+"_{0}".format(col)) + temp.append("{0}_{1}".format(bl_name, col)) + temp.append("{0}_{1}".format(br_name, col)) for word in range(self.words_per_row): temp.append("sel_{}".format(word)) for bit in range(self.word_size): - temp.append(self.bl_names[self.port]+"_out_{0}".format(bit)) - temp.append(self.br_names[self.port]+"_out_{0}".format(bit)) + temp.append("{0}_out_{1}".format(bl_name, bit)) + temp.append("{0}_out_{1}".format(br_name, bit)) + temp.append("gnd") self.connect_inst(temp) - def place_column_mux_array(self, offset): """ Placing Column Mux when words_per_row > 1 . """ if self.col_addr_size == 0: @@ -279,63 +330,79 @@ 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.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)) if self.words_per_row == 1: - temp.append(self.bl_names[self.port]+"_{0}".format(bit)) - temp.append(self.br_names[self.port]+"_{0}".format(bit)) + temp.append("{0}_{1}".format(bl_name, bit)) + temp.append("{0}_{1}".format(br_name, bit)) else: - temp.append(self.bl_names[self.port]+"_out_{0}".format(bit)) - temp.append(self.br_names[self.port]+"_out_{0}".format(bit)) - - temp.extend(["s_en", "vdd", "gnd"]) + temp.append("{0}_out_{1}".format(bl_name, bit)) + temp.append("{0}_out_{1}".format(br_name, bit)) + + for bit in range(self.num_spare_cols): + temp.append("dout_{}".format(self.word_size + bit)) + temp.append("spare{0}_{1}".format(bl_name, bit)) + temp.append("spare{0}_{1}".format(br_name, bit)) + + temp.append("s_en") + temp.extend(["vdd", "gnd"]) self.connect_inst(temp) - def place_sense_amp_array(self, offset): """ 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.get_bl_name(self.port) + br_name = self.get_br_name(self.port) temp = [] - for bit in range(self.word_size): + for bit in range(self.word_size + self.num_spare_cols): temp.append("din_{}".format(bit)) for bit in range(self.word_size): if (self.words_per_row == 1): - temp.append(self.bl_names[self.port] + "_{0}".format(bit)) - temp.append(self.br_names[self.port] + "_{0}".format(bit)) + temp.append("{0}_{1}".format(bl_name, bit)) + temp.append("{0}_{1}".format(br_name, bit)) else: - temp.append(self.bl_names[self.port] + "_out_{0}".format(bit)) - temp.append(self.br_names[self.port] + "_out_{0}".format(bit)) + temp.append("{0}_out_{1}".format(bl_name, bit)) + temp.append("{0}_out_{1}".format(br_name, bit)) + + for bit in range(self.num_spare_cols): + temp.append("spare{0}_{1}".format(bl_name, bit)) + temp.append("spare{0}_{1}".format(br_name, bit)) if self.write_size is not None: for i in range(self.num_wmasks): temp.append("wdriver_sel_{}".format(i)) + for i in range(self.num_spare_cols): + temp.append("bank_spare_wen{}".format(i)) + + elif self.num_spare_cols and not self.write_size: + temp.append("w_en") + for i in range(self.num_spare_cols): + temp.append("bank_spare_wen{}".format(i)) else: temp.append("w_en") temp.extend(["vdd", "gnd"]) 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), @@ -350,12 +417,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) @@ -410,28 +475,24 @@ 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): + for bit in range(self.word_size + self.num_spare_cols): data_pin = self.sense_amp_array_inst.get_pin("data_{}".format(bit)) self.add_layout_pin_rect_center(text="dout_{0}".format(bit), - layer=data_pin.layer, + 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 """ - for row in range(self.word_size): + for row in range(self.word_size + self.num_spare_cols): data_name = "data_{}".format(row) din_name = "din_{}".format(row) self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name) - def route_write_mask_and_array_in(self, port): """ Add pins for the write mask and array input """ @@ -440,47 +501,38 @@ class port_data(design.design): bank_wmask_name = "bank_wmask_{}".format(bit) self.copy_layout_pin(self.write_mask_and_array_inst, wmask_in_name, bank_wmask_name) + def route_write_mask_and_array_to_write_driver(self, port): + """ + Routing of wdriver_sel_{} between write mask AND array and + write driver array. Adds layout pin for write + mask AND array output and via for write driver enable + """ - def route_write_mask_and_array_to_write_driver(self,port): - """ Routing of wdriver_sel_{} between write mask AND array and write driver array. Adds layout pin for write - mask AND array output and via for write driver enable """ + wmask_inst = self.write_mask_and_array_inst + wdriver_inst = self.write_driver_array_inst - inst1 = self.write_mask_and_array_inst - inst2 = self.write_driver_array_inst - - loc = 0 for bit in range(self.num_wmasks): # Bring write mask AND array output pin to port data level - self.copy_layout_pin(inst1, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit)) + self.copy_layout_pin(wmask_inst, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit)) - wmask_out_pin = inst1.get_pin("wmask_out_{0}".format(bit)) - wdriver_en_pin = inst2.get_pin("en_{0}".format(bit)) + wmask_out_pin = wmask_inst.get_pin("wmask_out_{0}".format(bit)) + wdriver_en_pin = wdriver_inst.get_pin("en_{0}".format(bit)) - # The metal2 wdriver_sel_{} wire must hit the en_{} pin after the closest bitline pin that's right of the - # the wdriver_sel_{} pin in the write driver AND array. - if bit == 0: - # When the write mask output pin is right of the bitline, the target is found - while (wmask_out_pin.lx() + self.m2_pitch > inst2.get_pin("data_{0}".format(loc)).rx()): - loc += 1 - length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch - debug.check(loc<=self.num_wmasks,"Couldn't route the write mask select.") - else: - # Stride by the write size rather than finding the next pin to the right - loc += self.write_size - length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch - - - beg_pos = wmask_out_pin.center() - middle_pos = vector(length,wmask_out_pin.cy()) - end_pos = vector(length, wdriver_en_pin.cy()) + wmask_pos = wmask_out_pin.center() + wdriver_pos = wdriver_en_pin.rc() - vector(self.m2_pitch, 0) + mid_pos = vector(wdriver_pos.x, wmask_pos.y) + # Add driver on mask output + self.add_via_stack_center(from_layer=wmask_out_pin.layer, + to_layer="m1", + offset=wmask_pos) # Add via for the write driver array's enable input - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=end_pos) + self.add_via_stack_center(from_layer=wdriver_en_pin.layer, + to_layer="m2", + offset=wdriver_pos) # Route between write mask AND array and write driver array - self.add_wire(("metal1","via1","metal2"), [beg_pos, middle_pos, end_pos]) - + self.add_wire(self.m1_stack, [wmask_pos, mid_pos, wdriver_pos]) def route_column_mux_to_precharge_array(self, port): """ Routing of BL and BR between col mux and precharge array """ @@ -488,14 +540,13 @@ class port_data(design.design): # 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) + start_bit = 1 if self.port == 0 else 0 + + self.connect_bitlines(inst1=self.column_mux_array_inst, + inst2=self.precharge_array_inst, + num_bits=self.num_cols, + inst2_start_bit=start_bit) def route_sense_amp_to_column_mux_or_precharge_array(self, port): """ Routing of BL and BR between sense_amp and column mux or precharge array """ @@ -504,47 +555,109 @@ 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) - - + # spare cols connected to precharge array since they are read independently + if self.num_spare_cols and self.col_addr_size>0: + if self.port==0: + off = 1 + else: + off = 0 + + self.channel_route_bitlines(inst1=self.column_mux_array_inst, + inst1_bls_template="{inst}_out_{bit}", + inst2=inst2, + num_bits=self.word_size, + inst1_start_bit=start_bit) + + self.channel_route_bitlines(inst1=self.precharge_array_inst, + inst1_bls_template="{inst}_{bit}", + inst2=inst2, + num_bits=self.num_spare_cols, + inst1_start_bit=self.num_cols + off, + inst2_start_bit=self.word_size) + + # This could be a channel route, but in some techs the bitlines + # are too close together. + elif OPTS.tech_name == "sky130": + self.connect_bitlines(inst1=inst1, + inst1_bls_template=inst1_bls_templ, + inst2=inst2, + num_bits=self.word_size, + inst1_start_bit=start_bit) + else: + self.channel_route_bitlines(inst1=inst1, + inst1_bls_template=inst1_bls_templ, + inst2=inst2, + num_bits=self.word_size + self.num_spare_cols, + inst1_start_bit=start_bit) + def route_write_driver_to_column_mux_or_precharge_array(self, port): """ Routing of BL and BR between sense_amp and column mux or precharge array """ inst2 = self.write_driver_array_inst - + 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) + if self.port==0: + off = 1 + else: + off = 0 + + # Channel route spare columns' bitlines + if self.num_spare_cols and self.col_addr_size>0: + if self.port==0: + off = 1 + else: + off = 0 + + self.channel_route_bitlines(inst1=self.column_mux_array_inst, + inst1_bls_template="{inst}_out_{bit}", + inst2=inst2, + num_bits=self.word_size, + inst1_start_bit=start_bit) + + self.channel_route_bitlines(inst1=self.precharge_array_inst, + inst1_bls_template="{inst}_{bit}", + inst2=inst2, + num_bits=self.num_spare_cols, + inst1_start_bit=self.num_cols + off, + inst2_start_bit=self.word_size) + + # This could be a channel route, but in some techs the bitlines + # are too close together. + elif OPTS.tech_name == "sky130": + self.connect_bitlines(inst1=inst1, inst2=inst2, + num_bits=self.word_size, + inst1_bls_template=inst1_bls_templ, + inst1_start_bit=start_bit) + else: + self.channel_route_bitlines(inst1=inst1, inst2=inst2, + num_bits=self.word_size+self.num_spare_cols, + inst1_bls_template=inst1_bls_templ, + inst1_start_bit=start_bit) + def route_write_driver_to_sense_amp(self, port): """ Routing of BL and BR between write driver and sense amp """ @@ -552,10 +665,11 @@ class port_data(design.design): inst1 = self.write_driver_array_inst inst2 = self.sense_amp_array_inst - # These should be pitch matched in the cell library, - # but just in case, do a channel route. - self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size) - + # This could be a channel route, but in some techs the bitlines + # are too close together. + self.connect_bitlines(inst1=inst1, + inst2=inst2, + num_bits=self.word_size + self.num_spare_cols) def route_bitline_pins(self): """ Add the bitline pins for the given port """ @@ -566,16 +680,32 @@ class port_data(design.design): self.copy_layout_pin(self.precharge_array_inst, "br_0", "rbl_br") bit_offset=1 elif self.port==1: - self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(self.num_cols), "rbl_bl") - self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(self.num_cols), "rbl_br") + self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(self.num_cols + self.num_spare_cols), "rbl_bl") + self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(self.num_cols + self.num_spare_cols), "rbl_br") bit_offset=0 else: bit_offset=0 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.") + + # Copy bitlines of spare columns + for bit in range(self.num_spare_cols): + if self.precharge_array_inst: + self.copy_layout_pin(self.precharge_array_inst, + "bl_{}".format(self.num_cols + bit + bit_offset), + "sparebl_{}".format(bit)) + self.copy_layout_pin(self.precharge_array_inst, + "br_{}".format(self.num_cols + bit + bit_offset), + "sparebr_{}".format(bit)) else: debug.error("Didn't find precharge array.") @@ -589,79 +719,119 @@ class port_data(design.design): for pin_name in sel_names: self.copy_layout_pin(self.column_mux_array_inst, pin_name) if self.sense_amp_array_inst: - self.copy_layout_pin(self.sense_amp_array_inst, "en", "s_en") + self.copy_layout_pin(self.sense_amp_array_inst, "en", "s_en") if self.write_driver_array_inst: if self.write_mask_and_array_inst: for bit in range(self.num_wmasks): # Add write driver's en_{} pins self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit), "wdriver_sel_{}".format(bit)) + for bit in range(self.num_spare_cols): + # Add spare columns' en_{} pins + self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit + self.num_wmasks), "bank_spare_wen{}".format(bit)) + elif self.num_spare_cols and not self.write_mask_and_array_inst: + self.copy_layout_pin(self.write_driver_array_inst, "en_0", "w_en") + for bit in range(self.num_spare_cols): + self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit + 1), "bank_spare_wen{}".format(bit)) else: self.copy_layout_pin(self.write_driver_array_inst, "en", "w_en") if self.write_mask_and_array_inst: 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_nonpref_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.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() + + layer_pitch = getattr(self, "{}_pitch".format(top_bl_pin.layer)) + self.add_zjog(bot_bl_pin.layer, bot_bl, top_bl, "V", fixed_offset=top_bl_pin.by() - layer_pitch) + self.add_zjog(bot_br_pin.layer, bot_br, top_br, "V", fixed_offset=top_bl_pin.by() - 2 * layer_pitch) - yoffset = 0.5*(top_bl.y+bottom_bl.y) - self.add_path("metal2",[bottom_bl, vector(bottom_bl.x,yoffset), - vector(top_bl.x,yoffset), top_bl]) - self.add_path("metal2",[bottom_br, vector(bottom_br.x,yoffset), - vector(top_br.x,yoffset), top_br]) - def graph_exclude_precharge(self): """Precharge adds a loop between bitlines, can be excluded to reduce complexity""" if self.precharge_array_inst: self.graph_inst_exclude.add(self.precharge_array_inst) - diff --git a/compiler/modules/precharge_array.py b/compiler/modules/precharge_array.py index 2d98ba14..d37de64f 100644 --- a/compiler/modules/precharge_array.py +++ b/compiler/modules/precharge_array.py @@ -7,10 +7,11 @@ # import design import debug -from tech import drc from vector import vector from sram_factory import factory from globals import OPTS +from tech import layer + class precharge_array(design.design): """ @@ -18,7 +19,7 @@ class precharge_array(design.design): of bit line columns, height is the height of the bit-cell array. """ - def __init__(self, name, columns, size=1, bitcell_bl="bl", bitcell_br="br"): + def __init__(self, name, columns, size=1, bitcell_bl="bl", bitcell_br="br", column_offset=0): design.design.__init__(self, name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("cols: {0} size: {1} bl: {2} br: {3}".format(columns, size, bitcell_bl, bitcell_br)) @@ -27,11 +28,25 @@ class precharge_array(design.design): self.size = size self.bitcell_bl = bitcell_bl self.bitcell_br = bitcell_br + self.column_offset = column_offset + if OPTS.tech_name == "sky130": + self.en_bar_layer = "m3" + else: + self.en_bar_layer = "m1" + self.create_netlist() if not OPTS.netlist_only: self.create_layout() + def get_bl_name(self): + bl_name = self.pc_cell.get_bl_names() + return bl_name + + def get_br_name(self): + br_name = self.pc_cell.get_br_names() + return br_name + def add_pins(self): """Adds pins for spice file""" for i in range(self.columns): @@ -60,38 +75,29 @@ 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", - layer="metal1", - offset=self.pc_cell.get_pin("en_bar").ll(), - width=self.width, - height=drc("minwidth_metal1")) + en_pin = self.pc_cell.get_pin("en_bar") + start_offset = en_pin.lc().scale(0, 1) + end_offset = start_offset + vector(self.width, 0) + self.add_layout_pin_segment_center(text="en_bar", + layer=self.en_bar_layer, + start=start_offset, + end=end_offset) for inst in self.local_insts: + self.add_via_stack_center(from_layer=en_pin.layer, + to_layer=self.en_bar_layer, + offset=inst.get_pin("en_bar").center()) self.copy_layout_pin(inst, "vdd") for i in range(len(self.local_insts)): inst = self.local_insts[i] - bl_pin = inst.get_pin("bl") - self.add_layout_pin(text="bl_{0}".format(i), - layer="metal2", - offset=bl_pin.ll(), - width=drc("minwidth_metal2"), - height=bl_pin.height()) - br_pin = inst.get_pin("br") - self.add_layout_pin(text="br_{0}".format(i), - layer="metal2", - offset=br_pin.ll(), - width=drc("minwidth_metal2"), - 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 = [] @@ -104,16 +110,28 @@ 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 + xoffset = 0 for i in range(self.columns): - offset = vector(self.pc_cell.width * i, 0) - self.local_insts[i].place(offset) + tempx = xoffset + if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: + mirror = "MY" + tempx = tempx + self.pc_cell.width + else: + mirror = "" + + offset = vector(tempx, 0) + self.local_insts[i].place(offset=offset, mirror=mirror) + xoffset = xoffset + self.pc_cell.width def get_en_cin(self): - """Get the relative capacitance of all the clk connections in the precharge array""" - #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/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index 639d0714..0c7e412e 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -1,19 +1,16 @@ # See LICENSE for licensing information. # -# Copyright (c) 2016-2019 Regents of the University of California +# Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # import debug import design -from tech import drc, spice +from tech import drc, spice, cell_properties from vector import vector from globals import OPTS from sram_factory import factory -import logical_effort -import bitcell_array -import replica_column -import dummy_array + class replica_bitcell_array(design.design): """ @@ -35,22 +32,23 @@ class replica_bitcell_array(design.design): self.right_rbl = right_rbl self.bitcell_ports = bitcell_ports - debug.check(left_rbl+right_rbl==len(self.all_ports),"Invalid number of RBLs for port configuration.") - debug.check(left_rbl+right_rbl==len(self.bitcell_ports),"Bitcell ports must match total RBLs.") - + debug.check(left_rbl + right_rbl == len(self.all_ports), + "Invalid number of RBLs for port configuration.") + debug.check(left_rbl + right_rbl == len(self.bitcell_ports), + "Bitcell ports must match total RBLs.") + # Two dummy rows/cols plus replica for each port self.extra_rows = 2 + left_rbl + right_rbl self.extra_cols = 2 + left_rbl + right_rbl - + self.create_netlist() if not OPTS.netlist_only: self.create_layout() # We don't offset this because we need to align # the replica bitcell in the control logic - #self.offset_all_coordinates() - - + # self.offset_all_coordinates() + def create_netlist(self): """ Create and connect the netlist """ self.add_modules() @@ -58,15 +56,15 @@ class replica_bitcell_array(design.design): self.create_instances() def add_modules(self): - """ Array and dummy/replica columns + """ Array and dummy/replica columns d or D = dummy cell (caps to distinguish grouping) r or R = replica cell (caps to distinguish grouping) - b or B = bitcell - replica columns 1 + b or B = bitcell + replica columns 1 v v - bdDDDDDDDDDDDDDDdb <- Dummy row - bdDDDDDDDDDDDDDDrb <- Dummy row + bdDDDDDDDDDDDDDDdb <- Dummy row + bdDDDDDDDDDDDDDDrb <- Dummy row br--------------rb br| Array |rb br| row x col |rb @@ -86,71 +84,109 @@ class replica_bitcell_array(design.design): # Bitcell array self.bitcell_array = factory.create(module_type="bitcell_array", + column_offset=1 + self.left_rbl, cols=self.column_size, rows=self.row_size) self.add_mod(self.bitcell_array) # Replica bitlines self.replica_columns = {} - for bit in range(self.left_rbl+self.right_rbl): + for bit in range(self.left_rbl + self.right_rbl): + # Creating left_rbl if bit1 port) for port in range(self.left_rbl): # Make names for all RBLs - wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.all_ports))] + wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x), port) for x in range(len(self.cell.get_all_wl_names()))] # Keep track of the pin that is the RBL self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]] self.replica_col_wl_names.extend(wl_names) # Regular WLs self.replica_col_wl_names.extend(self.bitcell_array_wl_names) # Right port WLs (one dummy for each port when we allow >1 port) - for port in range(self.left_rbl,self.left_rbl+self.right_rbl): + for port in range(self.left_rbl, self.left_rbl + self.right_rbl): # Make names for all RBLs - wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.all_ports))] + wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x), port) for x in range(len(self.cell.get_all_wl_names()))] # Keep track of the pin that is the RBL self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]] self.replica_col_wl_names.extend(wl_names) @@ -159,14 +195,13 @@ class replica_bitcell_array(design.design): # Left/right dummy columns are connected identically to the replica column self.dummy_col_wl_names = self.replica_col_wl_names - # Per port bitline names self.replica_bl_names = {} self.replica_wl_names = {} # Array of all port bitline names - for port in range(self.left_rbl+self.right_rbl): - left_names=["rbl_{0}_{1}".format(self.cell.get_bl_name(x),port) for x in range(len(self.all_ports))] - right_names=["rbl_{0}_{1}".format(self.cell.get_br_name(x),port) for x in range(len(self.all_ports))] + for port in range(self.left_rbl + self.right_rbl): + left_names=["rbl_{0}_{1}".format(self.cell.get_bl_name(x), port) for x in range(len(self.all_ports))] + right_names=["rbl_{0}_{1}".format(self.cell.get_br_name(x), port) for x in range(len(self.all_ports))] # Keep track of the left pins that are the RBL self.rbl_bl_names[port]=left_names[self.bitcell_ports[port]] self.rbl_br_names[port]=right_names[self.bitcell_ports[port]] @@ -174,36 +209,33 @@ class replica_bitcell_array(design.design): bl_names = [x for t in zip(left_names, right_names) for x in t] self.replica_bl_names[port] = bl_names - wl_names = ["rbl_{0}_{1}".format(x,port) for x in self.cell.get_all_wl_names()] - #wl_names[port] = "rbl_wl{}".format(port) + wl_names = ["rbl_{0}_{1}".format(x, port) for x in self.cell.get_all_wl_names()] self.replica_wl_names[port] = wl_names - # External pins self.add_pin_list(self.bitcell_array_bl_names, "INOUT") # Need to sort by port order since dictionary values may not be in order bl_names = [self.rbl_bl_names[x] for x in sorted(self.rbl_bl_names.keys())] br_names = [self.rbl_br_names[x] for x in sorted(self.rbl_br_names.keys())] - for (bl_name,br_name) in zip(bl_names,br_names): - self.add_pin(bl_name,"OUTPUT") - self.add_pin(br_name,"OUTPUT") + for (bl_name, br_name) in zip(bl_names, br_names): + self.add_pin(bl_name, "OUTPUT") + self.add_pin(br_name, "OUTPUT") self.add_pin_list(self.bitcell_array_wl_names, "INPUT") - # Need to sort by port order since dictionary values may not be in order + # Need to sort by port order since dictionary values may not be in order wl_names = [self.rbl_wl_names[x] for x in sorted(self.rbl_wl_names.keys())] for pin_name in wl_names: - self.add_pin(pin_name,"INPUT") + self.add_pin(pin_name, "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") - def create_instances(self): """ Create the module instances used in this design """ supplies = ["vdd", "gnd"] - + # Used for names/dimensions only self.cell = factory.create(module_type="bitcell") - + # Main array self.bitcell_array_inst=self.add_inst(name="bitcell_array", mod=self.bitcell_array) @@ -211,161 +243,162 @@ class replica_bitcell_array(design.design): # Replica columns self.replica_col_inst = {} - for port in range(self.left_rbl+self.right_rbl): + for port in range(self.left_rbl + self.right_rbl): self.replica_col_inst[port]=self.add_inst(name="replica_col_{}".format(port), - mod=self.replica_columns[port]) + mod=self.replica_columns[port]) self.connect_inst(self.replica_bl_names[port] + self.replica_col_wl_names + supplies) - - + # Dummy rows under the bitcell array (connected with with the replica cell wl) self.dummy_row_replica_inst = {} - for port in range(self.left_rbl+self.right_rbl): + for port in range(self.left_rbl + self.right_rbl): self.dummy_row_replica_inst[port]=self.add_inst(name="dummy_row_{}".format(port), mod=self.dummy_row) self.connect_inst(self.dummy_row_bl_names + self.replica_wl_names[port] + supplies) - - - # Top/bottom dummy rows - self.dummy_row_bot_inst=self.add_inst(name="dummy_row_bot", - mod=self.dummy_row) - self.connect_inst(self.dummy_row_bl_names + [x+"_bot" for x in self.dummy_cell_wl_names] + supplies) - self.dummy_row_top_inst=self.add_inst(name="dummy_row_top", - mod=self.dummy_row) - self.connect_inst(self.dummy_row_bl_names + [x+"_top" for x in self.dummy_cell_wl_names] + supplies) + # Top/bottom dummy rows or col caps + self.dummy_row_bot_inst=self.add_inst(name="dummy_row_bot", + mod=self.edge_row) + self.connect_inst(self.dummy_row_bl_names + [x + "_bot" for x in self.dummy_cell_wl_names] + supplies) + self.dummy_row_top_inst=self.add_inst(name="dummy_row_top", + mod=self.edge_row) + self.connect_inst(self.dummy_row_bl_names + [x + "_top" for x in self.dummy_cell_wl_names] + supplies) # Left/right Dummy columns self.dummy_col_left_inst=self.add_inst(name="dummy_col_left", - mod=self.dummy_col) - self.connect_inst([x+"_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) + mod=self.edge_col_left) + self.connect_inst([x + "_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) self.dummy_col_right_inst=self.add_inst(name="dummy_col_right", - mod=self.dummy_col) - self.connect_inst([x+"_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) - + mod=self.edge_col_right) + self.connect_inst([x + "_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) - def create_layout(self): - self.height = (self.row_size+self.extra_rows)*self.dummy_row.height - self.width = (self.column_size+self.extra_cols)*self.cell.width + self.height = (self.row_size + self.extra_rows) * self.dummy_row.height + self.width = (self.column_size + self.extra_cols) * self.cell.width # This is a bitcell x bitcell offset to scale offset = vector(self.cell.width, self.cell.height) - - self.bitcell_array_inst.place(offset=[0,0]) + + self.bitcell_array_inst.place(offset=[0, 0]) # To the left of the bitcell array for bit in range(self.left_rbl): - self.replica_col_inst[bit].place(offset=offset.scale(-bit-1,-self.left_rbl-1)) + self.replica_col_inst[bit].place(offset=offset.scale(-bit - 1, -self.left_rbl - 1)) # To the right of the bitcell array for bit in range(self.right_rbl): - self.replica_col_inst[self.left_rbl+bit].place(offset=offset.scale(bit,-self.left_rbl-1)+self.bitcell_array_inst.lr()) - + self.replica_col_inst[self.left_rbl + bit].place(offset=offset.scale(bit, -self.left_rbl - 1) + self.bitcell_array_inst.lr()) + # FIXME: These depend on the array size itself # Far top dummy row (first row above array is NOT flipped) - flip_dummy = self.right_rbl%2 - self.dummy_row_top_inst.place(offset=offset.scale(0,self.right_rbl+flip_dummy)+self.bitcell_array_inst.ul(), + flip_dummy = self.right_rbl % 2 + self.dummy_row_top_inst.place(offset=offset.scale(0, self.right_rbl + flip_dummy) + self.bitcell_array_inst.ul(), mirror="MX" if flip_dummy else "R0") + # FIXME: These depend on the array size itself # Far bottom dummy row (first row below array IS flipped) - flip_dummy = (self.left_rbl+1)%2 - self.dummy_row_bot_inst.place(offset=offset.scale(0,-self.left_rbl-1+flip_dummy), + flip_dummy = (self.left_rbl + 1) % 2 + self.dummy_row_bot_inst.place(offset=offset.scale(0, -self.left_rbl - 1 + flip_dummy), mirror="MX" if flip_dummy else "R0") # Far left dummy col - self.dummy_col_left_inst.place(offset=offset.scale(-self.left_rbl-1,-self.left_rbl-1)) + self.dummy_col_left_inst.place(offset=offset.scale(-self.left_rbl - 1, -self.left_rbl - 1)) # Far right dummy col - self.dummy_col_right_inst.place(offset=offset.scale(self.right_rbl,-self.left_rbl-1)+self.bitcell_array_inst.lr()) + self.dummy_col_right_inst.place(offset=offset.scale(self.right_rbl, -self.left_rbl - 1) + self.bitcell_array_inst.lr()) # Replica dummy rows for bit in range(self.left_rbl): - self.dummy_row_replica_inst[bit].place(offset=offset.scale(0,-bit-bit%2), - mirror="R0" if bit%2 else "MX") + self.dummy_row_replica_inst[bit].place(offset=offset.scale(0, -bit - bit % 2), + mirror="R0" if bit % 2 else "MX") for bit in range(self.right_rbl): - self.dummy_row_replica_inst[self.left_rbl+bit].place(offset=offset.scale(0,bit+bit%2)+self.bitcell_array_inst.ul(), - mirror="MX" if bit%2 else "R0") - + self.dummy_row_replica_inst[self.left_rbl + bit].place(offset=offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul(), + mirror="MX" if bit % 2 else "R0") + + self.translate_all(offset.scale(-1 - self.left_rbl, -1 - self.left_rbl)) - self.translate_all(offset.scale(-1-self.left_rbl,-1-self.left_rbl)) - self.add_layout_pins() - + self.add_boundary() - + self.DRC_LVS() - def add_layout_pins(self): """ Add the layout pins """ # Main array wl and bl/br pin_names = self.bitcell_array.get_pin_names() for pin_name in pin_names: - if pin_name.startswith("wl"): - pin_list = self.bitcell_array_inst.get_pins(pin_name) - for pin in pin_list: - self.add_layout_pin(text=pin_name, - layer=pin.layer, - offset=pin.ll().scale(0,1), - width=self.width, - height=pin.height()) - elif pin_name.startswith("bl") or pin_name.startswith("br"): - pin_list = self.bitcell_array_inst.get_pins(pin_name) - for pin in pin_list: - self.add_layout_pin(text=pin_name, - layer=pin.layer, - offset=pin.ll().scale(1,0), - width=pin.width(), - height=self.height) - + for wl in self.bitcell_array_wl_names: + if wl in pin_name: + pin_list = self.bitcell_array_inst.get_pins(pin_name) + for pin in pin_list: + self.add_layout_pin(text=pin_name, + layer=pin.layer, + offset=pin.ll().scale(0, 1), + width=self.width, + height=pin.height()) + for bitline in self.bitcell_array_bl_names: + if bitline in pin_name: + pin_list = self.bitcell_array_inst.get_pins(pin_name) + for pin in pin_list: + self.add_layout_pin(text=pin_name, + layer=pin.layer, + offset=pin.ll().scale(1, 0), + width=pin.width(), + height=self.height) # Replica wordlines - for port in range(self.left_rbl+self.right_rbl): + for port in range(self.left_rbl + self.right_rbl): inst = self.replica_col_inst[port] - for (pin_name,wl_name) in zip(self.cell.get_all_wl_names(),self.replica_wl_names[port]): + for (pin_name, wl_name) in zip(self.cell.get_all_wl_names(), self.replica_wl_names[port]): # +1 for dummy row - pin_bit = port+1 - # +row_size if above the array + pin_bit = port + 1 + # +row_size if above the array if port>=self.left_rbl: pin_bit += self.row_size - + pin_name += "_{}".format(pin_bit) pin = inst.get_pin(pin_name) if wl_name in self.rbl_wl_names.values(): self.add_layout_pin(text=wl_name, layer=pin.layer, - offset=pin.ll().scale(0,1), + offset=pin.ll().scale(0, 1), width=self.width, height=pin.height()) - # Replica bitlines - for port in range(self.left_rbl+self.right_rbl): + for port in range(self.left_rbl + self.right_rbl): inst = self.replica_col_inst[port] - for (pin_name, bl_name) in zip(self.cell.get_all_bitline_names(),self.replica_bl_names[port]): + for (pin_name, bl_name) in zip(self.cell.get_all_bitline_names(), self.replica_bl_names[port]): pin = inst.get_pin(pin_name) if bl_name in self.rbl_bl_names or bl_name in self.rbl_br_names: name = bl_name else: - name = "rbl_{0}_{1}".format(pin_name,port) + name = "rbl_{0}_{1}".format(pin_name, port) self.add_layout_pin(text=name, layer=pin.layer, - offset=pin.ll().scale(1,0), + offset=pin.ll().scale(1, 0), width=pin.width(), height=self.height) - - for pin_name in ["vdd","gnd"]: - for inst in self.insts: + # vdd/gnd are only connected in the perimeter cells + # replica column should only have a vdd/gnd in the dummy cell on top/bottom + supply_insts = [self.dummy_col_left_inst, self.dummy_col_right_inst, + self.dummy_row_top_inst, self.dummy_row_bot_inst] + for pin_name in ["vdd", "gnd"]: + for inst in supply_insts: pin_list = inst.get_pins(pin_name) for pin in pin_list: - 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(), + directions=("V", "V"), + start_layer=pin.layer) + + for inst in list(self.replica_col_inst.values()): + self.copy_layout_pin(inst, pin_name) + self.copy_layout_pin(inst, pin_name) - - - def get_rbl_wl_name(self, port): """ Return the WL for the given RBL port """ return self.rbl_wl_names[port] - + def get_rbl_bl_name(self, port): """ Return the BL for the given RBL port """ return self.rbl_bl_names[port] @@ -376,19 +409,17 @@ class replica_bitcell_array(design.design): def analytical_power(self, corner, load): """Power of Bitcell array and bitline in nW.""" - from tech import drc, parameter - # Dynamic Power from Bitline bl_wire = self.gen_bl_wire() - cell_load = 2 * bl_wire.return_input_cap() + cell_load = 2 * bl_wire.return_input_cap() bl_swing = OPTS.rbl_delay_percentage freq = spice["default_event_frequency"] bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing) - - #Calculate the bitcell power which currently only includes leakage + + # Calculate the bitcell power which currently only includes leakage cell_power = self.cell.analytical_power(corner, load) - - #Leakage power grows with entire array and bitlines. + + # Leakage power grows with entire array and bitlines. total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size, cell_power.leakage * self.column_size * self.row_size) return total_power @@ -399,13 +430,13 @@ class replica_bitcell_array(design.design): else: height = self.height bl_pos = 0 - bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), height, drc("minwidth_metal1")) + bl_wire = self.generate_rc_net(int(self.row_size - bl_pos), height, drc("minwidth_m1")) bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell return bl_wire def get_wordline_cin(self): """Get the relative input capacitance from the wordline connections in all the bitcell""" - #A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns + # A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns bitcell_wl_cin = self.cell.get_wl_cin() total_cin = bitcell_wl_cin * self.column_size return total_cin @@ -413,13 +444,13 @@ class replica_bitcell_array(design.design): def graph_exclude_bits(self, targ_row, targ_col): """Excludes bits in column from being added to graph except target""" self.bitcell_array.graph_exclude_bits(targ_row, targ_col) - + def graph_exclude_replica_col_bits(self): """Exclude all replica/dummy cells in the replica columns except the replica bit.""" - - for port in range(self.left_rbl+self.right_rbl): + + for port in range(self.left_rbl + self.right_rbl): self.replica_columns[port].exclude_all_but_replica() def get_cell_name(self, inst_name, row, col): """Gets the spice name of the target bitcell.""" - return self.bitcell_array.get_cell_name(inst_name+'.x'+self.bitcell_array_inst.name, row, col) + return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col) diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index c3f63b19..9613e6fa 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -1,26 +1,27 @@ # See LICENSE for licensing information. # -# Copyright (c) 2016-2019 Regents of the University of California +# Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # import debug import design -from tech import drc -import contact +from tech import cell_properties from sram_factory import factory from vector import vector from globals import OPTS + class replica_column(design.design): """ Generate a replica bitline column for the replica array. Rows is the total number of rows i the main array. Left_rbl and right_rbl are the number of left and right replica bitlines. - Replica bit specifies which replica column this is (to determine where to put the + Replica bit specifies which replica column this is (to determine where to put the replica cell. """ - def __init__(self, name, rows, left_rbl, right_rbl, replica_bit): + def __init__(self, name, rows, left_rbl, right_rbl, replica_bit, + column_offset=0): design.design.__init__(self, name) self.rows = rows @@ -28,24 +29,30 @@ class replica_column(design.design): self.right_rbl = right_rbl self.replica_bit = replica_bit # left, right, regular rows plus top/bottom dummy cells - self.total_size = self.left_rbl+rows+self.right_rbl+2 - - debug.check(replica_bit!=0 and replica_bit!=rows,"Replica bit cannot be the dummy row.") - debug.check(replica_bit<=left_rbl or replica_bit>=self.total_size-right_rbl-1, - "Replica bit cannot be in the regular array.") + self.total_size = self.left_rbl + rows + self.right_rbl + 2 + self.column_offset = column_offset + + debug.check(replica_bit != 0 and replica_bit != rows, + "Replica bit cannot be the dummy row.") + debug.check(replica_bit <= left_rbl or replica_bit >= self.total_size - right_rbl - 1, + "Replica bit cannot be in the regular array.") + if OPTS.tech_name == "sky130": + debug.check(rows % 2 == 0 and (left_rbl + 1) % 2 == 0, + "sky130 currently requires rows to be even and to start with X mirroring" + + " (left_rbl must be odd) for LVS.") self.create_netlist() if not OPTS.netlist_only: self.create_layout() - + def create_netlist(self): self.add_modules() self.add_pins() self.create_instances() def create_layout(self): - self.height = self.total_size*self.cell.height - self.width = self.cell.width + self.height = self.total_size * self.cell.height + self.width = self.cell.width self.place_instances() self.add_layout_pins() @@ -53,109 +60,170 @@ class replica_column(design.design): self.DRC_LVS() def add_pins(self): - + for bl_name in self.cell.get_all_bitline_names(): # In the replica column, these are only outputs! - self.add_pin("{0}_{1}".format(bl_name,0), "OUTPUT") + self.add_pin("{0}_{1}".format(bl_name, 0), "OUTPUT") for row in range(self.total_size): for wl_name in self.cell.get_all_wl_names(): - self.add_pin("{0}_{1}".format(wl_name,row), "INPUT") - + self.add_pin("{0}_{1}".format(wl_name, row), "INPUT") + self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") def add_modules(self): - self.replica_cell = factory.create(module_type="replica_bitcell") + self.replica_cell = factory.create(module_type="replica_{}".format(OPTS.bitcell)) self.add_mod(self.replica_cell) - self.dummy_cell = factory.create(module_type="dummy_bitcell") + self.dummy_cell = factory.create(module_type="dummy_{}".format(OPTS.bitcell)) self.add_mod(self.dummy_cell) + try: + edge_module_type = ("col_cap" if cell_properties.bitcell.end_caps else "dummy") + except AttributeError: + edge_module_type = "dummy" + self.edge_cell = factory.create(module_type=edge_module_type + "_" + OPTS.bitcell) + self.add_mod(self.edge_cell) # Used for pin names only self.cell = factory.create(module_type="bitcell") - + def create_instances(self): + + try: + end_caps_enabled = cell_properties.bitcell.end_caps + except AttributeError: + end_caps_enabled = False + self.cell_inst = {} for row in range(self.total_size): name="rbc_{0}".format(row) # Top/bottom cell are always dummy cells. # Regular array cells are replica cells (>left_rbl and self.left_rbl and row self.left_rbl and row < self.total_size - self.right_rbl - 1): self.cell_inst[row]=self.add_inst(name=name, mod=self.replica_cell) + self.connect_inst(self.get_bitcell_pins(0, row)) elif row==self.replica_bit: self.cell_inst[row]=self.add_inst(name=name, mod=self.replica_cell) + self.connect_inst(self.get_bitcell_pins(0, row)) + elif (row == 0 or row == self.total_size - 1): + self.cell_inst[row]=self.add_inst(name=name, + mod=self.edge_cell) + if end_caps_enabled: + self.connect_inst(self.get_bitcell_pins_col_cap(0, row)) + else: + self.connect_inst(self.get_bitcell_pins(0, row)) else: self.cell_inst[row]=self.add_inst(name=name, mod=self.dummy_cell) - self.connect_inst(self.get_bitcell_pins(0, row)) - - def place_instances(self): + self.connect_inst(self.get_bitcell_pins(0, row)) + def place_instances(self): + from tech import cell_properties # Flip the mirrors if we have an odd number of replica+dummy rows at the bottom # so that we will start with mirroring rather than not mirroring - rbl_offset = (self.left_rbl+1)%2 - + rbl_offset = (self.left_rbl + 1) %2 + + # if our bitcells are mirrored on the y axis, check if we are in global + # column that needs to be flipped. + dir_y = False + xoffset = 0 + if cell_properties.bitcell.mirror.y and self.column_offset % 2: + dir_y = True + xoffset = self.replica_cell.width + for row in range(self.total_size): - name = "bit_r{0}_{1}".format(row,"rbl") - offset = vector(0,self.cell.height*(row+(row+rbl_offset)%2)) - if (row+rbl_offset)%2: + # name = "bit_r{0}_{1}".format(row, "rbl") + dir_x = cell_properties.bitcell.mirror.x and (row + rbl_offset) % 2 + + offset = vector(xoffset, self.cell.height * (row + (row + rbl_offset) % 2)) + + if dir_x and dir_y: + dir_key = "XY" + elif dir_x: dir_key = "MX" + elif dir_y: + dir_key = "MY" else: - dir_key = "R0" + dir_key = "" self.cell_inst[row].place(offset=offset, mirror=dir_key) - - def add_layout_pins(self): """ Add the layout pins """ - + for bl_name in self.cell.get_all_bitline_names(): bl_pin = self.cell_inst[0].get_pin(bl_name) self.add_layout_pin(text=bl_name, - layer="metal2", - offset=bl_pin.ll(), + layer=bl_pin.layer, + offset=bl_pin.ll().scale(1, 0), width=bl_pin.width(), height=self.height) - for row in range(self.total_size): + try: + end_caps_enabled = cell_properties.bitcell.end_caps + except AttributeError: + end_caps_enabled = False + + if end_caps_enabled: + row_range_max = self.total_size - 1 + row_range_min = 1 + else: + row_range_max = self.total_size + row_range_min = 0 + + for row in range(row_range_min, row_range_max): for wl_name in self.cell.get_all_wl_names(): wl_pin = self.cell_inst[row].get_pin(wl_name) - self.add_layout_pin(text="{0}_{1}".format(wl_name,row), - layer="metal1", - offset=wl_pin.ll().scale(0,1), + self.add_layout_pin(text="{0}_{1}".format(wl_name, row), + layer=wl_pin.layer, + offset=wl_pin.ll().scale(0, 1), width=self.width, height=wl_pin.height()) - # For every second row and column, add a via for gnd and vdd - for row in range(self.total_size): - inst = self.cell_inst[row] + # Supplies are only connected in the ends + for (index, inst) in self.cell_inst.items(): for pin_name in ["vdd", "gnd"]: - self.copy_layout_pin(inst, pin_name) + if inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]: + self.copy_power_pins(inst, pin_name) + else: + self.copy_layout_pin(inst, pin_name) def get_bitcell_pins(self, col, row): - """ Creates a list of connections in the bitcell, + """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ bitcell_pins = [] - + pin_names = self.cell.get_all_bitline_names() for pin in pin_names: - bitcell_pins.append(pin+"_{0}".format(col)) + bitcell_pins.append(pin + "_{0}".format(col)) pin_names = self.cell.get_all_wl_names() for pin in pin_names: - bitcell_pins.append(pin+"_{0}".format(row)) + bitcell_pins.append(pin + "_{0}".format(row)) bitcell_pins.append("vdd") bitcell_pins.append("gnd") - + return bitcell_pins - + + def get_bitcell_pins_col_cap(self, col, row): + """ Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array """ + + bitcell_pins = [] + + pin_names = self.cell.get_all_bitline_names() + for pin in pin_names: + bitcell_pins.append(pin + "_{0}".format(col)) + bitcell_pins.append("vdd") + + return bitcell_pins + def exclude_all_but_replica(self): """Excludes all bits except the replica cell (self.replica_bit).""" - + for row, cell in self.cell_inst.items(): if row != self.replica_bit: self.graph_inst_exclude.add(cell) diff --git a/compiler/modules/row_cap_array.py b/compiler/modules/row_cap_array.py new file mode 100644 index 00000000..f108d86e --- /dev/null +++ b/compiler/modules/row_cap_array.py @@ -0,0 +1,117 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +from bitcell_base_array import bitcell_base_array +from sram_factory import factory +from globals import OPTS +from tech import cell_properties + + +class row_cap_array(bitcell_base_array): + """ + Generate a dummy row/column for the replica array. + """ + def __init__(self, cols, rows, column_offset=0, mirror=0, name=""): + super().__init__(cols, rows, name, column_offset) + self.mirror = mirror + self.no_instances = True + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + """ Create and connect the netlist """ + self.add_modules() + self.add_pins() + self.create_instances() + + def create_layout(self): + + self.place_array("dummy_r{0}_c{1}", self.mirror) + self.add_layout_pins() + self.add_boundary() + self.DRC_LVS() + + def add_modules(self): + """ Add the modules used in this design """ + self.dummy_cell = factory.create(module_type="row_cap_{}".format(OPTS.bitcell)) + self.add_mod(self.dummy_cell) + + self.cell = factory.create(module_type="bitcell") + + def create_instances(self): + """ Create the module instances used in this design """ + self.cell_inst = {} + for col in range(self.column_size): + for row in range(1, self.row_size - 1): + name = "bit_r{0}_c{1}".format(row, col) + self.cell_inst[row, col]=self.add_inst(name=name, + mod=self.dummy_cell) + self.connect_inst(self.get_bitcell_pins(col, row)) + + def get_bitcell_pins(self, col, row): + """ + Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array + """ + + pin_name = cell_properties.bitcell.cell_1rw1r.pin + bitcell_pins = ["{0}_{1}".format(pin_name.wl0, row), + "{0}_{1}".format(pin_name.wl1, row), + "gnd"] + + return bitcell_pins + + def place_array(self, name_template, row_offset=0): + # We increase it by a well enclosure so the precharges don't overlap our wells + self.height = self.row_size * self.cell.height + self.width = self.column_size * self.cell.width + + xoffset = 0.0 + for col in range(self.column_size): + yoffset = self.cell.height + tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset) + + for row in range(1, self.row_size - 1): + tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset) + + if dir_x and dir_y: + dir_key = "XY" + elif dir_x: + dir_key = "MX" + elif dir_y: + dir_key = "MY" + else: + dir_key = "" + + self.cell_inst[row, col].place(offset=[tempx, tempy], + mirror=dir_key) + yoffset += self.cell.height + xoffset += self.cell.width + + def add_layout_pins(self): + """ Add the layout pins """ + + row_list = self.cell.get_all_wl_names() + + for row in range(1, self.row_size - 1): + for cell_row in row_list: + wl_pin = self.cell_inst[row, 0].get_pin(cell_row) + self.add_layout_pin(text=cell_row + "_{0}".format(row), + layer=wl_pin.layer, + offset=wl_pin.ll().scale(0, 1), + width=self.width, + height=wl_pin.height()) + + # Add vdd/gnd via stacks + for row in range(1, self.row_size - 1): + for col in range(self.column_size): + inst = self.cell_inst[row, col] + for pin_name in ["vdd", "gnd"]: + for pin in inst.get_pins(pin_name): + self.add_power_pin(name=pin.name, + loc=pin.center(), + start_layer=pin.layer) + diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py index e77d577f..35fbdf42 100644 --- a/compiler/modules/sense_amp.py +++ b/compiler/modules/sense_amp.py @@ -8,9 +8,12 @@ import design import debug import utils -from tech import GDS,layer, parameter,drc +from tech import GDS, layer, parameter, drc +from tech import cell_properties as props +from globals import OPTS import logical_effort + class sense_amp(design.design): """ This module implements the single sense amp cell used in the design. It @@ -18,11 +21,33 @@ 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"] - (width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"]) - pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"]) + if not OPTS.netlist_only: + (width, height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"]) + else: + (width, height) = (0, 0) + pin_map = [] + + def get_bl_names(self): + return props.sense_amp.pin.bl + + def get_br_names(self): + 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) @@ -37,42 +62,41 @@ class sense_amp(design.design): # FIXME: This input load will be applied to both the s_en timing and bitline timing. - #Input load for the bitlines which are connected to the source/drain of a TX. Not the selects. - from tech import spice, parameter + # Input load for the bitlines which are connected to the source/drain of a TX. Not the selects. + from tech import spice # Default is 8x. Per Samira and Hodges-Jackson book: # "Column-mux transistors driven by the decoder must be sized for optimal speed" - bitline_pmos_size = 8 #FIXME: This should be set somewhere and referenced. Probably in tech file. - return spice["min_tx_drain_c"]*(bitline_pmos_size)#ff + bitline_pmos_size = 8 # FIXME: This should be set somewhere and referenced. Probably in tech file. + return spice["min_tx_drain_c"] * bitline_pmos_size # ff def get_stage_effort(self, load): - #Delay of the sense amp will depend on the size of the amp and the output load. + # Delay of the sense amp will depend on the size of the amp and the output load. parasitic_delay = 1 - cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"])/drc("minwidth_tx") - sa_size = parameter["sa_inv_nmos_size"]/drc("minwidth_tx") + cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"]) / drc("minwidth_tx") + sa_size = parameter["sa_inv_nmos_size"] / drc("minwidth_tx") cc_inv_cin = cin - return logical_effort.logical_effort('column_mux', sa_size, cin, load+cc_inv_cin, parasitic_delay, False) + return logical_effort.logical_effort('column_mux', sa_size, cin, load + cc_inv_cin, parasitic_delay, False) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" - #Power in this module currently not defined. Returns 0 nW (leakage and dynamic). + # Power in this module currently not defined. Returns 0 nW (leakage and dynamic). total_power = self.return_power() return total_power def get_en_cin(self): """Get the relative capacitance of sense amp enable gate cin""" - pmos_cin = parameter["sa_en_pmos_size"]/drc("minwidth_tx") - nmos_cin = parameter["sa_en_nmos_size"]/drc("minwidth_tx") - #sen is connected to 2 pmos isolation TX and 1 nmos per sense amp. - return 2*pmos_cin + nmos_cin + pmos_cin = parameter["sa_en_pmos_size"] / drc("minwidth_tx") + nmos_cin = parameter["sa_en_nmos_size"] / drc("minwidth_tx") + # sen is connected to 2 pmos isolation TX and 1 nmos per sense amp. + return 2 * pmos_cin + nmos_cin def get_enable_name(self): """Returns name used for enable net""" - #FIXME: A better programmatic solution to designate pins - enable_name = "en" + # FIXME: A better programmatic solution to designate pins + enable_name = self.en_name debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(enable_name)) return enable_name - def build_graph(self, graph, inst_name, port_nets): + def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) - \ No newline at end of file + self.add_graph_edges(graph, port_nets) diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index 598886c0..98cbee66 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -13,26 +13,54 @@ import debug from globals import OPTS import logical_effort + class sense_amp_array(design.design): """ Array of sense amplifiers to read the bitlines through the column mux. Dynamically generated sense amp array for all bitlines. """ - def __init__(self, name, word_size, words_per_row): + def __init__(self, name, word_size, words_per_row, num_spare_cols=None, column_offset=0): + design.design.__init__(self, name) debug.info(1, "Creating {0}".format(self.name)) - self.add_comment("word_size {0}".format(word_size)) + self.add_comment("word_size {0}".format(word_size)) self.add_comment("words_per_row: {0}".format(words_per_row)) self.word_size = word_size self.words_per_row = words_per_row + if not num_spare_cols: + self.num_spare_cols = 0 + else: + self.num_spare_cols = num_spare_cols + + self.column_offset = column_offset self.row_size = self.word_size * self.words_per_row + if OPTS.tech_name == "sky130": + self.en_layer = "m3" + else: + self.en_layer = "m1" + self.create_netlist() if not OPTS.netlist_only: self.create_layout() + def get_bl_name(self): + bl_name = "bl" + return bl_name + + 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() @@ -41,11 +69,11 @@ class sense_amp_array(design.design): def create_layout(self): self.height = self.amp.height - + if self.bitcell.width > self.amp.width: - self.width = self.bitcell.width * self.word_size * self.words_per_row + self.width = self.bitcell.width * (self.word_size * self.words_per_row + self.num_spare_cols) else: - self.width = self.amp.width * self.word_size * self.words_per_row + self.width = self.amp.width * (self.word_size * self.words_per_row + self.num_spare_cols) self.place_sense_amp_array() self.add_layout_pins() @@ -54,105 +82,126 @@ class sense_amp_array(design.design): self.DRC_LVS() 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") + for i in range(0, self.word_size + self.num_spare_cols): + self.add_pin(self.data_name + "_{0}".format(i), "OUTPUT") + self.add_pin(self.get_bl_name() + "_{0}".format(i), "INPUT") + self.add_pin(self.get_br_name() + "_{0}".format(i), "INPUT") + self.add_pin(self.en_name, "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") - + def add_modules(self): self.amp = factory.create(module_type="sense_amp") - + self.add_mod(self.amp) # This is just used for measurements, # so don't add the module self.bitcell = factory.create(module_type="bitcell") - + def create_sense_amp_array(self): self.local_insts = [] - for i in range(0,self.word_size): - + for i in range(0, self.word_size + self.num_spare_cols): name = "sa_d{0}".format(i) self.local_insts.append(self.add_inst(name=name, mod=self.amp)) - 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): - - if self.bitcell.width > self.amp.width: - amp_spacing = self.bitcell.width * self.words_per_row - else: - amp_spacing = self.amp.width * self.words_per_row - for i in range(0,self.word_size): - amp_position = vector(amp_spacing * i, 0) - self.local_insts[i].place(amp_position) + from tech import cell_properties + + for i in range(0, self.row_size, self.words_per_row): + index = int(i / self.words_per_row) + xoffset = i * self.bitcell.width + + if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: + mirror = "MY" + xoffset = xoffset + self.amp.width + else: + mirror = "" + + amp_position = vector(xoffset, 0) + self.local_insts[index].place(offset=amp_position, mirror=mirror) + + # place spare sense amps (will share the same enable as regular sense amps) + for i in range(0, self.num_spare_cols): + index = self.word_size + i + xoffset = ((self.word_size * self.words_per_row) + i) * self.bitcell.width + + if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: + mirror = "MY" + xoffset = xoffset + self.amp.width + else: + mirror = "" + + amp_position = vector(xoffset, 0) + self.local_insts[index].place(offset=amp_position, mirror=mirror) - def add_layout_pins(self): for i in range(len(self.local_insts)): inst = self.local_insts[i] - - gnd_pos = inst.get_pin("gnd").center() - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=gnd_pos) - self.add_layout_pin_rect_center(text="gnd", - layer="metal3", - offset=gnd_pos) - vdd_pos = inst.get_pin("vdd").center() - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=vdd_pos) - self.add_layout_pin_rect_center(text="vdd", - layer="metal3", - offset=vdd_pos) - bl_pin = inst.get_pin("bl") - br_pin = inst.get_pin("br") - dout_pin = inst.get_pin("dout") - - self.add_layout_pin(text="bl_{0}".format(i), - layer="metal2", + for gnd_pin in inst.get_pins("gnd"): + self.add_power_pin(name="gnd", + loc=gnd_pin.center(), + start_layer=gnd_pin.layer, + directions=("V", "V")) + + for vdd_pin in inst.get_pins("vdd"): + self.add_power_pin(name="vdd", + loc=vdd_pin.center(), + start_layer=vdd_pin.layer, + directions=("V", "V")) + + bl_pin = inst.get_pin(inst.mod.get_bl_names()) + br_pin = inst.get_pin(inst.mod.get_br_names()) + dout_pin = inst.get_pin(inst.mod.dout_name) + + self.add_layout_pin(text=self.get_bl_name() + "_{0}".format(i), + layer=bl_pin.layer, offset=bl_pin.ll(), width=bl_pin.width(), height=bl_pin.height()) - self.add_layout_pin(text="br_{0}".format(i), - layer="metal2", + self.add_layout_pin(text=self.get_br_name() + "_{0}".format(i), + layer=br_pin.layer, offset=br_pin.ll(), width=br_pin.width(), height=br_pin.height()) - - self.add_layout_pin(text="data_{0}".format(i), - layer="metal2", + + self.add_layout_pin(text=self.data_name + "_{0}".format(i), + layer=dout_pin.layer, offset=dout_pin.ll(), width=dout_pin.width(), height=dout_pin.height()) - - + 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", - layer="metal1", - offset=sclk_offset, - width=self.width, - height=drc("minwidth_metal1")) + # Add enable across the array + en_pin = self.amp.get_pin(self.amp.en_name) + start_offset = en_pin.lc().scale(0, 1) + end_offset = start_offset + vector(self.width, 0) + self.add_layout_pin_segment_center(text=self.en_name, + layer=self.en_layer, + start=start_offset, + end=end_offset) + for inst in self.local_insts: + self.add_via_stack_center(from_layer=en_pin.layer, + to_layer=self.en_layer, + offset=inst.get_pin(self.amp.en_name).center()) def input_load(self): return self.amp.input_load() - + def get_en_cin(self): """Get the relative capacitance of all the sense amp enable connections in the array""" sense_amp_en_cin = self.amp.get_en_cin() return sense_amp_en_cin * self.word_size - + def get_drain_cin(self): """Get the relative capacitance of the drain of the PMOS isolation TX""" from tech import parameter - #Bitcell drain load being used to estimate PMOS drain load + # Bitcell drain load being used to estimate PMOS drain load drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap']) return drain_load diff --git a/compiler/modules/single_level_column_mux_array.py b/compiler/modules/single_level_column_mux_array.py index 7e3beaad..8b01d111 100644 --- a/compiler/modules/single_level_column_mux_array.py +++ b/compiler/modules/single_level_column_mux_array.py @@ -5,56 +5,77 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from math import log import design -import contact -from tech import drc import debug -import math +from tech import layer, preferred_directions from vector import vector from sram_factory import factory from globals import OPTS import logical_effort + class single_level_column_mux_array(design.design): """ Dynamically generated column mux array. Array of column mux to read the bitlines through the 6T. """ - def __init__(self, name, columns, word_size, bitcell_bl="bl", bitcell_br="br"): + def __init__(self, name, columns, word_size, bitcell_bl="bl", bitcell_br="br", column_offset=0): design.design.__init__(self, name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("cols: {0} word_size: {1} bl: {2} br: {3}".format(columns, word_size, bitcell_bl, bitcell_br)) - + self.columns = columns self.word_size = word_size self.words_per_row = int(self.columns / self.word_size) self.bitcell_bl = bitcell_bl self.bitcell_br = bitcell_br - + self.column_offset = column_offset + + if OPTS.tech_name == "sky130": + self.sel_layer = "m3" + self.sel_pitch = self.m3_pitch + self.bitline_layer = "m1" + else: + self.sel_layer = "m1" + self.sel_pitch = self.m2_pitch + self.bitline_layer = "m2" + + if preferred_directions[self.sel_layer] == "V": + self.via_directions = ("H", "H") + else: + self.via_directions = "pref" + self.create_netlist() if not OPTS.netlist_only: self.create_layout() + def get_bl_name(self): + bl_name = self.mux.get_bl_names() + return bl_name + + def get_br_name(self, port=0): + br_name = self.mux.get_br_names() + return br_name + def create_netlist(self): self.add_modules() self.add_pins() self.create_array() - + def create_layout(self): self.setup_layout_constants() self.place_array() self.add_routing() # Find the highest shapes to determine height before adding well highest = self.find_highest_coords() - self.height = highest.y + self.height = highest.y self.add_layout_pins() - self.add_enclosure(self.mux_inst, "pwell") - + if "pwell" in layer: + self.add_enclosure(self.mux_inst, "pwell") self.add_boundary() self.DRC_LVS() - + def add_pins(self): for i in range(self.columns): self.add_pin("bl_{}".format(i)) @@ -66,23 +87,19 @@ class single_level_column_mux_array(design.design): self.add_pin("br_out_{}".format(i)) self.add_pin("gnd") - def add_modules(self): self.mux = factory.create(module_type="single_level_column_mux", bitcell_bl=self.bitcell_bl, bitcell_br=self.bitcell_br) self.add_mod(self.mux) - def setup_layout_constants(self): - self.column_addr_size = num_of_inputs = int(self.words_per_row / 2) + self.column_addr_size = int(self.words_per_row / 2) self.width = self.columns * self.mux.width # one set of metal1 routes for select signals and a pair to interconnect the mux outputs bl/br # one extra route pitch is to space from the sense amp - self.route_height = (self.words_per_row + 3)*self.m1_pitch - + self.route_height = (self.words_per_row + 3) * self.sel_pitch - def create_array(self): self.mux_inst = [] # For every column, add a pass gate @@ -90,60 +107,67 @@ class single_level_column_mux_array(design.design): name = "XMUX{0}".format(col_num) self.mux_inst.append(self.add_inst(name=name, mod=self.mux)) - + self.connect_inst(["bl_{}".format(col_num), "br_{}".format(col_num), - "bl_out_{}".format(int(col_num/self.words_per_row)), - "br_out_{}".format(int(col_num/self.words_per_row)), + "bl_out_{}".format(int(col_num / self.words_per_row)), + "br_out_{}".format(int(col_num / self.words_per_row)), "sel_{}".format(col_num % self.words_per_row), "gnd"]) def place_array(self): + from tech import cell_properties # For every column, add a pass gate for col_num in range(self.columns): - name = "XMUX{0}".format(col_num) - x_off = vector(col_num * self.mux.width, self.route_height) - self.mux_inst[col_num].place(x_off) - + xoffset = col_num * self.mux.width + if cell_properties.bitcell.mirror.y and (col_num + self.column_offset) % 2: + mirror = "MY" + xoffset = xoffset + self.mux.width + else: + mirror = "" + + offset = vector(xoffset, self.route_height) + self.mux_inst[col_num].place(offset=offset, mirror=mirror) def add_layout_pins(self): """ Add the pins after we determine the height. """ # For every column, add a pass gate for col_num in range(self.columns): mux_inst = self.mux_inst[col_num] - offset = mux_inst.get_pin("bl").ll() + bl_pin = mux_inst.get_pin("bl") + offset = bl_pin.ll() self.add_layout_pin(text="bl_{}".format(col_num), - layer="metal2", + layer=bl_pin.layer, offset=offset, - height=self.height-offset.y) + height=self.height - offset.y) - offset = mux_inst.get_pin("br").ll() + br_pin = mux_inst.get_pin("br") + offset = br_pin.ll() self.add_layout_pin(text="br_{}".format(col_num), - layer="metal2", + layer=br_pin.layer, offset=offset, - height=self.height-offset.y) + height=self.height - offset.y) for inst in self.mux_inst: self.copy_layout_pin(inst, "gnd") - def add_routing(self): self.add_horizontal_input_rail() self.add_vertical_poly_rail() self.route_bitlines() def add_horizontal_input_rail(self): - """ Create address input rails on M1 below the mux transistors """ + """ Create address input rails below the mux transistors """ for j in range(self.words_per_row): - offset = vector(0, self.route_height + (j-self.words_per_row)*self.m1_pitch) + offset = vector(0, self.route_height + (j - self.words_per_row) * self.sel_pitch) self.add_layout_pin(text="sel_{}".format(j), - layer="metal1", + layer=self.sel_layer, offset=offset, width=self.mux.width * self.columns) def add_vertical_poly_rail(self): """ Connect the poly to the address rails """ - + # Offset to the first transistor gate in the pass gate for col in range(self.columns): # which select bit should this column connect to depends on the position in the word @@ -151,68 +175,65 @@ class single_level_column_mux_array(design.design): # Add the column x offset to find the right select bit gate_offset = self.mux_inst[col].get_pin("sel").bc() # height to connect the gate to the correct horizontal row - sel_height = self.get_pin("sel_{}".format(sel_index)).by() + # sel_height = self.get_pin("sel_{}".format(sel_index)).by() # use the y offset from the sel pin and the x offset from the gate - offset = vector(gate_offset.x,self.get_pin("sel_{}".format(sel_index)).cy()) - # Add the poly contact with a shift to account for the rotation - self.add_via_center(layers=("metal1", "contact", "poly"), - offset=offset) + offset = vector(gate_offset.x, + self.get_pin("sel_{}".format(sel_index)).cy()) + self.add_via_stack_center(from_layer="poly", + to_layer=self.sel_layer, + offset=offset, + directions=self.via_directions) self.add_path("poly", [offset, gate_offset]) def route_bitlines(self): """ Connect the output bit-lines to form the appropriate width mux """ for j in range(self.columns): - bl_offset = self.mux_inst[j].get_pin("bl_out").bc() - br_offset = self.mux_inst[j].get_pin("br_out").bc() - bl_out_offset = bl_offset - vector(0,(self.words_per_row+1)*self.m1_pitch) - br_out_offset = br_offset - vector(0,(self.words_per_row+2)*self.m1_pitch) + bl_offset_begin = self.mux_inst[j].get_pin("bl_out").bc() + br_offset_begin = self.mux_inst[j].get_pin("br_out").bc() - bl_out_offset_end = bl_out_offset + vector(0,self.route_height) - br_out_offset_end = br_out_offset + vector(0,self.route_height) + bl_out_offset_begin = bl_offset_begin - vector(0, (self.words_per_row + 1) * self.sel_pitch) + br_out_offset_begin = br_offset_begin - vector(0, (self.words_per_row + 2) * self.sel_pitch) - if (j % self.words_per_row) == 0: - # Create the metal1 to connect the n-way mux output from the pass gate - # These will be located below the select lines. Yes, these are M2 width - # to ensure vias are enclosed and M1 min width rules. - width = self.m2_width + self.mux.width * (self.words_per_row - 1) - self.add_path("metal1", [bl_out_offset, bl_out_offset+vector(width,0)]) - self.add_path("metal1", [br_out_offset, br_out_offset+vector(width,0)]) + # Add the horizontal wires for the first bit + if j % self.words_per_row == 0: + bl_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("bl_out").bc() + br_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("br_out").bc() + bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.sel_pitch) + br_out_offset_end = br_offset_end - vector(0, (self.words_per_row + 2) * self.sel_pitch) + + self.add_path(self.sel_layer, [bl_out_offset_begin, bl_out_offset_end]) + self.add_path(self.sel_layer, [br_out_offset_begin, br_out_offset_end]) # Extend the bitline output rails and gnd downward on the first bit of each n-way mux - self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j/self.words_per_row)), - layer="metal2", - start=bl_out_offset, - end=bl_out_offset_end) - self.add_layout_pin_segment_center(text="br_out_{}".format(int(j/self.words_per_row)), - layer="metal2", - start=br_out_offset, - end=br_out_offset_end) - - - # This via is on the right of the wire - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=bl_out_offset) - - # This via is on the left of the wire - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=br_out_offset) + self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j / self.words_per_row)), + layer=self.bitline_layer, + start=bl_offset_begin, + end=bl_out_offset_begin) + self.add_layout_pin_segment_center(text="br_out_{}".format(int(j / self.words_per_row)), + layer=self.bitline_layer, + start=br_offset_begin, + end=br_out_offset_begin) else: - - self.add_path("metal2", [ bl_out_offset, bl_out_offset_end]) - self.add_path("metal2", [ br_out_offset, br_out_offset_end]) - - # This via is on the right of the wire - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=bl_out_offset) - # This via is on the left of the wire - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=br_out_offset) + self.add_path(self.bitline_layer, [bl_out_offset_begin, bl_offset_begin]) + self.add_path(self.bitline_layer, [br_out_offset_begin, br_offset_begin]) + + # This via is on the right of the wire + self.add_via_stack_center(from_layer=self.bitline_layer, + to_layer=self.sel_layer, + offset=bl_out_offset_begin, + directions=self.via_directions) + + # This via is on the left of the wire + self.add_via_stack_center(from_layer=self.bitline_layer, + to_layer=self.sel_layer, + offset=br_out_offset_begin, + directions=self.via_directions) def get_drain_cin(self): """Get the relative capacitance of the drain of the NMOS pass TX""" from tech import parameter - #Bitcell drain load being used to estimate mux NMOS drain load + # Bitcell drain load being used to estimate mux NMOS drain load drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap']) - return drain_load + return drain_load diff --git a/compiler/modules/tri_gate_array.py b/compiler/modules/tri_gate_array.py index 7d1c21d0..e7ebd802 100644 --- a/compiler/modules/tri_gate_array.py +++ b/compiler/modules/tri_gate_array.py @@ -83,14 +83,14 @@ class tri_gate_array(design.design): in_pin = self.tri_inst[i].get_pin("in") self.add_layout_pin(text="in_{0}".format(index), - layer="metal2", + layer="m2", offset=in_pin.ll(), width=in_pin.width(), height=in_pin.height()) out_pin = self.tri_inst[i].get_pin("out") self.add_layout_pin(text="out_{0}".format(index), - layer="metal2", + layer="m2", offset=out_pin.ll(), width=out_pin.width(), height=out_pin.height()) @@ -100,24 +100,24 @@ class tri_gate_array(design.design): for n in ["vdd", "gnd"]: for supply_pin in self.tri_inst[i].get_pins(n): pin_pos = supply_pin.center() - self.add_via_center(layers=("metal2", "via2", "metal3"), + self.add_via_center(layers=self.m2_stack, offset=pin_pos) self.add_layout_pin_rect_center(text=n, - layer="metal3", + layer="m3", offset=pin_pos) width = self.tri.width * self.columns - (self.words_per_row - 1) * self.tri.width en_pin = self.tri_inst[0].get_pin("en") self.add_layout_pin(text="en", - layer="metal1", + layer="m1", offset=en_pin.ll().scale(0, 1), width=width, - height=drc("minwidth_metal1")) + height=drc("minwidth_m1")) enbar_pin = self.tri_inst[0].get_pin("en_bar") self.add_layout_pin(text="en_bar", - layer="metal1", + layer="m1", offset=enbar_pin.ll().scale(0, 1), width=width, - height=drc("minwidth_metal1")) \ No newline at end of file + height=drc("minwidth_m1")) \ No newline at end of file diff --git a/compiler/modules/wordline_driver.py b/compiler/modules/wordline_driver.py deleted file mode 100644 index b1292560..00000000 --- a/compiler/modules/wordline_driver.py +++ /dev/null @@ -1,231 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2019 Regents of the University of California and The Board -# of Regents for the Oklahoma Agricultural and Mechanical College -# (acting for and on behalf of Oklahoma State University) -# All rights reserved. -# -from tech import drc, parameter -import debug -import design -import contact -from math import log -from math import sqrt -import math -from vector import vector -from sram_factory import factory -from globals import OPTS - -class wordline_driver(design.design): - """ - Creates a Wordline Driver - Generates the wordline-driver to drive the bitcell - """ - - def __init__(self, name, rows, cols): - design.design.__init__(self, name) - debug.info(1, "Creating {0}".format(self.name)) - self.add_comment("rows: {0} cols: {1}".format(rows, cols)) - - self.rows = rows - self.cols = cols - - self.create_netlist() - if not OPTS.netlist_only: - self.create_layout() - - def create_netlist(self): - self.add_modules() - self.add_pins() - self.create_drivers() - - def create_layout(self): - self.place_drivers() - self.route_layout() - self.route_vdd_gnd() - self.offset_all_coordinates() - self.add_boundary() - self.DRC_LVS() - - def add_pins(self): - # inputs to wordline_driver. - for i in range(self.rows): - self.add_pin("in_{0}".format(i), "INPUT") - # Outputs from wordline_driver. - for i in range(self.rows): - self.add_pin("wl_{0}".format(i), "OUTPUT") - self.add_pin("en", "INPUT") - self.add_pin("vdd", "POWER") - self.add_pin("gnd", "GROUND") - - - def add_modules(self): - # This is just used for measurements, - # so don't add the module - - self.inv = factory.create(module_type="pdriver", - fanout=self.cols, - neg_polarity=True) - self.add_mod(self.inv) - - self.inv_no_output = factory.create(module_type="pinv", - route_output=False) - self.add_mod(self.inv_no_output) - - self.nand2 = factory.create(module_type="pnand2") - self.add_mod(self.nand2) - - - 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 - a_xoffset = self.nand_inst[0].rx() - b_xoffset = self.inv2_inst[0].lx() - for num in range(self.rows): - # this will result in duplicate polygons for rails, but who cares - - # use the inverter offset even though it will be the nand's too - (gate_offset, y_dir) = self.get_gate_offset(0, self.inv.height, num) - - # Route both supplies - for name in ["vdd", "gnd"]: - supply_pin = self.inv2_inst[num].get_pin(name) - - # Add pins in two locations - for xoffset in [a_xoffset, b_xoffset]: - pin_pos = vector(xoffset, supply_pin.cy()) - self.add_power_pin(name, pin_pos) - - - - def create_drivers(self): - self.nand_inst = [] - self.inv2_inst = [] - for row in range(self.rows): - name_nand = "wl_driver_nand{}".format(row) - name_inv2 = "wl_driver_inv{}".format(row) - - # add nand 2 - self.nand_inst.append(self.add_inst(name=name_nand, - mod=self.nand2)) - self.connect_inst(["en", - "in_{0}".format(row), - "wl_bar_{0}".format(row), - "vdd", "gnd"]) - # add inv2 - self.inv2_inst.append(self.add_inst(name=name_inv2, - mod=self.inv)) - self.connect_inst(["wl_bar_{0}".format(row), - "wl_{0}".format(row), - "vdd", "gnd"]) - - - def place_drivers(self): - nand2_xoffset = 2*self.m1_width + 5*self.m1_space - inv2_xoffset = nand2_xoffset + self.nand2.width - - self.width = inv2_xoffset + self.inv.width - self.height = self.inv.height * self.rows - - for row in range(self.rows): - if (row % 2): - y_offset = self.inv.height*(row + 1) - inst_mirror = "MX" - else: - y_offset = self.inv.height*row - inst_mirror = "R0" - - nand2_offset=[nand2_xoffset, y_offset] - inv2_offset=[inv2_xoffset, y_offset] - - # add nand 2 - self.nand_inst[row].place(offset=nand2_offset, - mirror=inst_mirror) - # add inv2 - self.inv2_inst[row].place(offset=inv2_offset, - mirror=inst_mirror) - - - def route_layout(self): - """ Route all of the signals """ - - # Wordline enable connection - en_pin=self.add_layout_pin(text="en", - layer="metal2", - offset=[self.m1_width + 2*self.m1_space,0], - width=self.m2_width, - height=self.height) - - - for row in range(self.rows): - nand_inst = self.nand_inst[row] - inv2_inst = self.inv2_inst[row] - - # en connection - a_pin = nand_inst.get_pin("A") - a_pos = a_pin.lc() - clk_offset = vector(en_pin.bc().x,a_pos.y) - self.add_segment_center(layer="metal1", - start=clk_offset, - end=a_pos) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=clk_offset) - - # Nand2 out to 2nd inv - 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) - self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos]) - - # connect the decoder input pin to nand2 B - b_pin = nand_inst.get_pin("B") - b_pos = b_pin.lc() - # needs to move down since B nand input is nearly aligned with A inv input - up_or_down = self.m2_space if row%2 else -self.m2_space - input_offset = vector(0,b_pos.y + up_or_down) - mid_via_offset = vector(clk_offset.x,input_offset.y) + vector(0.5*self.m2_width+self.m2_space+0.5*contact.m1m2.width,0) - # must under the clk line in M1 - self.add_layout_pin_segment_center(text="in_{0}".format(row), - layer="metal1", - start=input_offset, - end=mid_via_offset) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=mid_via_offset, - directions=("V","V")) - - # now connect to the nand2 B - self.add_path("metal2", [mid_via_offset, b_pos]) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=b_pos - vector(0.5*contact.m1m2.height,0), - directions=("H","H")) - - - # output each WL on the right - wl_offset = inv2_inst.get_pin("Z").rc() - self.add_layout_pin_segment_center(text="wl_{0}".format(row), - layer="metal1", - start=wl_offset, - 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""" - stage_effort_list = [] - - stage1_cout = self.inv.get_cin() - stage1 = self.nand2.get_stage_effort(stage1_cout, inp_is_rise) - stage_effort_list.append(stage1) - last_stage_is_rise = stage1.is_rise - - stage2 = self.inv.get_stage_efforts(external_cout, last_stage_is_rise) - stage_effort_list.extend(stage2) - - return stage_effort_list - - def get_wl_en_cin(self): - """Get the relative capacitance of all the enable connections in the bank""" - #The enable is connected to a nand2 for every row. - total_cin = self.nand2.get_cin() * self.rows - return total_cin diff --git a/compiler/modules/wordline_driver_array.py b/compiler/modules/wordline_driver_array.py new file mode 100644 index 00000000..e8a3c110 --- /dev/null +++ b/compiler/modules/wordline_driver_array.py @@ -0,0 +1,181 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +import design +from tech import drc, layer +from vector import vector +from sram_factory import factory +from globals import OPTS + +class wordline_driver_array(design.design): + """ + Creates a Wordline Driver + Generates the wordline-driver to drive the bitcell + """ + + def __init__(self, name, rows, cols): + design.design.__init__(self, name) + debug.info(1, "Creating {0}".format(self.name)) + self.add_comment("rows: {0} cols: {1}".format(rows, cols)) + + self.rows = rows + self.cols = cols + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.add_modules() + self.add_pins() + self.create_drivers() + + def create_layout(self): + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + self.place_drivers() + self.route_layout() + self.route_vdd_gnd() + self.offset_all_coordinates() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + # inputs to wordline_driver. + for i in range(self.rows): + self.add_pin("in_{0}".format(i), "INPUT") + # Outputs from wordline_driver. + for i in range(self.rows): + self.add_pin("wl_{0}".format(i), "OUTPUT") + self.add_pin("en", "INPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def add_modules(self): + self.wl_driver = factory.create(module_type="wordline_driver", + size=self.cols) + self.add_mod(self.wl_driver) + + def route_vdd_gnd(self): + """ + Add a pin for each row of vdd/gnd which + are must-connects next level up. + """ + if OPTS.tech_name == "sky130": + for name in ["vdd", "gnd"]: + supply_pins = self.wld_inst[0].get_pins(name) + for pin in supply_pins: + self.add_layout_pin_segment_center(text=name, + layer=pin.layer, + start=pin.bc(), + end=vector(pin.cx(), self.height)) + else: + # Find the x offsets for where the vias/pins should be placed + xoffset_list = [self.wld_inst[0].rx()] + for num in range(self.rows): + # this will result in duplicate polygons for rails, but who cares + + # use the inverter offset even though it will be the and's too + (gate_offset, y_dir) = self.get_gate_offset(0, + self.wl_driver.height, + num) + # Route both supplies + for name in ["vdd", "gnd"]: + supply_pin = self.wld_inst[num].get_pin(name) + + # Add pins in two locations + for xoffset in xoffset_list: + pin_pos = vector(xoffset, supply_pin.cy()) + self.add_power_pin(name, pin_pos) + + def create_drivers(self): + self.wld_inst = [] + for row in range(self.rows): + name_and = "wl_driver_and{}".format(row) + + # add and2 + self.wld_inst.append(self.add_inst(name=name_and, + mod=self.wl_driver)) + self.connect_inst(["in_{0}".format(row), + "en", + "wl_{0}".format(row), + "vdd", "gnd"]) + + def place_drivers(self): + + for row in range(self.rows): + if (row % 2): + y_offset = self.wl_driver.height * (row + 1) + inst_mirror = "MX" + else: + y_offset = self.wl_driver.height * row + inst_mirror = "R0" + + and2_offset = [self.wl_driver.width, y_offset] + + # add and2 + self.wld_inst[row].place(offset=and2_offset, + mirror=inst_mirror) + + # Leave a well gap to separate the bitcell array well from this well + well_gap = 2 * drc("pwell_to_nwell") + drc("nwell_enclose_active") + self.width = self.wl_driver.width + well_gap + self.height = self.wl_driver.height * self.rows + + def route_layout(self): + """ Route all of the signals """ + + # Wordline enable connection + en_pin = self.wld_inst[0].get_pin("B") + en_bottom_pos = vector(en_pin.lx(), 0) + en_pin = self.add_layout_pin(text="en", + layer="m2", + offset=en_bottom_pos, + height=self.height) + + for row in range(self.rows): + and_inst = self.wld_inst[row] + + # Drop a via + b_pin = and_inst.get_pin("B") + self.add_via_stack_center(from_layer=b_pin.layer, + to_layer="m2", + offset=b_pin.center()) + + # connect the decoder input pin to and2 A + self.copy_layout_pin(and_inst, "A", "in_{0}".format(row)) + + # output each WL on the right + wl_offset = and_inst.get_pin("Z").rc() + self.add_layout_pin_segment_center(text="wl_{0}".format(row), + layer=self.route_layer, + start=wl_offset, + 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. + """ + stage_effort_list = [] + + stage1 = self.wl_driver.get_stage_effort(external_cout, inp_is_rise) + stage_effort_list.append(stage1) + + return stage_effort_list + + def get_wl_en_cin(self): + """ + Get the relative capacitance of all + the enable connections in the bank + """ + # The enable is connected to a and2 for every row. + total_cin = self.wl_driver.get_cin() * self.rows + return total_cin diff --git a/compiler/modules/write_driver_array.py b/compiler/modules/write_driver_array.py index 100ee3a2..a6eb1384 100644 --- a/compiler/modules/write_driver_array.py +++ b/compiler/modules/write_driver_array.py @@ -5,68 +5,96 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from math import log import design -from tech import drc import debug +from tech import drc from sram_factory import factory from vector import vector from globals import OPTS + class write_driver_array(design.design): """ Array of tristate drivers to write to the bitlines through the column mux. Dynamically generated write driver array of all bitlines. """ - def __init__(self, name, columns, word_size,write_size=None): + def __init__(self, name, columns, word_size, num_spare_cols=None, write_size=None, column_offset=0): + design.design.__init__(self, name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("columns: {0}".format(columns)) - self.add_comment("word_size {0}".format(word_size)) + self.add_comment("word_size {0}".format(word_size)) self.columns = columns self.word_size = word_size self.write_size = write_size + self.column_offset = column_offset self.words_per_row = int(columns / word_size) + if not num_spare_cols: + self.num_spare_cols = 0 + else: + self.num_spare_cols = num_spare_cols if self.write_size: - self.num_wmasks = int(self.word_size/self.write_size) + self.num_wmasks = int(self.word_size / self.write_size) self.create_netlist() if not OPTS.netlist_only: self.create_layout() + def get_bl_name(self): + bl_name = "bl" + return bl_name + + 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() self.add_pins() self.create_write_array() - + def create_layout(self): - + if self.bitcell.width > self.driver.width: - self.width = self.columns * self.bitcell.width + self.width = (self.columns + self.num_spare_cols) * self.bitcell.width + self.width_regular_cols = self.columns * self.bitcell.width + self.single_col_width = self.bitcell.width else: - self.width = self.columns * self.driver.width + self.width = (self.columns + self.num_spare_cols) * self.driver.width + self.width_regular_cols = self.columns * self.driver.width + self.single_col_width = self.driver.width self.height = self.driver.height - + self.place_write_array() self.add_layout_pins() self.add_boundary() self.DRC_LVS() 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") + for i in range(self.word_size + self.num_spare_cols): + self.add_pin(self.data_name + "_{0}".format(i), "INPUT") + for i in range(self.word_size + self.num_spare_cols): + self.add_pin(self.get_bl_name() + "_{0}".format(i), "OUTPUT") + self.add_pin(self.get_br_name() + "_{0}".format(i), "OUTPUT") if self.write_size: - for i in range(self.num_wmasks): - self.add_pin("en_{0}".format(i), "INPUT") + for i in range(self.num_wmasks + self.num_spare_cols): + self.add_pin(self.en_name + "_{0}".format(i), "INPUT") + elif self.num_spare_cols and not self.write_size: + for i in range(self.num_spare_cols + 1): + self.add_pin(self.en_name + "_{0}".format(i), "INPUT") else: - self.add_pin("en", "INPUT") + self.add_pin(self.en_name, "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") @@ -82,58 +110,102 @@ class write_driver_array(design.design): self.driver_insts = {} w = 0 windex=0 - for i in range(0,self.columns,self.words_per_row): + for i in range(0, self.columns, self.words_per_row): name = "write_driver{}".format(i) - index = int(i/self.words_per_row) + index = int(i / self.words_per_row) self.driver_insts[index]=self.add_inst(name=name, 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 + + elif self.num_spare_cols and not self.write_size: + self.connect_inst([self.data_name + "_{0}".format(index), + self.get_bl_name() + "_{0}".format(index), + self.get_br_name() + "_{0}".format(index), + self.en_name + "_{0}".format(0), "vdd", "gnd"]) + else: - self.connect_inst(["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"]) + for i in range(self.num_spare_cols): + index = self.word_size + i + if self.write_size: + offset = self.num_wmasks + else: + offset = 1 + name = "write_driver{}".format(self.columns + i) + self.driver_insts[index]=self.add_inst(name=name, + mod=self.driver) + + self.connect_inst([self.data_name + "_{0}".format(index), + self.get_bl_name() + "_{0}".format(index), + self.get_br_name() + "_{0}".format(index), + self.en_name + "_{0}".format(i + offset), "vdd", "gnd"]) def place_write_array(self): + from tech import cell_properties if self.bitcell.width > self.driver.width: self.driver_spacing = self.bitcell.width else: self.driver_spacing = self.driver.width - for i in range(0,self.columns,self.words_per_row): - index = int(i/self.words_per_row) - base = vector(i * self.driver_spacing, 0) - self.driver_insts[index].place(base) + for i in range(0, self.columns, self.words_per_row): + index = int(i / self.words_per_row) + xoffset = i * self.driver_spacing + + if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: + mirror = "MY" + xoffset = xoffset + self.driver.width + else: + mirror = "" + + base = vector(xoffset, 0) + self.driver_insts[index].place(offset=base, mirror=mirror) + + # place spare write drivers (if spare columns are specified) + for i in range(self.num_spare_cols): + index = self.word_size + i + xoffset = (self.columns + i) * self.driver_spacing + + if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2: + mirror = "MY" + xoffset = xoffset + self.driver.width + else: + mirror = "" + + base = vector(xoffset, 0) + self.driver_insts[index].place(offset=base, mirror=mirror) - def add_layout_pins(self): - for i in range(self.word_size): - din_pin = self.driver_insts[i].get_pin("din") - self.add_layout_pin(text="data_{0}".format(i), - layer="metal2", + for i in range(self.word_size + self.num_spare_cols): + inst = self.driver_insts[i] + din_pin = inst.get_pin(inst.mod.din_name) + self.add_layout_pin(text=self.data_name + "_{0}".format(i), + layer=din_pin.layer, offset=din_pin.ll(), width=din_pin.width(), height=din_pin.height()) - bl_pin = self.driver_insts[i].get_pin("bl") - self.add_layout_pin(text="bl_{0}".format(i), - layer="metal2", + bl_pin = inst.get_pin(inst.mod.get_bl_names()) + self.add_layout_pin(text=self.get_bl_name() + "_{0}".format(i), + layer=bl_pin.layer, offset=bl_pin.ll(), width=bl_pin.width(), height=bl_pin.height()) - - br_pin = self.driver_insts[i].get_pin("br") - self.add_layout_pin(text="br_{0}".format(i), - layer="metal2", + + br_pin = inst.get_pin(inst.mod.get_br_names()) + self.add_layout_pin(text=self.get_br_name() + "_{0}".format(i), + layer=br_pin.layer, offset=br_pin.ll(), width=br_pin.width(), height=br_pin.height()) @@ -141,38 +213,59 @@ class write_driver_array(design.design): for n in ["vdd", "gnd"]: pin_list = self.driver_insts[i].get_pins(n) for pin in pin_list: - pin_pos = pin.center() - # Add the M2->M3 stack - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=pin_pos) - self.add_layout_pin_rect_center(text=n, - layer="metal3", - offset=pin_pos) + self.add_power_pin(name=n, + loc=pin.center(), + directions=("V", "V"), + start_layer=pin.layer) 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) + wmask_en_len = self.words_per_row * (self.write_size * self.driver_spacing) if (self.words_per_row == 1): en_gap = self.driver_spacing - en_pin.width() 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, + width=wmask_en_len - en_gap, height=en_pin.height()) + + for i in range(self.num_spare_cols): + inst = self.driver_insts[self.word_size + i] + en_pin = inst.get_pin(inst.mod.en_name) + self.add_layout_pin(text=self.en_name + "_{0}".format(i + self.num_wmasks), + layer="m1", + offset=en_pin.lr() + vector(-drc("minwidth_m1"),0)) + + elif self.num_spare_cols and not self.write_size: + # shorten enable rail to accomodate those for spare write drivers + inst = self.driver_insts[0] + en_pin = inst.get_pin(inst.mod.en_name) + self.add_layout_pin(text=self.en_name + "_{0}".format(0), + layer="m1", + offset=en_pin.ll(), + width=self.width_regular_cols - self.words_per_row * en_pin.width()) + + # individual enables for every spare write driver + for i in range(self.num_spare_cols): + inst = self.driver_insts[self.word_size + i] + en_pin = inst.get_pin(inst.mod.en_name) + self.add_layout_pin(text=self.en_name + "_{0}".format(i + 1), + layer="m1", + offset=en_pin.lr() + vector(-drc("minwidth_m1"),0)) + else: - self.add_layout_pin(text="en", - layer="metal1", - offset=self.driver_insts[0].get_pin("en").ll().scale(0,1), + inst = self.driver_insts[0] + self.add_layout_pin(text=self.en_name, + layer="m1", + offset=inst.get_pin(inst.mod.en_name).ll().scale(0, 1), width=self.width) - - - def get_w_en_cin(self): """Get the relative capacitance of all the enable connections in the bank""" - #The enable is connected to a nand2 for every row. + # The enable is connected to a nand2 for every row. return self.driver.get_w_en_cin() * len(self.driver_insts) diff --git a/compiler/modules/write_mask_and_array.py b/compiler/modules/write_mask_and_array.py index 258bbd8d..d48aefef 100644 --- a/compiler/modules/write_mask_and_array.py +++ b/compiler/modules/write_mask_and_array.py @@ -5,14 +5,12 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from math import log import design -from tech import drc import debug from sram_factory import factory from vector import vector from globals import OPTS - +from tech import layer class write_mask_and_array(design.design): """ @@ -20,7 +18,7 @@ class write_mask_and_array(design.design): The write mask AND array goes between the write driver array and the sense amp array. """ - def __init__(self, name, columns, word_size, write_size, port=0): + def __init__(self, name, columns, word_size, write_size, column_offset=0): design.design.__init__(self, name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("columns: {0}".format(columns)) @@ -30,7 +28,7 @@ class write_mask_and_array(design.design): self.columns = columns self.word_size = word_size self.write_size = write_size - self.port = port + self.column_offset = column_offset self.words_per_row = int(columns / word_size) self.num_wmasks = int(word_size / write_size) @@ -43,33 +41,28 @@ class write_mask_and_array(design.design): self.add_pins() self.create_and2_array() - def create_layout(self): self.place_and2_array() - spacing = self.wmask_en_len - self.and2.width - self.width = (self.num_wmasks*self.and2.width) + ((self.num_wmasks-1)*spacing) - self.height = self.and2.height self.add_layout_pins() self.add_boundary() self.DRC_LVS() def add_pins(self): for bit in range(self.num_wmasks): - self.add_pin("wmask_in_{}".format(bit),"INPUT") + self.add_pin("wmask_in_{}".format(bit), "INPUT") self.add_pin("en", "INPUT") for bit in range(self.num_wmasks): - self.add_pin("wmask_out_{}".format(bit),"OUTPUT") - self.add_pin("vdd","POWER") - self.add_pin("gnd","GROUND") + self.add_pin("wmask_out_{}".format(bit), "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def add_modules(self): # Size the AND gate for the number of write drivers it drives, which is equal to the write size. # Assume stage effort of 3 to compute the size self.and2 = factory.create(module_type="pand2", - size=self.write_size/4.0) + size=max(self.write_size / 4.0, 1)) self.add_mod(self.and2) - def create_and2_array(self): self.and2_insts = {} for bit in range(self.num_wmasks): @@ -81,7 +74,6 @@ class write_mask_and_array(design.design): "wmask_out_{}".format(bit), "vdd", "gnd"]) - def place_and2_array(self): # Place the write mask AND array at the start of each write driver enable length. # This ensures the write mask AND array will be directly under the corresponding write driver enable wire. @@ -96,61 +88,60 @@ class write_mask_and_array(design.design): self.wmask_en_len = self.words_per_row * (self.write_size * self.driver_spacing) debug.check(self.wmask_en_len >= self.and2.width, - "Write mask AND is wider than the corresponding write drivers {0} vs {1}.".format(self.and2.width,self.wmask_en_len)) + "Write mask AND is wider than the corresponding write drivers {0} vs {1}.".format(self.and2.width, + self.wmask_en_len)) + + self.width = self.bitcell.width * self.columns + self.height = self.and2.height for i in range(self.num_wmasks): base = vector(i * self.wmask_en_len, 0) self.and2_insts[i].place(base) - def add_layout_pins(self): - self.nand2 = factory.create(module_type="pnand2") - supply_pin=self.nand2.get_pin("vdd") # Create the enable pin that connects all write mask AND array's B pins - beg_en_pin = self.and2_insts[0].get_pin("B") - end_en_pin = self.and2_insts[self.num_wmasks-1].get_pin("B") - if self.port % 2: - # Extend metal3 to edge of AND array in multiport - en_to_edge = self.and2.width - beg_en_pin.cx() - self.add_layout_pin(text="en", - layer="metal3", - offset=beg_en_pin.bc(), - width=end_en_pin.cx() - beg_en_pin.cx() + en_to_edge) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=vector(end_en_pin.cx() + en_to_edge, end_en_pin.cy())) - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=vector(end_en_pin.cx() + en_to_edge, end_en_pin.cy())) - else: - self.add_layout_pin(text="en", - layer="metal3", - offset=beg_en_pin.bc(), - width=end_en_pin.cx() - beg_en_pin.cx()) + en_pin = self.and2_insts[0].get_pin("B") + self.add_layout_pin_segment_center(text="en", + layer="m3", + start=vector(0, en_pin.cy()), + end=vector(self.width, en_pin.cy())) for i in range(self.num_wmasks): + # Route the A pin over to the left so that it doesn't conflict with the sense + # amp output which is usually in the center + a_pin = self.and2_insts[i].get_pin("A") + a_pos = a_pin.center() + in_pos = vector(self.and2_insts[i].lx(), + a_pos.y) + self.add_via_stack_center(from_layer=a_pin.layer, + to_layer="m2", + offset=in_pos) + self.add_layout_pin_rect_center(text="wmask_in_{0}".format(i), + layer="m2", + offset=in_pos) + self.add_path(a_pin.layer, [in_pos, a_pos]) + # Copy remaining layout pins - self.copy_layout_pin(self.and2_insts[i],"A","wmask_in_{0}".format(i)) - self.copy_layout_pin(self.and2_insts[i],"Z","wmask_out_{0}".format(i)) + self.copy_layout_pin(self.and2_insts[i], "Z", "wmask_out_{0}".format(i)) # Add via connections to metal3 for AND array's B pin en_pin = self.and2_insts[i].get_pin("B") - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=en_pin.center()) - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=en_pin.center()) + en_pos = en_pin.center() + self.add_via_stack_center(from_layer=en_pin.layer, + to_layer="m3", + offset=en_pos) - self.add_power_pin("gnd", vector(supply_pin.width() + i * self.wmask_en_len, 0)) - self.add_power_pin("vdd", vector(supply_pin.width() + i * self.wmask_en_len, self.height)) - # Route power and ground rails together - if i < self.num_wmasks-1: - for n in ["gnd","vdd"]: - pin = self.and2_insts[i].get_pin(n) - next_pin = self.and2_insts[i+1].get_pin(n) - self.add_path("metal1",[pin.center(),next_pin.center()]) + for supply in ["gnd", "vdd"]: + supply_pin=self.and2_insts[i].get_pin(supply) + self.add_power_pin(supply, supply_pin.center(), start_layer=supply_pin.layer) + + for supply in ["gnd", "vdd"]: + supply_pin_left = self.and2_insts[0].get_pin(supply) + supply_pin_right = self.and2_insts[self.num_wmasks - 1].get_pin(supply) + self.add_path(supply_pin_left.layer, [supply_pin_left.lc(), supply_pin_right.rc()]) def get_cin(self): """Get the relative capacitance of all the input connections in the bank""" # The enable is connected to an and2 for every row. return self.and2.get_cin() * len(self.and2_insts) - - diff --git a/compiler/openram.py b/compiler/openram.py index 5fec7102..f046fdb1 100755 --- a/compiler/openram.py +++ b/compiler/openram.py @@ -31,8 +31,12 @@ if len(args) != 1: # These depend on arguments, so don't load them until now. import debug +# Parse config file and set up all the options g.init_openram(config_file=args[0], is_unit_test=False) +# Ensure that the right bitcell exists or use the parameterised one +g.setup_bitcell() + # Only print banner here so it's not in unit tests g.print_banner() @@ -49,10 +53,14 @@ from sram_config import sram_config # Configure the SRAM organization c = sram_config(word_size=OPTS.word_size, num_words=OPTS.num_words, - write_size=OPTS.write_size) + write_size=OPTS.write_size, + num_banks=OPTS.num_banks, + words_per_row=OPTS.words_per_row, + num_spare_rows=OPTS.num_spare_rows, + num_spare_cols=OPTS.num_spare_cols) debug.print_raw("Words per row: {}".format(c.words_per_row)) -output_extensions = ["sp", "v", "lib", "py", "html", "log"] +output_extensions = ["lvs", "sp", "v", "lib", "py", "html", "log"] # Only output lef/gds if back-end if not OPTS.netlist_only: output_extensions.extend(["lef", "gds"]) diff --git a/compiler/options.py b/compiler/options.py index 56bd1757..d97ea300 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -21,10 +21,10 @@ class options(optparse.Values): ################### # This is the technology directory. openram_tech = "" - + # This is the name of the technology. tech_name = "" - + # Port configuration (1-2 ports allowed) num_rw_ports = 1 num_r_ports = 0 @@ -32,8 +32,9 @@ class options(optparse.Values): # Write mask size, default will be overwritten with word_size if not user specified write_size = None - + # These will get initialized by the user or the tech file + nominal_corner_only = False supply_voltages = "" temperatures = "" process_corners = "" @@ -43,23 +44,26 @@ class options(optparse.Values): # word_size = 0 # You can manually specify banks, but it is better to auto-detect it. num_banks = 1 + words_per_row = None + num_spare_rows = 0 + num_spare_cols = 0 ################### # Optimization options ################### # Approximate percentage of delay compared to bitlines rbl_delay_percentage = 0.5 - + # Allow manual adjustment of the delay chain over automatic use_tech_delay_chain_size = False delay_chain_stages = 9 delay_chain_fanout_per_stage = 4 - - + + accuracy_requirement = 0.75 ################### # Debug options. - ################### + ################### # This is the temp directory where all intermediate results are stored. try: # If user defined the temporary location in their environment, use it @@ -90,7 +94,7 @@ class options(optparse.Values): # Run with extracted parasitics use_pex = False - + ################### # Tool options ################### @@ -107,11 +111,12 @@ class options(optparse.Values): drc_exe = None lvs_exe = None pex_exe = None - + # For sky130, we need magic for filtering. + magic_exe = None + # Should we print out the banner at startup print_banner = True - # Use detailed LEF blockages - detailed_blockages = True + # Define the output file paths output_path = "." # Define the output file base name @@ -121,9 +126,14 @@ class options(optparse.Values): analytical_delay = True # Purge the temp directory after a successful # run (doesn't purge on errors, anyhow) + + # Route the input/output pins to the perimeter + perimeter_pins = False + purge_temp = True # These are the default modules that can be over-riden + bitcell_suffix = "" bank_select = "bank_select" bitcell_array = "bitcell_array" bitcell = "bitcell" @@ -133,10 +143,12 @@ class options(optparse.Values): delay_chain = "delay_chain" dff_array = "dff_array" dff = "dff" - dummy_bitcell = "dummy_bitcell" + inv_dec = "pinv" + nand2_dec = "pnand2" + nand3_dec = "pnand3" + nand4_dec = "pnand4" # Not available right now precharge_array = "precharge_array" ptx = "ptx" - replica_bitcell = "replica_bitcell" replica_bitline = "replica_bitline" sense_amp_array = "sense_amp_array" sense_amp = "sense_amp" @@ -146,4 +158,3 @@ class options(optparse.Values): write_driver_array = "write_driver_array" write_driver = "write_driver" write_mask_and_array = "write_mask_and_array" - diff --git a/compiler/pgates/pand2.py b/compiler/pgates/pand2.py index d410a8c7..435ace1f 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -13,16 +13,16 @@ from sram_factory import factory class pand2(pgate.pgate): """ - This is a simple buffer used for driving loads. + This is an AND (or NAND) with configurable drive strength. """ - def __init__(self, name, size=1, height=None): - debug.info(1, "Creating pnand2 {}".format(name)) + def __init__(self, name, size=1, height=None, vertical=False, add_wells=True): + debug.info(1, "Creating pand2 {}".format(name)) self.add_comment("size: {}".format(size)) - + + self.vertical = vertical self.size = size - - # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): self.add_pins() @@ -30,21 +30,30 @@ class pand2(pgate.pgate): self.create_insts() def create_modules(self): - # Shield the cap, but have at least a stage effort of 4 - self.nand = factory.create(module_type="pnand2", height=self.height) - self.add_mod(self.nand) + self.nand = factory.create(module_type="pnand2", + height=self.height, + add_wells=self.vertical) self.inv = factory.create(module_type="pdriver", - neg_polarity=True, - fanout=3*self.size, - height=self.height) + size_list=[self.size], + height=self.height, + add_wells=self.add_wells) + + self.add_mod(self.nand) self.add_mod(self.inv) def create_layout(self): - self.width = self.nand.width + self.inv.width + if self.vertical: + self.height = 2 * self.nand.height + self.width = max(self.nand.width, self.inv.width) + else: + self.width = self.nand.width + self.inv.width + self.place_insts() self.add_wires() self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() self.DRC_LVS() def add_pins(self): @@ -67,35 +76,62 @@ class pand2(pgate.pgate): # Add NAND to the right self.nand_inst.place(offset=vector(0, 0)) - # Add INV to the right - self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + if self.vertical: + # Add INV above + self.inv_inst.place(offset=vector(self.inv.width, + 2 * self.nand.height), + mirror="XY") + else: + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + + # Second gnd of the inverter gate + if self.vertical: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + if self.vertical: + # Shared between two gates + y_offset = 0.5 * self.height + else: + y_offset = self.height + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, y_offset), + width=self.width) + def add_wires(self): # nand Z to inv A z1_pin = self.nand_inst.get_pin("Z") a2_pin = self.inv_inst.get_pin("A") - mid1_point = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy()) - mid2_point = vector(mid1_point, a2_pin.cy()) - self.add_path("metal1", - [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) + if self.vertical: + route_layer = "m2" + self.add_via_stack_center(offset=z1_pin.center(), + from_layer=z1_pin.layer, + to_layer=route_layer) + self.add_zjog(route_layer, + z1_pin.uc(), + a2_pin.bc(), + "V") + self.add_via_stack_center(offset=a2_pin.center(), + from_layer=a2_pin.layer, + to_layer=route_layer) + else: + route_layer = self.route_layer + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) def add_layout_pins(self): - # Continous vdd rail along with label. - vdd_pin = self.inv_inst.get_pin("vdd") - self.add_layout_pin(text="vdd", - layer="metal1", - offset=vdd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - - # Continous gnd rail along with label. - gnd_pin = self.inv_inst.get_pin("gnd") - self.add_layout_pin(text="gnd", - layer="metal1", - offset=gnd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - pin = self.inv_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Z", layer=pin.layer, diff --git a/compiler/pgates/pand3.py b/compiler/pgates/pand3.py index b02e18e4..92429921 100644 --- a/compiler/pgates/pand3.py +++ b/compiler/pgates/pand3.py @@ -15,14 +15,15 @@ class pand3(pgate.pgate): """ This is a simple buffer used for driving loads. """ - def __init__(self, name, size=1, height=None): + def __init__(self, name, size=1, height=None, vertical=False, add_wells=True): debug.info(1, "Creating pand3 {}".format(name)) self.add_comment("size: {}".format(size)) - + + self.vertical = vertical self.size = size # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): self.add_pins() @@ -31,19 +32,32 @@ class pand3(pgate.pgate): def create_modules(self): # Shield the cap, but have at least a stage effort of 4 - self.nand = factory.create(module_type="pnand3", height=self.height) - self.add_mod(self.nand) + self.nand = factory.create(module_type="pnand3", + height=self.height, + add_wells=self.vertical) - self.inv = factory.create(module_type="pinv", - size=self.size, - height=self.height) + # Add the well tap to the inverter because when stacked + # vertically it is sometimes narrower + self.inv = factory.create(module_type="pdriver", + size_list=[self.size], + height=self.height, + add_wells=self.add_wells) + + self.add_mod(self.nand) self.add_mod(self.inv) def create_layout(self): - self.width = self.nand.width + self.inv.width + if self.vertical: + self.height = 2 * self.nand.height + self.width = max(self.nand.width, self.inv.width) + else: + self.width = self.nand.width + self.inv.width + self.place_insts() self.add_wires() self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() self.DRC_LVS() def add_pins(self): @@ -67,35 +81,62 @@ class pand3(pgate.pgate): # Add NAND to the right self.nand_inst.place(offset=vector(0, 0)) - # Add INV to the right - self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + if self.vertical: + # Add INV above + self.inv_inst.place(offset=vector(self.inv.width, + 2 * self.nand.height), + mirror="XY") + else: + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + + # Second gnd of the inverter gate + if self.vertical: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + if self.vertical: + # Shared between two gates + y_offset = 0.5 * self.height + else: + y_offset = self.height + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, y_offset), + width=self.width) + def add_wires(self): # nand Z to inv A z1_pin = self.nand_inst.get_pin("Z") a2_pin = self.inv_inst.get_pin("A") - mid1_point = vector(0.5 * (z1_pin.cx()+a2_pin.cx()), z1_pin.cy()) - mid2_point = vector(mid1_point, a2_pin.cy()) - self.add_path("metal1", - [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) - + if self.vertical: + route_layer = "m2" + self.add_via_stack_center(offset=z1_pin.center(), + from_layer=z1_pin.layer, + to_layer=route_layer) + self.add_zjog(route_layer, + z1_pin.uc(), + a2_pin.bc(), + "V") + self.add_via_stack_center(offset=a2_pin.center(), + from_layer=a2_pin.layer, + to_layer=route_layer) + else: + route_layer = self.route_layer + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + def add_layout_pins(self): - # Continous vdd rail along with label. - vdd_pin = self.inv_inst.get_pin("vdd") - self.add_layout_pin(text="vdd", - layer="metal1", - offset=vdd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - - # Continous gnd rail along with label. - gnd_pin = self.inv_inst.get_pin("gnd") - self.add_layout_pin(text="gnd", - layer="metal1", - offset=gnd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - pin = self.inv_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Z", layer=pin.layer, diff --git a/compiler/pgates/pbuf.py b/compiler/pgates/pbuf.py index 0149f75f..8b9c4eab 100644 --- a/compiler/pgates/pbuf.py +++ b/compiler/pgates/pbuf.py @@ -37,6 +37,8 @@ class pbuf(pgate.pgate): self.place_insts() self.add_wires() self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() def add_pins(self): self.add_pin("A", "INPUT") @@ -54,7 +56,8 @@ class pbuf(pgate.pgate): self.inv2 = factory.create(module_type="pinv", size=self.size, - height=self.height) + height=self.height, + add_wells=False) self.add_mod(self.inv2) def create_insts(self): @@ -77,26 +80,9 @@ class pbuf(pgate.pgate): # inv1 Z to inv2 A z1_pin = self.inv1_inst.get_pin("Z") a2_pin = self.inv2_inst.get_pin("A") - mid_point = vector(z1_pin.cx(), a2_pin.cy()) - self.add_path("metal1", [z1_pin.center(), mid_point, a2_pin.center()]) + self.add_zjog(self.route_layer, z1_pin.center(), a2_pin.center()) def add_layout_pins(self): - # Continous vdd rail along with label. - vdd_pin = self.inv1_inst.get_pin("vdd") - self.add_layout_pin(text="vdd", - layer="metal1", - offset=vdd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - - # Continous gnd rail along with label. - gnd_pin = self.inv1_inst.get_pin("gnd") - self.add_layout_pin(text="gnd", - layer="metal1", - offset=gnd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - z_pin = self.inv2_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Z", layer=z_pin.layer, diff --git a/compiler/pgates/pdriver.py b/compiler/pgates/pdriver.py index bb7739b7..578a11c4 100644 --- a/compiler/pgates/pdriver.py +++ b/compiler/pgates/pdriver.py @@ -17,13 +17,13 @@ class pdriver(pgate.pgate): sized for driving a load. """ - def __init__(self, name, neg_polarity=False, fanout=0, size_list=None, height=None): + def __init__(self, name, inverting=False, fanout=0, size_list=None, height=None, add_wells=True): debug.info(1, "creating pdriver {}".format(name)) self.stage_effort = 3 self.height = height - self.neg_polarity = neg_polarity + self.inverting = inverting self.size_list = size_list self.fanout = fanout @@ -31,11 +31,11 @@ class pdriver(pgate.pgate): debug.error("Either fanout or size list must be specified.", -1) if self.size_list and self.fanout != 0: debug.error("Cannot specify both size_list and fanout.", -1) - if self.size_list and self.neg_polarity: - debug.error("Cannot specify both size_list and neg_polarity.", -1) + if self.size_list and self.inverting: + debug.error("Cannot specify both size_list and inverting.", -1) # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def compute_sizes(self): # size_list specified @@ -47,9 +47,9 @@ class pdriver(pgate.pgate): int(round(self.fanout ** (1 / self.stage_effort)))) # Increase the number of stages if we need to fix polarity - if self.neg_polarity and (self.num_stages % 2 == 0): + if self.inverting and (self.num_stages % 2 == 0): self.num_stages += 1 - elif not self.neg_polarity and (self.num_stages % 2): + elif not self.inverting and (self.num_stages % 2): self.num_stages += 1 self.size_list = [] @@ -73,9 +73,11 @@ class pdriver(pgate.pgate): self.place_modules() self.route_wires() self.add_layout_pins() - self.width = self.inv_inst_list[-1].rx() self.height = self.inv_inst_list[0].height + self.extend_wells() + self.route_supply_rails() + self.add_boundary() def add_pins(self): self.add_pin("A", "INPUT") @@ -85,10 +87,13 @@ class pdriver(pgate.pgate): def add_modules(self): self.inv_list = [] + add_well = self.add_wells for size in self.size_list: temp_inv = factory.create(module_type="pinv", size=size, - height=self.height) + height=self.height, + add_wells=add_well) + add_well=False self.inv_list.append(temp_inv) self.add_mod(temp_inv) @@ -140,26 +145,11 @@ class pdriver(pgate.pgate): z_inst_list.append(self.inv_inst_list[x].get_pin("Z")) a_inst_list.append(self.inv_inst_list[x + 1].get_pin("A")) mid_point = vector(z_inst_list[x].cx(), a_inst_list[x].cy()) - self.add_path("metal1", + self.add_path(self.route_layer, [z_inst_list[x].center(), mid_point, a_inst_list[x].center()]) def add_layout_pins(self): - # Continous vdd rail along with label. - vdd_pin = self.inv_inst_list[0].get_pin("vdd") - self.add_layout_pin(text="vdd", - layer="metal1", - offset=vdd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - - # Continous gnd rail along with label. - gnd_pin = self.inv_inst_list[0].get_pin("gnd") - self.add_layout_pin(text="gnd", - layer="metal1", - offset=gnd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) z_pin = self.inv_inst_list[len(self.inv_inst_list) - 1].get_pin("Z") self.add_layout_pin_rect_center(text="Z", diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 9113146b..1e55d5fb 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -8,28 +8,52 @@ import contact import design import debug -from tech import drc +import math +from bisect import bisect_left +from tech import layer, drc from vector import vector from globals import OPTS -from sram_factory import factory +if(OPTS.tech_name == "sky130"): + from tech import nmos_bins, pmos_bins + class pgate(design.design): """ This is a module that implements some shared functions for parameterized gates. """ - def __init__(self, name, height=None): + def __init__(self, name, height=None, add_wells=True): """ Creates a generic cell """ design.design.__init__(self, name) if height: self.height = height elif not height: - b = factory.create(module_type="bitcell") - self.height = b.height + # By default, something simple + self.height = 14 * self.m1_pitch + self.add_wells = add_wells + + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + self.route_layer_width = getattr(self, "{}_width".format(self.route_layer)) + self.route_layer_space = getattr(self, "{}_space".format(self.route_layer)) + self.route_layer_pitch = getattr(self, "{}_pitch".format(self.route_layer)) + # hack for enclosing input pin with npc + self.input_pin_vias = [] + + # This is the space from a S/D contact to the supply rail + contact_to_vdd_rail_space = 0.5 * self.route_layer_width + self.route_layer_space + # This is a poly-to-poly of a flipped cell + poly_to_poly_gate_space = self.poly_extend_active + 0.5 * self.poly_space + + self.top_bottom_space = max(contact_to_vdd_rail_space, + poly_to_poly_gate_space) + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -44,28 +68,28 @@ class pgate(design.design): """ Pure virtual function """ debug.error("Must over-ride create_layout.", -1) - def connect_pin_to_rail(self, inst, pin, supply): + def connect_pin_to_rail(self, inst, pin_name, supply_name): """ Connects a ptx pin to a supply rail. """ - source_pin = inst.get_pin(pin) - supply_pin = self.get_pin(supply) - if supply_pin.overlaps(source_pin): - return - - if supply == "gnd": - height = supply_pin.by() - source_pin.by() - elif supply == "vdd": - height = supply_pin.uy() - source_pin.by() - else: - debug.error("Invalid supply name.", -1) + supply_pin = self.get_pin(supply_name) - if abs(height) > 0: - self.add_rect(layer="metal1", + source_pins = inst.get_pins(pin_name) + for source_pin in source_pins: + + if supply_name == "gnd": + height = supply_pin.by() - source_pin.by() + elif supply_name == "vdd": + height = supply_pin.uy() - source_pin.by() + else: + debug.error("Invalid supply name.", -1) + + debug.check(supply_pin.layer == source_pin.layer, "Supply pin is not on correct layer.") + self.add_rect(layer=source_pin.layer, offset=source_pin.ll(), height=height, width=source_pin.width()) - def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left", rotate=False): - """ + def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left", directions=None): + """ Route the input gate to the left side of the cell for access. Position specifies to place the contact the left, center, or right of gate. @@ -75,11 +99,13 @@ class pgate(design.design): pmos_gate_pin = pmos_inst.get_pin("G") # Check if the gates are aligned and give an error if they aren't! + if nmos_gate_pin.ll().x != pmos_gate_pin.ll().x: + self.gds_write("unaliged_gates.gds") debug.check(nmos_gate_pin.ll().x == pmos_gate_pin.ll().x, - "Connecting unaligned gates not supported.") + "Connecting unaligned gates not supported. See unaligned_gates.gds.") - # Pick point on the left of NMOS and connect down to PMOS - nmos_gate_pos = nmos_gate_pin.ll() + vector(0.5 * self.poly_width, 0) + # Pick point on the left of NMOS and up to PMOS + nmos_gate_pos = nmos_gate_pin.ul() + vector(0.5 * self.poly_width, 0) pmos_gate_pos = vector(nmos_gate_pos.x, pmos_gate_pin.bc().y) self.add_path("poly", [nmos_gate_pos, pmos_gate_pos]) @@ -87,108 +113,115 @@ class pgate(design.design): left_gate_offset = vector(nmos_gate_pin.lx(), ypos) # Center is completely symmetric. - if rotate: - contact_width = contact.poly.height - contact_m1_width = contact.poly.second_layer_height - contact_m1_height = contact.poly.second_layer_width - directions = ("H", "V") - else: - contact_width = contact.poly.width - contact_m1_width = contact.poly.second_layer_width - contact_m1_height = contact.poly.second_layer_height - directions = ("V", "H") + contact_width = contact.poly_contact.width if position == "center": contact_offset = left_gate_offset \ + vector(0.5 * self.poly_width, 0) elif position == "farleft": contact_offset = left_gate_offset \ - - vector(0.5 * contact.poly.width, 0) + - vector(0.5 * contact.poly_contact.width, 0) elif position == "left": contact_offset = left_gate_offset \ - vector(0.5 * contact_width - 0.5 * self.poly_width, 0) 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) - # Non-preferred direction via - - self.add_via_center(layers=("poly", "contact", "metal1"), - offset=contact_offset, - directions=directions) + via = self.add_via_stack_center(from_layer="poly", + to_layer=self.route_layer, + offset=contact_offset, + directions=directions) self.add_layout_pin_rect_center(text=name, - layer="metal1", + layer=self.route_layer, offset=contact_offset, - width=contact_m1_width, - height=contact_m1_height) - + width=via.mod.second_layer_width, + height=via.mod.second_layer_height) # This is to ensure that the contact is # connected to the gate mid_point = contact_offset.scale(0.5, 1) \ + left_gate_offset.scale(0.5, 0) self.add_rect_center(layer="poly", offset=mid_point, - height=contact.poly.first_layer_width, + height=contact.poly_contact.first_layer_width, width=left_gate_offset.x - contact_offset.x) - def extend_wells(self, middle_position): + return via + + def extend_wells(self): """ Extend the n/p wells to cover whole cell """ - # Add a rail width to extend the well to the top of the rail - max_y_offset = self.height + 0.5 * self.m1_width - self.nwell_position = middle_position - nwell_height = max_y_offset - middle_position.y - if drc("has_nwell"): - self.add_rect(layer="nwell", - offset=middle_position, - width=self.well_width, - height=nwell_height) - self.add_rect(layer="vtg", - offset=self.nwell_position, - width=self.well_width, - height=nwell_height) + # This should match the cells in the cell library + self.nwell_yoffset = 0.48 * self.height + full_height = self.height + 0.5 * self.m1_width - pwell_position = vector(0, -0.5 * self.m1_width) - pwell_height = middle_position.y - pwell_position.y - if drc("has_pwell"): + + # FIXME: float rounding problem + if "nwell" in layer: + # Add a rail width to extend the well to the top of the rail + nwell_max_offset = max(self.find_highest_layer_coords("nwell").y, + full_height) + nwell_position = vector(0, self.nwell_yoffset) - vector(self.well_extend_active, 0) + nwell_height = nwell_max_offset - self.nwell_yoffset + self.add_rect(layer="nwell", + offset=nwell_position, + width=self.width + 2 * self.well_extend_active, + height=nwell_height) + if "vtg" in layer: + self.add_rect(layer="vtg", + offset=nwell_position, + width=self.width + 2 * self.well_extend_active, + height=nwell_height) + + # Start this half a rail width below the cell + if "pwell" in layer: + pwell_min_offset = min(self.find_lowest_layer_coords("pwell").y, + -0.5 * self.m1_width) + pwell_position = vector(-self.well_extend_active, pwell_min_offset) + pwell_height = self.nwell_yoffset - pwell_position.y self.add_rect(layer="pwell", offset=pwell_position, - width=self.well_width, + width=self.width + 2 * self.well_extend_active, height=pwell_height) - self.add_rect(layer="vtg", - offset=pwell_position, - width=self.well_width, - height=pwell_height) + if "vtg" in layer: + self.add_rect(layer="vtg", + offset=pwell_position, + width=self.width + 2 * self.well_extend_active, + height=pwell_height) + + if OPTS.tech_name == "sky130": + self.extend_implants() def add_nwell_contact(self, pmos, pmos_pos): """ Add an nwell contact next to the given pmos device. """ - layer_stack = ("active", "contact", "metal1") + layer_stack = self.active_stack # To the right a spacing away from the pmos right active edge contact_xoffset = pmos_pos.x + pmos.active_width \ - + drc("active_to_body_active") + + self.active_space # Must be at least an well enclosure of active down # from the top of the well # OR align the active with the top of PMOS active. max_y_offset = self.height + 0.5 * self.m1_width contact_yoffset = min(pmos_pos.y + pmos.active_height - pmos.active_contact.first_layer_height, - max_y_offset - pmos.active_contact.first_layer_height / 2 - self.well_enclose_active) + max_y_offset - pmos.active_contact.first_layer_height / 2 - self.nwell_enclose_active) contact_offset = vector(contact_xoffset, contact_yoffset) # Offset by half a contact in x and y contact_offset += vector(0.5 * pmos.active_contact.first_layer_width, - 0.5 * pmos.active_contact.first_layer_height) + 0.5 * pmos.active_contact.first_layer_height) self.nwell_contact = self.add_via_center(layers=layer_stack, offset=contact_offset, - directions=("H", "V"), implant_type="n", - well_type="n") - self.add_rect_center(layer="metal1", - offset=contact_offset + vector(0, 0.5 * (self.height-contact_offset.y)), + well_type="n", + directions=("V", "V")) + + self.add_rect_center(layer=self.route_layer, + offset=contact_offset + vector(0, 0.5 * (self.height - contact_offset.y)), width=self.nwell_contact.mod.second_layer_width, height=self.height - contact_offset.y) @@ -216,20 +249,64 @@ class pgate(design.design): # Return the top of the well + def extend_implants(self): + """ + Add top-to-bottom implants for adjacency issues in s8. + """ + if self.add_wells: + rightx = None + else: + rightx = self.width + + nmos_insts = self.get_tx_insts("nmos") + if len(nmos_insts) > 0: + self.add_enclosure(nmos_insts, + layer="nimplant", + extend=self.implant_enclose_active, + leftx=0, + rightx=rightx, + boty=0) + + pmos_insts = self.get_tx_insts("pmos") + if len(pmos_insts) > 0: + self.add_enclosure(pmos_insts, + layer="pimplant", + extend=self.implant_enclose_active, + leftx=0, + rightx=rightx, + topy=self.height) + + try: + ntap_insts = [self.nwell_contact] + self.add_enclosure(ntap_insts, + layer="nimplant", + extend=self.implant_enclose_active, + rightx=self.width, + topy=self.height) + except AttributeError: + pass + try: + ptap_insts = [self.pwell_contact] + self.add_enclosure(ptap_insts, + layer="pimplant", + extend=self.implant_enclose_active, + rightx=self.width, + boty=0) + except AttributeError: + pass + def add_pwell_contact(self, nmos, nmos_pos): """ Add an pwell contact next to the given nmos device. """ - layer_stack = ("active", "contact", "metal1") + layer_stack = self.active_stack - pwell_position = vector(0, -0.5 * self.m1_width) - # To the right a spacing away from the nmos right active edge contact_xoffset = nmos_pos.x + nmos.active_width \ - + drc("active_to_body_active") + + self.active_space # Must be at least an well enclosure of active up # from the bottom of the well contact_yoffset = max(nmos_pos.y, - self.well_enclose_active \ + self.nwell_enclose_active \ - nmos.active_contact.first_layer_height / 2) contact_offset = vector(contact_xoffset, contact_yoffset) @@ -238,14 +315,15 @@ class pgate(design.design): 0.5 * nmos.active_contact.first_layer_height) self.pwell_contact= self.add_via_center(layers=layer_stack, offset=contact_offset, - directions=("H", "V"), implant_type="p", - well_type="p") - self.add_rect_center(layer="metal1", - offset=contact_offset.scale(1,0.5), + well_type="p", + directions=("V", "V")) + + self.add_rect_center(layer=self.route_layer, + offset=contact_offset.scale(1, 0.5), width=self.pwell_contact.mod.second_layer_width, height=contact_offset.y) - + # Now add the full active and implant for the NMOS # active_offset = nmos_pos + vector(nmos.active_width,0) # This might be needed if the spacing between the actives @@ -265,3 +343,107 @@ class pgate(design.design): # offset=implant_offset, # width=implant_width, # height=implant_height) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top and bottom. """ + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + + def determine_width(self): + """ Determine the width based on the well contacts (assumed to be on the right side) """ + + # It was already set or is left as default (minimum) + # Width is determined by well contact and spacing and allowing a supply via between each cell + if self.add_wells: + width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * contact.m1_via.width + # Height is an input parameter, so it is not recomputed. + else: + max_active_xoffset = self.find_highest_layer_coords("active").x + max_route_xoffset = self.find_highest_layer_coords(self.route_layer).x + 0.5 * self.m1_space + width = max(max_active_xoffset, max_route_xoffset) + + self.width = width + + @staticmethod + def best_bin(tx_type, target_width): + """ + Determine the width transistor that meets the accuracy requirement and is larger than target_width. + """ + + # Find all of the relavent scaled bins and multiples + scaled_bins = pgate.scaled_bins(tx_type, target_width) + + for (scaled_width, multiple) in scaled_bins: + if abs(target_width - scaled_width) / target_width <= 1 - OPTS.accuracy_requirement: + break + else: + debug.error("failed to bin tx size {}, try reducing accuracy requirement".format(target_width), 1) + + debug.info(2, "binning {0} tx, target: {4}, found {1} x {2} = {3}".format(tx_type, + multiple, + scaled_width / multiple, + scaled_width, + target_width)) + + return(scaled_width / multiple, multiple) + + @staticmethod + def scaled_bins(tx_type, target_width): + """ + Determine a set of widths and multiples that could be close to the right size + sorted by the fewest number of fingers. + """ + if tx_type == "nmos": + bins = nmos_bins[drc("minwidth_poly")] + elif tx_type == "pmos": + bins = pmos_bins[drc("minwidth_poly")] + else: + debug.error("invalid tx type") + + # Prune out bins that are too big, except for one bigger + bins = bins[0:bisect_left(bins, target_width) + 1] + + # Determine multiple of target width for each bin + if len(bins) == 1: + scaled_bins = [(bins[0], math.ceil(target_width / bins[0]))] + else: + scaled_bins = [] + # Add the biggest size as 1x multiple + scaled_bins.append((bins[-1], 1)) + # Compute discrete multiple of other sizes + for width in reversed(bins[:-1]): + multiple = math.ceil(target_width / width) + scaled_bins.append((multiple * width, multiple)) + + return(scaled_bins) + + @staticmethod + def nearest_bin(tx_type, target_width): + """ + Determine the nearest width to the given target_width + while assuming a single multiple. + """ + if tx_type == "nmos": + bins = nmos_bins[drc("minwidth_poly")] + elif tx_type == "pmos": + bins = pmos_bins[drc("minwidth_poly")] + else: + debug.error("invalid tx type") + + # Find the next larger bin + bin_loc = bisect_left(bins, target_width) + if bin_loc < len(bins): + return bins[bin_loc] + else: + return bins[-1] + + @staticmethod + def bin_accuracy(ideal_width, width): + return 1 - abs((ideal_width - width) / ideal_width) diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index 2b8ec7b7..4caf2a18 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -8,6 +8,7 @@ import contact import pgate import debug +import operator from tech import drc, parameter, spice from vector import vector from math import ceil @@ -15,6 +16,7 @@ from globals import OPTS from utils import round_to_grid import logical_effort from sram_factory import factory +from errors import drc_error class pinv(pgate.pgate): @@ -23,11 +25,13 @@ class pinv(pgate.pgate): size is specified as the drive size (relative to minimum NMOS) and a beta value for choosing the pmos size. The inverter's cell height is usually the same as the 6t library cell and is measured - from center of rail to rail.. The route_output will route the - output to the right side of the cell for easier access. + from center of rail to rail. """ + # binning %error tracker + bin_count = 0 + bin_error = 0 - def __init__(self, name, size=1, beta=parameter["beta"], height=None, route_output=True): + def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True): debug.info(2, "creating pinv structure {0} with size of {1}".format(name, @@ -35,12 +39,12 @@ class pinv(pgate.pgate): self.add_comment("size: {}".format(size)) self.size = size + debug.check(self.size >= 1, "Must have a size greater than or equal to 1.") self.nmos_size = size self.pmos_size = beta * size self.beta = beta - self.route_output = False - - pgate.pgate.__init__(self, name, height) + + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): """ Calls all functions related to the generation of the netlist """ @@ -51,18 +55,20 @@ class pinv(pgate.pgate): def create_layout(self): """ Calls all functions related to the generation of the layout """ - self.setup_layout_constants() - self.route_supply_rails() self.place_ptx() - self.add_well_contacts() - self.extend_wells(self.well_pos) - self.connect_rails() + if self.add_wells: + self.add_well_contacts() + self.determine_width() + self.extend_wells() self.route_input_gate(self.pmos_inst, self.nmos_inst, self.output_pos.y, "A", position="farleft") self.route_outputs() + self.route_supply_rails() + self.connect_rails() + self.add_boundary() def add_pins(self): """ Adds pins for spice netlist """ @@ -81,6 +87,9 @@ class pinv(pgate.pgate): self.tx_mults = 1 self.nmos_width = self.nmos_size * drc("minwidth_tx") self.pmos_width = self.pmos_size * drc("minwidth_tx") + if OPTS.tech_name == "sky130": + (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) + (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) return # Do a quick sanity check and bail if unlikely feasible height @@ -89,25 +98,23 @@ class pinv(pgate.pgate): # Assume we need 3 metal 1 pitches (2 power rails, one # between the tx for the drain) # plus the tx height - nmos = factory.create(module_type="ptx", tx_type="nmos") + nmos = factory.create(module_type="ptx", + tx_type="nmos") pmos = factory.create(module_type="ptx", width=drc("minwidth_tx"), tx_type="pmos") tx_height = nmos.poly_height + pmos.poly_height # rotated m1 pitch or poly to active spacing - min_channel = max(contact.poly.width + self.m1_space, - contact.poly.width + 2 * drc("poly_to_active")) + min_channel = max(contact.poly_contact.width + self.m1_space, + contact.poly_contact.width + 2 * self.poly_to_active) - # This is the extra space needed to ensure DRC rules - # to the active contacts - extra_contact_space = max(-nmos.get_pin("D").by(), 0) - # This is a poly-to-poly of a flipped cell - self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space, - drc("poly_extend_active"), self.poly_space) total_height = tx_height + min_channel + 2 * self.top_bottom_space - debug.check(self.height > total_height, - "Cell height {0} too small for simple min height {1}.".format(self.height, - total_height)) + # debug.check(self.height > total_height, + # "Cell height {0} too small for simple min height {1}.".format(self.height, + # total_height)) + if total_height > self.height: + msg = "Cell height {0} too small for simple min height {1}.".format(self.height, total_height) + raise drc_error(msg) # Determine the height left to the transistors to determine # the number of fingers @@ -115,8 +122,8 @@ class pinv(pgate.pgate): # Divide the height in half. Could divide proportional to beta, # but this makes connecting wells of multiple cells easier. # Subtract the poly space under the rail of the tx - nmos_height_available = 0.5 * tx_height_available - 0.5 * drc("poly_to_poly") - pmos_height_available = 0.5 * tx_height_available - 0.5 * drc("poly_to_poly") + nmos_height_available = 0.5 * tx_height_available - 0.5 * self.poly_space + pmos_height_available = 0.5 * tx_height_available - 0.5 * self.poly_space debug.info(2, "Height avail {0:.4f} PMOS {1:.4f} NMOS {2:.4f}".format(tx_height_available, @@ -125,68 +132,92 @@ class pinv(pgate.pgate): # Determine the number of mults for each to fit width # into available space - self.nmos_width = self.nmos_size * drc("minwidth_tx") - self.pmos_width = self.pmos_size * drc("minwidth_tx") - nmos_required_mults = max(int(ceil(self.nmos_width / nmos_height_available)), 1) - pmos_required_mults = max(int(ceil(self.pmos_width / pmos_height_available)), 1) - # The mults must be the same for easy connection of poly - self.tx_mults = max(nmos_required_mults, pmos_required_mults) + if OPTS.tech_name != "sky130": + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + nmos_required_mults = max(int(ceil(self.nmos_width / nmos_height_available)), 1) + pmos_required_mults = max(int(ceil(self.pmos_width / pmos_height_available)), 1) + # The mults must be the same for easy connection of poly + self.tx_mults = max(nmos_required_mults, pmos_required_mults) - # Recompute each mult width and check it isn't too small - # This could happen if the height is narrow and the size is small - # User should pick a bigger size to fix it... - # We also need to round the width to the grid or we will end up - # with LVS property mismatch errors when fingers are not a grid - # length and get rounded in the offset geometry. - self.nmos_width = round_to_grid(self.nmos_width / self.tx_mults) - debug.check(self.nmos_width >= drc("minwidth_tx"), - "Cannot finger NMOS transistors to fit cell height.") - self.pmos_width = round_to_grid(self.pmos_width / self.tx_mults) - debug.check(self.pmos_width >= drc("minwidth_tx"), - "Cannot finger PMOS transistors to fit cell height.") + # Recompute each mult width and check it isn't too small + # This could happen if the height is narrow and the size is small + # User should pick a bigger size to fix it... + # We also need to round the width to the grid or we will end up + # with LVS property mismatch errors when fingers are not a grid + # length and get rounded in the offset geometry. + self.nmos_width = round_to_grid(self.nmos_width / self.tx_mults) + # debug.check(self.nmos_width >= drc("minwidth_tx"), + # "Cannot finger NMOS transistors to fit cell height.") + if self.nmos_width < drc("minwidth_tx"): + raise drc_error("Cannot finger NMOS transistors to fit cell height.") - def setup_layout_constants(self): - """ - Pre-compute some handy layout parameters. - """ + self.pmos_width = round_to_grid(self.pmos_width / self.tx_mults) + #debug.check(self.pmos_width >= drc("minwidth_tx"), + # "Cannot finger PMOS transistors to fit cell height.") + if self.pmos_width < drc("minwidth_tx"): + raise drc_error("Cannot finger NMOS transistors to fit cell height.") + else: + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + nmos_bins = self.scaled_bins("nmos", self.nmos_width) + pmos_bins = self.scaled_bins("pmos", self.pmos_width) + + valid_pmos = [] + for bin in pmos_bins: + if abs(self.bin_accuracy(self.pmos_width, bin[0])) > OPTS.accuracy_requirement and abs(self.bin_accuracy(self.pmos_width, bin[0])) <= 1: + valid_pmos.append(bin) + valid_pmos.sort(key = operator.itemgetter(1)) + + valid_nmos = [] + for bin in nmos_bins: + if abs(self.bin_accuracy(self.nmos_width, bin[0])) > OPTS.accuracy_requirement and abs(self.bin_accuracy(self.nmos_width, bin[0])) <= 1: + valid_nmos.append(bin) + valid_nmos.sort(key = operator.itemgetter(1)) + + for bin in valid_pmos: + if bin[0]/bin[1] < pmos_height_available: + self.pmos_width = bin[0]/bin[1] + pmos_mults = bin[1] + break + + for bin in valid_nmos: + if bin[0]/bin[1] < nmos_height_available: + self.nmos_width = bin[0]/bin[1] + nmos_mults = bin[1] + break + + self.tx_mults = max(pmos_mults, nmos_mults) + debug.info(2, "prebinning {0} tx, target: {4}, found {1} x {2} = {3}".format("pmos", self.pmos_width, pmos_mults, self.pmos_width * pmos_mults, self.pmos_size * drc("minwidth_tx"))) + debug.info(2, "prebinning {0} tx, target: {4}, found {1} x {2} = {3}".format("nmos", self.nmos_width, nmos_mults, self.nmos_width * nmos_mults, self.nmos_size * drc("minwidth_tx"))) + pinv.bin_count += 1 + pinv.bin_error += abs(((self.pmos_width * pmos_mults) - (self.pmos_size * drc("minwidth_tx")))/(self.pmos_size * drc("minwidth_tx"))) + pinv.bin_count += 1 + pinv.bin_error += abs(((self.nmos_width * nmos_mults) - (self.nmos_size * drc("minwidth_tx")))/(self.nmos_size * drc("minwidth_tx"))) + debug.info(2, "pinv bin count: {0} pinv bin error: {1} percent error {2}".format(pinv.bin_count, pinv.bin_error, pinv.bin_error/pinv.bin_count)) - # the well width is determined the multi-finger PMOS device width plus - # the well contact width and half well enclosure on both sides - self.well_width = self.pmos.active_width + self.pmos.active_contact.width \ - + drc("active_to_body_active") + 2*drc("well_enclosure_active") - self.width = self.well_width - # Height is an input parameter, so it is not recomputed. - def add_ptx(self): """ Create the PMOS and NMOS transistors. """ self.nmos = factory.create(module_type="ptx", width=self.nmos_width, mults=self.tx_mults, tx_type="nmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer, connect_poly=True, - connect_active=True) + connect_drain_active=True) self.add_mod(self.nmos) self.pmos = factory.create(module_type="ptx", width=self.pmos_width, mults=self.tx_mults, tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer, connect_poly=True, - connect_active=True) + connect_drain_active=True) self.add_mod(self.pmos) - def route_supply_rails(self): - """ Add vdd/gnd rails to the top and bottom. """ - self.add_layout_pin_rect_center(text="gnd", - layer="metal1", - offset=vector(0.5 * self.width, 0), - width=self.width) - - self.add_layout_pin_rect_center(text="vdd", - layer="metal1", - offset=vector(0.5 * self.width, self.height), - width=self.width) - def create_ptx(self): """ Create the PMOS and NMOS netlist. @@ -222,15 +253,12 @@ class pinv(pgate.pgate): nmos_drain_pos = self.nmos_inst.get_pin("D").ul() self.output_pos = vector(0, 0.5 * (pmos_drain_pos.y + nmos_drain_pos.y)) - # This will help with the wells - self.well_pos = vector(0, self.nmos_inst.uy()) - def route_outputs(self): """ Route the output (drains) together. Optionally, routes output to edge. """ - + # Get the drain pins nmos_drain_pin = self.nmos_inst.get_pin("D") pmos_drain_pin = self.pmos_inst.get_pin("D") @@ -238,24 +266,16 @@ class pinv(pgate.pgate): # Pick point at right most of NMOS and connect down to PMOS nmos_drain_pos = nmos_drain_pin.bc() pmos_drain_pos = vector(nmos_drain_pos.x, pmos_drain_pin.uc().y) - self.add_path("metal1", [nmos_drain_pos, pmos_drain_pos]) + self.add_path(self.route_layer, [nmos_drain_pos, pmos_drain_pos]) # Remember the mid for the output mid_drain_offset = vector(nmos_drain_pos.x, self.output_pos.y) - if self.route_output: - # This extends the output to the edge of the cell - output_offset = mid_drain_offset.scale(0, 1) + vector(self.width, 0) - self.add_layout_pin_segment_center(text="Z", - layer="metal1", - start=mid_drain_offset, - end=output_offset) - else: - # This leaves the output as an internal pin (min sized) - self.add_layout_pin_rect_center(text="Z", - layer="metal1", - offset=mid_drain_offset \ - + vector(0.5 * self.m1_width, 0)) + # This leaves the output as an internal pin (min sized) + output_offset = mid_drain_offset + vector(0.5 * self.route_layer_width, 0) + self.add_layout_pin_rect_center(text="Z", + layer=self.route_layer, + offset=output_offset) def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ diff --git a/compiler/pgates/pinv_dec.py b/compiler/pgates/pinv_dec.py new file mode 100644 index 00000000..672bde2d --- /dev/null +++ b/compiler/pgates/pinv_dec.py @@ -0,0 +1,236 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import contact +import pinv +import debug +from tech import drc, parameter, layer +from vector import vector +from globals import OPTS +from sram_factory import factory + + +class pinv_dec(pinv.pinv): + """ + This is another version of pinv but with layout for the decoder. + Other stuff is the same (netlist, sizes, etc.) + """ + + def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True): + + debug.info(2, + "creating pinv_dec structure {0} with size of {1}".format(name, + size)) + if not height: + b = factory.create(module_type="bitcell") + self.cell_height = b.height + else: + self.cell_height = height + + # Inputs to cells are on input layer + # Outputs from cells are on output layer + if OPTS.tech_name == "sky130": + self.supply_layer = "m1" + else: + self.supply_layer = "m2" + + pinv.pinv.__init__(self, name, size, beta, self.cell_height, add_wells) + + def determine_tx_mults(self): + """ + Determines the number of fingers needed to achieve the size within + the height constraint. This may fail if the user has a tight height. + """ + + # This is always 1 tx, because we have horizontal transistors. + self.tx_mults = 1 + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + if OPTS.tech_name == "sky130": + self.nmos_width = self.nearest_bin("nmos", self.nmos_width) + self.pmos_width = self.nearest_bin("pmos", self.pmos_width) + + # Over-ride the route input gate to call the horizontal version. + # Other top-level netlist and layout functions are not changed. + def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left", directions=None): + """ + Route the input gate to the left side of the cell for access. + Position is actually ignored and is left to be compatible with the pinv. + """ + + nmos_gate_pin = nmos_inst.get_pin("G") + pmos_gate_pin = pmos_inst.get_pin("G") + + # Check if the gates are aligned and give an error if they aren't! + if nmos_gate_pin.ll().y != pmos_gate_pin.ll().y: + self.gds_write("unaliged_gates.gds") + debug.check(nmos_gate_pin.ll().y == pmos_gate_pin.ll().y, + "Connecting unaligned gates not supported. See unaligned_gates.gds.") + + # Pick point on the left of NMOS and up to PMOS + nmos_gate_pos = nmos_gate_pin.rc() + pmos_gate_pos = pmos_gate_pin.lc() + self.add_path("poly", [nmos_gate_pos, pmos_gate_pos]) + + # Center is completely symmetric. + contact_width = contact.poly_contact.width + contact_offset = nmos_gate_pin.lc() \ + - vector(self.poly_extend_active + 0.5 * contact_width, 0) + via = self.add_via_stack_center(from_layer="poly", + to_layer=self.route_layer, + offset=contact_offset, + directions=directions) + self.add_path("poly", [contact_offset, nmos_gate_pin.lc()]) + + self.add_layout_pin_rect_center(text=name, + layer=self.route_layer, + offset=contact_offset, + width=via.mod.second_layer_width, + height=via.mod.second_layer_height) + + def determine_width(self): + self.width = self.pmos_inst.rx() + self.well_extend_active + + def extend_wells(self): + """ Extend bottom to top for each well. """ + + if "pwell" in layer: + ll = (self.nmos_inst.ll() - vector(2 * [self.well_enclose_active])).scale(1, 0) + ur = self.nmos_inst.ur() + vector(2 * [self.well_enclose_active]) + self.add_rect(layer="pwell", + offset=ll, + width=ur.x - ll.x, + height=self.height - ll.y + 0.5 * self.pwell_contact.height + self.well_enclose_active) + + if "nwell" in layer: + ll = (self.pmos_inst.ll() - vector(2 * [self.well_enclose_active])).scale(1, 0) + ur = self.pmos_inst.ur() + vector(2 * [self.well_enclose_active]) + self.add_rect(layer="nwell", + offset=ll, + width=ur.x - ll.x, + height=self.height - ll.y + 0.5 * self.nwell_contact.height + self.well_enclose_active) + + def place_ptx(self): + """ + """ + # center the transistors in the y-dimension (it is rotated, so use the width) + y_offset = 0.5 * (self.height - self.nmos.width) + self.nmos.width + + # offset so that the input contact is over from the left edge by poly spacing + x_offset = self.nmos.active_offset.y + contact.poly_contact.width + self.poly_space + self.nmos_pos = vector(x_offset, y_offset) + self.nmos_inst.place(self.nmos_pos, + rotate=270) + # place PMOS so it is half a poly spacing down from the top + xoffset = self.nmos_inst.rx() + 2 * self.poly_extend_active + 2 * self.well_extend_active + drc("pwell_to_nwell") + self.pmos_pos = vector(xoffset, y_offset) + self.pmos_inst.place(self.pmos_pos, + rotate=270) + + # Output position will be in between the PMOS and NMOS drains + pmos_drain_pos = self.pmos_inst.get_pin("D").center() + nmos_drain_pos = self.nmos_inst.get_pin("D").center() + self.output_pos = vector(0.5 * (pmos_drain_pos.x + nmos_drain_pos.x), nmos_drain_pos.y) + + if OPTS.tech_name == "sky130": + self.add_implants() + + def add_implants(self): + """ + Add top-to-bottom implants for adjacency issues in s8. + """ + # Route to the bottom + ll = (self.nmos_inst.ll() - vector(2 * [self.implant_enclose_active])).scale(1, 0) + # Don't route to the top + ur = self.nmos_inst.ur() + vector(self.implant_enclose_active, 0) + self.add_rect("nimplant", + ll, + ur.x - ll.x, + ur.y - ll.y) + + # Route to the bottom + ll = (self.pmos_inst.ll() - vector(2 * [self.implant_enclose_active])).scale(1, 0) + # Don't route to the top + ur = self.pmos_inst.ur() + vector(self.implant_enclose_active, 0) + self.add_rect("pimplant", + ll, + ur.x - ll.x, + ur.y - ll.y) + + def route_outputs(self): + """ + Route the output (drains) together. + Optionally, routes output to edge. + """ + + # Get the drain pin + nmos_drain_pin = self.nmos_inst.get_pin("D") + + # Pick point at right most of NMOS and connect over to PMOS + nmos_drain_pos = nmos_drain_pin.lc() + right_side = vector(self.width, nmos_drain_pos.y) + + self.add_layout_pin_segment_center("Z", + self.route_layer, + nmos_drain_pos, + right_side) + + def add_well_contacts(self): + """ Add n/p well taps to the layout and connect to supplies """ + + source_pos = self.pmos_inst.get_pin("S").center() + contact_pos = vector(source_pos.x, self.height) + self.nwell_contact = self.add_via_center(layers=self.active_stack, + offset=contact_pos, + implant_type="n", + well_type="n") + self.add_via_stack_center(offset=contact_pos, + from_layer=self.active_stack[2], + to_layer=self.supply_layer) + + source_pos = self.nmos_inst.get_pin("S").center() + contact_pos = vector(source_pos.x, self.height) + self.pwell_contact= self.add_via_center(layers=self.active_stack, + offset=contact_pos, + implant_type="p", + well_type="p") + self.add_via_stack_center(offset=contact_pos, + from_layer=self.active_stack[2], + to_layer=self.supply_layer) + + def route_supply_rails(self): + pin = self.nmos_inst.get_pin("S") + source_pos = pin.center() + bottom_pos = source_pos.scale(1, 0) + top_pos = bottom_pos + vector(0, self.height) + self.add_layout_pin_segment_center("gnd", + self.supply_layer, + start=bottom_pos, + end=top_pos) + + pin = self.pmos_inst.get_pin("S") + source_pos = pin.center() + bottom_pos = source_pos.scale(1, 0) + top_pos = bottom_pos + vector(0, self.height) + self.add_layout_pin_segment_center("vdd", + self.supply_layer, + start=bottom_pos, + end=top_pos) + + def connect_rails(self): + """ Connect the nmos and pmos to its respective power rails """ + + source_pos = self.nmos_inst.get_pin("S").center() + self.add_via_stack_center(offset=source_pos, + from_layer=self.route_layer, + to_layer=self.supply_layer) + + source_pos = self.pmos_inst.get_pin("S").center() + self.add_via_stack_center(offset=source_pos, + from_layer=self.route_layer, + to_layer=self.supply_layer) + diff --git a/compiler/pgates/pinvbuf.py b/compiler/pgates/pinvbuf.py index 75b7ce6f..5b286e9b 100644 --- a/compiler/pgates/pinvbuf.py +++ b/compiler/pgates/pinvbuf.py @@ -9,7 +9,7 @@ import debug import pgate from vector import vector from sram_factory import factory - +from tech import layer class pinvbuf(pgate.pgate): """ @@ -47,6 +47,7 @@ class pinvbuf(pgate.pgate): self.place_modules() self.route_wires() self.add_layout_pins() + self.add_boundary() self.offset_all_coordinates() @@ -110,33 +111,45 @@ class pinvbuf(pgate.pgate): mirror="MX") def route_wires(self): + if "li" in layer: + route_stack = self.li_stack + else: + route_stack = self.m1_stack + # inv1 Z to inv2 A z1_pin = self.inv1_inst.get_pin("Z") a2_pin = self.inv2_inst.get_pin("A") mid_point = vector(z1_pin.cx(), a2_pin.cy()) - self.add_path("metal1", [z1_pin.center(), mid_point, a2_pin.center()]) + self.add_path(z1_pin.layer, [z1_pin.center(), mid_point, a2_pin.center()]) + self.add_via_stack_center(from_layer=z1_pin.layer, + to_layer=a2_pin.layer, + offset=a2_pin.center()) # inv2 Z to inv3 A z2_pin = self.inv2_inst.get_pin("Z") a3_pin = self.inv3_inst.get_pin("A") mid_point = vector(z2_pin.cx(), a3_pin.cy()) - self.add_path("metal1", [z2_pin.center(), mid_point, a3_pin.center()]) + self.add_path(z2_pin.layer, [z2_pin.center(), mid_point, a3_pin.center()]) + self.add_via_stack_center(from_layer=z2_pin.layer, + to_layer=a3_pin.layer, + offset=a3_pin.center()) # inv1 Z to inv4 A (up and over) z1_pin = self.inv1_inst.get_pin("Z") a4_pin = self.inv4_inst.get_pin("A") mid_point = vector(z1_pin.cx(), a4_pin.cy()) - self.add_wire(("metal1", "via1", "metal2"), + self.add_wire(route_stack, [z1_pin.center(), mid_point, a4_pin.center()]) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=z1_pin.center()) + self.add_via_stack_center(from_layer=z1_pin.layer, + to_layer=route_stack[2], + offset=z1_pin.center()) def add_layout_pins(self): # Continous vdd rail along with label. vdd_pin = self.inv1_inst.get_pin("vdd") self.add_layout_pin(text="vdd", - layer="metal1", + layer=vdd_pin.layer, offset=vdd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) @@ -144,7 +157,7 @@ class pinvbuf(pgate.pgate): # Continous vdd rail along with label. gnd_pin = self.inv4_inst.get_pin("gnd") self.add_layout_pin(text="gnd", - layer="metal1", + layer=gnd_pin.layer, offset=gnd_pin.ll().scale(0, 1), width=self.width, height=gnd_pin.height()) @@ -152,31 +165,25 @@ class pinvbuf(pgate.pgate): # Continous gnd rail along with label. gnd_pin = self.inv1_inst.get_pin("gnd") self.add_layout_pin(text="gnd", - layer="metal1", + layer=gnd_pin.layer, offset=gnd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) z_pin = self.inv4_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Z", - layer="metal2", + layer=z_pin.layer, offset=z_pin.center()) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=z_pin.center()) zb_pin = self.inv3_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Zb", - layer="metal2", + layer=zb_pin.layer, offset=zb_pin.center()) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=zb_pin.center()) a_pin = self.inv1_inst.get_pin("A") self.add_layout_pin_rect_center(text="A", - layer="metal2", + layer=a_pin.layer, offset=a_pin.center()) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=a_pin.center()) def determine_clk_buf_stage_efforts(self, external_cout, inp_is_rise=False): """Get the stage efforts of the clk -> clk_buf path""" diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index c5c69fd4..fb6bb210 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -5,13 +5,14 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import contact import pgate import debug from tech import drc, parameter, spice +from globals import OPTS from vector import vector import logical_effort from sram_factory import factory +import contact class pnand2(pgate.pgate): @@ -19,7 +20,7 @@ class pnand2(pgate.pgate): This module generates gds of a parametrically sized 2-input nand. This model use ptx to generate a 2-input nand within a cetrain height. """ - def __init__(self, name, size=1, height=None): + def __init__(self, name, size=1, height=None, add_wells=True): """ Creates a cell for a simple 2 input nand """ debug.info(2, @@ -37,8 +38,12 @@ class pnand2(pgate.pgate): debug.check(size == 1, "Size 1 pnand2 is only supported now.") self.tx_mults = 1 + if OPTS.tech_name == "sky130": + self.nmos_width = self.nearest_bin("nmos", self.nmos_width) + self.pmos_width = self.nearest_bin("pmos", self.pmos_width) + # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): self.add_pins() @@ -49,14 +54,17 @@ 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.extend_wells(self.well_pos) - self.route_inputs() + if self.add_wells: + self.add_well_contacts() self.route_output() - + self.determine_width() + self.route_supply_rails() + self.connect_rails() + self.extend_wells() + self.route_inputs() + self.add_boundary() + def add_pins(self): """ Adds pins for spice netlist """ pin_list = ["A", "B", "Z", "vdd", "gnd"] @@ -65,64 +73,45 @@ class pnand2(pgate.pgate): def add_ptx(self): """ Create the PMOS and NMOS transistors. """ - self.nmos = factory.create(module_type="ptx", - width=self.nmos_width, - mults=self.tx_mults, - tx_type="nmos", - connect_poly=True, - connect_active=True) - self.add_mod(self.nmos) + self.nmos_left = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact=self.route_layer, + add_drain_contact="active") + self.add_mod(self.nmos_left) + + self.nmos_right = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact="active", + add_drain_contact=self.route_layer) + self.add_mod(self.nmos_right) - self.pmos = factory.create(module_type="ptx", - width=self.pmos_width, - mults=self.tx_mults, - tx_type="pmos", - connect_poly=True, - connect_active=True) - self.add_mod(self.pmos) + self.pmos_left = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_left) + self.pmos_right = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_right) + def setup_layout_constants(self): """ Pre-compute some handy layout parameters. """ - # metal spacing to allow contacts on any layer - self.input_spacing = max(self.poly_space + contact.poly.first_layer_width, - self.m1_space + contact.m1m2.first_layer_width, - self.m2_space + contact.m2m3.first_layer_width, - self.m3_space + contact.m2m3.second_layer_width) - - # Compute the other pmos2 location, # but determining offset to overlap 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.well_width = 2 * self.pmos.active_width + contact.active.width \ - + 2 * drc("active_to_body_active") \ - + 2 * drc("well_enclosure_active") - - self.width = self.well_width - # 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) - # This is a poly-to-poly of a flipped cell - self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space, - drc("poly_extend_active"), self.poly_space) - - def route_supply_rails(self): - """ Add vdd/gnd rails to the top and bottom. """ - self.add_layout_pin_rect_center(text="gnd", - layer="metal1", - offset=vector(0.5*self.width, 0), - width=self.width) - - self.add_layout_pin_rect_center(text="vdd", - layer="metal1", - offset=vector(0.5 * self.width, self.height), - width=self.width) + self.overlap_offset = self.pmos_left.get_pin("D").center() - self.pmos_left.get_pin("S").center() def create_ptx(self): """ @@ -130,19 +119,19 @@ class pnand2(pgate.pgate): """ self.pmos1_inst = self.add_inst(name="pnand2_pmos1", - mod=self.pmos) + mod=self.pmos_left) self.connect_inst(["vdd", "A", "Z", "vdd"]) self.pmos2_inst = self.add_inst(name="pnand2_pmos2", - mod=self.pmos) + mod=self.pmos_right) self.connect_inst(["Z", "B", "vdd", "vdd"]) self.nmos1_inst = self.add_inst(name="pnand2_nmos1", - mod=self.nmos) + mod=self.nmos_left) self.connect_inst(["Z", "B", "net1", "gnd"]) self.nmos2_inst = self.add_inst(name="pnand2_nmos2", - mod=self.nmos) + mod=self.nmos_right) self.connect_inst(["net1", "A", "gnd", "gnd"]) def place_ptx(self): @@ -151,36 +140,29 @@ class pnand2(pgate.pgate): to provide maximum routing in channel """ - pmos1_pos = vector(self.pmos.active_offset.x, - self.height - self.pmos.active_height \ + pmos1_pos = vector(self.pmos_left.active_offset.x, + self.height - self.pmos_left.active_height \ - self.top_bottom_space) self.pmos1_inst.place(pmos1_pos) self.pmos2_pos = pmos1_pos + self.overlap_offset self.pmos2_inst.place(self.pmos2_pos) - nmos1_pos = vector(self.pmos.active_offset.x, + nmos1_pos = vector(self.pmos_left.active_offset.x, self.top_bottom_space) self.nmos1_inst.place(nmos1_pos) self.nmos2_pos = nmos1_pos + self.overlap_offset self.nmos2_inst.place(self.nmos2_pos) - # Output position will be in between the PMOS and NMOS - self.output_pos = vector(0, - 0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos.active_height)) - - # This will help with the wells - self.well_pos = vector(0, self.nmos1_inst.uy()) - def add_well_contacts(self): - """ + """ Add n/p well taps to the layout and connect to supplies AFTER the wells are created """ - self.add_nwell_contact(self.pmos, self.pmos2_pos) - self.add_pwell_contact(self.nmos, self.nmos2_pos) + self.add_nwell_contact(self.pmos_right, self.pmos2_pos) + self.add_pwell_contact(self.nmos_left, self.nmos2_pos) def connect_rails(self): """ Connect the nmos and pmos to its respective power rails """ @@ -193,58 +175,103 @@ 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 - self.route_input_gate(self.pmos2_inst, - self.nmos2_inst, - inputB_yoffset, - "B", - position="center") + + # Top of NMOS drain + bottom_pin = self.nmos1_inst.get_pin("D") + # active contact metal to poly contact metal spacing + active_contact_to_poly_contact = bottom_pin.uy() + self.route_layer_space + 0.5 * contact.poly_contact.second_layer_height + # active diffusion to poly contact spacing + # doesn't use nmos uy because that is calculated using offset + poly height + active_top = self.nmos1_inst.by() + self.nmos1_inst.mod.active_height + active_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height + active_to_poly_contact2 = active_top + self.poly_contact_to_gate + 0.5 * self.route_layer_width + self.inputA_yoffset = max(active_contact_to_poly_contact, + active_to_poly_contact, + active_to_poly_contact2) + apin = self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.inputA_yoffset, + "A", + position="center") + + self.inputB_yoffset = self.inputA_yoffset + 2 * self.m3_pitch + # # active contact metal to poly contact metal spacing + # active_contact_to_poly_contact = self.output_yoffset - self.route_layer_space - 0.5 * contact.poly_contact.second_layer_height + # active_bottom = self.pmos1_inst.by() + # active_to_poly_contact = active_bottom - self.poly_to_active - 0.5 * contact.poly_contact.first_layer_height + # active_to_poly_contact2 = active_bottom - self.poly_contact_to_gate - 0.5 * self.route_layer_width + # self.inputB_yoffset = min(active_contact_to_poly_contact, + # active_to_poly_contact, + # active_to_poly_contact2) + # This will help with the wells and the input/output placement - self.inputA_yoffset = inputB_yoffset + self.input_spacing - self.route_input_gate(self.pmos1_inst, - self.nmos1_inst, - self.inputA_yoffset, - "A") + bpin = self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + self.inputB_yoffset, + "B", + position="center") + + if OPTS.tech_name == "sky130": + self.add_enclosure([apin, bpin], "npc", drc("npc_enclose_poly")) + def route_output(self): """ Route the Z output """ + + # One routing track layer below the PMOS contacts + route_layer_offset = 0.5 * contact.poly_contact.second_layer_height + self.route_layer_space + self.output_yoffset = self.pmos1_inst.get_pin("D").by() - route_layer_offset + + # PMOS1 drain pmos_pin = self.pmos1_inst.get_pin("D") - top_pin_offset = pmos_pin.center() + top_pin_offset = pmos_pin.bc() # NMOS2 drain nmos_pin = self.nmos2_inst.get_pin("D") - bottom_pin_offset = nmos_pin.center() + bottom_pin_offset = nmos_pin.uc() # Output pin - out_offset = vector(nmos_pin.center().x + self.m1_pitch, - self.inputA_yoffset) + out_offset = vector(nmos_pin.cx() + self.route_layer_pitch, + self.output_yoffset) - # Midpoints of the L routes go horizontal first then vertical - mid1_offset = vector(out_offset.x, top_pin_offset.y) - mid2_offset = vector(out_offset.x, bottom_pin_offset.y) - - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=pmos_pin.center(), - directions=("V", "H")) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=nmos_pin.center(), - directions=("V", "H")) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=out_offset) - - # PMOS1 to mid-drain to NMOS2 drain - self.add_path("metal2", - [top_pin_offset, mid1_offset, out_offset, - mid2_offset, bottom_pin_offset]) + # This routes on M2 + # # Midpoints of the L routes go horizontal first then vertical + # mid1_offset = vector(out_offset.x, top_pin_offset.y) + # mid2_offset = vector(out_offset.x, bottom_pin_offset.y) + # # Non-preferred active contacts + # self.add_via_center(layers=self.m1_stack, + # directions=("V", "H"), + # offset=pmos_pin.center()) + # # Non-preferred active contacts + # self.add_via_center(layers=self.m1_stack, + # directions=("V", "H"), + # offset=nmos_pin.center()) + # self.add_via_center(layers=self.m1_stack, + # offset=out_offset) + + # # PMOS1 to mid-drain to NMOS2 drain + # self.add_path("m2", + # [top_pin_offset, mid1_offset, out_offset, + # mid2_offset, bottom_pin_offset]) + + # This routes on route_layer + # Midpoints of the L routes goes vertical first then horizontal + top_mid_offset = vector(top_pin_offset.x, out_offset.y) + # Top transistors + self.add_path(self.route_layer, + [top_pin_offset, top_mid_offset, out_offset]) + + bottom_mid_offset = bottom_pin_offset + vector(0, self.route_layer_pitch) + # Bottom transistors + self.add_path(self.route_layer, + [out_offset, bottom_mid_offset, bottom_pin_offset]) + # This extends the output to the edge of the cell self.add_layout_pin_rect_center(text="Z", - layer="metal1", - offset=out_offset, - width=contact.m1m2.first_layer_height, - height=contact.m1m2.first_layer_width) + layer=self.route_layer, + offset=out_offset) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 621829f1..e4e71e61 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -5,13 +5,14 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import contact import pgate import debug from tech import drc, parameter, spice from vector import vector import logical_effort from sram_factory import factory +from globals import OPTS +import contact class pnand3(pgate.pgate): @@ -19,7 +20,7 @@ class pnand3(pgate.pgate): This module generates gds of a parametrically sized 2-input nand. This model use ptx to generate a 2-input nand within a cetrain height. """ - def __init__(self, name, size=1, height=None): + def __init__(self, name, size=1, height=None, add_wells=True): """ Creates a cell for a simple 3 input nand """ debug.info(2, @@ -36,11 +37,16 @@ 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 + if OPTS.tech_name == "sky130": + self.nmos_width = self.nearest_bin("nmos", self.nmos_width) + self.pmos_width = self.nearest_bin("pmos", self.pmos_width) + # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def add_pins(self): """ Adds pins for spice netlist """ @@ -57,70 +63,77 @@ 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.extend_wells(self.well_pos) + if self.add_wells: + self.add_well_contacts() self.route_inputs() self.route_output() - + self.determine_width() + self.route_supply_rails() + self.connect_rails() + self.extend_wells() + self.add_boundary() + def add_ptx(self): """ Create the PMOS and NMOS transistors. """ - self.nmos = factory.create(module_type="ptx", - width=self.nmos_width, - mults=self.tx_mults, - tx_type="nmos", - connect_poly=True, - connect_active=True) - self.add_mod(self.nmos) + self.nmos_center = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact="active", + add_drain_contact="active") + self.add_mod(self.nmos_center) - self.pmos = factory.create(module_type="ptx", - width=self.pmos_width, - mults=self.tx_mults, - tx_type="pmos", - connect_poly=True, - connect_active=True) - self.add_mod(self.pmos) + self.nmos_right = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact="active", + add_drain_contact=self.route_layer) + self.add_mod(self.nmos_right) + self.nmos_left = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact=self.route_layer, + add_drain_contact="active") + self.add_mod(self.nmos_left) + + self.pmos_left = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_left) + + self.pmos_center = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_center) + + self.pmos_right = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_right) + def setup_layout_constants(self): """ 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.well_width = 3 * self.pmos.active_width + self.pmos.active_contact.width \ - + 2 * drc("active_to_body_active") + 2 * drc("well_enclosure_active") \ - - self.overlap_offset.x - self.width = self.well_width - # Height is an input parameter, so it is not recomputed. - - # This will help with the wells and the input/output placement - self.output_pos = vector(0, 0.5*self.height) - + self.ptx_offset = self.pmos_left.get_pin("D").center() - self.pmos_left.get_pin("S").center() + # This is the extra space needed to ensure DRC rules # to the active contacts nmos = factory.create(module_type="ptx", tx_type="nmos") extra_contact_space = max(-nmos.get_pin("D").by(), 0) - # This is a poly-to-poly of a flipped cell - self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space \ - + extra_contact_space, - drc("poly_extend_active"), - self.poly_space) - - def route_supply_rails(self): - """ Add vdd/gnd rails to the top and bottom. """ - self.add_layout_pin_rect_center(text="gnd", - layer="metal1", - offset=vector(0.5 * self.width, 0), - width=self.width) - - self.add_layout_pin_rect_center(text="vdd", - layer="metal1", - offset=vector(0.5 * self.width, self.height), - width=self.width) def create_ptx(self): """ @@ -128,27 +141,27 @@ class pnand3(pgate.pgate): """ self.pmos1_inst = self.add_inst(name="pnand3_pmos1", - mod=self.pmos) + mod=self.pmos_left) self.connect_inst(["vdd", "A", "Z", "vdd"]) self.pmos2_inst = self.add_inst(name="pnand3_pmos2", - mod=self.pmos) + mod=self.pmos_center) self.connect_inst(["Z", "B", "vdd", "vdd"]) self.pmos3_inst = self.add_inst(name="pnand3_pmos3", - mod=self.pmos) + mod=self.pmos_right) self.connect_inst(["Z", "C", "vdd", "vdd"]) self.nmos1_inst = self.add_inst(name="pnand3_nmos1", - mod=self.nmos) + mod=self.nmos_left) self.connect_inst(["Z", "C", "net1", "gnd"]) self.nmos2_inst = self.add_inst(name="pnand3_nmos2", - mod=self.nmos) + mod=self.nmos_center) self.connect_inst(["net1", "B", "net2", "gnd"]) self.nmos3_inst = self.add_inst(name="pnand3_nmos3", - mod=self.nmos) + mod=self.nmos_right) self.connect_inst(["net2", "A", "gnd", "gnd"]) def place_ptx(self): @@ -157,78 +170,89 @@ class pnand3(pgate.pgate): and lowest position to provide maximum routing in channel """ - pmos1_pos = vector(self.pmos.active_offset.x, - self.height - self.pmos.active_height \ - - self.top_bottom_space) + pmos1_pos = vector(self.pmos_left.active_offset.x, + self.height - self.pmos_left.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, + nmos1_pos = vector(self.pmos_left.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 should be placed at the top of the NMOS well - self.well_pos = vector(0, self.nmos1_inst.uy()) - + def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ - self.add_nwell_contact(self.pmos, self.pmos3_pos) - self.add_pwell_contact(self.nmos, self.nmos3_pos) + self.add_nwell_contact(self.pmos_right, + self.pmos3_pos + vector(self.m1_pitch, 0)) + self.add_pwell_contact(self.nmos_right, + self.nmos3_pos + vector(self.m1_pitch, 0)) def connect_rails(self): """ Connect the nmos and pmos to its respective power rails """ 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.width + 0.5 * self.m1_width) + """ Route the A and B and C inputs """ - active_spacing = max(self.m1_space, - 0.5 * contact.poly.first_layer_width + drc("poly_to_active")) - inputC_yoffset = self.nmos3_pos.y + self.nmos.active_height + active_spacing - self.route_input_gate(self.pmos3_inst, - self.nmos3_inst, - inputC_yoffset, - "C", - position="center") + # We can use this pitch because the contacts and overlap won't be adjacent + non_contact_pitch = 0.5 * self.m1_width + self.m1_space + 0.5 * contact.poly_contact.second_layer_height + pmos_drain_bottom = self.pmos1_inst.get_pin("D").by() + self.output_yoffset = pmos_drain_bottom - 0.5 * self.route_layer_width - self.route_layer_space - inputB_yoffset = inputC_yoffset + metal_spacing - self.route_input_gate(self.pmos2_inst, - self.nmos2_inst, - inputB_yoffset, - "B", - position="center") + bottom_pin = self.nmos1_inst.get_pin("D") + # active contact metal to poly contact metal spacing + active_contact_to_poly_contact = bottom_pin.uy() + self.m1_space + 0.5 * contact.poly_contact.second_layer_height + # active diffusion to poly contact spacing + # doesn't use nmos uy because that is calculated using offset + poly height + active_top = self.nmos1_inst.by() + self.nmos1_inst.mod.active_height + active_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height + active_to_poly_contact2 = active_top + self.poly_contact_to_gate + 0.5 * self.route_layer_width + self.inputA_yoffset = max(active_contact_to_poly_contact, + active_to_poly_contact, + active_to_poly_contact2) + + apin = self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.inputA_yoffset, + "A", + position="left") - self.inputA_yoffset = inputB_yoffset + metal_spacing - self.route_input_gate(self.pmos1_inst, - self.nmos1_inst, - self.inputA_yoffset, - "A", - position="center") + self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch + bpin = self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + self.inputB_yoffset, + "B", + position="center") + self.inputC_yoffset = self.inputB_yoffset + self.m3_pitch + cpin = self.route_input_gate(self.pmos3_inst, + self.nmos3_inst, + self.inputC_yoffset, + "C", + position="right") + + if OPTS.tech_name == "sky130": + self.add_enclosure([apin, bpin, cpin], "npc", drc("npc_enclose_poly")) + def route_output(self): """ Route the Z output """ + # PMOS1 drain pmos1_pin = self.pmos1_inst.get_pin("D") # PMOS3 drain @@ -236,28 +260,51 @@ class pnand3(pgate.pgate): # NMOS3 drain nmos3_pin = self.nmos3_inst.get_pin("D") + out_offset = vector(nmos3_pin.cx() + self.route_layer_pitch, + self.output_yoffset) + # Go up to metal2 for ease on all output pins - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=pmos1_pin.center()) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=pmos3_pin.center()) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=nmos3_pin.center()) + # self.add_via_center(layers=self.m1_stack, + # offset=pmos1_pin.center(), + # directions=("V", "V")) + # self.add_via_center(layers=self.m1_stack, + # offset=pmos3_pin.center(), + # directions=("V", "V")) + # self.add_via_center(layers=self.m1_stack, + # offset=nmos3_pin.center(), + # directions=("V", "V")) - # PMOS3 and NMOS3 are drain aligned - self.add_path("metal2", [pmos3_pin.bc(), nmos3_pin.uc()]) - # Route in the A input track (top track) - mid_offset = vector(nmos3_pin.center().x, self.inputA_yoffset) - self.add_path("metal2", [pmos1_pin.bc(), mid_offset, nmos3_pin.uc()]) + # # Route in the A input track (top track) + # mid_offset = vector(nmos3_pin.center().x, self.inputA_yoffset) + # self.add_path("m1", [pmos1_pin.center(), mid_offset, nmos3_pin.uc()]) # This extends the output to the edge of the cell - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=mid_offset) + # self.add_via_center(layers=self.m1_stack, + # offset=mid_offset) + + top_left_pin_offset = pmos1_pin.center() + top_right_pin_offset = pmos3_pin.center() + bottom_pin_offset = nmos3_pin.center() + + # PMOS1 to output + self.add_path(self.route_layer, [top_left_pin_offset, + vector(top_left_pin_offset.x, out_offset.y), + out_offset]) + # PMOS3 to output + self.add_path(self.route_layer, [top_right_pin_offset, + vector(top_right_pin_offset.x, out_offset.y), + out_offset]) + # NMOS3 to output + mid2_offset = vector(out_offset.x, bottom_pin_offset.y) + self.add_path(self.route_layer, + [bottom_pin_offset, mid2_offset], + width=nmos3_pin.height()) + mid3_offset = vector(out_offset.x, nmos3_pin.by()) + self.add_path(self.route_layer, [mid3_offset, out_offset]) + self.add_layout_pin_rect_center(text="Z", - layer="metal1", - offset=mid_offset, - width=contact.m1m2.first_layer_width, - height=contact.m1m2.first_layer_height) + layer=self.route_layer, + offset=out_offset) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" @@ -275,7 +322,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 a99253cd..aad405e8 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -8,6 +8,7 @@ import contact import pgate import debug +from globals import OPTS from tech import drc, parameter, spice from vector import vector from sram_factory import factory @@ -18,7 +19,7 @@ class pnor2(pgate.pgate): This module generates gds of a parametrically sized 2-input nor. This model use ptx to generate a 2-input nor within a cetrain height. """ - def __init__(self, name, size=1, height=None): + def __init__(self, name, size=1, height=None, add_wells=True): """ Creates a cell for a simple 2 input nor """ debug.info(2, @@ -36,8 +37,12 @@ class pnor2(pgate.pgate): debug.check(size==1, "Size 1 pnor2 is only supported now.") self.tx_mults = 1 + if OPTS.tech_name == "sky130": + self.nmos_width = self.nearest_bin("nmos", self.nmos_width) + self.pmos_width = self.nearest_bin("pmos", self.pmos_width) + # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): self.add_pins() @@ -48,13 +53,16 @@ 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.extend_wells(self.well_pos) + if self.add_wells: + self.add_well_contacts() self.route_inputs() self.route_output() + self.determine_width() + self.route_supply_rails() + self.connect_rails() + self.extend_wells() + self.add_boundary() def add_pins(self): """ Adds pins for spice netlist """ @@ -64,65 +72,54 @@ class pnor2(pgate.pgate): def add_ptx(self): """ Create the PMOS and NMOS transistors. """ - self.nmos = factory.create(module_type="ptx", - width=self.nmos_width, - mults=self.tx_mults, - tx_type="nmos", - connect_poly=True, - connect_active=True) - self.add_mod(self.nmos) + self.nmos_left = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.nmos_left) - self.pmos = factory.create(module_type="ptx", - width=self.pmos_width, - mults=self.tx_mults, - tx_type="pmos", - connect_poly=True, - connect_active=True) - self.add_mod(self.pmos) + self.nmos_right = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.nmos_right) + + self.pmos_left = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact="active") + self.add_mod(self.pmos_left) + self.pmos_right = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact="active", + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_right) + def setup_layout_constants(self): """ Pre-compute some handy layout parameters. """ - # metal spacing to allow contacts on any layer - self.input_spacing = max(self.poly_space + contact.poly.first_layer_width, - self.m1_space + contact.m1m2.first_layer_width, - self.m2_space + contact.m2m3.first_layer_width, - self.m3_space + contact.m2m3.second_layer_width) - # Compute the other pmos2 location, but determining # offset to overlap the source and drain pins - self.overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll() + self.overlap_offset = self.pmos_right.get_pin("D").center() - self.pmos_left.get_pin("S").center() # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. - self.well_width = 2 * self.pmos.active_width \ - + self.pmos.active_contact.width \ - + 2 * drc("active_to_body_active") \ - + 2 * drc("well_enclosure_active") - - self.width = self.well_width + self.width = 2 * self.pmos_right.active_width \ + + self.pmos_right.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) - # This is a poly-to-poly of a flipped cell - self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space, - drc("poly_extend_active"), - self.poly_space) - def route_supply_rails(self): - """ Add vdd/gnd rails to the top and bottom. """ - self.add_layout_pin_rect_center(text="gnd", - layer="metal1", - offset=vector(0.5 * self.width, 0), - width=self.width) - - self.add_layout_pin_rect_center(text="vdd", - layer="metal1", - offset=vector(0.5 * self.width, self.height), - width=self.width) - def create_ptx(self): """ Add PMOS and NMOS to the layout at the upper-most and lowest position @@ -130,20 +127,19 @@ class pnor2(pgate.pgate): """ self.pmos1_inst = self.add_inst(name="pnor2_pmos1", - mod=self.pmos) + mod=self.pmos_left) self.connect_inst(["vdd", "A", "net1", "vdd"]) self.pmos2_inst = self.add_inst(name="pnor2_pmos2", - mod=self.pmos) + mod=self.pmos_right) self.connect_inst(["net1", "B", "Z", "vdd"]) - self.nmos1_inst = self.add_inst(name="pnor2_nmos1", - mod=self.nmos) + mod=self.nmos_left) self.connect_inst(["Z", "A", "gnd", "gnd"]) self.nmos2_inst = self.add_inst(name="pnor2_nmos2", - mod=self.nmos) + mod=self.nmos_right) self.connect_inst(["Z", "B", "gnd", "gnd"]) def place_ptx(self): @@ -151,33 +147,35 @@ class pnor2(pgate.pgate): Add PMOS and NMOS to the layout at the upper-most and lowest position to provide maximum routing in channel """ + # Some of the S/D contacts may extend beyond the active, + # but this needs to be done in the gate itself + contact_extend_active_space = max(-self.nmos_right.get_pin("D").by(), 0) + # Assume the contact starts at the active edge + contact_to_vdd_rail_space = 0.5 * self.m1_width + self.m1_space + contact_extend_active_space + # This is a poly-to-poly of a flipped cell + poly_to_poly_gate_space = self.poly_extend_active + self.poly_space + # Recompute this since it has a small txwith the added contact extend active spacing + self.top_bottom_space = max(contact_to_vdd_rail_space, + poly_to_poly_gate_space) - pmos1_pos = vector(self.pmos.active_offset.x, - self.height - self.pmos.active_height \ - - self.top_bottom_space) + pmos1_pos = vector(self.pmos_right.active_offset.x, + self.height - self.pmos_right.active_height - self.top_bottom_space) self.pmos1_inst.place(pmos1_pos) self.pmos2_pos = pmos1_pos + self.overlap_offset self.pmos2_inst.place(self.pmos2_pos) - nmos1_pos = vector(self.pmos.active_offset.x, self.top_bottom_space) + nmos1_pos = vector(self.pmos_right.active_offset.x, self.top_bottom_space) self.nmos1_inst.place(nmos1_pos) self.nmos2_pos = nmos1_pos + self.overlap_offset self.nmos2_inst.place(self.nmos2_pos) - - # Output position will be in between the PMOS and NMOS - self.output_pos = vector(0, - 0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos.active_height)) - # This will help with the wells - self.well_pos = vector(0, self.nmos1_inst.uy()) - def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ - self.add_nwell_contact(self.pmos, self.pmos2_pos) - self.add_pwell_contact(self.nmos, self.nmos2_pos) + self.add_nwell_contact(self.pmos_right, self.pmos2_pos) + self.add_pwell_contact(self.nmos_right, self.nmos2_pos) def connect_rails(self): """ Connect the nmos and pmos to its respective power rails """ @@ -190,54 +188,56 @@ class pnor2(pgate.pgate): def route_inputs(self): """ Route the A and B inputs """ - # Use M2 spaces so we can drop vias on the pins later! - inputB_yoffset = self.nmos2_pos.y + self.nmos.active_height \ - + self.m2_space + self.m2_width - self.route_input_gate(self.pmos2_inst, - self.nmos2_inst, - inputB_yoffset, - "B", - position="center") + + # Top of NMOS drain + nmos_pin = self.nmos2_inst.get_pin("D") + bottom_pin_offset = nmos_pin.uy() + self.inputB_yoffset = bottom_pin_offset + self.m1_nonpref_pitch + self.inputA_yoffset = self.inputB_yoffset + self.m1_nonpref_pitch + + bpin = self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + self.inputB_yoffset, + "B", + position="right", + directions=("V", "V")) # This will help with the wells and the input/output placement - self.inputA_yoffset = inputB_yoffset + self.input_spacing - self.route_input_gate(self.pmos1_inst, - self.nmos1_inst, - self.inputA_yoffset, - "A") + apin = self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.inputA_yoffset, + "A", + directions=("V", "V")) + + self.output_yoffset = self.inputA_yoffset + self.m1_nonpref_pitch + + if OPTS.tech_name == "sky130": + self.add_enclosure([apin, bpin], "npc", drc("npc_enclose_poly")) def route_output(self): """ Route the Z output """ - # PMOS2 drain + # PMOS2 (right) drain pmos_pin = self.pmos2_inst.get_pin("D") - # NMOS1 drain + # NMOS1 (left) drain nmos_pin = self.nmos1_inst.get_pin("D") - # NMOS2 drain (for output via placement) + # NMOS2 (right) drain (for output via placement) nmos2_pin = self.nmos2_inst.get_pin("D") # Go up to metal2 for ease on all output pins - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=pmos_pin.center()) - m1m2_contact = self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=nmos_pin.center()) - - mid1_offset = vector(pmos_pin.center().x, nmos2_pin.center().y) - mid2_offset = vector(pmos_pin.center().x, self.inputA_yoffset) - mid3_offset = mid2_offset + vector(self.m2_width, 0) + # self.add_via_center(layers=self.m1_stack, + # offset=pmos_pin.center()) + # m1m2_contact = self.add_via_center(layers=self.m1_stack, + # offset=nmos_pin.center()) + mid1_offset = vector(nmos_pin.center().x, self.output_yoffset) + mid2_offset = vector(pmos_pin.center().x, self.output_yoffset) + # PMOS1 to mid-drain to NMOS2 drain - self.add_path("metal2", - [pmos_pin.bc(), mid2_offset, mid3_offset]) - self.add_path("metal2", - [nmos_pin.rc(), mid1_offset, mid2_offset]) - # This extends the output to the edge of the cell - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=mid3_offset) + self.add_path(self.route_layer, + [nmos_pin.center(), mid1_offset, mid2_offset, pmos_pin.center()]) self.add_layout_pin_rect_center(text="Z", - layer="metal1", - offset=mid3_offset, - width=contact.m1m2.first_layer_height, - height=contact.m1m2.first_layer_width) + layer=self.route_layer, + offset=mid2_offset) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index bd391ecc..52d24390 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -8,7 +8,8 @@ import contact import design import debug -from tech import drc, parameter +from pgate import pgate +from tech import parameter, drc from vector import vector from globals import OPTS from sram_factory import factory @@ -27,10 +28,20 @@ class precharge(design.design): self.bitcell = factory.create(module_type="bitcell") self.beta = parameter["beta"] self.ptx_width = self.beta * parameter["min_tx_size"] + self.ptx_mults = 1 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() @@ -38,12 +49,19 @@ class precharge(design.design): self.create_layout() self.DRC_LVS() + def get_bl_names(self): + return "bl" + + def get_br_names(self): + return "br" + def create_netlist(self): self.add_pins() self.add_ptx() self.create_ptx() def create_layout(self): + self.place_ptx() self.connect_poly() self.route_en() @@ -51,7 +69,8 @@ class precharge(design.design): self.route_vdd_rail() self.route_bitlines() self.connect_to_bitlines() - + self.add_boundary() + def add_pins(self): self.add_pin_list(["bl", "br", "en_bar", "vdd"], ["OUTPUT", "OUTPUT", "INPUT", "POWER"]) @@ -60,8 +79,11 @@ class precharge(design.design): """ Initializes the upper and lower pmos """ + if(OPTS.tech_name == "sky130"): + self.ptx_width = pgate.nearest_bin("pmos", self.ptx_width) self.pmos = factory.create(module_type="ptx", width=self.ptx_width, + mults=self.ptx_mults, tx_type="pmos") self.add_mod(self.pmos) @@ -72,18 +94,26 @@ class precharge(design.design): # Adds the rail across the width of the cell vdd_position = vector(0.5 * self.width, self.height) - self.add_rect_center(layer="metal1", + 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("metal1", [pmos_pin.uc(), pmos_vdd_pos]) + self.add_path(self.en_layer, [pmos_pin.center(), pmos_vdd_pos]) + + self.add_power_pin("vdd", + self.well_contact_pos, + directions=("V", "V")) - # Add vdd pin above the transistor - self.add_power_pin("vdd", pmos_pin.center(), vertical=True) + self.add_via_stack_center(from_layer=pmos_pin.layer, + to_layer=self.en_layer, + offset=pmos_pin.center(), + directions=("V", "V")) def create_ptx(self): """ @@ -107,24 +137,23 @@ 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() - # This is how much the contact is placed inside the ptx active - contact_xdiff = self.pmos.get_pin("S").lx() # adds the lower pmos to layout - bl_xoffset = self.bitcell.get_pin(self.bitcell_bl).lx() - self.lower_pmos_position = vector(max(bl_xoffset - contact_xdiff, - self.well_enclose_active), - self.pmos.active_offset.y) + self.lower_pmos_position = vector(self.well_enclose_active + 0.5 * self.m1_width, + self.initial_yoffset) self.lower_pmos_inst.place(self.lower_pmos_position) - # adds the upper pmos(s) to layout - ydiff = self.pmos.height + 2 * self.m1_space + contact.poly.width + # adds the upper pmos(s) to layout with 2 M2 tracks + ydiff = self.pmos.height + 2 * 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) @@ -133,7 +162,7 @@ class precharge(design.design): Connects the upper and lower pmos together """ - offset = self.lower_pmos_inst.get_pin("G").ll() + offset = self.lower_pmos_inst.get_pin("G").ul() # connects the top and bottom pmos' gates together ylength = self.upper_pmos1_inst.get_pin("G").ll().y - offset.y self.add_rect(layer="poly", @@ -155,16 +184,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 - offset = self.lower_pmos_inst.get_pin("G").ul() \ - + vector(0, 0.5 * self.poly_space) - self.add_via_center(layers=("poly", "contact", "metal1"), - offset=offset) - # adds the en rail on metal1 + # adds the en contact to connect the gates to the en rail + pin_offset = self.lower_pmos_inst.get_pin("G").lr() + # This is an extra space down for some techs with contact to active spacing + contact_space = max(self.poly_space, + self.poly_contact_to_gate) + 0.5 * contact.poly_contact.first_layer_height + offset = pin_offset - vector(0, contact_space) + self.add_via_stack_center(from_layer="poly", + to_layer=self.en_layer, + offset=offset) + self.add_path("poly", + [self.lower_pmos_inst.get_pin("G").bc(), offset]) + # adds the en rail self.add_layout_pin_segment_center(text="en_bar", - layer="metal1", + layer=self.en_layer, start=offset.scale(0, 1), end=offset.scale(0, 1) + vector(self.width, 0)) @@ -174,16 +208,20 @@ class precharge(design.design): """ # adds the contact from active to metal1 - well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1, 0) \ - + vector(0, self.upper_pmos1_inst.uy() + contact.well.height / 2 \ - + drc("well_extend_active")) - self.add_via_center(layers=("active", "contact", "metal1"), - offset=well_contact_pos, - implant_type="n", - well_type="n") + offset_height = self.upper_pmos1_inst.uy() + \ + contact.active_contact.height + \ + self.nwell_extend_active + self.well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1, 0) + \ + vector(0, offset_height) + self.well_contact = self.add_via_center(layers=self.active_stack, + offset=self.well_contact_pos, + implant_type="n", + well_type="n") + self.add_via_stack_center(from_layer=self.active_stack[2], + to_layer=self.bitline_layer, + offset=self.well_contact_pos) - # leave an extra pitch for the height - self.height = well_contact_pos.y + contact.well.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", @@ -195,83 +233,73 @@ 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="metal2", - offset=offset, - width=drc("minwidth_metal2"), - height=self.height) + layer_pitch = getattr(self, "{}_pitch".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="metal2", - offset=offset, - width=drc("minwidth_metal2"), - height=self.height) + # adds the BL + self.bl_xoffset = layer_pitch + top_pos = vector(self.bl_xoffset, self.height) + pin_pos = vector(self.bl_xoffset, 0) + self.add_path(self.bitline_layer, [top_pos, pin_pos]) + 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_pitch + top_pos = vector(self.br_xoffset, self.height) + pin_pos = vector(self.br_xoffset, 0) + self.add_path(self.bitline_layer, [top_pos, pin_pos]) + 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 """ - stack = ("metal1", "via1", "metal2") - upper_pin = self.upper_pmos1_inst.get_pin("S") - lower_pin = self.lower_pmos_inst.get_pin("S") - - # BL goes up to M2 at the transistor - self.bl_contact =self.add_via_center(layers=stack, - offset=upper_pin.center(), - directions=("V", "V")) - self.add_via_center(layers=stack, - offset=lower_pin.center(), - directions=("V", "V")) + # BL + for lower_pin in [self.lower_pmos_inst.get_pin("S"), self.lower_pmos_inst.get_pin("D")]: + self.add_via_stack_center(from_layer=lower_pin.layer, + to_layer=self.bitline_layer, + offset=lower_pin.center(), + directions=("V", "V")) - # BR routes over on M1 first - self.add_via_center(layers=stack, - offset=vector(self.br_pin.cx(), upper_pin.cy()), - directions=("V", "V")) - self.add_via_center(layers=stack, - offset=vector(self.br_pin.cx(), lower_pin.cy()), - directions=("V", "V")) + # BR + for upper_pin in [self.upper_pmos1_inst.get_pin("S"), self.upper_pmos2_inst.get_pin("D")]: + self.add_via_stack_center(from_layer=upper_pin.layer, + to_layer=self.bitline_layer, + offset=upper_pin.center(), + directions=("V", "V")) - def connect_pmos_m1(self, pmos_pin, bit_pin): + 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("metal1", [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("metal2", [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/ptristate_inv.py b/compiler/pgates/ptristate_inv.py index 2a972407..9fd5f8b6 100644 --- a/compiler/pgates/ptristate_inv.py +++ b/compiler/pgates/ptristate_inv.py @@ -44,7 +44,7 @@ class ptristate_inv(pgate.pgate): """ Calls all functions related to the generation of the netlist """ self.add_pins() self.add_ptx() - self.create_ptx() + self.create_ptx() def create_layout(self): """ Calls all functions related to the generation of the layout """ @@ -56,12 +56,12 @@ class ptristate_inv(pgate.pgate): self.connect_rails() self.route_inputs() self.route_outputs() + self.add_boundary() def add_pins(self): """ Adds pins for spice netlist """ self.add_pin_list(["in", "out", "en", "en_bar", "vdd", "gnd"]) - def setup_layout_constants(self): """ Pre-compute some handy layout parameters. @@ -73,16 +73,12 @@ class ptristate_inv(pgate.pgate): # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. - self.well_width = 2 * self.pmos.active_width + drc("well_enclosure_active") + self.well_width = 2 * self.pmos.active_width + self.nwell_enclose_active # Add an extra space because we route the output on the right of the S/D self.width = self.well_width + 0.5 * self.m1_space # Height is an input parameter, so it is not recomputed. - # Make sure we can put a well above and below - self.top_bottom_space = max(contact.well.width, contact.well.height) - - def add_ptx(self): """ Create the PMOS and NMOS transistors. """ self.nmos = factory.create(module_type="ptx", @@ -95,18 +91,17 @@ class ptristate_inv(pgate.pgate): width=self.pmos_width, mults=1, tx_type="pmos") - self.add_mod(self.pmos) def route_supply_rails(self): """ Add vdd/gnd rails to the top and bottom. """ self.add_layout_pin_rect_center(text="gnd", - layer="metal1", + layer="m1", offset=vector(0.5 * self.width, 0), width=self.width) self.add_layout_pin_rect_center(text="vdd", - layer="metal1", + layer="m1", offset=vector(0.5 * self.width, self.height), width=self.width) @@ -138,8 +133,8 @@ class ptristate_inv(pgate.pgate): """ pmos_yoff = self.height - self.pmos.active_height \ - - self.top_bottom_space - 0.5 * contact.well.height - nmos_yoff = self.top_bottom_space + 0.5 * contact.well.height + - self.top_bottom_space - 0.5 * contact.active_contact.height + nmos_yoff = self.top_bottom_space + 0.5 * contact.active_contact.height # Tristate transistors pmos1_pos = vector(self.pmos.active_offset.x, pmos_yoff) @@ -181,7 +176,7 @@ class ptristate_inv(pgate.pgate): pmos_drain_pos = pmos_drain_pin.ur() self.add_layout_pin(text="out", - layer="metal1", + layer="m1", offset=nmos_drain_pos, height=pmos_drain_pos.y - nmos_drain_pos.y) @@ -191,7 +186,7 @@ class ptristate_inv(pgate.pgate): supplies AFTER the wells are created """ - layer_stack = ("active", "contact", "metal1") + layer_stack = self.active_stack drain_pos = self.nmos1_inst.get_pin("S").center() vdd_pos = self.get_pin("vdd").center() diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index 3cc767d7..b6f839f3 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -7,9 +7,13 @@ # import design import debug -from tech import drc, spice +from tech import layer, drc, spice from vector import vector from sram_factory import factory +import contact +import logical_effort +from globals import OPTS +from pgate import pgate class ptx(design.design): @@ -19,23 +23,51 @@ class ptx(design.design): the transistor width. Mults is the number of transistors of the given width. Total width is therefore mults*width. Options allow you to connect the fingered gates and active for parallel devices. + The add_*_contact option tells which layer to bring source/drain up to. + ll, ur, width and height refer to the active area. + Wells and poly may extend beyond this. + """ def __init__(self, name="", width=drc("minwidth_tx"), mults=1, tx_type="nmos", - connect_active=False, + add_source_contact=None, + add_drain_contact=None, + series_devices=False, + connect_drain_active=False, + connect_source_active=False, connect_poly=False, num_contacts=None): + + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + + # Default contacts are the lowest layer + if not add_source_contact: + add_source_contact = self.route_layer + + # Default contacts are the lowest layer + if not add_drain_contact: + add_drain_contact = self.route_layer + # We need to keep unique names because outputting to GDSII # will use the last record with a given name. I.e., you will # over-write a design in GDS if one has and the other doesn't # have poly connected, for example. name = "{0}_m{1}_w{2:.3f}".format(tx_type, mults, width) - if connect_active: - name += "_a" + name += "_s{}".format(add_source_contact) + name += "_d{}".format(add_drain_contact) + if series_devices: + name += "_sd" + if connect_drain_active: + name += "_da" + if connect_source_active: + name += "_sa" if connect_poly: name += "_p" if num_contacts: @@ -48,17 +80,30 @@ class ptx(design.design): self.tx_type = tx_type self.mults = mults self.tx_width = width - self.connect_active = connect_active + self.connect_drain_active = connect_drain_active + self.connect_source_active = connect_source_active self.connect_poly = connect_poly + self.add_source_contact = add_source_contact + self.add_drain_contact = add_drain_contact + self.series_devices = series_devices self.num_contacts = num_contacts - # Do NOT create the netlist and layout (not a pgate) + self.route_layer_width = drc("minwidth_{}".format(self.route_layer)) + self.route_layer_space = drc("{0}_to_{0}".format(self.route_layer)) + # Since it has variable height, it is not a pgate. self.create_netlist() # We must always create ptx layout for pbitcell # some transistor sizes in other netlist depend on pbitcell self.create_layout() - + + ll = self.find_lowest_coords() + ur = self.find_highest_coords() + self.add_boundary(ll, ur) + + # (0,0) will be the corner of the active area (not the larger well) + self.translate_all(self.active_offset) + def create_layout(self): """Calls all functions related to the generation of the layout""" self.setup_layout_constants() @@ -66,7 +111,6 @@ class ptx(design.design): self.add_well_implant() self.add_poly() self.add_active_contacts() - self.translate_all(self.active_offset) # for run-time, we won't check every transitor DRC independently # but this may be uncommented for debug purposes @@ -75,36 +119,56 @@ class ptx(design.design): def create_netlist(self): pin_list = ["D", "G", "S", "B"] if self.tx_type == "nmos": - body_dir = 'GROUND' + body_dir = "GROUND" else: - # Assumed that the check for either pmos or nmos is done elsewhere. - body_dir = 'POWER' - dir_list = ['INOUT', 'INPUT', 'INOUT', body_dir] + body_dir = "POWER" + dir_list = ["INOUT", "INPUT", "INOUT", body_dir] self.add_pin_list(pin_list, dir_list) - - # self.spice.append("\n.SUBCKT {0} {1}".format(self.name, - # " ".join(self.pins))) + # Just make a guess since these will actually # be decided in the layout later. - area_sd = 2.5 * drc("minwidth_poly") * self.tx_width - perimeter_sd = 2 * drc("minwidth_poly") + 2 * self.tx_width - main_str = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type], - self.mults, - self.tx_width, - drc("minwidth_poly")) - area_str = "pd={0:.2f}u ps={0:.2f}u as={1:.2f}p ad={1:.2f}p".format(perimeter_sd, - area_sd) - self.spice_device= main_str + area_str + area_sd = 2.5 * self.poly_width * self.tx_width + perimeter_sd = 2 * self.poly_width + 2 * self.tx_width + if OPTS.tech_name == "sky130" and OPTS.lvs_exe and OPTS.lvs_exe[0] == "calibre": + # sky130 simulation cannot use the mult parameter in simulation + (self.tx_width, self.mults) = pgate.best_bin(self.tx_type, self.tx_width) + main_str = "M{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type], + self.mults, + self.tx_width, + drc("minwidth_poly")) + # Perimeters are in microns + # Area is in u since it is microns square + area_str = "pd={0:.2f} ps={0:.2f} as={1:.2f}u ad={1:.2f}u".format(perimeter_sd, + area_sd) + else: + main_str = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type], + self.mults, + self.tx_width, + drc("minwidth_poly")) + area_str = "pd={0:.2f}u ps={0:.2f}u as={1:.2f}p ad={1:.2f}p".format(perimeter_sd, + area_sd) + self.spice_device = main_str + area_str self.spice.append("\n* ptx " + self.spice_device) - # self.spice.append(".ENDS {0}".format(self.name)) + + if OPTS.tech_name == "sky130" and OPTS.lvs_exe and OPTS.lvs_exe[0] == "calibre": + # sky130 requires mult parameter too + self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2} l={3} mult={1}".format(spice[self.tx_type], + self.mults, + self.tx_width, + drc("minwidth_poly")) + else: + self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type], + self.mults, + self.tx_width, + drc("minwidth_poly")) def setup_layout_constants(self): """ Pre-compute some handy layout parameters. """ - if self.num_contacts==None: - self.num_contacts=self.calculate_num_contacts() + if not self.num_contacts: + self.num_contacts = self.calculate_num_contacts() # Determine layer types needed if self.tx_type == "nmos": @@ -114,71 +178,68 @@ class ptx(design.design): self.implant_type = "p" self.well_type = "n" else: - self.error("Invalid transitor type.",-1) - + self.error("Invalid transitor type.", -1) # This is not actually instantiated but used for calculations self.active_contact = factory.create(module_type="contact", - layer_stack=("active", "contact", "metal1"), + layer_stack=self.active_stack, + directions=("V", "V"), dimensions=(1, self.num_contacts)) + # 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 - # The contacted poly pitch (or uncontacted in an odd technology) - self.poly_pitch = max(2 * self.contact_to_gate + self.contact_width + self.poly_width, - self.poly_space) + # 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.active_contact_to_gate + self.poly_width + self.contact_width + self.poly_pitch = max(self.min_poly_pitch, + self.contacted_poly_pitch, + self.contact_pitch) - # The contacted poly pitch (or uncontacted in an odd technology) - self.contact_pitch = 2 * self.contact_to_gate + self.contact_width + self.poly_width + self.end_to_contact = 0.5 * self.active_contact.width - # The enclosure of an active contact. Not sure about second term. - active_enclose_contact = max(drc("active_enclosure_contact"), - (self.active_width - self.contact_width) / 2) - - # This is the distance from the edge of poly to the contacted end of active - self.end_to_poly = active_enclose_contact + self.contact_width + self.contact_to_gate - - # Active width is determined by enclosure on both ends and contacted pitch, # at least one poly and n-1 poly pitches - self.active_width = 2 * self.end_to_poly + self.poly_width + (self.mults - 1) * self.poly_pitch + self.active_width = 2 * self.end_to_contact + self.active_contact.width \ + + 2 * self.active_contact_to_gate + self.poly_width + (self.mults - 1) * self.poly_pitch # Active height is just the transistor width self.active_height = self.tx_width # Poly height must include poly extension over active self.poly_height = self.tx_width + 2 * self.poly_extend_active - - # The active offset is due to the well extension + self.active_offset = vector([self.well_enclose_active] * 2) # Well enclosure of active, ensure minwidth as well - if drc("has_{}well".format(self.well_type)): - self.cell_well_width = max(self.active_width + 2 * self.well_enclose_active, - self.well_width) - self.cell_well_height = max(self.tx_width + 2 * self.well_enclose_active, - self.well_width) - # We are going to shift the 0,0, so include that in the width and height - self.height = self.cell_well_height - self.active_offset.y - self.width = self.cell_well_width - self.active_offset.x + well_name = "{}well".format(self.well_type) + if well_name in layer: + well_width_rule = drc("minwidth_" + well_name) + self.well_width = max(self.active_width + 2 * self.well_enclose_active, + well_width_rule) + self.well_height = max(self.active_height + 2 * self.well_enclose_active, + well_width_rule) else: - # If no well, use the boundary of the active and poly - self.height = self.poly_height - self.width = self.active_width - - # The active offset is due to the well extension - self.active_offset = vector([self.well_enclose_active] * 2) + self.well_height = self.height + self.well_width = self.width + + # We are going to shift the 0,0, so include that in the width and height + self.height = self.active_height + self.width = self.active_width # This is the center of the first active contact offset (centered vertically) - self.contact_offset = self.active_offset + vector(active_enclose_contact + 0.5 * self.contact_width, + self.contact_offset = self.active_offset + vector(0.5 * self.active_contact.width, 0.5 * self.active_height) - # Min area results are just flagged for now. - debug.check(self.active_width * self.active_height >= drc("minarea_active"), + debug.check(self.active_width * self.active_height >= self.minarea_active, "Minimum active area violated.") # We do not want to increase the poly dimensions to fix # an area problem as it would cause an LVS issue. - debug.check(self.poly_width * self.poly_height >= drc("minarea_poly"), + debug.check(self.poly_width * self.poly_height >= self.minarea_poly, "Minimum poly area violated.") def connect_fingered_poly(self, poly_positions): @@ -208,74 +269,62 @@ class ptx(design.design): poly_offset = poly_positions[0] + vector(-0.5 * self.poly_width, distance_above_active) # Remove the old pin and add the new one - self.remove_layout_pin("G") # only keep the main pin + # only keep the main pin + self.remove_layout_pin("G") self.add_layout_pin(text="G", layer="poly", offset=poly_offset, width=poly_width, - height=drc("minwidth_poly")) + height=self.poly_width) - - def connect_fingered_active(self, drain_positions, source_positions): + def connect_fingered_active(self, positions, pin_name, top): """ Connect each contact up/down to a source or drain pin """ - + + if len(positions) <= 1: + return + + layer_space = getattr(self, "{}_space".format(self.route_layer)) + layer_width = getattr(self, "{}_width".format(self.route_layer)) + # This is the distance that we must route up or down from the center # of the contacts to avoid DRC violations to the other contacts - pin_offset = vector(0, 0.5 * self.active_contact.second_layer_height \ - + self.m1_space + 0.5 * self.m1_width) + pin_offset = vector(0, + 0.5 * self.active_contact.second_layer_height + layer_space + 0.5 * layer_width) # This is the width of a m1 extend the ends of the pin - end_offset = vector(self.m1_width/2,0) + end_offset = vector(layer_width / 2.0, 0) - # drains always go to the MIDDLE of the cell, - # so top of NMOS, bottom of PMOS - # so reverse the directions for NMOS compared to PMOS. - if self.tx_type == "pmos": - drain_dir = -1 - source_dir = 1 + # We move the opposite direction from the bottom + if not top: + offset = pin_offset.scale(-1, -1) else: - drain_dir = 1 - source_dir = -1 - - if len(source_positions) > 1: - source_offset = pin_offset.scale(source_dir,source_dir) - self.remove_layout_pin("S") # remove the individual connections - # Add each vertical segment - for a in source_positions: - self.add_path(("metal1"), - [a, a + pin_offset.scale(source_dir, - source_dir)]) - # Add a single horizontal pin - self.add_layout_pin_segment_center(text="S", - layer="metal1", - start=source_positions[0] + source_offset - end_offset, - end=source_positions[-1] + source_offset + end_offset) + offset = pin_offset + + # remove the individual connections + self.remove_layout_pin(pin_name) + # Add each vertical segment + for a in positions: + self.add_path(self.route_layer, + [a, a + offset]) + # Add a single horizontal pin + self.add_layout_pin_segment_center(text=pin_name, + layer=self.route_layer, + start=positions[0] + offset - end_offset, + end=positions[-1] + offset + end_offset) - if len(drain_positions)>1: - drain_offset = pin_offset.scale(drain_dir,drain_dir) - self.remove_layout_pin("D") # remove the individual connections - # Add each vertical segment - for a in drain_positions: - self.add_path(("metal1"), [a,a+drain_offset]) - # Add a single horizontal pin - self.add_layout_pin_segment_center(text="D", - layer="metal1", - start=drain_positions[0] + drain_offset - end_offset, - end=drain_positions[-1] + drain_offset + end_offset) - def add_poly(self): """ 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.active_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 @@ -286,114 +335,197 @@ 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): """ Adding the diffusion (active region = diffusion region) """ - self.add_rect(layer="active", - offset=self.active_offset, - width=self.active_width, - height=self.active_height) + self.active = self.add_rect(layer="active", + offset=self.active_offset, + width=self.active_width, + height=self.active_height) # If the implant must enclose the active, shift offset # and increase width/height - enclose_width = drc("implant_enclosure_active") + enclose_width = self.implant_enclose_active enclose_offset = [enclose_width] * 2 - self.add_rect(layer="{}implant".format(self.implant_type), - offset=self.active_offset - enclose_offset, - width=self.active_width + 2 * enclose_width, - height=self.active_height + 2 * enclose_width) + self.implant = self.add_rect(layer="{}implant".format(self.implant_type), + offset=self.active_offset - enclose_offset, + width=self.active_width + 2 * enclose_width, + height=self.active_height + 2 * enclose_width) def add_well_implant(self): """ Add an (optional) well and implant for the type of transistor. """ - if drc("has_{}well".format(self.well_type)): - self.add_rect(layer="{}well".format(self.well_type), - offset=(0,0), - width=self.cell_well_width, - height=self.cell_well_height) - self.add_rect(layer="vtg", - offset=(0,0), - width=self.cell_well_width, - height=self.cell_well_height) + well_name = "{}well".format(self.well_type) + if not (well_name in layer or "vtg" in layer): + return + center_pos = self.active_offset + vector(0.5 * self.active_width, + 0.5 * self.active_height) + well_ll = center_pos - vector(0.5 * self.well_width, + 0.5 * self.well_height) + if well_name in layer: + well = self.add_rect(layer=well_name, + offset=well_ll, + width=self.well_width, + height=self.well_height) + setattr(self, well_name, well) + + if "vtg" in layer: + self.add_rect(layer="vtg", + offset=well_ll, + width=self.well_width, + height=self.well_height) def calculate_num_contacts(self): - """ + """ Calculates the possible number of source/drain contacts in a finger. For now, it is hard set as 1. """ return 1 - - def get_contact_positions(self): - """ - Create a list of the centers of drain and source contact positions. - """ - # 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_pitch, 0)) - else: - # It's a drain... so offset from previous source. - drain_positions.append(source_positions[-1] + vector(self.contact_pitch, 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() + # 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 + if self.add_source_contact: + contact = self.add_diff_contact(label, pos) + self.source_contacts.append(contact) + else: + self.add_layout_pin_rect_center(text=label, + layer="active", + offset=pos) + source_positions.append(pos) - for pos in source_positions: - contact=self.add_via_center(layers=("active", "contact", "metal1"), - offset=pos, - size=(1, self.num_contacts), - directions=("H","V"), - implant_type=self.implant_type, - well_type=self.well_type) - self.add_layout_pin_rect_center(text="S", - layer="metal1", - offset=pos, - width=contact.mod.second_layer_width, - height=contact.mod.second_layer_height) + # 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) + + if (label=="S" and self.add_source_contact): + contact = self.add_diff_contact(label, pos) + self.source_contacts.append(contact) + elif (label=="D" and self.add_drain_contact): + contact = self.add_diff_contact(label, pos) + self.drain_contacts.append(contact) + else: + self.add_layout_pin_rect_center(text=label, + layer="active", + offset=pos) + 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) + + if (label=="S" and self.add_source_contact): + contact = self.add_diff_contact(label, pos) + self.source_contacts.append(contact) + elif (label=="D" and self.add_drain_contact): + contact = self.add_diff_contact(label, pos) + self.drain_contacts.append(contact) + else: + self.add_layout_pin_rect_center(text=label, + layer="active", + offset=pos) - for pos in drain_positions: - contact=self.add_via_center(layers=("active", "contact", "metal1"), - offset=pos, - size=(1, self.num_contacts), - directions=("H","V"), - implant_type=self.implant_type, - well_type=self.well_type) - self.add_layout_pin_rect_center(text="D", - layer="metal1", - offset=pos, - width=contact.mod.second_layer_width, - height=contact.mod.second_layer_height) - - if self.connect_active: - self.connect_fingered_active(drain_positions, source_positions) + if self.connect_source_active: + self.connect_fingered_active(source_positions, "S", top=(self.tx_type=="pmos")) + if self.connect_drain_active: + self.connect_fingered_active(drain_positions, "D", top=(self.tx_type=="nmos")) + + def get_stage_effort(self, cout): + """Returns an object representing the parameters for delay in tau units.""" + + # FIXME: Using the same definition as the pinv.py. + parasitic_delay = 1 + size = self.mults * self.tx_width / drc("minwidth_tx") + return logical_effort.logical_effort(self.name, + size, + self.input_load(), + cout, + parasitic_delay) + + def input_load(self): + """ + Returns the relative gate cin of the tx + """ + + # FIXME: this will be applied for the loads of the drain/source + return self.mults * self.tx_width / drc("minwidth_tx") + + def add_diff_contact(self, label, pos): + + if label == "S": + layer = self.add_source_contact + elif label == "D": + layer = self.add_drain_contact + else: + debug.error("Invalid source drain name.") + + if layer != "active": + via=self.add_via_stack_center(offset=pos, + from_layer="active", + to_layer=layer, + size=(1, self.num_contacts), + directions=("V", "V"), + implant_type=self.implant_type, + well_type=self.well_type) + + pin_height = via.mod.second_layer_height + pin_width = via.mod.second_layer_width + else: + via = None + + pin_height = None + pin_width = None + + # Source drain vias are all vertical + self.add_layout_pin_rect_center(text=label, + layer=layer, + offset=pos, + width=pin_width, + height=pin_height) + + return(via) + def get_cin(self): """Returns the relative gate cin of the tx""" return self.tx_width / drc("minwidth_tx") diff --git a/compiler/pgates/pwrite_driver.py b/compiler/pgates/pwrite_driver.py index a808737b..87db7b20 100644 --- a/compiler/pgates/pwrite_driver.py +++ b/compiler/pgates/pwrite_driver.py @@ -6,14 +6,13 @@ #All rights reserved. # import design -from tech import drc, parameter, spice +from tech import parameter import debug -import math -from tech import drc from vector import vector from globals import OPTS from sram_factory import factory + class pwrite_driver(design.design): """ The pwrite_driver is two tristate inverters that drive the bitlines. @@ -40,12 +39,10 @@ class pwrite_driver(design.design): # Creates the netlist and layout # Since it has variable height, it is not a pgate. self.create_netlist() - if not OPTS.netlist_only: + if not OPTS.netlist_only: self.create_layout() self.DRC_LVS() - - def create_netlist(self): self.add_pins() self.add_modules() @@ -55,8 +52,7 @@ class pwrite_driver(design.design): self.place_modules() self.route_wires() self.route_supplies() - - + self.add_boundary() def add_pins(self): self.add_pin("din", "INPUT") @@ -66,17 +62,19 @@ class pwrite_driver(design.design): self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") - def add_modules(self): # Tristate inverter self.tri = factory.create(module_type="ptristate_inv", height="min") self.add_mod(self.tri) - debug.check(self.tri.width nmos_upper/D on metal1 # bl_out -> nmos_upper/S on metal2 - self.add_path("metal1", + self.add_path(self.col_mux_stack[0], [bl_pin.ll(), vector(nmos_upper_d_pin.cx(), bl_pin.by()), nmos_upper_d_pin.center()]) # halfway up, move over mid1 = bl_out_pin.uc().scale(1, 0.4) \ + nmos_upper_s_pin.bc().scale(0, 0.4) mid2 = bl_out_pin.uc().scale(0, 0.4) \ - + nmos_upper_s_pin.bc().scale(1, 0.4) - self.add_path("metal2", - [bl_out_pin.uc(), mid1, mid2, nmos_upper_s_pin.bc()]) - + + nmos_upper_s_pin.bc().scale(1, 0.4) + self.add_path(self.col_mux_stack[2], + [bl_out_pin.uc(), mid1, mid2, nmos_upper_s_pin.center()]) + # br -> nmos_lower/D on metal2 # br_out -> nmos_lower/S on metal1 - self.add_path("metal1", + self.add_path(self.col_mux_stack[0], [br_out_pin.uc(), vector(nmos_lower_s_pin.cx(), br_out_pin.uy()), nmos_lower_s_pin.center()]) # halfway up, move over - mid1 = br_pin.bc().scale(1,0.5) \ - + nmos_lower_d_pin.uc().scale(0,0.5) - mid2 = br_pin.bc().scale(0,0.5) \ - + nmos_lower_d_pin.uc().scale(1,0.5) - self.add_path("metal2", - [br_pin.bc(), mid1, mid2, nmos_lower_d_pin.uc()]) - - def add_wells(self): + mid1 = br_pin.bc().scale(1, 0.5) \ + + nmos_lower_d_pin.uc().scale(0, 0.5) + mid2 = br_pin.bc().scale(0, 0.5) \ + + nmos_lower_d_pin.uc().scale(1, 0.5) + self.add_path(self.col_mux_stack[2], + [br_pin.bc(), mid1, mid2, nmos_lower_d_pin.center()]) + + def add_implants(self): + """ + Add top-to-bottom implants for adjacency issues in s8. + """ + # Route to the bottom + ll = (self.nmos_lower.ll() - vector(2 * [self.implant_enclose_active])).scale(1, 0) + # Don't route to the top + ur = self.nmos_upper.ur() + vector(self.implant_enclose_active, 0) + self.add_rect("nimplant", + ll, + ur.x - ll.x, + ur.y - ll.y) + + def add_pn_wells(self): """ Add a well and implant over the whole cell. Also, add the pwell contact (if it exists) @@ -175,26 +221,27 @@ class single_level_column_mux(pgate.pgate): # Add it to the right, aligned in between the two tx active_pos = vector(self.bitcell.width, self.nmos_upper.by() - 0.5 * self.poly_space) - self.add_via_center(layers=("active", "contact", "metal1"), + self.add_via_center(layers=self.active_stack, offset=active_pos, implant_type="p", well_type="p") - # Add the M1->M2->M3 stack - self.add_via_center(layers=("metal1", "via1", "metal2"), + # If there is a li layer, include it in the power stack + self.add_via_center(layers=self.col_mux_stack, offset=active_pos) - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=active_pos) - self.add_layout_pin_rect_center(text="gnd", - layer="metal3", - offset=active_pos) + + # Add the M1->..->power_grid_layer stack + self.add_power_pin(name="gnd", + loc=active_pos, + start_layer="m1") # Add well enclosure over all the tx and contact - self.add_rect(layer="pwell", - offset=vector(0, 0), - width=self.bitcell.width, - height=self.height) - + if "pwell" in layer: + self.add_rect(layer="pwell", + offset=vector(0, 0), + width=self.bitcell.width, + height=self.height) + def get_stage_effort(self, corner, slew, load): """ Returns relative delay that the column mux. @@ -209,4 +256,3 @@ class single_level_column_mux(pgate.pgate): load, parasitic_delay, False) - diff --git a/compiler/pgates/wordline_driver.py b/compiler/pgates/wordline_driver.py new file mode 100644 index 00000000..c8cf1326 --- /dev/null +++ b/compiler/pgates/wordline_driver.py @@ -0,0 +1,151 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +from vector import vector +import design +from sram_factory import factory +from globals import OPTS +from tech import layer + + +class wordline_driver(design.design): + """ + This is an AND (or NAND) with configurable drive strength to drive the wordlines. + It is matched to the bitcell height. + """ + def __init__(self, name, size=1, height=None): + debug.info(1, "Creating wordline_driver {}".format(name)) + self.add_comment("size: {}".format(size)) + design.design.__init__(self, name) + + if height is None: + b = factory.create(module_type="bitcell") + self.height = b.height + else: + self.height = height + self.size = size + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.add_pins() + self.create_modules() + self.create_insts() + + def create_modules(self): + self.nand = factory.create(module_type="nand2_dec", + height=self.height) + + self.driver = factory.create(module_type="inv_dec", + size=self.size, + height=self.nand.height) + + self.add_mod(self.nand) + self.add_mod(self.driver) + + def create_layout(self): + self.width = self.nand.width + self.driver.width + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + + self.place_insts() + self.route_wires() + self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + self.add_pin("A", "INPUT") + self.add_pin("B", "INPUT") + self.add_pin("Z", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def create_insts(self): + self.nand_inst = self.add_inst(name="wld_nand", + mod=self.nand) + self.connect_inst(["A", "B", "zb_int", "vdd", "gnd"]) + + self.driver_inst = self.add_inst(name="wl_driver", + mod=self.driver) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + + def place_insts(self): + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) + + # Add INV to the right + self.driver_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + if OPTS.tech_name == "sky130": + for name in ["vdd", "gnd"]: + for inst in [self.nand_inst, self.driver_inst]: + self.copy_layout_pin(inst, name) + else: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + + y_offset = self.height + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, y_offset), + width=self.width) + + def route_wires(self): + + # nand Z to inv A + z1_pin = self.nand_inst.get_pin("Z") + a2_pin = self.driver_inst.get_pin("A") + if OPTS.tech_name == "sky130": + mid1_point = vector(a2_pin.cx(), z1_pin.cy()) + else: + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(self.route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + + def add_layout_pins(self): + pin = self.driver_inst.get_pin("Z") + self.add_layout_pin_rect_center(text="Z", + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + for pin_name in ["A", "B"]: + pin = self.nand_inst.get_pin(pin_name) + self.add_layout_pin_rect_center(text=pin_name, + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + def get_stage_efforts(self, external_cout, inp_is_rise=False): + """Get the stage efforts of the A or B -> Z path""" + stage_effort_list = [] + stage1_cout = self.driver.get_cin() + stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) + stage_effort_list.append(stage1) + + stage2 = self.driver.get_stage_effort(external_cout, stage1.is_rise) + stage_effort_list.append(stage2) + + return stage_effort_list + + def get_cin(self): + """Return the relative input capacitance of a single input""" + return self.nand.get_cin() + diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py index 99986e76..8a1362c2 100644 --- a/compiler/router/pin_group.py +++ b/compiler/router/pin_group.py @@ -7,42 +7,44 @@ # from direction import direction from pin_layout import pin_layout -from vector3d import vector3d from vector import vector -import grid_utils -from tech import drc import debug + class pin_group: """ - A class to represent a group of rectangular design pin. - It requires a router to define the track widths and blockages which - determine how pin shapes get mapped to tracks. + A class to represent a group of rectangular design pin. + It requires a router to define the track widths and blockages which + determine how pin shapes get mapped to tracks. It is initially constructed with a single set of (touching) pins. """ + def __init__(self, name, pin_set, router): self.name = name # Flag for when it is routed self.routed = False # Flag for when it is enclosed self.enclosed = False - + # Remove any redundant pins (i.e. contained in other pins) irredundant_pin_set = self.remove_redundant_shapes(list(pin_set)) - - # This is a list because we can have a pin group of disconnected sets of pins + + # This is a list because we can have a pin + # group of disconnected sets of pins # and these are represented by separate lists self.pins = set(irredundant_pin_set) self.router = router # These are the corresponding pin grids for each pin group. self.grids = set() - # These are the secondary grids that could or could not be part of the pin + # These are the secondary grids that could + # or could not be part of the pin self.secondary_grids = set() - + # The corresponding set of partially blocked grids for each pin group. - # These are blockages for other nets but unblocked for routing this group. - # These are also blockages if we used a simple enclosure to route to a rail. + # These are blockages for other nets but unblocked + # for routing this group. These are also blockages if we + # used a simple enclosure to route to a rail. self.blockages = set() # This is a set of pin_layout shapes to cover the grids @@ -51,16 +53,16 @@ class pin_group: def __str__(self): """ override print function output """ total_string = "(pg {} ".format(self.name) - + pin_string = "\n pins={}".format(self.pins) total_string += pin_string - + grids_string = "\n grids={}".format(self.grids) total_string += grids_string grids_string = "\n secondary={}".format(self.secondary_grids) total_string += grids_string - + if self.enclosed: enclosure_string = "\n enclose={}".format(self.enclosures) total_string += enclosure_string @@ -71,7 +73,7 @@ class pin_group: def __repr__(self): """ override repr function output """ return str(self) - + def size(self): return len(self.grids) @@ -80,7 +82,7 @@ class pin_group: def is_routed(self): return self.routed - + def remove_redundant_shapes(self, pin_list): """ Remove any pin layout that is contained within another. @@ -88,39 +90,40 @@ class pin_group: """ local_debug = False if local_debug: - debug.info(0,"INITIAL: {}".format(pin_list)) - + debug.info(0, "INITIAL: {}".format(pin_list)) + # Make a copy of the list to start new_pin_list = pin_list.copy() remove_indices = set() # This is n^2, but the number is small - for index1,pin1 in enumerate(pin_list): + for index1, pin1 in enumerate(pin_list): # If we remove this pin, it can't contain other pins if index1 in remove_indices: continue - - for index2,pin2 in enumerate(pin_list): - # Can't contain yourself, but compare the indices and not the pins + + for index2, pin2 in enumerate(pin_list): + # Can't contain yourself, + # but compare the indices and not the pins # so you can remove duplicate copies. - if index1==index2: + if index1 == index2: continue # If we already removed it, can't remove it again... if index2 in remove_indices: continue - + if pin1.contains(pin2): if local_debug: - debug.info(0,"{0} contains {1}".format(pin1,pin2)) + debug.info(0, "{0} contains {1}".format(pin1, pin2)) remove_indices.add(index2) # Remove them in decreasing order to not invalidate the indices for i in sorted(remove_indices, reverse=True): del new_pin_list[i] - + if local_debug: - debug.info(0,"FINAL : {}".format(new_pin_list)) - + debug.info(0, "FINAL : {}".format(new_pin_list)) + return new_pin_list def compute_enclosures(self): @@ -130,23 +133,32 @@ class pin_group: # Enumerate every possible enclosure pin_list = [] for seed in self.grids: - (ll, ur) = self.enclose_pin_grids(seed, direction.NORTH, direction.EAST) + (ll, ur) = self.enclose_pin_grids(seed, + direction.NORTH, + direction.EAST) enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z) pin_list.append(enclosure) - (ll, ur) = self.enclose_pin_grids(seed, direction.EAST, direction.NORTH) + (ll, ur) = self.enclose_pin_grids(seed, + direction.EAST, + direction.NORTH) enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z) pin_list.append(enclosure) + debug.check(len(pin_list) > 0, + "Did not find any enclosures.") - # Now simplify the enclosure list + # Now simplify the enclosure list new_pin_list = self.remove_redundant_shapes(pin_list) - - return new_pin_list + + debug.check(len(new_pin_list) > 0, + "Did not find any enclosures.") + return new_pin_list + def compute_connector(self, pin, enclosure): - """ - Compute a shape to connect the pin to the enclosure shape. + """ + Compute a shape to connect the pin to the enclosure shape. This assumes the shape will be the dimension of the pin. """ if pin.xoverlaps(enclosure): @@ -154,9 +166,9 @@ class pin_group: plc = pin.lc() prc = pin.rc() elc = enclosure.lc() - erc = enclosure.rc() - ymin = min(plc.y,elc.y) - ymax = max(plc.y,elc.y) + # erc = enclosure.rc() + ymin = min(plc.y, elc.y) + ymax = max(plc.y, elc.y) ll = vector(plc.x, ymin) ur = vector(prc.x, ymax) elif pin.yoverlaps(enclosure): @@ -164,9 +176,9 @@ class pin_group: pbc = pin.bc() puc = pin.uc() ebc = enclosure.bc() - euc = enclosure.uc() - xmin = min(pbc.x,ebc.x) - xmax = max(pbc.x,ebc.x) + # euc = enclosure.uc() + xmin = min(pbc.x, ebc.x) + xmax = max(pbc.x, ebc.x) ll = vector(xmin, pbc.y) ur = vector(xmax, puc.y) else: @@ -180,7 +192,7 @@ class pin_group: ll = vector(xmin, ymin) ur = vector(xmax, ymax) - if ll.x==ur.x or ll.y==ur.y: + if ll.x == ur.x or ll.y == ur.y: return None p = pin_layout(pin.name, [ll, ur], pin.layer) return p @@ -195,20 +207,20 @@ class pin_group: for shape in enclosures: if shape.xcontains(pin): edge_list.append(shape) - + # Sort them by their bottom edge edge_list.sort(key=lambda x: x.by(), reverse=True) # Find the bottom edge that is next to the pin's top edge above_item = None for item in edge_list: - if item.by()>=pin.uy(): + if item.by() >= pin.uy(): above_item = item else: break - # There was nothing - if above_item==None: + # There was nothing + if not above_item: return None # If it already overlaps, no connector needed if above_item.overlaps(pin): @@ -219,7 +231,7 @@ class pin_group: return p def find_below_connector(self, pin, enclosures): - """ + """ Find the enclosure that is below the pin and make a connector to it's upper edge. """ @@ -228,31 +240,31 @@ class pin_group: for shape in enclosures: if shape.xcontains(pin): edge_list.append(shape) - + # Sort them by their upper edge edge_list.sort(key=lambda x: x.uy()) # Find the upper edge that is next to the pin's bottom edge bottom_item = None for item in edge_list: - if item.uy()<=pin.by(): + if item.uy() <= pin.by(): bottom_item = item else: break # There was nothing to the left - if bottom_item==None: + if not bottom_item: return None # If it already overlaps, no connector needed if bottom_item.overlaps(pin): return None - + # Otherwise, make a connector to the item p = self.compute_connector(pin, bottom_item) return p - + def find_left_connector(self, pin, enclosures): - """ + """ Find the enclosure that is to the left of the pin and make a connector to it's right edge. """ @@ -261,31 +273,31 @@ class pin_group: for shape in enclosures: if shape.ycontains(pin): edge_list.append(shape) - + # Sort them by their right edge edge_list.sort(key=lambda x: x.rx()) # Find the right edge that is to the pin's left edge left_item = None for item in edge_list: - if item.rx()<=pin.lx(): + if item.rx() <= pin.lx(): left_item = item else: break # There was nothing to the left - if left_item==None: + if not left_item: return None # If it already overlaps, no connector needed if left_item.overlaps(pin): return None - + # Otherwise, make a connector to the item p = self.compute_connector(pin, left_item) return p - + def find_right_connector(self, pin, enclosures): - """ + """ Find the enclosure that is to the right of the pin and make a connector to it's left edge. """ @@ -294,79 +306,79 @@ class pin_group: for shape in enclosures: if shape.ycontains(pin): edge_list.append(shape) - + # Sort them by their right edge edge_list.sort(key=lambda x: x.lx(), reverse=True) # Find the left edge that is next to the pin's right edge right_item = None for item in edge_list: - if item.lx()>=pin.rx(): + if item.lx() >= pin.rx(): right_item = item else: break # There was nothing to the right - if right_item==None: + if not right_item: return None # If it already overlaps, no connector needed if right_item.overlaps(pin): return None - + # Otherwise, make a connector to the item p = self.compute_connector(pin, right_item) return p - + def find_smallest_connector(self, pin_list, shape_list): """ - Compute all of the connectors between the overlapping pins and enclosure shape list.. + Compute all of the connectors between the overlapping + pins and enclosure shape list. Return the smallest. """ smallest = None for pin in pin_list: for enclosure in shape_list: new_enclosure = self.compute_connector(pin, enclosure) - if smallest == None or new_enclosure.area() min_width: - if smallest_shape == None or other.area() biggest.area(): biggest = pin - + return pin def enclose_pin_grids(self, ll, dir1=direction.NORTH, dir2=direction.EAST): @@ -398,19 +409,18 @@ class pin_group: dir1 and dir2 should be two orthogonal directions. """ - offset1= direction.get_offset(dir1) - offset2= direction.get_offset(dir2) + offset1 = direction.get_offset(dir1) + offset2 = direction.get_offset(dir2) # We may have started with an empty set - if not self.grids: - return None + debug.check(len(self.grids) > 0, "Cannot seed an grid empty set.") # Start with the ll and make the widest row row = [ll] # Move in dir1 while we can while True: next_cell = row[-1] + offset1 - # Can't move if not in the pin shape + # Can't move if not in the pin shape if next_cell in self.grids and next_cell not in self.router.blocked_grids: row.append(next_cell) else: @@ -419,7 +429,7 @@ class pin_group: while True: next_row = [x+offset2 for x in row] for cell in next_row: - # Can't move if any cell is not in the pin shape + # Can't move if any cell is not in the pin shape if cell not in self.grids or cell in self.router.blocked_grids: break else: @@ -431,74 +441,87 @@ class pin_group: # Add a shape from ll to ur ur = row[-1] - return (ll,ur) + return (ll, ur) - def enclose_pin(self): """ - If there is one set of connected pin shapes, - this will find the smallest rectangle enclosure that overlaps with any pin. + If there is one set of connected pin shapes, + this will find the smallest rectangle enclosure that + overlaps with any pin. If there is not, it simply returns all the enclosures. """ self.enclosed = True - + # Compute the enclosure pin_layout list of the set of tracks self.enclosures = self.compute_enclosures() # Find a connector to every pin and add it to the enclosures for pin in self.pins: - + # If it is contained, it won't need a connector if pin.contained_by_any(self.enclosures): continue # Find a connector in the cardinal directions - # If there is overlap, but it isn't contained, these could all be None - # These could also be none if the pin is diagonal from the enclosure + # If there is overlap, but it isn't contained, + # these could all be None + # These could also be none if the pin is + # diagonal from the enclosure left_connector = self.find_left_connector(pin, self.enclosures) right_connector = self.find_right_connector(pin, self.enclosures) above_connector = self.find_above_connector(pin, self.enclosures) below_connector = self.find_below_connector(pin, self.enclosures) - connector_list = [left_connector, right_connector, above_connector, below_connector] - filtered_list = list(filter(lambda x: x!=None, connector_list)) - if (len(filtered_list)>0): + connector_list = [left_connector, + right_connector, + above_connector, + below_connector] + filtered_list = list(filter(lambda x: x != None, connector_list)) + if (len(filtered_list) > 0): import copy - bbox_connector = copy.copy(pin) + bbox_connector = copy.copy(pin) bbox_connector.bbox(filtered_list) self.enclosures.append(bbox_connector) - # Now, make sure each pin touches an enclosure. If not, add another (diagonal) connector. - # This could only happen when there was no enclosure in any cardinal direction from a pin + # Now, make sure each pin touches an enclosure. + # If not, add another (diagonal) connector. + # This could only happen when there was no enclosure + # in any cardinal direction from a pin if not self.overlap_any_shape(self.pins, self.enclosures): - connector = self.find_smallest_connector(self.pins, self.enclosures) - if connector==None: - debug.error("Could not find a connector for {} with {}".format(self.pins, self.enclosures)) + connector = self.find_smallest_connector(self.pins, + self.enclosures) + if not connector: + debug.error("Could not find a connector for {} with {}".format(self.pins, + self.enclosures)) self.router.write_debug_gds("no_connector.gds") + import pdb; pdb.set_trace() self.enclosures.append(connector) - - # At this point, the pins are overlapping, but there might be more than one! + + # At this point, the pins are overlapping, + # but there might be more than one! overlap_set = set() for pin in self.pins: overlap_set.update(self.transitive_overlap(pin, self.enclosures)) - # Use the new enclosures and recompute the grids that correspond to them - if len(overlap_set)0: - debug.info(2,"Removing pins {}".format(shared_set)) + if len(shared_set) > 0: + debug.info(2, "Removing pins {}".format(shared_set)) pin_set.difference_update(shared_set) shared_set = partial_set & self.router.blocked_grids - if len(shared_set)>0: - debug.info(2,"Removing pins {}".format(shared_set)) + if len(shared_set) > 0: + debug.info(2, "Removing pins {}".format(shared_set)) partial_set.difference_update(shared_set) shared_set = blockage_set & self.router.blocked_grids - if len(shared_set)>0: - debug.info(2,"Removing blocks {}".format(shared_set)) + if len(shared_set) > 0: + debug.info(2, "Removing blocks {}".format(shared_set)) blockage_set.difference_update(shared_set) - + # At least one of the groups must have some valid tracks - if (len(pin_set)==0 and len(partial_set)==0 and len(blockage_set)==0): - #debug.warning("Pin is very close to metal blockage.\nAttempting to expand blocked pin {}".format(self.pins)) - + if (len(pin_set) == 0 and len(partial_set) == 0 and len(blockage_set) == 0): + # debug.warning("Pin is very close to metal blockage.\nAttempting to expand blocked pin {}".format(self.pins)) + for pin in self.pins: debug.warning(" Expanding conversion {0}".format(pin)) - # Determine which tracks the pin overlaps - (sufficient,insufficient)=self.router.convert_pin_to_tracks(self.name, pin, expansion=1) + # Determine which tracks the pin overlaps + (sufficient, insufficient) = self.router.convert_pin_to_tracks(self.name, + pin, + expansion=1) pin_set.update(sufficient) partial_set.update(insufficient) - - if len(pin_set)==0 and len(partial_set)==0: - debug.error("Unable to find unblocked pin {} {}".format(self.name, self.pins)) + + if len(pin_set) == 0 and len(partial_set) == 0: + debug.error("Unable to find unblocked pin {} {}".format(self.name, + self.pins)) self.router.write_debug_gds("blocked_pin.gds") # Consider all the grids that would be blocked - self.grids = pin_set | partial_set - # Remember the secondary grids for removing adjacent pins - self.secondary_grids = partial_set + self.grids = pin_set | partial_set + if len(self.grids) < 0: + debug.error("Did not find any unblocked grids: {}".format(str(self.pins))) + self.router.write_debug_gds("blocked_pin.gds") + + # Remember the secondary grids for removing adjacent pins + self.secondary_grids = partial_set - debug.info(2," pins {}".format(self.grids)) - debug.info(2," secondary {}".format(self.secondary_grids)) - - - + debug.info(2, " pins {}".format(self.grids)) + debug.info(2, " secondary {}".format(self.secondary_grids)) diff --git a/compiler/router/router.py b/compiler/router/router.py index fd95def6..cacd5117 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -5,9 +5,9 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys + import gdsMill -from tech import drc,GDS +from tech import drc, GDS from tech import layer as techlayer import math import debug @@ -15,23 +15,23 @@ from router_tech import router_tech from pin_layout import pin_layout from pin_group import pin_group from vector import vector -from vector3d import vector3d -from globals import OPTS,print_time -from pprint import pformat +from vector3d import vector3d +from globals import OPTS, print_time import grid_utils from datetime import datetime + class router(router_tech): """ A router class to read an obstruction map from a gds and plan a route on a given layer. This is limited to two layer routes. It populates blockages on a grid class. """ - def __init__(self, layers, design, gds_filename=None, rail_track_width=1): """ This will instantiate a copy of the gds file or the module at (0,0) and - route on top of this. The blockages from the gds/module will be considered. + route on top of this. The blockages from the gds/module will be + considered. """ router_tech.__init__(self, layers, rail_track_width) @@ -39,7 +39,7 @@ class router(router_tech): # If didn't specify a gds blockage file, write it out to read the gds # This isn't efficient, but easy for now - #start_time = datetime.now() + # start_time = datetime.now() if not gds_filename: gds_filename = OPTS.openram_temp+"temp.gds" self.cell.gds_write(gds_filename) @@ -49,36 +49,41 @@ class router(router_tech): self.reader = gdsMill.Gds2reader(self.layout) self.reader.loadFromFile(gds_filename) self.top_name = self.layout.rootStructureName - #print_time("GDS read",datetime.now(), start_time) + # print_time("GDS read",datetime.now(), start_time) - ### The pin data structures + # The pin data structures # A map of pin names to a set of pin_layout structures # (i.e. pins with a given label) self.pins = {} - # This is a set of all pins (ignoring names) so that can quickly not create blockages for pins - # (They will be blocked when we are routing other nets based on their name.) + # This is a set of all pins (ignoring names) so that can quickly + # not create blockages for pins + # (They will be blocked when we are routing other + # nets based on their name.) self.all_pins = set() - # The labeled pins above categorized into pin groups that are touching/connected. + # The labeled pins above categorized into pin groups + # that are touching/connected. self.pin_groups = {} - ### The blockage data structures - # A list of metal shapes (using the same pin_layout structure) that are not pins but blockages. - self.blockages=[] + # The blockage data structures + # A list of metal shapes (using the same pin_layout structure) + # that are not pins but blockages. + self.blockages = [] # The corresponding set of blocked grids for above pin shapes self.blocked_grids = set() - ### The routed data structures + # The routed data structures # A list of paths that have been "routed" self.paths = [] # A list of path blockages (they might be expanded for wide metal DRC) self.path_blockages = [] - # The boundary will determine the limits to the size of the routing grid + # The boundary will determine the limits to the size + # of the routing grid self.boundary = self.layout.measureBoundary(self.top_name) # These must be un-indexed to get rid of the matrix type self.ll = vector(self.boundary[0][0], self.boundary[0][1]) - self.ur = vector(self.boundary[1][0], self.boundary[1][1]) + self.ur = vector(self.boundary[1][0], self.boundary[1][1]) def clear_pins(self): """ @@ -87,93 +92,93 @@ class router(router_tech): """ self.pins = {} self.all_pins = set() - self.pin_groups = {} + self.pin_groups = {} # DO NOT clear the blockages as these don't change self.rg.reinit() - - def set_top(self,top_name): + def set_top(self, top_name): """ If we want to route something besides the top-level cell.""" self.top_name = top_name - - def is_wave(self,path): + def is_wave(self, path): """ - Determines if this is a multi-track width wave (True) or a normal route (False) + Determines if this is a multi-track width wave (True) + # or a normal route (False) """ - return len(path[0])>1 + return len(path[0]) > 1 - - def retrieve_pins(self,pin_name): + def retrieve_pins(self, pin_name): """ Retrieve the pin shapes on metal 3 from the layout. """ - debug.info(2,"Retrieving pins for {}.".format(pin_name)) - shape_list=self.layout.getAllPinShapes(str(pin_name)) + debug.info(2, "Retrieving pins for {}.".format(pin_name)) + shape_list = self.layout.getAllPinShapes(str(pin_name)) pin_set = set() for shape in shape_list: - (layer,boundary)=shape + (layer, boundary) = shape # GDSMill boundaries are in (left, bottom, right, top) order # so repack and snap to the grid - ll = vector(boundary[0],boundary[1]).snap_to_grid() - ur = vector(boundary[2],boundary[3]).snap_to_grid() - rect = [ll,ur] + ll = vector(boundary[0], boundary[1]).snap_to_grid() + ur = vector(boundary[2], boundary[3]).snap_to_grid() + rect = [ll, ur] pin = pin_layout(pin_name, rect, layer) pin_set.add(pin) - debug.check(len(pin_set)>0,"Did not find any pin shapes for {0}.".format(str(pin_name))) + debug.check(len(pin_set) > 0, + "Did not find any pin shapes for {0}.".format(str(pin_name))) self.pins[pin_name] = pin_set self.all_pins.update(pin_set) for pin in self.pins[pin_name]: - debug.info(3,"Retrieved pin {}".format(str(pin))) - + debug.info(3, "Retrieved pin {}".format(str(pin))) def find_blockages(self): """ Iterate through all the layers and write the obstacles to the routing grid. - This doesn't consider whether the obstacles will be pins or not. They get reset later - if they are not actually a blockage. + This doesn't consider whether the obstacles will be pins or not. + They get reset later if they are not actually a blockage. """ - debug.info(1,"Finding blockages.") - for layer in [self.vert_layer_number,self.horiz_layer_number]: - self.retrieve_blockages(layer) + debug.info(1, "Finding blockages.") + for lpp in [self.vert_lpp, self.horiz_lpp]: + self.retrieve_blockages(lpp) def find_pins_and_blockages(self, pin_list): """ - Find the pins and blockages in the design + Find the pins and blockages in the design """ - # This finds the pin shapes and sorts them into "groups" that are connected - # This must come before the blockages, so we can not count the pins themselves + # This finds the pin shapes and sorts them into "groups" that + # are connected. This must come before the blockages, so we + # can not count the pins themselves # as blockages. start_time = datetime.now() for pin_name in pin_list: self.retrieve_pins(pin_name) - print_time("Retrieving pins",datetime.now(), start_time, 4) + print_time("Retrieving pins", datetime.now(), start_time, 4) start_time = datetime.now() for pin_name in pin_list: self.analyze_pins(pin_name) - print_time("Analyzing pins",datetime.now(), start_time, 4) + print_time("Analyzing pins", datetime.now(), start_time, 4) # This will get all shapes as blockages and convert to grid units # This ignores shapes that were pins start_time = datetime.now() self.find_blockages() - print_time("Finding blockages",datetime.now(), start_time, 4) + print_time("Finding blockages", datetime.now(), start_time, 4) # Convert the blockages to grid units start_time = datetime.now() self.convert_blockages() - print_time("Converting blockages",datetime.now(), start_time, 4) + print_time("Converting blockages", datetime.now(), start_time, 4) # This will convert the pins to grid units - # It must be done after blockages to ensure no DRCs between expanded pins and blocked grids + # It must be done after blockages to ensure no DRCs + # between expanded pins and blocked grids start_time = datetime.now() for pin in pin_list: self.convert_pins(pin) - print_time("Converting pins",datetime.now(), start_time, 4) + print_time("Converting pins", datetime.now(), start_time, 4) # Combine adjacent pins into pin groups to reduce run-time # by reducing the number of maze routes. @@ -184,17 +189,18 @@ class router(router_tech): # print_time("Combining adjacent pins",datetime.now(), start_time, 4) - # Separate any adjacent grids of differing net names that overlap + # Separate any adjacent grids of differing net names + # that overlap # Must be done before enclosing pins start_time = datetime.now() self.separate_adjacent_pins(0) - print_time("Separating adjacent pins",datetime.now(), start_time, 4) + print_time("Separating adjacent pins", datetime.now(), start_time, 4) - # Enclose the continguous grid units in a metal rectangle to fix some DRCs + # Enclose the continguous grid units in a metal + # rectangle to fix some DRCs start_time = datetime.now() self.enclose_pins() - print_time("Enclosing pins",datetime.now(), start_time, 4) - + print_time("Enclosing pins", datetime.now(), start_time, 4) # MRG: Removing this code for now. The later compute enclosure code # assumes that all pins are touching and this may produce sets of pins @@ -249,57 +255,63 @@ class router(router_tech): # # Use the new pin group! # self.pin_groups[pin_name] = new_pin_groups # removed_pairs = old_size - len(new_pin_groups) - # debug.info(1, "Combined {0} pin groups for {1}".format(removed_pairs,pin_name)) + # debug.info(1, + # "Combined {0} pin groups for {1}".format(removed_pairs,pin_name)) # return removed_pairs - def separate_adjacent_pins(self, separation): """ - This will try to separate all grid pins by the supplied number of separation - tracks (default is to prevent adjacency). + This will try to separate all grid pins by the supplied + number of separation tracks (default is to prevent adjacency). """ # Commented out to debug with SCMOS - #if separation==0: - # return + # if separation==0: + # return pin_names = self.pin_groups.keys() - for i,pin_name1 in enumerate(pin_names): - for j,pin_name2 in enumerate(pin_names): - if i==j: + for i, pin_name1 in enumerate(pin_names): + for j, pin_name2 in enumerate(pin_names): + if i == j: continue - if i>j: + if i > j: return self.separate_adjacent_pin(pin_name1, pin_name2, separation) def separate_adjacent_pin(self, pin_name1, pin_name2, separation): """ - Go through all of the pin groups and check if any other pin group is + Go through all of the pin groups and check if any other pin group is within a separation of it. If so, reduce the pin group grid to not include the adjacent grid. Try to do this intelligently to keep th pins enclosed. """ - debug.info(1,"Comparing {0} and {1} adjacency".format(pin_name1, pin_name2)) + debug.info(1, + "Comparing {0} and {1} adjacency".format(pin_name1, + pin_name2)) removed_grids = 0 - for index1,pg1 in enumerate(self.pin_groups[pin_name1]): - for index2,pg2 in enumerate(self.pin_groups[pin_name2]): + for index1, pg1 in enumerate(self.pin_groups[pin_name1]): + for index2, pg2 in enumerate(self.pin_groups[pin_name2]): adj_grids = pg1.adjacent_grids(pg2, separation) removed_grids += len(adj_grids) # These should have the same length, so... - if len(adj_grids)>0: - debug.info(3,"Adjacent grids {0} {1} adj={2}".format(index1,index2,adj_grids)) + if len(adj_grids) > 0: + debug.info(3, + "Adjacent grids {0} {1} adj={2}".format(index1, + index2, + adj_grids)) self.remove_adjacent_grid(pg1, pg2, adj_grids) - debug.info(1,"Removed {} adjacent grids.".format(removed_grids)) + debug.info(1, "Removed {} adjacent grids.".format(removed_grids)) def remove_adjacent_grid(self, pg1, pg2, adj_grids): """ Remove one of the adjacent grids in a heuristic manner. - This will try to keep the groups similar sized by removing from the bigger group. + This will try to keep the groups similar sized by + removing from the bigger group. """ - if pg1.size()>pg2.size(): + if pg1.size() > pg2.size(): bigger = pg1 smaller = pg2 else: @@ -309,54 +321,58 @@ class router(router_tech): for adj in adj_grids: - # If the adjacent grids are a subset of the secondary grids (i.e. not necessary) - # remove them from each + # If the adjacent grids are a subset of the secondary + # grids (i.e. not necessary) remove them from each if adj in bigger.secondary_grids: - debug.info(3,"Removing {} from bigger secondary {}".format(adj, bigger)) + debug.info(3,"Removing {} from bigger secondary {}".format(adj, + bigger)) bigger.grids.remove(adj) bigger.secondary_grids.remove(adj) self.blocked_grids.add(adj) elif adj in smaller.secondary_grids: - debug.info(3,"Removing {} from smaller secondary {}".format(adj, smaller)) + debug.info(3,"Removing {} from smaller secondary {}".format(adj, + smaller)) smaller.grids.remove(adj) smaller.secondary_grids.remove(adj) self.blocked_grids.add(adj) else: - # If we couldn't remove from a secondary grid, we must remove from the primary + # If we couldn't remove from a secondary grid, + # we must remove from the primary # grid of at least one pin if adj in bigger.grids: - debug.info(3,"Removing {} from bigger primary {}".format(adj, bigger)) + debug.info(3,"Removing {} from bigger primary {}".format(adj, + bigger)) bigger.grids.remove(adj) elif adj in smaller.grids: - debug.info(3,"Removing {} from smaller primary {}".format(adj, smaller)) + debug.info(3,"Removing {} from smaller primary {}".format(adj, + smaller)) smaller.grids.remove(adj) - - - def prepare_blockages(self, pin_name): """ Reset and add all of the blockages in the design. Names is a list of pins to add as a blockage. """ - debug.info(3,"Preparing blockages.") + debug.info(3, "Preparing blockages.") # Start fresh. Not the best for run-time, but simpler. self.clear_blockages() # This adds the initial blockges of the design - #print("BLOCKING:",self.blocked_grids) - self.set_blockages(self.blocked_grids,True) + #print("BLOCKING:", self.blocked_grids) + self.set_blockages(self.blocked_grids, True) - # Block all of the supply rails (some will be unblocked if they're a target) + # Block all of the supply rails + # (some will be unblocked if they're a target) self.set_supply_rail_blocked(True) - # Block all of the pin components (some will be unblocked if they're a source/target) + # Block all of the pin components + # (some will be unblocked if they're a source/target) # Also block the previous routes for name in self.pin_groups: blockage_grids = {y for x in self.pin_groups[name] for y in x.grids} - self.set_blockages(blockage_grids,True) + self.set_blockages(blockage_grids, True) blockage_grids = {y for x in self.pin_groups[name] for y in x.blockages} - self.set_blockages(blockage_grids,True) + self.set_blockages(blockage_grids, True) # FIXME: These duplicate a bit of work # These are the paths that have already been routed. @@ -366,22 +382,20 @@ class router(router_tech): # directly to a rail, but unblock all the source components so we can # route over them blockage_grids = {y for x in self.pin_groups[pin_name] for y in x.grids} - self.set_blockages(blockage_grids,False) - + self.set_blockages(blockage_grids, False) def convert_shape_to_units(self, shape): - """ - Scale a shape (two vector list) to user units + """ + Scale a shape (two vector list) to user units """ unit_factor = [GDS["unit"][0]] * 2 - ll=shape[0].scale(unit_factor) - ur=shape[1].scale(unit_factor) - return [ll,ur] - + ll = shape[0].scale(unit_factor) + ur = shape[1].scale(unit_factor) + return [ll, ur] def min_max_coord(self, coord): - """ - Find the lowest and highest corner of a Rectangle + """ + Find the lowest and highest corner of a Rectangle """ coordinate = [] minx = min(coord[0][0], coord[1][0], coord[2][0], coord[3][0]) @@ -392,24 +406,24 @@ class router(router_tech): coordinate += [vector(maxx, maxy)] return coordinate - def get_inertia(self,p0,p1): - """ - Sets the direction based on the previous direction we came from. + def get_inertia(self, p0, p1): + """ + Sets the direction based on the previous direction we came from. """ # direction (index) of movement - if p0.x!=p1.x: + if p0.x != p1.x: return 0 - elif p0.y!=p1.y: + elif p0.y != p1.y: return 1 else: # z direction return 2 def clear_blockages(self): - """ + """ Clear all blockages on the grid. """ - debug.info(3,"Clearing all blockages") + debug.info(3, "Clearing all blockages") self.rg.clear_blockages() def set_blockages(self, blockages, value=True): @@ -417,137 +431,142 @@ class router(router_tech): self.rg.set_blocked(blockages, value) def get_blockage_tracks(self, ll, ur, z): - debug.info(3,"Converting blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) + debug.info(3, "Converting blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) block_list = [] - for x in range(int(ll[0]),int(ur[0])+1): - for y in range(int(ll[1]),int(ur[1])+1): - block_list.append(vector3d(x,y,z)) + for x in range(int(ll[0]), int(ur[0])+1): + for y in range(int(ll[1]), int(ur[1])+1): + block_list.append(vector3d(x, y, z)) return set(block_list) def convert_blockage(self, blockage): - """ - Convert a pin layout blockage shape to routing grid tracks. + """ + Convert a pin layout blockage shape to routing grid tracks. """ # Inflate the blockage by half a spacing rule - [ll,ur]=self.convert_blockage_to_tracks(blockage.inflate()) - zlayer = self.get_zindex(blockage.layer_num) + [ll, ur] = self.convert_blockage_to_tracks(blockage.inflate()) + zlayer = self.get_zindex(blockage.lpp) blockage_tracks = self.get_blockage_tracks(ll, ur, zlayer) return blockage_tracks def convert_blockages(self): """ Convert blockages to grid tracks. """ - debug.info(1,"Converting blockages.") + debug.info(1, "Converting blockages.") for blockage in self.blockages: - debug.info(3,"Converting blockage {}".format(str(blockage))) + debug.info(3, "Converting blockage {}".format(str(blockage))) blockage_list = self.convert_blockage(blockage) self.blocked_grids.update(blockage_list) - - def retrieve_blockages(self, layer_num): + def retrieve_blockages(self, lpp): """ Recursive find boundaries as blockages to the routing grid. """ - shapes = self.layout.getAllShapes(layer_num) + shapes = self.layout.getAllShapes(lpp) for boundary in shapes: - ll = vector(boundary[0],boundary[1]) - ur = vector(boundary[2],boundary[3]) - rect = [ll,ur] - new_pin = pin_layout("blockage{}".format(len(self.blockages)),rect,layer_num) + ll = vector(boundary[0], boundary[1]) + ur = vector(boundary[2], boundary[3]) + rect = [ll, ur] + new_pin = pin_layout("blockage{}".format(len(self.blockages)), + rect, + lpp) - # If there is a rectangle that is the same in the pins, it isn't a blockage! + # If there is a rectangle that is the same in the pins, + # it isn't a blockage! if new_pin not in self.all_pins: self.blockages.append(new_pin) - def convert_point_to_units(self, p): """ Convert a path set of tracks to center line path. """ pt = vector3d(p) - pt = pt.scale(self.track_widths[0],self.track_widths[1],1) + pt = pt.scale(self.track_widths[0], self.track_widths[1], 1) return pt def convert_wave_to_units(self, wave): - """ - Convert a wave to a set of center points + """ + Convert a wave to a set of center points """ return [self.convert_point_to_units(i) for i in wave] - def convert_blockage_to_tracks(self, shape): - """ + """ Convert a rectangular blockage shape into track units. """ - (ll,ur) = shape + (ll, ur) = shape ll = snap_to_grid(ll) ur = snap_to_grid(ur) # to scale coordinates to tracks - debug.info(3,"Converting [ {0} , {1} ]".format(ll,ur)) - old_ll = ll - old_ur = ur - ll=ll.scale(self.track_factor) - ur=ur.scale(self.track_factor) + debug.info(3, "Converting [ {0} , {1} ]".format(ll, ur)) + ll = ll.scale(self.track_factor) + ur = ur.scale(self.track_factor) # We can round since we are using inflated shapes # and the track points are at the center ll = ll.round() ur = ur.round() - return [ll,ur] + return [ll, ur] def convert_pin_to_tracks(self, pin_name, pin, expansion=0): - """ - Convert a rectangular pin shape into a list of track locations,layers. - If no pins are "on-grid" (i.e. sufficient overlap) it makes the one with most overlap if it is not blocked. - If expansion>0, expamine areas beyond the current pin when it is blocked. """ - (ll,ur) = pin.rect - debug.info(3,"Converting pin [ {0} , {1} ]".format(ll,ur)) + Convert a rectangular pin shape into a list of track locations,layers. + If no pins are "on-grid" (i.e. sufficient overlap) + it makes the one with most overlap if it is not blocked. + If expansion>0, expamine areas beyond the current pin + when it is blocked. + """ + (ll, ur) = pin.rect + debug.info(3, "Converting pin [ {0} , {1} ]".format(ll, ur)) # scale the size bigger to include neaby tracks - ll=ll.scale(self.track_factor).floor() - ur=ur.scale(self.track_factor).ceil() + ll = ll.scale(self.track_factor).floor() + ur = ur.scale(self.track_factor).ceil() # Keep tabs on tracks with sufficient and insufficient overlap sufficient_list = set() insufficient_list = set() - zindex=self.get_zindex(pin.layer_num) - for x in range(int(ll[0])+expansion,int(ur[0])+1+expansion): - for y in range(int(ll[1]+expansion),int(ur[1])+1+expansion): - (full_overlap, partial_overlap) = self.convert_pin_coord_to_tracks(pin, vector3d(x,y,zindex)) + zindex = self.get_zindex(pin.lpp) + for x in range(int(ll[0]) + expansion, int(ur[0]) + 1 + expansion): + for y in range(int(ll[1] + expansion), int(ur[1]) + 1 + expansion): + (full_overlap, partial_overlap) = self.convert_pin_coord_to_tracks(pin, + vector3d(x, + y, + zindex)) if full_overlap: sufficient_list.update([full_overlap]) if partial_overlap: insufficient_list.update([partial_overlap]) - debug.info(2,"Converting [ {0} , {1} ] full={2}".format(x,y, full_overlap)) + debug.info(2, + "Converting [ {0} , {1} ] full={2}".format(x, + y, + full_overlap)) # Return all grids with any potential overlap (sufficient or not) - return (sufficient_list,insufficient_list) - + return (sufficient_list, insufficient_list) def get_all_offgrid_pin(self, pin, insufficient_list): - """ + """ Find a list of all pins with some overlap. """ - #print("INSUFFICIENT LIST",insufficient_list) + # print("INSUFFICIENT LIST",insufficient_list) # Find the coordinate with the most overlap any_overlap = set() for coord in insufficient_list: full_pin = self.convert_track_to_pin(coord) # Compute the overlap with that rectangle - overlap_rect=pin.compute_overlap(full_pin) + overlap_rect = pin.compute_overlap(full_pin) # Determine the max x or y overlap max_overlap = max(overlap_rect) - if max_overlap>0: + if max_overlap > 0: any_overlap.update([coord]) return any_overlap def get_best_offgrid_pin(self, pin, insufficient_list): - """ + """ Find a list of the single pin with the most overlap. """ # Find the coordinate with the most overlap @@ -556,17 +575,17 @@ class router(router_tech): for coord in insufficient_list: full_pin = self.convert_track_to_pin(coord) # Compute the overlap with that rectangle - overlap_rect=pin.compute_overlap(full_pin) + overlap_rect = pin.compute_overlap(full_pin) # Determine the min x or y overlap min_overlap = min(overlap_rect) - if min_overlap>best_overlap: - best_overlap=min_overlap - best_coord=coord + if min_overlap > best_overlap: + best_overlap = min_overlap + best_coord = coord return set([best_coord]) def get_furthest_offgrid_pin(self, pin, insufficient_list): - """ + """ Get a grid cell that is the furthest from the blocked grids. """ @@ -575,14 +594,14 @@ class router(router_tech): best_dist = math.inf for coord in insufficient_list: min_dist = grid_utils.distance_set(coord, self.blocked_grids) - if min_dist 0: - debug.info(2," Overlap: {0} >? {1}".format(overlap_length,0)) - return (coord,None) + if overlap_length == math.inf or overlap_length > 0: + debug.info(2," Overlap: {0} >? {1}".format(overlap_length, 0)) + return (coord, None) # If it overlaps with the inflated pin, it is partial - elif inflated_overlap_length==math.inf or inflated_overlap_length > 0: - debug.info(2," Partial overlap: {0} >? {1}".format(inflated_overlap_length,0)) - return (None,coord) + elif inflated_overlap_length == math.inf or inflated_overlap_length > 0: + debug.info(2," Partial overlap: {0} >? {1}".format(inflated_overlap_length, 0)) + return (None, coord) else: - debug.info(2," No overlap: {0} {1}".format(overlap_length,0)) - return (None,None) - + debug.info(2, " No overlap: {0} {1}".format(overlap_length, 0)) + return (None, None) def convert_track_to_pin(self, track): - """ + """ Convert a grid point into a rectangle shape that is centered track in the track and leaves half a DRC space in each direction. """ - # calculate lower left - x = track.x*self.track_width - 0.5*self.track_width + 0.5*self.track_space - y = track.y*self.track_width - 0.5*self.track_width + 0.5*self.track_space + # calculate lower left + x = track.x * self.track_width - 0.5 * self.track_width + 0.5 * self.track_space + y = track.y * self.track_width - 0.5 * self.track_width + 0.5 * self.track_space ll = snap_to_grid(vector(x,y)) # calculate upper right - x = track.x*self.track_width + 0.5*self.track_width - 0.5*self.track_space - y = track.y*self.track_width + 0.5*self.track_width - 0.5*self.track_space - ur = snap_to_grid(vector(x,y)) + x = track.x * self.track_width + 0.5 * self.track_width - 0.5 * self.track_space + y = track.y * self.track_width + 0.5 * self.track_width - 0.5 * self.track_space + ur = snap_to_grid(vector(x, y)) p = pin_layout("", [ll, ur], self.get_layer(track[2])) return p def convert_track_to_shape_pin(self, track): - """ - Convert a grid point into a rectangle shape that occupies the entire centered - track. + """ + Convert a grid point into a rectangle shape + that occupies the entire centered track. """ # to scale coordinates to tracks x = track[0]*self.track_width - 0.5*self.track_width y = track[1]*self.track_width - 0.5*self.track_width # offset lowest corner object to to (-track halo,-track halo) - ll = snap_to_grid(vector(x,y)) - ur = snap_to_grid(ll + vector(self.track_width,self.track_width)) + ll = snap_to_grid(vector(x, y)) + ur = snap_to_grid(ll + vector(self.track_width, self.track_width)) p = pin_layout("", [ll, ur], self.get_layer(track[2])) return p def convert_track_to_shape(self, track): - """ - Convert a grid point into a rectangle shape that occupies the entire centered - track. + """ + Convert a grid point into a rectangle shape + that occupies the entire centered track. """ # to scale coordinates to tracks try: x = track[0]*self.track_width - 0.5*self.track_width except TypeError: - print(track[0],type(track[0]),self.track_width,type(self.track_width)) + print(track[0], type(track[0]), self.track_width, type(self.track_width)) y = track[1]*self.track_width - 0.5*self.track_width # offset lowest corner object to to (-track halo,-track halo) - ll = snap_to_grid(vector(x,y)) - ur = snap_to_grid(ll + vector(self.track_width,self.track_width)) + ll = snap_to_grid(vector(x, y)) + ur = snap_to_grid(ll + vector(self.track_width, self.track_width)) - return [ll,ur] + return [ll, ur] def convert_track_to_inflated_pin(self, track): - """ - Convert a grid point into a rectangle shape that is inflated by a half DRC space. """ - # calculate lower left + Convert a grid point into a rectangle shape + that is inflated by a half DRC space. + """ + # calculate lower left x = track.x*self.track_width - 0.5*self.track_width - 0.5*self.track_space y = track.y*self.track_width - 0.5*self.track_width - 0.5*self.track_space ll = snap_to_grid(vector(x,y)) @@ -691,16 +717,17 @@ class router(router_tech): # calculate upper right x = track.x*self.track_width + 0.5*self.track_width + 0.5*self.track_space y = track.y*self.track_width + 0.5*self.track_width + 0.5*self.track_space - ur = snap_to_grid(vector(x,y)) + ur = snap_to_grid(vector(x, y)) p = pin_layout("", [ll, ur], self.get_layer(track[2])) return p def analyze_pins(self, pin_name): - """ - Analyze the shapes of a pin and combine them into pin_groups which are connected. """ - debug.info(2,"Analyzing pin groups for {}.".format(pin_name)) + Analyze the shapes of a pin and combine + them into pin_groups which are connected. + """ + debug.info(2, "Analyzing pin groups for {}.".format(pin_name)) pin_set = self.pins[pin_name] # This will be a list of pin tuples that overlap @@ -713,8 +740,8 @@ class router(router_tech): y_coordinates.sort(key=lambda x: x[0]) # Map the pins to the lower indices - bottom_index_map = {x[1]:i for i,x in enumerate(y_coordinates) if x[2]=="bottom"} - top_index_map = {x[1]:i for i,x in enumerate(y_coordinates) if x[2]=="bottom"} + bottom_index_map = {x[1]: i for i, x in enumerate(y_coordinates) if x[2] == "bottom"} + # top_index_map = {x[1]: i for i, x in enumerate(y_coordinates) if x[2] == "bottom"} # Sort the pin list by x coordinate pin_list = list(pin_set) @@ -725,10 +752,10 @@ class router(router_tech): # start at pin's lower y coordinate bottom_index = bottom_index_map[pin] compared_pins = set() - for i in range(bottom_index,len(y_coordinates)): + for i in range(bottom_index, len(y_coordinates)): compare_pin = y_coordinates[i][1] # Don't overlap yourself - if pin==compare_pin: + if pin == compare_pin: continue # Done when we encounter any shape above the pin if compare_pin.by() > pin.uy(): @@ -739,7 +766,7 @@ class router(router_tech): compared_pins.add(compare_pin) # If we overlap, add them to the list if pin.overlaps(compare_pin): - overlap_list.append((pin,compare_pin)) + overlap_list.append((pin, compare_pin)) # Initial unique group assignments group_id = {} @@ -749,48 +776,46 @@ class router(router_tech): gid += 1 for p in overlap_list: - (p1,p2) = p + (p1, p2) = p for pin in pin_list: if group_id[pin] == group_id[p2]: group_id[pin] = group_id[p1] - # For each pin add it to it's group group_map = {} for pin in pin_list: gid = group_id[pin] if gid not in group_map: - group_map[gid] = pin_group(name=pin_name, pin_set=[], router=self) + group_map[gid] = pin_group(name=pin_name, + pin_set=[], + router=self) # We always add it to the first set since they are touching group_map[gid].pins.add(pin) self.pin_groups[pin_name] = list(group_map.values()) - def convert_pins(self, pin_name): - """ + """ Convert the pin groups into pin tracks and blockage tracks. """ - debug.info(1,"Converting pins for {}.".format(pin_name)) + debug.info(1, "Converting pins for {}.".format(pin_name)) for pg in self.pin_groups[pin_name]: pg.convert_pin() - - def enclose_pins(self): """ This will find the biggest rectangle enclosing some grid squares and - put a rectangle over it. It does not enclose grid squares that are blocked - by other shapes. + put a rectangle over it. It does not enclose grid squares + that are blocked by other shapes. """ for pin_name in self.pin_groups: - debug.info(1,"Enclosing pins for {}".format(pin_name)) + debug.info(1, "Enclosing pins for {}".format(pin_name)) for pg in self.pin_groups[pin_name]: pg.enclose_pin() pg.add_enclosure(self.cell) def add_source(self, pin_name): - """ + """ This will mark the grids for all pin components as a source. Marking as source or target also clears blockage status. """ @@ -798,7 +823,7 @@ class router(router_tech): self.add_pin_component_source(pin_name, i) def add_target(self, pin_name): - """ + """ This will mark the grids for all pin components as a target. Marking as source or target also clears blockage status. """ @@ -806,44 +831,46 @@ class router(router_tech): self.add_pin_component_target(pin_name, i) def num_pin_components(self, pin_name): - """ + """ This returns how many disconnected pin components there are. """ return len(self.pin_groups[pin_name]) def add_pin_component_source(self, pin_name, index): - """ - This will mark only the pin tracks from the indexed pin component as a source. + """ + This will mark only the pin tracks + from the indexed pin component as a source. It also unsets it as a blockage. """ - debug.check(index1: + if len(self.layers) > 1: self.cell.add_route(layers=self.layers, coordinates=abs_path, layer_widths=self.layer_widths) @@ -908,83 +934,81 @@ class router(router_tech): def add_single_enclosure(self, track): """ - Add a metal enclosure that is the size of the routing grid minus a spacing on each side. + Add a metal enclosure that is the size of + the routing grid minus a spacing on each side. """ pin = self.convert_track_to_pin(track) - (ll,ur) = pin.rect + (ll, ur) = pin.rect self.cell.add_rect(layer=self.get_layer(track.z), offset=ll, width=ur.x-ll.x, height=ur.y-ll.y) - - - def add_via(self,loc,size=1): - """ + def add_via(self, loc, size=1): + """ Add a via centered at the current location """ - loc = self.convert_point_to_units(vector3d(loc[0],loc[1],0)) + loc = self.convert_point_to_units(vector3d(loc[0], loc[1], 0)) self.cell.add_via_center(layers=self.layers, - offset=vector(loc.x,loc.y), - size=(size,size)) + offset=vector(loc.x, loc.y), + size=(size, size)) def compute_pin_enclosure(self, ll, ur, zindex, name=""): """ Enclose the tracks from ll to ur in a single rectangle that meets - the track DRC rules. + the track DRC rules. """ layer = self.get_layer(zindex) - # This finds the pin shape enclosed by the track with DRC spacing on the sides + # This finds the pin shape enclosed by the + # track with DRC spacing on the sides pin = self.convert_track_to_pin(ll) - (abs_ll,unused) = pin.rect + (abs_ll, unused) = pin.rect pin = self.convert_track_to_pin(ur) - (unused,abs_ur) = pin.rect + (unused, abs_ur) = pin.rect pin = pin_layout(name, [abs_ll, abs_ur], layer) return pin - def contract_path(self,path): - """ + def contract_path(self, path): + """ Remove intermediate points in a rectilinear path or a wave. """ # Waves are always linear, so just return the first and last. if self.is_wave(path): - return [path[0],path[-1]] + return [path[0], path[-1]] # Make a list only of points that change inertia of the path newpath = [path[0]] - for i in range(1,len(path)-1): - prev_inertia=self.get_inertia(path[i-1][0],path[i][0]) - next_inertia=self.get_inertia(path[i][0],path[i+1][0]) + for i in range(1, len(path) - 1): + prev_inertia = self.get_inertia(path[i-1][0], path[i][0]) + next_inertia = self.get_inertia(path[i][0], path[i+1][0]) # if we switch directions, add the point, otherwise don't - if prev_inertia!=next_inertia: + if prev_inertia != next_inertia: newpath.append(path[i]) # always add the last path unless it was a single point - if len(path)>1: + if len(path) > 1: newpath.append(path[-1]) return newpath - - def run_router(self, detour_scale): """ - This assumes the blockages, source, and target are all set up. + This assumes the blockages, source, and target are all set up. """ # Double check source and taget are not same node, if so, we are done! - for k,v in self.rg.map.items(): + for k, v in self.rg.map.items(): if v.source and v.target: debug.error("Grid cell is source and target! {}".format(k)) return False # returns the path in tracks - (path,cost) = self.rg.route(detour_scale) + (path, cost) = self.rg.route(detour_scale) if path: - debug.info(1,"Found path: cost={0} ".format(cost)) - debug.info(1,str(path)) + debug.info(1, "Found path: cost={0} ".format(cost)) + debug.info(1, str(path)) self.paths.append(path) self.add_route(path) @@ -998,34 +1022,34 @@ class router(router_tech): return False return True - def annotate_pin_and_tracks(self, pin, tracks): """" Annotate some shapes for debug purposes """ - debug.info(0,"Annotating\n pin {0}\n tracks {1}".format(pin,tracks)) + debug.info(0, "Annotating\n pin {0}\n tracks {1}".format(pin, tracks)) for coord in tracks: - (ll,ur) = self.convert_track_to_shape(coord) + (ll, ur) = self.convert_track_to_shape(coord) self.cell.add_rect(layer="text", offset=ll, width=ur[0]-ll[0], height=ur[1]-ll[1]) - (ll,ur) = self.convert_track_to_pin(coord).rect + (ll, ur) = self.convert_track_to_pin(coord).rect self.cell.add_rect(layer="boundary", offset=ll, width=ur[0]-ll[0], height=ur[1]-ll[1]) - (ll,ur) = pin.rect + (ll, ur) = pin.rect self.cell.add_rect(layer="text", offset=ll, width=ur[0]-ll[0], height=ur[1]-ll[1]) def write_debug_gds(self, gds_name="debug_route.gds", stop_program=True): - """ - Write out a GDS file with the routing grid and search information annotated on it. """ - debug.info(0,"Writing annotated router gds file to {}".format(gds_name)) + Write out a GDS file with the routing grid and + search information annotated on it. + """ + debug.info(0, "Writing annotated router gds file to {}".format(gds_name)) self.del_router_info() self.add_router_info() self.cell.gds_write(gds_name) @@ -1039,41 +1063,41 @@ class router(router_tech): Display grid information in the GDS file for a single grid cell. """ shape = self.convert_track_to_shape(g) - partial_track=vector(0,self.track_width/6.0) + partial_track = vector(0,self.track_width/6.0) self.cell.add_rect(layer="text", offset=shape[0], width=shape[1].x-shape[0].x, height=shape[1].y-shape[0].y) - t=self.rg.map[g].get_type() + t = self.rg.map[g].get_type() # midpoint offset - off=vector((shape[1].x+shape[0].x)/2, + off = vector((shape[1].x+shape[0].x)/2, (shape[1].y+shape[0].y)/2) - if t!=None: - if g[2]==1: + if t != None: + if g[2] == 1: # Upper layer is upper right label - type_off=off+partial_track + type_off = off + partial_track else: # Lower layer is lower left label - type_off=off-partial_track + type_off = off - partial_track self.cell.add_label(text=str(t), layer="text", offset=type_off) - t=self.rg.map[g].get_cost() - partial_track=vector(self.track_width/6.0,0) - if t!=None: - if g[2]==1: + t = self.rg.map[g].get_cost() + partial_track = vector(self.track_width/6.0, 0) + if t: + if g[2] == 1: # Upper layer is right label - type_off=off+partial_track + type_off = off + partial_track else: # Lower layer is left label - type_off=off-partial_track + type_off = off - partial_track self.cell.add_label(text=str(t), layer="text", offset=type_off) - self.cell.add_label(text="{0},{1}".format(g[0],g[1]), + self.cell.add_label(text="{0},{1}".format(g[0], g[1]), layer="text", offset=shape[0], zoom=0.05) @@ -1082,18 +1106,17 @@ class router(router_tech): """ Erase all of the comments on the current level. """ - debug.info(0,"Erasing router info") + debug.info(0, "Erasing router info") layer_num = techlayer["text"] self.cell.objs = [x for x in self.cell.objs if x.layerNumber != layer_num] - def add_router_info(self): """ - Write the routing grid and router cost, blockage, pins on - the boundary layer for debugging purposes. This can only be + Write the routing grid and router cost, blockage, pins on + the boundary layer for debugging purposes. This can only be called once or the labels will overlap. """ - debug.info(0,"Adding router info") + debug.info(0, "Adding router info") show_blockages = False show_blockage_grids = False @@ -1108,14 +1131,14 @@ class router(router_tech): if show_blockages: # Display the inflated blockage for blockage in self.blockages: - debug.info(1,"Adding {}".format(blockage)) - (ll,ur) = blockage.inflate() + debug.info(1, "Adding {}".format(blockage)) + (ll, ur) = blockage.inflate() self.cell.add_rect(layer="text", offset=ll, width=ur.x-ll.x, height=ur.y-ll.y) if show_blockage_grids: - self.set_blockages(self.blocked_grids,True) + self.set_blockages(self.blocked_grids, True) for g in self.rg.map: self.annotate_grid(g) @@ -1125,22 +1148,27 @@ class router(router_tech): if not pg.enclosed: continue for pin in pg.enclosures: - #print("enclosure: ",pin.name,pin.ll(),pin.width(),pin.height()) + # print("enclosure: ", + # pin.name, + # pin.ll(), + # pin.width(), + # pin.height()) self.cell.add_rect(layer="text", offset=pin.ll(), width=pin.width(), height=pin.height()) - -# FIXME: This should be replaced with vector.snap_to_grid at some point + +# FIXME: This should be replaced with vector.snap_to_grid at some point def snap_to_grid(offset): """ Changes the coodrinate to match the grid settings """ xoff = snap_val_to_grid(offset[0]) - yoff = snap_val_to_grid(offset[1]) + yoff = snap_val_to_grid(offset[1]) return vector(xoff, yoff) + def snap_val_to_grid(x): grid = drc("grid") xgrid = int(round(round((x / grid), 2), 0)) diff --git a/compiler/router/router_tech.py b/compiler/router/router_tech.py index 49df06fd..27156eeb 100644 --- a/compiler/router/router_tech.py +++ b/compiler/router/router_tech.py @@ -5,29 +5,28 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from tech import drc,layer +from tech import drc, layer, preferred_directions from contact import contact -from pin_group import pin_group from vector import vector import debug import math + class router_tech: """ This is a class to hold the router tech constants. """ def __init__(self, layers, rail_track_width): """ - Allows us to change the layers that we are routing on. First layer - is always horizontal, middle is via, and last is always - vertical. + Allows us to change the layers that we are routing on. + This uses the preferreed directions. """ self.layers = layers self.rail_track_width = rail_track_width - if len(self.layers)==1: + if len(self.layers) == 1: self.horiz_layer_name = self.vert_layer_name = self.layers[0] - self.horiz_layer_number = self.vert_layer_number = layer[self.layers[0]] + self.horiz_lpp = self.vert_lpp = layer[self.layers[0]] (self.vert_layer_minwidth, self.vert_layer_spacing) = self.get_supply_layer_width_space(1) (self.horiz_layer_minwidth, self.horiz_layer_spacing) = self.get_supply_layer_width_space(0) @@ -35,13 +34,31 @@ class router_tech: self.horiz_track_width = self.horiz_layer_minwidth + self.horiz_layer_spacing self.vert_track_width = self.vert_layer_minwidth + self.vert_layer_spacing else: - (self.horiz_layer_name, self.via_layer_name, self.vert_layer_name) = self.layers + (try_horiz_layer, self.via_layer_name, try_vert_layer) = self.layers + + # figure out wich of the two layers prefers horizontal/vertical + # routing + self.horiz_layer_name = None + self.vert_layer_name = None + + if preferred_directions[try_horiz_layer] == "H": + self.horiz_layer_name = try_horiz_layer + else: + self.horiz_layer_name = try_vert_layer + if preferred_directions[try_vert_layer] == "V": + self.vert_layer_name = try_vert_layer + else: + self.vert_layer_name = try_horiz_layer + + if not self.horiz_layer_name or not self.vert_layer_name: + raise ValueError("Layer '{}' and '{}' are using the wrong " + "preferred_directions '{}' and '{}'.") via_connect = contact(self.layers, (1, 1)) max_via_size = max(via_connect.width,via_connect.height) - self.horiz_layer_number = layer[self.horiz_layer_name] - self.vert_layer_number = layer[self.vert_layer_name] + self.horiz_lpp = layer[self.horiz_layer_name] + self.vert_lpp = layer[self.vert_layer_name] (self.vert_layer_minwidth, self.vert_layer_spacing) = self.get_supply_layer_width_space(1) (self.horiz_layer_minwidth, self.horiz_layer_spacing) = self.get_supply_layer_width_space(0) @@ -68,8 +85,18 @@ class router_tech: # When we actually create the routes, make them the width of the track (minus 1/2 spacing on each side) self.layer_widths = [self.track_wire, 1, self.track_wire] - def get_zindex(self,layer_num): - if layer_num==self.horiz_layer_number: + def same_lpp(self, lpp1, lpp2): + """ + Check if the layers and purposes are the same. + Ignore if purpose is a None. + """ + if lpp1[1] == None or lpp2[1] == None: + return lpp1[0] == lpp2[0] + + return lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1] + + def get_zindex(self, lpp): + if self.same_lpp(lpp, self.horiz_lpp): return 0 else: return 1 diff --git a/compiler/router/supply_grid_router.py b/compiler/router/supply_grid_router.py index 5ddabc98..cd7b6b7b 100644 --- a/compiler/router/supply_grid_router.py +++ b/compiler/router/supply_grid_router.py @@ -5,21 +5,15 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import gdsMill -import tech -import math import debug -from globals import OPTS,print_time -from contact import contact -from pin_group import pin_group -from pin_layout import pin_layout -from vector3d import vector3d +from globals import print_time +from vector3d import vector3d from router import router from direction import direction from datetime import datetime -import grid import grid_utils + class supply_grid_router(router): """ A router class to read an obstruction map from a gds and @@ -31,6 +25,8 @@ class supply_grid_router(router): This will route on layers in design. It will get the blockages from either the gds file name or the design itself (by saving to a gds file). """ + start_time = datetime.now() + # Power rail width in minimum wire widths self.rail_track_width = 3 @@ -40,30 +36,29 @@ class supply_grid_router(router): self.supply_rails = {} # This is the same as above but as a sigle set for the all the rails self.supply_rail_tracks = {} - - + print_time("Init supply router", datetime.now(), start_time, 3) def create_routing_grid(self): """ Create a sprase routing grid with A* expansion functions. """ size = self.ur - self.ll - debug.info(1,"Size: {0} x {1}".format(size.x,size.y)) + debug.info(1, "Size: {0} x {1}".format(size.x, size.y)) import supply_grid self.rg = supply_grid.supply_grid(self.ll, self.ur, self.track_width) def route(self, vdd_name="vdd", gnd_name="gnd"): - """ + """ Add power supply rails and connect all pins to these rails. """ - debug.info(1,"Running supply router on {0} and {1}...".format(vdd_name, gnd_name)) + debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name)) self.vdd_name = vdd_name self.gnd_name = gnd_name # Clear the pins if we have previously routed - if (hasattr(self,'rg')): + if (hasattr(self, 'rg')): self.clear_pins() else: # Creat a routing grid over the entire area @@ -74,33 +69,32 @@ class supply_grid_router(router): # Get the pin shapes start_time = datetime.now() self.find_pins_and_blockages([self.vdd_name, self.gnd_name]) - print_time("Finding pins and blockages",datetime.now(), start_time, 3) - + print_time("Finding pins and blockages", datetime.now(), start_time, 3) # Add the supply rails in a mesh network and connect H/V with vias start_time = datetime.now() # Block everything self.prepare_blockages(self.gnd_name) # Determine the rail locations - self.route_supply_rails(self.gnd_name,0) + self.route_supply_rails(self.gnd_name, 0) # Block everything self.prepare_blockages(self.vdd_name) # Determine the rail locations - self.route_supply_rails(self.vdd_name,1) - print_time("Routing supply rails",datetime.now(), start_time, 3) + self.route_supply_rails(self.vdd_name, 1) + print_time("Routing supply rails", datetime.now(), start_time, 3) start_time = datetime.now() self.route_simple_overlaps(vdd_name) self.route_simple_overlaps(gnd_name) - print_time("Simple overlap routing",datetime.now(), start_time, 3) + print_time("Simple overlap routing", datetime.now(), start_time, 3) # Route the supply pins to the supply rails # Route vdd first since we want it to be shorter start_time = datetime.now() self.route_pins_to_rails(vdd_name) self.route_pins_to_rails(gnd_name) - print_time("Maze routing supplies",datetime.now(), start_time, 3) - #self.write_debug_gds("final.gds",False) + print_time("Maze routing supplies", datetime.now(), start_time, 3) + # self.write_debug_gds("final.gds", False) # Did we route everything?? if not self.check_all_routed(vdd_name): @@ -110,9 +104,8 @@ class supply_grid_router(router): return True - def check_all_routed(self, pin_name): - """ + """ Check that all pin groups are routed. """ for pg in self.pin_groups[pin_name]: @@ -124,7 +117,7 @@ class supply_grid_router(router): This checks for simple cases where a pin component already overlaps a supply rail. It will add an enclosure to ensure the overlap in wide DRC rule cases. """ - debug.info(1,"Routing simple overlap pins for {0}".format(pin_name)) + debug.info(1, "Routing simple overlap pins for {0}".format(pin_name)) # These are the wire tracks wire_tracks = self.supply_rail_tracks[pin_name] @@ -141,10 +134,10 @@ class supply_grid_router(router): continue # Else, if we overlap some of the space track, we can patch it with an enclosure - #pg.create_simple_overlap_enclosure(pg.grids) - #pg.add_enclosure(self.cell) + # pg.create_simple_overlap_enclosure(pg.grids) + # pg.add_enclosure(self.cell) - debug.info(1,"Routed {} simple overlap pins".format(routed_count)) + debug.info(1, "Routed {} simple overlap pins".format(routed_count)) def finalize_supply_rails(self, name): """ @@ -157,7 +150,7 @@ class supply_grid_router(router): connections = set() via_areas = [] - for i1,r1 in enumerate(all_rails): + for i1, r1 in enumerate(all_rails): # Only consider r1 horizontal rails e = next(iter(r1)) if e.z==1: @@ -165,9 +158,9 @@ class supply_grid_router(router): # We need to move this rail to the other layer for the z indices to match # during the intersection. This also makes a copy. - new_r1 = {vector3d(i.x,i.y,1) for i in r1} + new_r1 = {vector3d(i.x, i.y, 1) for i in r1} - for i2,r2 in enumerate(all_rails): + for i2, r2 in enumerate(all_rails): # Never compare to yourself if i1==i2: continue @@ -183,16 +176,16 @@ class supply_grid_router(router): # the overlap area for placement of a via overlap = new_r1 & r2 if len(overlap) >= 1: - debug.info(3,"Via overlap {0} {1}".format(len(overlap),overlap)) - connections.update([i1,i2]) + debug.info(3, "Via overlap {0} {1}".format(len(overlap),overlap)) + connections.update([i1, i2]) via_areas.append(overlap) # Go through and add the vias at the center of the intersection for area in via_areas: ll = grid_utils.get_lower_left(area) ur = grid_utils.get_upper_right(area) - center = (ll + ur).scale(0.5,0.5,0) - self.add_via(center,1) + center = (ll + ur).scale(0.5, 0.5, 0) + self.add_via(center, 1) # Determien which indices were not connected to anything above missing_indices = set([x for x in range(len(self.supply_rails[name]))]) @@ -203,13 +196,12 @@ class supply_grid_router(router): for rail_index in sorted(missing_indices, reverse=True): ll = grid_utils.get_lower_left(all_rails[rail_index]) ur = grid_utils.get_upper_right(all_rails[rail_index]) - debug.info(1,"Removing disconnected supply rail {0} .. {1}".format(ll,ur)) + debug.info(1, "Removing disconnected supply rail {0} .. {1}".format(ll, ur)) self.supply_rails[name].pop(rail_index) # Make the supply rails into a big giant set of grids for easy blockages. # Must be done after we determine which ones are connected. self.create_supply_track_set(name) - def add_supply_rails(self, name): """ @@ -222,7 +214,7 @@ class supply_grid_router(router): ur = grid_utils.get_upper_right(rail) z = ll.z pin = self.compute_pin_enclosure(ll, ur, z, name) - debug.info(3,"Adding supply rail {0} {1}->{2} {3}".format(name,ll,ur,pin)) + debug.info(3, "Adding supply rail {0} {1}->{2} {3}".format(name, ll, ur, pin)) self.cell.add_layout_pin(text=name, layer=pin.layer, offset=pin.ll(), @@ -242,19 +234,18 @@ class supply_grid_router(router): max_xoffset = self.rg.ur.x min_yoffset = self.rg.ll.y min_xoffset = self.rg.ll.x - # Horizontal supply rails start_offset = min_yoffset + supply_number for offset in range(start_offset, max_yoffset, 2): # Seed the function at the location with the given width - wave = [vector3d(min_xoffset,offset,0)] + wave = [vector3d(min_xoffset, offset, 0)] # While we can keep expanding east in this horizontal track while wave and wave[0].x < max_xoffset: added_rail = self.find_supply_rail(name, wave, direction.EAST) if not added_rail: # Just seed with the next one - wave = [x+vector3d(1,0,0) for x in wave] + wave = [x+vector3d(1, 0, 0) for x in wave] else: # Seed with the neighbor of the end of the last rail wave = added_rail.neighbor(direction.EAST) @@ -263,15 +254,15 @@ class supply_grid_router(router): start_offset = min_xoffset + supply_number for offset in range(start_offset, max_xoffset, 2): # Seed the function at the location with the given width - wave = [vector3d(offset,min_yoffset,1)] + wave = [vector3d(offset, min_yoffset, 1)] # While we can keep expanding north in this vertical track while wave and wave[0].y < max_yoffset: added_rail = self.find_supply_rail(name, wave, direction.NORTH) if not added_rail: # Just seed with the next one - wave = [x+vector3d(0,1,0) for x in wave] + wave = [x + vector3d(0, 1, 0) for x in wave] else: - # Seed with the neighbor of the end of the last rail + # Seed with the neighbor of the end of the last rail wave = added_rail.neighbor(direction.NORTH) def find_supply_rail(self, name, seed_wave, direct): @@ -293,7 +284,6 @@ class supply_grid_router(router): # Return the rail whether we approved it or not, # as it will be used to find the next start location return wave_path - def probe_supply_rail(self, name, start_wave, direct): """ @@ -327,23 +317,19 @@ class supply_grid_router(router): data structure. Return whether it was added or not. """ # We must have at least 2 tracks to drop plus 2 tracks for a via - if len(wave_path)>=4*self.rail_track_width: + if len(wave_path) >= 4 * self.rail_track_width: grid_set = wave_path.get_grids() self.supply_rails[name].append(grid_set) return True return False - - - - def route_supply_rails(self, name, supply_number): """ Route the horizontal and vertical supply rails across the entire design. Must be done with lower left at 0,0 """ - debug.info(1,"Routing supply rail {0}.".format(name)) + debug.info(1, "Routing supply rail {0}.".format(name)) # Compute the grid locations of the supply rails self.compute_supply_rails(name, supply_number) @@ -354,7 +340,6 @@ class supply_grid_router(router): # Add the rails themselves self.add_supply_rails(name) - def create_supply_track_set(self, pin_name): """ Make a single set of all the tracks for the rail and wire itself. @@ -363,24 +348,22 @@ class supply_grid_router(router): for rail in self.supply_rails[pin_name]: rail_set.update(rail) self.supply_rail_tracks[pin_name] = rail_set - - def route_pins_to_rails(self, pin_name): """ - This will route each of the remaining pin components to the supply rails. + This will route each of the remaining pin components to the supply rails. After it is done, the cells are added to the pin blockage list. """ remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name]) - debug.info(1,"Maze routing {0} with {1} pin components to connect.".format(pin_name, - remaining_components)) + debug.info(1, "Maze routing {0} with {1} pin components to connect.".format(pin_name, + remaining_components)) - for index,pg in enumerate(self.pin_groups[pin_name]): + for index, pg in enumerate(self.pin_groups[pin_name]): if pg.is_routed(): continue - debug.info(3,"Routing component {0} {1}".format(pin_name, index)) + debug.info(3, "Routing component {0} {1}".format(pin_name, index)) # Clear everything in the routing grid. self.rg.reinit() @@ -399,28 +382,26 @@ class supply_grid_router(router): # Actually run the A* router if not self.run_router(detour_scale=5): - self.write_debug_gds("debug_route.gds",False) + self.write_debug_gds("debug_route.gds", False) - #if index==3 and pin_name=="vdd": - # self.write_debug_gds("route.gds",False) + # if index==3 and pin_name=="vdd": + # self.write_debug_gds("route.gds",False) - def add_supply_rail_target(self, pin_name): """ Add the supply rails of given name as a routing target. """ - debug.info(4,"Add supply rail target {}".format(pin_name)) + debug.info(4, "Add supply rail target {}".format(pin_name)) # Add the wire itself as the target self.rg.set_target(self.supply_rail_tracks[pin_name]) # But unblock all the rail tracks including the space - self.rg.set_blocked(self.supply_rail_tracks[pin_name],False) - + self.rg.set_blocked(self.supply_rail_tracks[pin_name], False) def set_supply_rail_blocked(self, value=True): """ Add the supply rails of given name as a routing target. """ - debug.info(4,"Blocking supply rail") + debug.info(4, "Blocking supply rail") for rail_name in self.supply_rail_tracks: self.rg.set_blocked(self.supply_rail_tracks[rail_name]) diff --git a/compiler/router/vector3d.py b/compiler/router/vector3d.py index 0d183021..066f843f 100644 --- a/compiler/router/vector3d.py +++ b/compiler/router/vector3d.py @@ -27,7 +27,7 @@ class vector3d(): self.x = x self.y = y self.z = z - + self._hash = hash((self.x,self.y,self.z)) def __str__(self): """ override print function output """ @@ -96,7 +96,7 @@ class vector3d(): Note: This assumes that you DON'T CHANGE THE VECTOR or it will break things. """ - return hash((self.x,self.y,self.z)) + return self._hash def __rsub__(self, other): diff --git a/compiler/sram/sram.py b/compiler/sram/sram.py index c4f41f77..1ec7d636 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,31 @@ 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 lvs_write(self, name): + self.s.lvs_write(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. """ @@ -89,6 +88,13 @@ class sram(): self.sp_write(spname) print_time("Spice writing", datetime.datetime.now(), start_time) + # Save the LVS file + start_time = datetime.datetime.now() + spname = OPTS.output_path + self.s.name + ".lvs" + debug.print_raw("LVS: Writing to {0}".format(spname)) + self.lvs_write(spname) + print_time("LVS writing", datetime.datetime.now(), start_time) + # Save the extracted spice file if OPTS.use_pex: import verify @@ -107,12 +113,11 @@ 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() from shutil import copyfile - copyfile(OPTS.config_file + '.py', OPTS.output_path + OPTS.output_name + '.py') + copyfile(OPTS.config_file, OPTS.output_path + OPTS.output_name + '.py') debug.print_raw("Config: Writing to {0}".format(OPTS.output_path + OPTS.output_name + '.py')) print_time("Config", datetime.datetime.now(), start_time) diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index a141765b..42fc3646 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -5,22 +5,11 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys -from tech import drc, spice import debug -from math import log,sqrt,ceil -import datetime -import getpass -import numpy as np from vector import vector -from globals import OPTS, print_time - from sram_base import sram_base -from bank import bank -from contact import m2m3 -from dff_buf_array import dff_buf_array -from dff_array import dff_array - +from contact import m2_via +from globals import OPTS class sram_1bank(sram_base): """ @@ -30,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) @@ -50,8 +39,13 @@ class sram_1bank(sram_base): else: self.data_dff_insts = self.create_data_dff() + if self.num_spare_cols: + self.spare_wen_dff_insts = self.create_spare_wen_dff() + else: + self.num_spare_cols = 0 + def place_instances(self): - """ + """ This places the instances for a single bank SRAM with control logic and up to 2 ports. """ @@ -64,64 +58,46 @@ 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) + spare_wen_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 - 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 + # These positions utilize the channel route sizes. + # FIXME: Auto-compute these rather than manual computation. + # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. + # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. + # So, m3 non-pref pitch means that this is routed on the m2 layer. + self.data_bus_gap = self.m4_nonpref_pitch * 2 + # Spare wen are on a separate layer so not included + # Start with 1 track minimum + self.data_bus_size = [1] * len(self.all_ports) + for port in self.all_ports: + # All ports need the col addr flops + self.data_bus_size[port] += self.col_addr_size + # Write ports need the data input flops and write mask flops + if port in self.write_ports: + self.data_bus_size[port] += self.num_wmasks + self.word_size + # This is for the din pins that get routed in the same channel + # when we have dout and din together + if port in self.readwrite_ports: + self.data_bus_size[port] += self.word_size + # Convert to length + self.data_bus_size[port] *= self.m4_nonpref_pitch + # Add the gap in unit length + self.data_bus_size[port] += self.data_bus_gap + # Port 0 port = 0 - if port in self.write_ports: - if self.write_size: - # Add the write mask flops below the write mask AND array. - wmask_pos[port] = vector(self.bank.bank_array_ll.x, - -max_gap_size_wmask - self.dff.height) - self.wmask_dff_insts[port].place(wmask_pos[port]) - - # Add the data flops below the write mask flops. - data_pos[port] = vector(self.bank.bank_array_ll.x, - -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 - # This relies on the lower-left of the array of the bank - # decoder in upper left, bank in upper right, sensing in lower right. - # These flops go below the sensing and leave a gap to channel route to the - # sense amps. - if port in self.write_ports: - data_pos[port] = vector(self.bank.bank_array_ll.x, - -max_gap_size - self.dff.height) - self.data_dff_insts[port].place(data_pos[port]) - else: - wmask_pos[port] = vector(self.bank.bank_array_ll.x, 0) - data_pos[port] = vector(self.bank.bank_array_ll.x,0) - - - # Add the col address flops below the bank to the left of the lower-left of bank array - if self.col_addr_dff: - if self.write_size: - col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap, - -max_gap_size_wmask - self.col_addr_dff_insts[port].height) - else: - col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap, - -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) - # 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 ) + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. + control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch, + self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y) self.control_logic_insts[port].place(control_pos[port]) # The row address bits are placed above the control logic aligned on the right. @@ -131,49 +107,56 @@ class sram_1bank(sram_base): row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port]) + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].rx() + self.dff.width + y_offset = - self.data_bus_size[port] - self.dff.height + if self.col_addr_dff: + col_addr_pos[port] = vector(x_offset, + y_offset) + self.col_addr_dff_insts[port].place(col_addr_pos[port]) + x_offset = self.col_addr_dff_insts[port].rx() + else: + col_addr_pos[port] = vector(x_offset, 0) + + if port in self.write_ports: + if self.write_size: + # Add the write mask flops below the write mask AND array. + wmask_pos[port] = vector(x_offset, + y_offset) + self.wmask_dff_insts[port].place(wmask_pos[port]) + x_offset = self.wmask_dff_insts[port].rx() + + # Add the data flops below the write mask flops. + data_pos[port] = vector(x_offset, + y_offset) + self.data_dff_insts[port].place(data_pos[port]) + x_offset = self.data_dff_insts[port].rx() + + # Add spare write enable flops to the right of data flops since the spare columns + # will be on the right + if self.num_spare_cols: + spare_wen_pos[port] = vector(x_offset, + y_offset) + self.spare_wen_dff_insts[port].place(spare_wen_pos[port]) + x_offset = self.spare_wen_dff_insts[port].rx() + + else: + wmask_pos[port] = vector(x_offset, y_offset) + data_pos[port] = vector(x_offset, y_offset) + spare_wen_pos[port] = vector(x_offset, y_offset) + if len(self.all_ports)>1: # Port 1 port = 1 - - if port in self.write_ports: - if self.write_size: - # Add the write mask flops below the write mask AND array. - wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.wmask_dff_insts[port].width, - self.bank.height + max_gap_size_wmask + self.dff.height) - self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX") - - # Add the data flops below the write mask flops - data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, - self.bank.height + 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 - # This relies on the upper-right of the array of the bank - # decoder in upper left, bank in upper right, sensing in lower right. - # These flops go below the sensing and leave a gap to channel route to the - # sense amps. - data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, - self.bank.height + max_gap_size + self.dff.height) - self.data_dff_insts[port].place(data_pos[port], mirror="MX") - - # Add the col address flops above the bank to the right of the upper-right of bank array - if self.col_addr_dff: - if self.write_size: - col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap, - self.bank.height + max_gap_size_wmask + self.dff.height) - else: - col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap, - self.bank.height + max_gap_size + self.dff.height) - self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX") - else: - col_addr_pos[port] = self.bank_inst.ur() # This includes 2 M2 pitches for the row addr clock line - 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() + # The delay line is aligned with the bitcell array while the control logic is aligned with the port_data + # using the control_logic_center value. + control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch, + self.bank.bank_array_ur.y + + self.control_logic_insts[port].height + - self.control_logic_insts[port].height + + self.control_logic_insts[port].mod.control_logic_center.y) 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. @@ -183,36 +166,172 @@ class sram_1bank(sram_base): row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="XY") + # Add the col address flops below the bank to the right of the control logic + x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width + y_offset = self.bank.height + self.data_bus_size[port] + self.dff.height + if self.col_addr_dff: + col_addr_pos[port] = vector(x_offset - self.col_addr_dff_insts[port].width, + y_offset) + self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX") + x_offset = self.col_addr_dff_insts[port].lx() + else: + col_addr_pos[port] = vector(x_offset, y_offset) + + if port in self.write_ports: + # Add spare write enable flops to the right of the data flops since the spare + # columns will be on the left + if self.num_spare_cols: + spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width, + y_offset) + self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX") + x_offset = self.spare_wen_dff_insts[port].lx() + if self.write_size: + # Add the write mask flops below the write mask AND array. + wmask_pos[port] = vector(x_offset - self.wmask_dff_insts[port].width, + y_offset) + self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX") + x_offset = self.wmask_dff_insts[port].lx() + + # Add the data flops below the write mask flops. + data_pos[port] = vector(x_offset - self.data_dff_insts[port].width, + y_offset) + self.data_dff_insts[port].place(data_pos[port], mirror="MX") + else: + wmask_pos[port] = vector(x_offset, y_offset) + data_pos[port] = vector(x_offset, y_offset) + spare_wen_pos[port] = vector(x_offset, y_offset) + def add_layout_pins(self): """ Add the top-level pins for a single bank SRAM with control. """ + highest_coord = self.find_highest_coords() + lowest_coord = self.find_lowest_coords() + bbox = [lowest_coord, highest_coord] + for port in self.all_ports: + # Depending on the port, use the bottom/top or left/right sides + # Port 0 is left/bottom + # Port 1 is right/top + bottom_or_top = "bottom" if port==0 else "top" + left_or_right = "left" if port==0 else "right" + # Connect the control pins as inputs - for signal in self.control_logic_inputs[port] + ["clk"]: - self.copy_layout_pin(self.control_logic_insts[port], signal, signal+"{}".format(port)) + for signal in self.control_logic_inputs[port]: + if signal == "clk": + continue + if OPTS.perimeter_pins: + self.add_perimeter_pin(name=signal + "{}".format(port), + pin=self.control_logic_insts[port].get_pin(signal), + side=left_or_right, + bbox=bbox) + else: + self.copy_layout_pin(self.control_logic_insts[port], + signal, + signal + "{}".format(port)) - if port in self.read_ports: - for bit in range(self.word_size): - self.copy_layout_pin(self.bank_inst, "dout{0}_{1}".format(port,bit), "dout{0}[{1}]".format(port,bit)) - - # Lower address bits - for bit in range(self.col_addr_size): - self.copy_layout_pin(self.col_addr_dff_insts[port], "din_{}".format(bit),"addr{0}[{1}]".format(port,bit)) - # Upper address bits - for bit in range(self.row_addr_size): - self.copy_layout_pin(self.row_addr_dff_insts[port], "din_{}".format(bit),"addr{0}[{1}]".format(port,bit+self.col_addr_size)) + if OPTS.perimeter_pins: + self.add_perimeter_pin(name="clk{}".format(port), + pin=self.control_logic_insts[port].get_pin("clk"), + side=bottom_or_top, + bbox=bbox) + else: + self.copy_layout_pin(self.control_logic_insts[port], + "clk", + "clk{}".format(port)) + # Data input pins go to BOTTOM/TOP + din_ports = [] if port in self.write_ports: - for bit in range(self.word_size): - self.copy_layout_pin(self.data_dff_insts[port], "din_{}".format(bit), "din{0}[{1}]".format(port,bit)) + for bit in range(self.word_size + self.num_spare_cols): + if OPTS.perimeter_pins: + p = self.add_perimeter_pin(name="din{0}[{1}]".format(port, bit), + pin=self.data_dff_insts[port].get_pin("din_{0}".format(bit)), + side=bottom_or_top, + bbox=bbox) + din_ports.append(p) + else: + self.copy_layout_pin(self.data_dff_insts[port], + "din_{}".format(bit), + "din{0}[{1}]".format(port, bit)) + + # Data output pins go to BOTTOM/TOP + if port in self.readwrite_ports and OPTS.perimeter_pins: + for bit in range(self.word_size + self.num_spare_cols): + # This should be routed next to the din pin + p = din_ports[bit] + self.add_layout_pin_rect_center(text="dout{0}[{1}]".format(port, bit), + layer=p.layer, + offset=p.center() + vector(self.m3_pitch, 0), + width=p.width(), + height=p.height()) + elif port in self.read_ports: + for bit in range(self.word_size + self.num_spare_cols): + if OPTS.perimeter_pins: + # This should have a clear route to the perimeter if there are no din routes + self.add_perimeter_pin(name="dout{0}[{1}]".format(port, bit), + pin=self.bank_inst.get_pin("dout{0}_{1}".format(port, bit)), + side=bottom_or_top, + bbox=bbox) + else: + self.copy_layout_pin(self.bank_inst, + "dout{0}_{1}".format(port, bit), + "dout{0}[{1}]".format(port, bit)) + + + # Lower address bits go to BOTTOM/TOP + for bit in range(self.col_addr_size): + if OPTS.perimeter_pins: + self.add_perimeter_pin(name="addr{0}[{1}]".format(port, bit), + pin=self.col_addr_dff_insts[port].get_pin("din_{}".format(bit)), + side=bottom_or_top, + bbox=bbox) + else: + self.copy_layout_pin(self.col_addr_dff_insts[port], + "din_{}".format(bit), + "addr{0}[{1}]".format(port, bit)) + + # Upper address bits go to LEFT/RIGHT + for bit in range(self.row_addr_size): + if OPTS.perimeter_pins: + self.add_perimeter_pin(name="addr{0}[{1}]".format(port, bit + self.col_addr_size), + pin=self.row_addr_dff_insts[port].get_pin("din_{}".format(bit)), + side=left_or_right, + bbox=bbox) + else: + self.copy_layout_pin(self.row_addr_dff_insts[port], + "din_{}".format(bit), + "addr{0}[{1}]".format(port, bit + self.col_addr_size)) + + # Write mask pins go to BOTTOM/TOP + if port in self.write_ports: if self.write_size: for bit in range(self.num_wmasks): - self.copy_layout_pin(self.wmask_dff_insts[port], "din_{}".format(bit), "wmask{0}[{1}]".format(port,bit)) + if OPTS.perimeter_pins: + self.add_perimeter_pin(name="wmask{0}[{1}]".format(port, bit), + pin=self.wmask_dff_insts[port].get_pin("din_{}".format(bit)), + side=bottom_or_top, + bbox=bbox) + else: + self.copy_layout_pin(self.wmask_dff_insts[port], + "din_{}".format(bit), + "wmask{0}[{1}]".format(port, bit)) + + # Spare wen pins go to BOTTOM/TOP + if port in self.write_ports: + for bit in range(self.num_spare_cols): + if OPTS.perimeter_pins: + self.add_perimeter_pin(name="spare_wen{0}[{1}]".format(port, bit), + pin=self.spare_wen_dff_insts[port].get_pin("din_{}".format(bit)), + side=left_or_right, + bbox=bbox) + else: + self.copy_layout_pin(self.spare_wen_dff_insts[port], + "din_{}".format(bit), + "spare_wen{0}[{1}]".format(port, bit)) - def route_layout(self): """ Route a single bank SRAM """ @@ -224,21 +343,89 @@ class sram_1bank(sram_base): self.route_row_addr_dff() - if self.col_addr_dff: - self.route_col_addr_dff() - - self.route_data_dff() - - if self.write_size: - self.route_wmask_dff() + for port in self.all_ports: + self.route_dff(port) + def route_dff(self, port): + + route_map = [] + + # column mux dff + if self.col_addr_size > 0: + dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] + dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + # wmask dff + if self.num_wmasks > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] + dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if port in self.write_ports: + # synchronized inputs from data dff + dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)] + dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if port in self.readwrite_ports and OPTS.perimeter_pins: + # outputs from sense amp + # These are the output pins which had their pin placed on the perimeter, so route from the + # sense amp which should not align with write driver input + sram_names = ["dout{0}[{1}]".format(port, x) for x in range(self.word_size + self.num_spare_cols)] + sram_pins = [self.get_pin(x) for x in sram_names] + bank_names = ["dout{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, sram_pins))) + + # spare wen dff + if self.num_spare_cols > 0 and port in self.write_ports: + dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] + dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] + bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] + bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + route_map.extend(list(zip(bank_pins, dff_pins))) + + if self.num_wmasks > 0 and port in self.write_ports: + layer_stack = self.m3_stack + else: + layer_stack = self.m1_stack + + if port == 0: + offset = vector(self.control_logic_insts[port].rx() + self.dff.width, + - self.data_bus_size[port] + 2 * self.m1_pitch) + else: + offset = vector(0, + self.bank.height + 2 * self.m1_space) + + if len(route_map) > 0: + self.create_horizontal_channel_route(netlist=route_map, + offset=offset, + layer_stack=layer_stack) + + # # Route these separately because sometimes the pin pitch on the write driver is too narrow for M3 (FreePDK45) + # # spare wen dff + # if self.num_spare_cols > 0 and port in self.write_ports: + # dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] + # dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names] + # bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)] + # bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] + # route_map = zip(bank_pins, dff_pins) + # self.create_horizontal_channel_route(netlist=route_map, + # offset=offset, + # layer_stack=self.m1_stack) + def route_clk(self): """ Route the clock network """ # This is the actual input to the SRAM for port in self.all_ports: - self.copy_layout_pin(self.control_logic_insts[port], "clk", "clk{}".format(port)) - # Connect all of these clock pins to the clock in the central bus # This is something like a "spine" clock distribution. The two spines # are clk_buf and clk_buf_bar @@ -248,7 +435,7 @@ class sram_1bank(sram_base): # This uses a metal2 track to the right (for port0) of the control/row addr DFF # to route vertically. For port1, it is to the left. row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk") - if port%2: + if port % 2: control_clk_buf_pos = control_clk_buf_pin.lc() row_addr_clk_pos = row_addr_clk_pin.lc() mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch, @@ -261,38 +448,33 @@ class sram_1bank(sram_base): # This is the steiner point where the net branches out clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y) - self.add_path("metal1", [control_clk_buf_pos, clk_steiner_pos]) - self.add_via_center(layers=("metal1","via1","metal2"), - offset=clk_steiner_pos) + self.add_path(control_clk_buf_pin.layer, [control_clk_buf_pos, clk_steiner_pos]) + self.add_via_stack_center(from_layer=control_clk_buf_pin.layer, + to_layer="m2", + offset=clk_steiner_pos) # Note, the via to the control logic is taken care of above - self.add_wire(("metal3","via2","metal2"),[row_addr_clk_pos, mid1_pos, clk_steiner_pos]) + self.add_wire(self.m2_stack[::-1], + [row_addr_clk_pos, mid1_pos, clk_steiner_pos]) if self.col_addr_dff: dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk") dff_clk_pos = dff_clk_pin.center() mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) - self.add_wire(("metal3","via2","metal2"),[dff_clk_pos, mid_pos, clk_steiner_pos]) - - if port in self.write_ports: + self.add_wire(self.m2_stack[::-1], + [dff_clk_pos, mid_pos, clk_steiner_pos]) + elif port in self.write_ports: data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk") data_dff_clk_pos = data_dff_clk_pin.center() mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y) # In some designs, the steiner via will be too close to the mid_pos via # so make the wire as wide as the contacts - self.add_path("metal2",[mid_pos, clk_steiner_pos], width=max(m2m3.width,m2m3.height)) - self.add_wire(("metal3","via2","metal2"),[data_dff_clk_pos, mid_pos, clk_steiner_pos]) + self.add_path("m2", + [mid_pos, clk_steiner_pos], + width=max(m2_via.width, m2_via.height)) + self.add_wire(self.m2_stack[::-1], + [data_dff_clk_pos, mid_pos, clk_steiner_pos]) - if self.write_size: - wmask_dff_clk_pin = self.wmask_dff_insts[port].get_pin("clk") - wmask_dff_clk_pos = wmask_dff_clk_pin.center() - mid_pos = vector(clk_steiner_pos.x, wmask_dff_clk_pos.y) - # In some designs, the steiner via will be too close to the mid_pos via - # so make the wire as wide as the contacts - self.add_path("metal2", [mid_pos, clk_steiner_pos], width=max(m2m3.width, m2m3.height)) - self.add_wire(("metal3", "via2", "metal2"), [wmask_dff_clk_pos, mid_pos, clk_steiner_pos]) - - def route_control_logic(self): """ Route the control logic pins that are not inputs """ @@ -302,138 +484,45 @@ 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)) - self.connect_vbus_m2m3(src_pin, dest_pin) + dest_pin = self.bank_inst.get_pin(signal + "{}".format(port)) + self.connect_vbus(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.add_wire(self.m2_stack[::-1], + [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) + self.add_via_stack_center(from_layer=src_pin.layer, + to_layer="m2", + offset=src_pin.center()) + self.add_via_stack_center(from_layer=dest_pin.layer, + to_layer="m2", + offset=dest_pin.center()) + def route_row_addr_dff(self): """ Connect the output of the row flops to the bank pins """ 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(("metal3","via2","metal2"),[flop_pos, mid_pos,bank_pos]) - self.add_via_center(layers=("metal2","via2","metal3"), - offset=flop_pos) - - def route_col_addr_dff(self): - """ Connect the output of the row flops to the bank pins """ - for port in self.all_ports: - if port%2: - offset = self.col_addr_dff_insts[port].ll() - vector(0, (self.word_size+2)*self.m1_pitch) - else: - offset = self.col_addr_dff_insts[port].ul() + vector(0, 2*self.m1_pitch) - - bus_names = ["addr_{}".format(x) for x in range(self.col_addr_size)] - col_addr_bus_offsets = self.create_horizontal_bus(layer="metal1", - pitch=self.m1_pitch, - offset=offset, - names=bus_names, - length=self.col_addr_dff_insts[port].width) - - dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] - data_dff_map = zip(dff_names, bus_names) - self.connect_horizontal_bus(data_dff_map, self.col_addr_dff_insts[port], col_addr_bus_offsets) - - bank_names = ["addr{0}_{1}".format(port,x) for x in range(self.col_addr_size)] - data_bank_map = zip(bank_names, bus_names) - self.connect_horizontal_bus(data_bank_map, self.bank_inst, col_addr_bus_offsets) - - - def route_data_dff(self): - """ Connect the output of the data flops to the write driver """ - # This is where the channel will start (y-dimension at least) - for port in self.write_ports: - if self.write_size: - if port % 2: - offset = self.data_dff_insts[port].ll() - vector(0, (self.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) - else: - 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] - if self.write_size: - for x in dff_names: - pin_offset = self.data_dff_insts[port].get_pin(x).center() - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=pin_offset, - directions = ("V", "V")) - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=pin_offset) - self.add_via_center(layers=("metal3", "via3", "metal4"), - offset=pin_offset) - - bank_names = ["din{0}_{1}".format(port,x) for x in range(self.word_size)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - if self.write_size: - for x in bank_names: - if port % 2: - pin_offset = self.bank_inst.get_pin(x).uc() - else: - pin_offset = self.bank_inst.get_pin(x).bc() - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=pin_offset) - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=pin_offset) - self.add_via_center(layers=("metal3", "via3", "metal4"), - offset=pin_offset) - - route_map = list(zip(bank_pins, dff_pins)) - if self.write_size: - self.create_horizontal_channel_route(netlist=route_map, - offset=offset, - layer_stack=("metal3", "via3", "metal4")) - else: - self.create_horizontal_channel_route(route_map, offset) - - def route_wmask_dff(self): - """ Connect the output of the wmask flops to the write mask AND array """ - # This is where the channel will start (y-dimension at least) - for port in self.write_ports: - if port % 2: - offset = self.wmask_dff_insts[port].ll() - vector(0, (self.num_wmasks+2) * self.m1_pitch) - else: - offset = self.wmask_dff_insts[port].ul() + vector(0, 2 * self.m1_pitch) - - dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] - dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] - for x in dff_names: - offset_pin = self.wmask_dff_insts[port].get_pin(x).center() - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=offset_pin, - directions=("V", "V")) - - bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)] - bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] - for x in bank_names: - offset_pin = self.bank_inst.get_pin(x).center() - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=offset_pin) - - - route_map = list(zip(bank_pins, dff_pins)) - self.create_horizontal_channel_route(route_map,offset) - + mid_pos = vector(bank_pos.x, flop_pos.y) + self.add_via_stack_center(from_layer=flop_pin.layer, + to_layer="m3", + offset=flop_pos) + self.add_path("m3", [flop_pos, mid_pos]) + self.add_via_stack_center(from_layer=bank_pin.layer, + to_layer="m3", + offset=mid_pos) + self.add_path(bank_pin.layer, [mid_pos, bank_pos]) def add_lvs_correspondence_points(self): - """ - 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. """ @@ -446,16 +535,19 @@ 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: for inst in self.wmask_dff_insts: self.graph_inst_exclude.add(inst) + if self.num_spare_cols: + for inst in self.spare_wen_dff_insts: + self.graph_inst_exclude.add(inst) def graph_exclude_addr_dff(self): """Removes data dff from search graph. """ - #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) @@ -465,27 +557,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) diff --git a/compiler/sram/sram_2bank.py b/compiler/sram/sram_2bank.py index e1224795..dace1ed9 100644 --- a/compiler/sram/sram_2bank.py +++ b/compiler/sram/sram_2bank.py @@ -103,25 +103,25 @@ class sram_2bank(sram_base): for n in self.control_logic_outputs + ["vdd", "gnd"]: pins = self.control_logic_inst.get_pins(n) for pin in pins: - if pin.layer=="metal2": + if pin.layer=="m2": pin_pos = pin.bc() break rail_pos = vector(pin_pos.x,self.horz_control_bus_positions[n].y) - self.add_path("metal2",[pin_pos,rail_pos]) - self.add_via_center(("metal1","via1","metal2"),rail_pos) + self.add_path("m2",[pin_pos,rail_pos]) + self.add_via_center(self.m1_stack,rail_pos) # connect the control logic cross bar for n in self.control_logic_outputs: cross_pos = vector(self.vert_control_bus_positions[n].x,self.horz_control_bus_positions[n].y) - self.add_via_center(("metal1","via1","metal2"),cross_pos) + self.add_via_center(self.m1_stack,cross_pos) # connect the bank select signals to the vertical bus for i in range(self.num_banks): pin = self.bank_inst[i].get_pin("bank_sel") pin_pos = pin.rc() if i==0 else pin.lc() rail_pos = vector(self.vert_control_bus_positions["bank_sel[{}]".format(i)].x,pin_pos.y) - self.add_path("metal3",[pin_pos,rail_pos]) - self.add_via_center(("metal2","via2","metal3"),rail_pos) + self.add_path("m3",[pin_pos,rail_pos]) + self.add_via_center(self.m2_stack,rail_pos) def route_single_msb_address(self): """ Route one MSB address bit for 2-bank SRAM """ @@ -129,37 +129,37 @@ class sram_2bank(sram_base): # connect the bank MSB flop supplies vdd_pins = self.msb_address_inst.get_pins("vdd") for vdd_pin in vdd_pins: - if vdd_pin.layer != "metal1": continue + if vdd_pin.layer != "m1": continue vdd_pos = vdd_pin.bc() down_pos = vdd_pos - vector(0,self.m1_pitch) rail_pos = vector(vdd_pos.x,self.horz_control_bus_positions["vdd"].y) - self.add_path("metal1",[vdd_pos,down_pos]) - self.add_via_center(("metal1","via1","metal2"),down_pos,rotate=90) - self.add_path("metal2",[down_pos,rail_pos]) - self.add_via_center(("metal1","via1","metal2"),rail_pos) + self.add_path("m1",[vdd_pos,down_pos]) + self.add_via_center(self.m1_stack,down_pos,rotate=90) + self.add_path("m2",[down_pos,rail_pos]) + self.add_via_center(self.m1_stack,rail_pos) gnd_pins = self.msb_address_inst.get_pins("gnd") # Only add the ground connection to the lowest metal2 rail in the flop array # FIXME: SCMOS doesn't have a vertical rail in the cell, or we could use those lowest_y = None for gnd_pin in gnd_pins: - if gnd_pin.layer != "metal2": continue + if gnd_pin.layer != "m2": continue if lowest_y==None or gnd_pin.by() 0.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 @@ -266,31 +274,29 @@ class sram_base(design, verilog, lef): self.control_bus_names[port].extend([wen, pen]) else: self.control_bus_names[port].extend([sen, wen, pen]) - self.vert_control_bus_positions = self.create_vertical_bus(layer="metal2", + self.vert_control_bus_positions = self.create_vertical_bus(layer="m2", pitch=self.m2_pitch, offset=self.vertical_bus_offset, 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.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="metal2", + 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.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="metal2", + 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_positions = self.create_horizontal_pin_bus(layer="metal3", + 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, names=self.data_bus_names, @@ -299,66 +305,63 @@ class sram_base(design, verilog, lef): # Horizontal control logic bus # vdd/gnd in bus go along whole SRAM # FIXME: Fatten these wires? - self.horz_control_bus_positions = self.create_horizontal_bus(layer="metal1", + self.horz_control_bus_positions = self.create_horizontal_bus(layer="m1", pitch=self.m1_pitch, offset=self.supply_bus_offset, names=["vdd"], length=self.supply_bus_width) # The gnd rail must not be the entire width since we protrude the right-most vdd rail up for # the decoder in 4-bank SRAMs - self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="metal1", + 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="metal1", + self.horz_control_bus_positions.update(self.create_horizontal_bus(layer="m1", pitch=self.m1_pitch, offset=self.control_bus_offset, 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) self.dff = factory.create(module_type="dff") # Create the address and control flops (but not the clk) - from dff_array import dff_array - self.row_addr_dff = dff_array(name="row_addr_dff", rows=self.row_addr_size, columns=1) + self.row_addr_dff = factory.create("dff_array", module_name="row_addr_dff", rows=self.row_addr_size, columns=1) self.add_mod(self.row_addr_dff) if self.col_addr_size > 0: - self.col_addr_dff = dff_array(name="col_addr_dff", rows=1, columns=self.col_addr_size) + self.col_addr_dff = factory.create("dff_array", module_name="col_addr_dff", rows=1, columns=self.col_addr_size) self.add_mod(self.col_addr_dff) else: self.col_addr_dff = None - self.data_dff = dff_array(name="data_dff", rows=1, columns=self.word_size) + self.data_dff = factory.create("dff_array", module_name="data_dff", rows=1, columns=self.word_size + self.num_spare_cols) self.add_mod(self.data_dff) if self.write_size: - self.wmask_dff = dff_array(name="wmask_dff", rows=1, columns=self.num_wmasks) + self.wmask_dff = factory.create("dff_array", module_name="wmask_dff", rows=1, columns=self.num_wmasks) self.add_mod(self.wmask_dff) + if self.num_spare_cols: + self.spare_wen_dff = factory.create("dff_array", module_name="spare_wen_dff", rows=1, columns=self.num_spare_cols) + self.add_mod(self.spare_wen_dff) # Create the bank module (up to four are instantiated) - from bank import bank - self.bank = bank(self.sram_config, - name="bank") + self.bank = factory.create("bank", sram_config=self.sram_config, module_name="bank") self.add_mod(self.bank) # Create bank decoder @@ -367,7 +370,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)) @@ -378,44 +382,47 @@ class sram_base(design, verilog, lef): self.control_logic_rw = self.mod_control_logic(num_rows=self.num_rows, words_per_row=self.words_per_row, word_size=self.word_size, + spare_columns=self.num_spare_cols, sram=self, port_type="rw") self.add_mod(self.control_logic_rw) 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, + spare_columns=self.num_spare_cols, 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, + spare_columns=self.num_spare_cols, 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)) + for bit in range(self.word_size + self.num_spare_cols): + temp.append("dout{0}[{1}]".format(port, bit)) for port in self.all_ports: temp.append("rbl_bl{0}".format(port)) for port in self.write_ports: - for bit in range(self.word_size): - temp.append("bank_din{0}[{1}]".format(port,bit)) + for bit in range(self.word_size + self.num_spare_cols): + temp.append("bank_din{0}[{1}]".format(port, bit)) for port in self.all_ports: for bit in range(self.bank_addr_size): - 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: @@ -424,6 +431,8 @@ class sram_base(design, verilog, lef): temp.append("w_en{0}".format(port)) for bit in range(self.num_wmasks): temp.append("bank_wmask{}[{}]".format(port, bit)) + for bit in range(self.num_spare_cols): + temp.append("bank_spare_wen{0}[{1}]".format(port, bit)) for port in self.all_ports: temp.append("wl_en{0}".format(port)) temp.extend(["vdd", "gnd"]) @@ -431,7 +440,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 """ @@ -462,7 +470,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 = [] @@ -474,13 +481,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 """ @@ -493,14 +499,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 = [] @@ -515,9 +520,9 @@ class sram_base(design, verilog, lef): # inputs, outputs/output/bar inputs = [] outputs = [] - for bit in range(self.word_size): - inputs.append("din{}[{}]".format(port,bit)) - outputs.append("bank_din{}[{}]".format(port,bit)) + for bit in range(self.word_size + self.num_spare_cols): + inputs.append("din{}[{}]".format(port, bit)) + outputs.append("bank_din{}[{}]".format(port, bit)) self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"]) @@ -544,8 +549,29 @@ class sram_base(design, verilog, lef): self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"]) return insts + + def create_spare_wen_dff(self): + """ Add all spare write enable flops """ + insts = [] + for port in self.all_ports: + if port in self.write_ports: + insts.append(self.add_inst(name="spare_wen_dff{}".format(port), + mod=self.spare_wen_dff)) + else: + insts.append(None) + continue - + # inputs, outputs/output/bar + inputs = [] + outputs = [] + for bit in range(self.num_spare_cols): + inputs.append("spare_wen{}[{}]".format(port, bit)) + outputs.append("bank_spare_wen{}[{}]".format(port, bit)) + + self.connect_inst(inputs + outputs + ["clk_buf{}".format(port), "vdd", "gnd"]) + + return insts + def create_control_logic(self): """ Add control logic instances """ @@ -577,35 +603,8 @@ class sram_base(design, verilog, lef): self.connect_inst(temp) return insts - - - def connect_vbus_m2m3(self, src_pin, dest_pin): - """ 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.""" - - if src_pin.cx() 0: - # Create a unique name and increment the index - module_name = "{0}_{1}".format(module_type, - self.module_indices[module_type]) - self.module_indices[module_type] += 1 + # If no prefered module name is provided, we generate one. + if not module_name: + # Use the default name for the first cell. + # This is especially for library cells so that the + # spice and gds files can be found. + # Subsequent objects will get unique names to help with GDS limitation. + if len(self.objects[real_module_type]) > 0: + # Create a unique name and increment the index + module_name = "{0}_{1}".format(real_module_type, + self.module_indices[real_module_type]) + self.module_indices[real_module_type] += 1 + else: + module_name = real_module_type else: - module_name = module_type + if self.is_duplicate_name(module_name): + raise ValueError("Modules with duplicate name are not allowed." \ + " '{}'".format(module_name)) - # type_str = "type={}".format(module_type) + # type_str = "type={}".format(real_module_type) # name_str = "name={}".format(module_name) # kwargs_str = "kwargs={}".format(str(kwargs)) # import debug # debug.info(0, "New module:" + type_str + name_str + kwargs_str) obj = mod(name=module_name, **kwargs) - self.objects[module_type].append((kwargs, obj)) + self.objects[real_module_type].append((kwargs, obj)) return obj def get_mods(self, module_type): diff --git a/compiler/tests/01_library_drc_test.py b/compiler/tests/01_library_drc_test.py index f8da685b..8b254bbc 100755 --- a/compiler/tests/01_library_drc_test.py +++ b/compiler/tests/01_library_drc_test.py @@ -17,19 +17,19 @@ import debug class library_drc_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import verify - (gds_dir, gds_files) = setup_files() + (gds_dir, allnames) = setup_files() drc_errors = 0 - debug.info(1, "\nPerforming DRC on: " + ", ".join(gds_files)) - for f in gds_files: - name = re.sub('\.gds$', '', f) - gds_name = "{0}/{1}".format(gds_dir, f) + debug.info(1, "\nPerforming DRC on: " + ", ".join(allnames)) + for f in allnames: + gds_name = "{0}/{1}.gds".format(gds_dir, f) if not os.path.isfile(gds_name): drc_errors += 1 debug.error("Missing GDS file: {}".format(gds_name)) - drc_errors += verify.run_drc(name, gds_name) + drc_errors += verify.run_drc(f, gds_name) # fails if there are any DRC errors on any cells self.assertEqual(drc_errors, 0) @@ -41,7 +41,23 @@ def setup_files(): files = os.listdir(gds_dir) nametest = re.compile("\.gds$", re.IGNORECASE) gds_files = list(filter(nametest.search, files)) - return (gds_dir, gds_files) + + tempnames = gds_files + + # remove the .gds and .sp suffixes + for i in range(len(tempnames)): + gds_files[i] = re.sub('\.gds$', '', tempnames[i]) + + try: + from tech import blackbox_cells + nameset = list(set(tempnames) - set(blackbox_cells)) + except ImportError: + # remove duplicate base names + nameset = set(tempnames) + + allnames = list(nameset) + + return (gds_dir, allnames) # run the test from the command line diff --git a/compiler/tests/02_library_lvs_test.py b/compiler/tests/02_library_lvs_test.py index ad150a2b..ed15770d 100755 --- a/compiler/tests/02_library_lvs_test.py +++ b/compiler/tests/02_library_lvs_test.py @@ -17,7 +17,8 @@ import debug class library_lvs_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import verify (gds_dir, sp_dir, allnames) = setup_files() @@ -34,7 +35,7 @@ class library_lvs_test(openram_test): debug.error("Missing GDS file {}".format(gds_name)) if not os.path.isfile(sp_name): lvs_errors += 1 - debug.error("Missing SPICE file {}".format(gds_name)) + debug.error("Missing SPICE file {}".format(sp_name)) drc_errors += verify.run_drc(name, gds_name) lvs_errors += verify.run_lvs(f, gds_name, sp_name) @@ -44,7 +45,9 @@ class library_lvs_test(openram_test): def setup_files(): gds_dir = OPTS.openram_tech + "/gds_lib" - sp_dir = OPTS.openram_tech + "/sp_lib" + sp_dir = OPTS.openram_tech + "/lvs_lib" + if not os.path.exists(sp_dir): + sp_dir = OPTS.openram_tech + "/sp_lib" files = os.listdir(gds_dir) nametest = re.compile("\.gds$", re.IGNORECASE) gds_files = list(filter(nametest.search, files)) @@ -61,8 +64,13 @@ def setup_files(): tempnames[i] = re.sub('\.gds$', '', tempnames[i]) tempnames[i] = re.sub('\.sp$', '', tempnames[i]) - # remove duplicate base names - nameset = set(tempnames) + try: + from tech import blackbox_cells + nameset = list(set(tempnames) - set(blackbox_cells)) + except ImportError: + # remove duplicate base names + nameset = set(tempnames) + allnames = list(nameset) return (gds_dir, sp_dir, allnames) diff --git a/compiler/tests/03_contact_test.py b/compiler/tests/03_contact_test.py index 3d7254c3..d2f0c7f6 100755 --- a/compiler/tests/03_contact_test.py +++ b/compiler/tests/03_contact_test.py @@ -18,14 +18,19 @@ import debug class contact_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) - for layer_stack in [("metal1", "via1", "metal2"), ("poly", "contact", "metal1")]: + from tech import active_stack, poly_stack, beol_stacks + + # Don't do active because of nwell contact rules + # Don't do metal3 because of min area rules + for layer_stack in [poly_stack] + [beol_stacks[0]]: stack_name = ":".join(map(str, layer_stack)) # Check single 1 x 1 contact" debug.info(2, "1 x 1 {} test".format(stack_name)) - c = factory.create(module_type="contact", layer_stack=layer_stack, dimensions=(1, 1)) + c = factory.create(module_type="contact", layer_stack=layer_stack, dimensions=(1, 1), directions=("H", "H")) self.local_drc_check(c) # Check single 1 x 1 contact" @@ -42,6 +47,10 @@ class contact_test(openram_test): debug.info(2, "1 x 1 {} test".format(stack_name)) c = factory.create(module_type="contact", layer_stack=layer_stack, dimensions=(1, 1), directions=("V","V")) self.local_drc_check(c) + + # Only do multiple contacts for BEOL + for layer_stack in beol_stacks: + stack_name = ":".join(map(str, layer_stack)) # check vertical array with one in the middle and two ends debug.info(2, "1 x 3 {} test".format(stack_name)) @@ -58,6 +67,25 @@ class contact_test(openram_test): c = factory.create(module_type="contact", layer_stack=layer_stack, dimensions=(3, 3)) self.local_drc_check(c) + # Test the well taps + # check vertical array with one in the middle and two ends + layer_stack = active_stack + stack_name = ":".join(map(str, layer_stack)) + + debug.info(2, "1 x 1 {} nwell".format(stack_name)) + c = factory.create(module_type="contact", + layer_stack=layer_stack, + implant_type="n", + well_type="n") + self.local_drc_check(c) + + debug.info(2, "1 x 1 {} pwell".format(stack_name)) + c = factory.create(module_type="contact", + layer_stack=layer_stack, + implant_type="p", + well_type="p") + self.local_drc_check(c) + globals.end_openram() diff --git a/compiler/tests/03_path_test.py b/compiler/tests/03_path_test.py index 21001718..94e7821a 100755 --- a/compiler/tests/03_path_test.py +++ b/compiler/tests/03_path_test.py @@ -17,13 +17,14 @@ import debug class path_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import wire_path import tech import design - min_space = 2 * tech.drc["minwidth_metal1"] - layer_stack = ("metal1") + min_space = 2 * tech.drc["minwidth_m1"] + layer_stack = ("m1") # checks if we can retrace a path position_list = [[0,0], [0, 3 * min_space ], @@ -36,8 +37,8 @@ class path_test(openram_test): self.local_drc_check(w) - min_space = 2 * tech.drc["minwidth_metal1"] - layer_stack = ("metal1") + min_space = 2 * tech.drc["minwidth_m1"] + layer_stack = ("m1") old_position_list = [[0, 0], [0, 3 * min_space], [1 * min_space, 3 * min_space], @@ -52,8 +53,8 @@ class path_test(openram_test): wire_path.wire_path(w,layer_stack, position_list) self.local_drc_check(w) - min_space = 2 * tech.drc["minwidth_metal2"] - layer_stack = ("metal2") + min_space = 2 * tech.drc["minwidth_m2"] + layer_stack = ("m2") old_position_list = [[0, 0], [0, 3 * min_space], [1 * min_space, 3 * min_space], @@ -68,8 +69,8 @@ class path_test(openram_test): wire_path.wire_path(w, layer_stack, position_list) self.local_drc_check(w) - min_space = 2 * tech.drc["minwidth_metal3"] - layer_stack = ("metal3") + min_space = 2 * tech.drc["minwidth_m3"] + layer_stack = ("m3") position_list = [[0, 0], [0, 3 * min_space], [1 * min_space, 3 * min_space], diff --git a/compiler/tests/03_ptx_1finger_nmos_test.py b/compiler/tests/03_ptx_1finger_nmos_test.py index f436d7d0..0774af4a 100755 --- a/compiler/tests/03_ptx_1finger_nmos_test.py +++ b/compiler/tests/03_ptx_1finger_nmos_test.py @@ -15,10 +15,12 @@ from globals import OPTS from sram_factory import factory import debug + class ptx_1finger_nmos_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import tech debug.info(2, "Checking min size NMOS with 1 finger") diff --git a/compiler/tests/03_ptx_1finger_pmos_test.py b/compiler/tests/03_ptx_1finger_pmos_test.py index ae8078e7..3bf3e293 100755 --- a/compiler/tests/03_ptx_1finger_pmos_test.py +++ b/compiler/tests/03_ptx_1finger_pmos_test.py @@ -18,7 +18,8 @@ import debug class ptx_1finger_pmos_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import tech debug.info(2, "Checking min size PMOS with 1 finger") diff --git a/compiler/tests/03_ptx_3finger_nmos_test.py b/compiler/tests/03_ptx_3finger_nmos_test.py index c010a948..0b24ffd5 100755 --- a/compiler/tests/03_ptx_3finger_nmos_test.py +++ b/compiler/tests/03_ptx_3finger_nmos_test.py @@ -18,7 +18,8 @@ import debug class ptx_3finger_nmos_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import tech debug.info(2, "Checking three fingers NMOS") @@ -26,7 +27,8 @@ class ptx_3finger_nmos_test(openram_test): width=tech.drc["minwidth_tx"], mults=3, tx_type="nmos", - connect_active=True, + connect_source_active=True, + connect_drain_active=True, connect_poly=True) self.local_drc_check(fet) diff --git a/compiler/tests/03_ptx_3finger_pmos_test.py b/compiler/tests/03_ptx_3finger_pmos_test.py index 85cca9e2..ccb8b586 100755 --- a/compiler/tests/03_ptx_3finger_pmos_test.py +++ b/compiler/tests/03_ptx_3finger_pmos_test.py @@ -18,7 +18,8 @@ import debug class ptx_3finger_pmos_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import tech debug.info(2, "Checking three fingers PMOS") @@ -26,7 +27,8 @@ class ptx_3finger_pmos_test(openram_test): width=tech.drc["minwidth_tx"], mults=3, tx_type="pmos", - connect_active=True, + connect_source_active=True, + connect_drain_active=True, connect_poly=True) self.local_drc_check(fet) diff --git a/compiler/tests/03_ptx_4finger_nmos_test.py b/compiler/tests/03_ptx_4finger_nmos_test.py index 4a410d51..f7f3db78 100755 --- a/compiler/tests/03_ptx_4finger_nmos_test.py +++ b/compiler/tests/03_ptx_4finger_nmos_test.py @@ -18,15 +18,17 @@ import debug class ptx_4finger_nmos_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import tech debug.info(2, "Checking three fingers NMOS") - fet = factory.create(module_type="ptx", + fet = factory.create(module_type="ptx", width= tech.drc["minwidth_tx"], mults=4, tx_type="nmos", - connect_active=True, + connect_source_active=True, + connect_drain_active=True, connect_poly=True) self.local_drc_check(fet) diff --git a/compiler/tests/03_ptx_4finger_pmos_test.py b/compiler/tests/03_ptx_4finger_pmos_test.py index 34fbaf9f..0bcdf909 100755 --- a/compiler/tests/03_ptx_4finger_pmos_test.py +++ b/compiler/tests/03_ptx_4finger_pmos_test.py @@ -18,15 +18,17 @@ import debug class ptx_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import tech debug.info(2, "Checking three fingers PMOS") - fet = factory.create(module_type="ptx", + fet = factory.create(module_type="ptx", width=tech.drc["minwidth_tx"], mults=4, tx_type="pmos", - connect_active=True, + connect_source_active=True, + connect_drain_active=True, connect_poly=True) self.local_drc_check(fet) diff --git a/compiler/tests/03_ptx_no_contacts_test.py b/compiler/tests/03_ptx_no_contacts_test.py new file mode 100755 index 00000000..ff39da79 --- /dev/null +++ b/compiler/tests/03_ptx_no_contacts_test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class ptx_no_contacts_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + import tech + + debug.info(2, "Checking single finger no source/drain") + fet = factory.create(module_type="ptx", + width=tech.drc["minwidth_tx"], + mults=1, + add_source_contact=False, + add_drain_contact=False, + tx_type="nmos") + self.local_drc_check(fet) + + debug.info(2, "Checking multifinger no source/drain") + fet = factory.create(module_type="ptx", + width=tech.drc["minwidth_tx"], + mults=4, + add_source_contact=False, + add_drain_contact=False, + tx_type="nmos") + self.local_drc_check(fet) + + debug.info(2, "Checking series ptx") + fet = factory.create(module_type="ptx", + width=tech.drc["minwidth_tx"], + mults=4, + series_devices=True, + tx_type="nmos") + self.local_drc_check(fet) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/03_wire_test.py b/compiler/tests/03_wire_test.py index 4ee360ce..61e21985 100755 --- a/compiler/tests/03_wire_test.py +++ b/compiler/tests/03_wire_test.py @@ -8,120 +8,47 @@ # import unittest from testutils import * -import sys,os +import sys +import os sys.path.append(os.getenv("OPENRAM_HOME")) import globals -from globals import OPTS -import debug + class wire_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import wire import tech import design - - min_space = 2 * (tech.drc["minwidth_poly"] + - tech.drc["minwidth_metal1"]) - layer_stack = ("poly", "contact", "metal1") - old_position_list = [[0, 0], - [0, 3 * min_space], - [1 * min_space, 3 * min_space], - [4 * min_space, 3 * min_space], - [4 * min_space, 0], - [7 * min_space, 0], - [7 * min_space, 4 * min_space], - [-1 * min_space, 4 * min_space], - [-1 * min_space, 0]] - position_list = [[x-min_space, y-min_space] for x,y in old_position_list] - w = design.design("wire_test1") - wire.wire(w, layer_stack, position_list) - self.local_drc_check(w) - min_space = 2 * (tech.drc["minwidth_poly"] + - tech.drc["minwidth_metal1"]) - layer_stack = ("metal1", "contact", "poly") - old_position_list = [[0, 0], - [0, 3 * min_space], - [1 * min_space, 3 * min_space], - [4 * min_space, 3 * min_space], - [4 * min_space, 0], - [7 * min_space, 0], - [7 * min_space, 4 * min_space], - [-1 * min_space, 4 * min_space], - [-1 * min_space, 0]] - position_list = [[x+min_space, y+min_space] for x,y in old_position_list] - w = design.design("wire_test2") - wire.wire(w, layer_stack, position_list) - self.local_drc_check(w) + layer_stacks = [tech.poly_stack] + tech.beol_stacks - min_space = 2 * (tech.drc["minwidth_metal2"] + - tech.drc["minwidth_metal1"]) - layer_stack = ("metal1", "via1", "metal2") - position_list = [[0, 0], - [0, 3 * min_space], - [1 * min_space, 3 * min_space], - [4 * min_space, 3 * min_space], - [4 * min_space, 0], - [7 * min_space, 0], - [7 * min_space, 4 * min_space], - [-1 * min_space, 4 * min_space], - [-1 * min_space, 0]] - w = design.design("wire_test3") - wire.wire(w, layer_stack, position_list) - self.local_drc_check(w) + for reverse in [False, True]: + for stack in layer_stacks: + if reverse: + layer_stack = stack[::-1] + else: + layer_stack = stack + # Just make a conservative spacing. Make it wire pitch instead? + min_space = 2 * (tech.drc["minwidth_{}".format(layer_stack[0])] + + tech.drc["minwidth_{}".format(layer_stack[2])]) - min_space = 2 * (tech.drc["minwidth_metal2"] + - tech.drc["minwidth_metal1"]) - layer_stack = ("metal2", "via1", "metal1") - position_list = [[0, 0], - [0, 3 * min_space], - [1 * min_space, 3 * min_space], - [4 * min_space, 3 * min_space], - [4 * min_space, 0], - [7 * min_space, 0], - [7 * min_space, 4 * min_space], - [-1 * min_space, 4 * min_space], - [-1 * min_space, 0]] - w = design.design("wire_test4") - wire.wire(w, layer_stack, position_list) - self.local_drc_check(w) - - min_space = 2 * (tech.drc["minwidth_metal2"] + - tech.drc["minwidth_metal3"]) - layer_stack = ("metal2", "via2", "metal3") - position_list = [[0, 0], - [0, 3 * min_space], - [1 * min_space, 3 * min_space], - [4 * min_space, 3 * min_space], - [4 * min_space, 0], - [7 * min_space, 0], - [7 * min_space, 4 * min_space], - [-1 * min_space, 4 * min_space], - [-1 * min_space, 0]] - position_list.reverse() - w = design.design("wire_test5") - wire.wire(w, layer_stack, position_list) - self.local_drc_check(w) - - min_space = 2 * (tech.drc["minwidth_metal2"] + - tech.drc["minwidth_metal3"]) - layer_stack = ("metal3", "via2", "metal2") - position_list = [[0, 0], - [0, 3 * min_space], - [1 * min_space, 3 * min_space], - [4 * min_space, 3 * min_space], - [4 * min_space, 0], - [7 * min_space, 0], - [7 * min_space, 4 * min_space], - [-1 * min_space, 4 * min_space], - [-1 * min_space, 0]] - position_list.reverse() - w = design.design("wire_test6") - wire.wire(w, layer_stack, position_list) - self.local_drc_check(w) + position_list = [[0, 0], + [0, 3 * min_space], + [1 * min_space, 3 * min_space], + [4 * min_space, 3 * min_space], + [4 * min_space, 0], + [7 * min_space, 0], + [7 * min_space, 4 * min_space], + [-1 * min_space, 4 * min_space], + [-1 * min_space, 0]] + position_list = [[x - min_space, y - min_space] for x, y in position_list] + w = design.design("wire_test_{}".format("_".join(layer_stack))) + wire.wire(w, layer_stack, position_list) + self.local_drc_check(w) globals.end_openram() diff --git a/compiler/tests/04_and2_dec_test.py b/compiler/tests/04_and2_dec_test.py new file mode 100755 index 00000000..97ac5749 --- /dev/null +++ b/compiler/tests/04_and2_dec_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class and2_dec_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + global verify + import verify + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing and2_dec gate") + a = factory.create(module_type="and2_dec") + self.local_check(a) + + globals.end_openram() + +# instantiate a copdsay of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_and3_dec_test.py b/compiler/tests/04_and3_dec_test.py new file mode 100755 index 00000000..ec83335b --- /dev/null +++ b/compiler/tests/04_and3_dec_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class and3_dec_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + global verify + import verify + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing and3_dec gate") + a = factory.create(module_type="and3_dec") + self.local_check(a) + + globals.end_openram() + +# instantiate a copdsay of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_and4_dec_test.py b/compiler/tests/04_and4_dec_test.py new file mode 100755 index 00000000..ffd7788a --- /dev/null +++ b/compiler/tests/04_and4_dec_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +@unittest.skip("SKIPPING 04_and4_dec_test") +class and4_dec_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + global verify + import verify + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing and4_dec gate") + a = factory.create(module_type="and4_dec") + self.local_check(a) + + globals.end_openram() + +# instantiate a copdsay of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/11_dff_buf_test.py b/compiler/tests/04_dff_buf_test.py similarity index 88% rename from compiler/tests/11_dff_buf_test.py rename to compiler/tests/04_dff_buf_test.py index 161deaa2..070cdb56 100755 --- a/compiler/tests/11_dff_buf_test.py +++ b/compiler/tests/04_dff_buf_test.py @@ -18,7 +18,8 @@ import debug class dff_buf_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Testing dff_buf 4x 8x") a = factory.create(module_type="dff_buf", inv1_size=4, inv2_size=8) diff --git a/compiler/tests/04_dummy_pbitcell_test.py b/compiler/tests/04_dummy_pbitcell_test.py index e8e3cc1c..b39e48ea 100755 --- a/compiler/tests/04_dummy_pbitcell_test.py +++ b/compiler/tests/04_dummy_pbitcell_test.py @@ -18,7 +18,8 @@ import debug class replica_pbitcell_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import dummy_pbitcell OPTS.bitcell = "pbitcell" diff --git a/compiler/tests/04_pand2_test.py b/compiler/tests/04_pand2_test.py index 42045b43..f7e5f304 100755 --- a/compiler/tests/04_pand2_test.py +++ b/compiler/tests/04_pand2_test.py @@ -18,7 +18,8 @@ import debug class pand2_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) global verify import verify diff --git a/compiler/tests/04_pand3_test.py b/compiler/tests/04_pand3_test.py index 4408f6e8..e58f1ee9 100755 --- a/compiler/tests/04_pand3_test.py +++ b/compiler/tests/04_pand3_test.py @@ -18,7 +18,8 @@ import debug class pand3_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) global verify import verify diff --git a/compiler/tests/04_pbitcell_test.py b/compiler/tests/04_pbitcell_test.py index 0f14c4c5..450c0263 100755 --- a/compiler/tests/04_pbitcell_test.py +++ b/compiler/tests/04_pbitcell_test.py @@ -19,7 +19,8 @@ from sram_factory import factory class pbitcell_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.num_rw_ports=1 OPTS.num_w_ports=1 diff --git a/compiler/tests/04_pbuf_test.py b/compiler/tests/04_pbuf_test.py index 35db8ccf..ffd06962 100755 --- a/compiler/tests/04_pbuf_test.py +++ b/compiler/tests/04_pbuf_test.py @@ -18,7 +18,8 @@ import debug class pbuf_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Testing inverter/buffer 4x 8x") a = factory.create(module_type="pbuf", size=8) diff --git a/compiler/tests/04_pdriver_test.py b/compiler/tests/04_pdriver_test.py index abaab4a0..41af86f6 100755 --- a/compiler/tests/04_pdriver_test.py +++ b/compiler/tests/04_pdriver_test.py @@ -18,7 +18,8 @@ import debug class pdriver_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Testing inverter/buffer 4x 8x") # a tests the error message for specifying conflicting conditions @@ -31,13 +32,13 @@ class pdriver_test(openram_test): c = factory.create(module_type="pdriver", fanout = 50) self.local_check(c) - d = factory.create(module_type="pdriver", fanout = 50, neg_polarity = True) + d = factory.create(module_type="pdriver", fanout = 50, inverting = True) self.local_check(d) e = factory.create(module_type="pdriver", fanout = 64) self.local_check(e) - f = factory.create(module_type="pdriver", fanout = 64, neg_polarity = True) + f = factory.create(module_type="pdriver", fanout = 64, inverting = True) self.local_check(f) globals.end_openram() diff --git a/compiler/tests/04_pinv_100x_test.py b/compiler/tests/04_pinv_100x_test.py new file mode 100755 index 00000000..91b55d5d --- /dev/null +++ b/compiler/tests/04_pinv_100x_test.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class pinv_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + debug.info(2, "Checking 100x inverter") + tx = factory.create(module_type="pinv", size=100) + self.local_check(tx) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_pinv_10x_test.py b/compiler/tests/04_pinv_10x_test.py index 2ccce34a..ae3f480b 100755 --- a/compiler/tests/04_pinv_10x_test.py +++ b/compiler/tests/04_pinv_10x_test.py @@ -18,7 +18,8 @@ import debug class pinv_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Checking 8x inverter") tx = factory.create(module_type="pinv", size=8) diff --git a/compiler/tests/04_pinv_1x_beta_test.py b/compiler/tests/04_pinv_1x_beta_test.py index 2f96020c..0b8c055a 100755 --- a/compiler/tests/04_pinv_1x_beta_test.py +++ b/compiler/tests/04_pinv_1x_beta_test.py @@ -18,7 +18,8 @@ import debug class pinv_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Checking 1x beta=3 size inverter") tx = factory.create(module_type="pinv", size=1, beta=3) diff --git a/compiler/tests/04_pinv_1x_test.py b/compiler/tests/04_pinv_1x_test.py index 9b0f1bc6..39704975 100755 --- a/compiler/tests/04_pinv_1x_test.py +++ b/compiler/tests/04_pinv_1x_test.py @@ -18,13 +18,14 @@ import debug class pinv_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Checking 1x size inverter") tx = factory.create(module_type="pinv", size=1) self.local_check(tx) - globals.end_openram() + globals.end_openram() # run the test from the command line if __name__ == "__main__": diff --git a/compiler/tests/04_pinv_2x_test.py b/compiler/tests/04_pinv_2x_test.py index d8a7598f..0a0e63ec 100755 --- a/compiler/tests/04_pinv_2x_test.py +++ b/compiler/tests/04_pinv_2x_test.py @@ -18,7 +18,8 @@ import debug class pinv_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Checking 2x size inverter") tx = factory.create(module_type="pinv", size=2) diff --git a/compiler/tests/04_pinv_dec_1x_test.py b/compiler/tests/04_pinv_dec_1x_test.py new file mode 100755 index 00000000..92772616 --- /dev/null +++ b/compiler/tests/04_pinv_dec_1x_test.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class pinv_dec_1x_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Checking 1x size decoder inverter") + tx = factory.create(module_type="pinv_dec", size=1) + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_pinvbuf_test.py b/compiler/tests/04_pinvbuf_test.py index 86af0708..df7645d1 100755 --- a/compiler/tests/04_pinvbuf_test.py +++ b/compiler/tests/04_pinvbuf_test.py @@ -18,7 +18,8 @@ import debug class pinvbuf_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Testing inverter/buffer 4x 8x") a = factory.create(module_type="pinvbuf", size=8) diff --git a/compiler/tests/04_pnand2_test.py b/compiler/tests/04_pnand2_test.py index bc066cfc..ae0668ae 100755 --- a/compiler/tests/04_pnand2_test.py +++ b/compiler/tests/04_pnand2_test.py @@ -18,12 +18,18 @@ import debug class pnand2_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Checking 2-input nand gate") tx = factory.create(module_type="pnand2", size=1) self.local_check(tx) + # debug.info(2, "Checking 2-input nand gate") + # tx = factory.create(module_type="pnand2", size=1, add_wells=False) + # # Only DRC because well contacts will fail LVS + # self.local_drc_check(tx) + globals.end_openram() diff --git a/compiler/tests/04_pnand3_test.py b/compiler/tests/04_pnand3_test.py index 8bf5098f..c03e32d6 100755 --- a/compiler/tests/04_pnand3_test.py +++ b/compiler/tests/04_pnand3_test.py @@ -18,12 +18,18 @@ import debug class pnand3_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Checking 3-input nand gate") tx = factory.create(module_type="pnand3", size=1) self.local_check(tx) + # debug.info(2, "Checking 3-input nand gate") + # tx = factory.create(module_type="pnand3", size=1, add_wells=False) + # # Only DRC because well contacts will fail LVS + # self.local_drc_check(tx) + globals.end_openram() diff --git a/compiler/tests/04_pnor2_test.py b/compiler/tests/04_pnor2_test.py index 0e524506..ea0d6dbc 100755 --- a/compiler/tests/04_pnor2_test.py +++ b/compiler/tests/04_pnor2_test.py @@ -18,7 +18,8 @@ import debug class pnor2_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Checking 2-input nor gate") tx = factory.create(module_type="pnor2", size=1) diff --git a/compiler/tests/04_precharge_1rw_1r_test.py b/compiler/tests/04_precharge_1rw_1r_test.py new file mode 100755 index 00000000..4765c77c --- /dev/null +++ b/compiler/tests/04_precharge_1rw_1r_test.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class precharge_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check precharge array in multi-port + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Checking precharge for 1rw1r port 0") + tx = factory.create(module_type="precharge", size=1, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(tx) + + factory.reset() + debug.info(2, "Checking precharge for 1rw1r port 1") + tx = factory.create(module_type="precharge", size=1, bitcell_bl="bl1", bitcell_br="br1") + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_precharge_pbitcell_test.py b/compiler/tests/04_precharge_pbitcell_test.py new file mode 100755 index 00000000..3eb7628d --- /dev/null +++ b/compiler/tests/04_precharge_pbitcell_test.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class precharge_pbitcell_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check precharge in multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 1 + + factory.reset() + debug.info(2, "Checking precharge for pbitcell (innermost connections)") + tx = factory.create(module_type="precharge", size=1, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(tx) + + factory.reset() + debug.info(2, "Checking precharge for pbitcell (innermost connections)") + tx = factory.create(module_type="precharge", size=1, bitcell_bl="bl1", bitcell_br="br1") + self.local_check(tx) + + factory.reset() + debug.info(2, "Checking precharge for pbitcell (outermost connections)") + tx = factory.create(module_type="precharge", size=1, bitcell_bl="bl2", bitcell_br="br2") + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_precharge_test.py b/compiler/tests/04_precharge_test.py index 9b2addd5..76d433a8 100755 --- a/compiler/tests/04_precharge_test.py +++ b/compiler/tests/04_precharge_test.py @@ -15,37 +15,18 @@ from globals import OPTS from sram_factory import factory import debug + class precharge_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) # check precharge in single port debug.info(2, "Checking precharge for handmade bitcell") tx = factory.create(module_type="precharge", size=1) self.local_check(tx) - # check precharge in multi-port - OPTS.bitcell = "pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_r_ports = 1 - OPTS.num_w_ports = 1 - - factory.reset() - debug.info(2, "Checking precharge for pbitcell (innermost connections)") - tx = factory.create(module_type="precharge", size=1, bitcell_bl="bl0", bitcell_br="br0") - self.local_check(tx) - - factory.reset() - debug.info(2, "Checking precharge for pbitcell (innermost connections)") - tx = factory.create(module_type="precharge", size=1, bitcell_bl="bl1", bitcell_br="br1") - self.local_check(tx) - - factory.reset() - debug.info(2, "Checking precharge for pbitcell (outermost connections)") - tx = factory.create(module_type="precharge", size=1, bitcell_bl="bl2", bitcell_br="br2") - self.local_check(tx) - globals.end_openram() # run the test from the command line diff --git a/compiler/tests/04_pwrite_driver_test.py b/compiler/tests/04_pwrite_driver_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/04_replica_pbitcell_test.py b/compiler/tests/04_replica_pbitcell_test.py index 65ce5ecf..77336c61 100755 --- a/compiler/tests/04_replica_pbitcell_test.py +++ b/compiler/tests/04_replica_pbitcell_test.py @@ -18,7 +18,8 @@ import debug class replica_pbitcell_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import replica_pbitcell OPTS.bitcell = "pbitcell" diff --git a/compiler/tests/04_single_level_column_mux_1rw_1r_test.py b/compiler/tests/04_single_level_column_mux_1rw_1r_test.py new file mode 100755 index 00000000..a7e79e9b --- /dev/null +++ b/compiler/tests/04_single_level_column_mux_1rw_1r_test.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class single_level_column_mux_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Checking column mux port 0") + tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(tx) + + debug.info(2, "Checking column mux port 1") + tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl1", bitcell_br="br1") + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_single_level_column_mux_pbitcell_test.py b/compiler/tests/04_single_level_column_mux_pbitcell_test.py new file mode 100755 index 00000000..18ab631f --- /dev/null +++ b/compiler/tests/04_single_level_column_mux_pbitcell_test.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +#@unittest.skip("SKIPPING 04_driver_test") + +class single_level_column_mux_pbitcell_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check single level column mux in multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 1 + + factory.reset() + debug.info(2, "Checking column mux for pbitcell (innermost connections)") + tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(tx) + + factory.reset() + debug.info(2, "Checking column mux for pbitcell (outermost connections)") + tx = factory.create(module_type="single_level_column_mux",tx_size=8, bitcell_bl="bl2", bitcell_br="br2") + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_single_level_column_mux_test.py b/compiler/tests/04_single_level_column_mux_test.py index 3ecbbe9d..20dfe968 100755 --- a/compiler/tests/04_single_level_column_mux_test.py +++ b/compiler/tests/04_single_level_column_mux_test.py @@ -15,36 +15,20 @@ from globals import OPTS from sram_factory import factory import debug -#@unittest.skip("SKIPPING 04_driver_test") class single_level_column_mux_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) - + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + # check single level column mux in single port debug.info(2, "Checking column mux") tx = factory.create(module_type="single_level_column_mux", tx_size=8) self.local_check(tx) - - # check single level column mux in multi-port - OPTS.bitcell = "pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_r_ports = 1 - OPTS.num_w_ports = 1 - - factory.reset() - debug.info(2, "Checking column mux for pbitcell (innermost connections)") - tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl0", bitcell_br="br0") - self.local_check(tx) - - factory.reset() - debug.info(2, "Checking column mux for pbitcell (outermost connections)") - tx = factory.create(module_type="single_level_column_mux",tx_size=8, bitcell_bl="bl2", bitcell_br="br2") - self.local_check(tx) globals.end_openram() - + # run the test from the command line if __name__ == "__main__": (OPTS, args) = globals.parse_args() diff --git a/compiler/tests/04_wordline_driver_test.py b/compiler/tests/04_wordline_driver_test.py new file mode 100755 index 00000000..ada65db7 --- /dev/null +++ b/compiler/tests/04_wordline_driver_test.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +#@unittest.skip("SKIPPING 04_driver_test") + +class wordline_driver_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check wordline driver for single port + debug.info(2, "Checking driver") + tx = factory.create(module_type="wordline_driver") + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/05_bitcell_1rw_1r_array_test.py b/compiler/tests/05_bitcell_array_1rw_1r_test.py similarity index 80% rename from compiler/tests/05_bitcell_1rw_1r_array_test.py rename to compiler/tests/05_bitcell_array_1rw_1r_test.py index 972fb8e6..0e7d5665 100755 --- a/compiler/tests/05_bitcell_1rw_1r_array_test.py +++ b/compiler/tests/05_bitcell_array_1rw_1r_test.py @@ -17,18 +17,16 @@ import debug #@unittest.skip("SKIPPING 05_bitcell_1rw_1r_array_test") -class bitcell_1rw_1r_array_test(openram_test): +class bitcell_array_1rw_1r_test(openram_test): def runTest(self): - - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell="dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 + globals.setup_bitcell() debug.info(2, "Testing 4x4 array for cell_1rw_1r") a = factory.create(module_type="bitcell_array", cols=4, rows=4) diff --git a/compiler/tests/05_bitcell_array_test.py b/compiler/tests/05_bitcell_array_test.py index 6a561019..256ad526 100755 --- a/compiler/tests/05_bitcell_array_test.py +++ b/compiler/tests/05_bitcell_array_test.py @@ -20,7 +20,8 @@ import debug class array_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Testing 4x4 array for 6t_cell") a = factory.create(module_type="bitcell_array", cols=4, rows=4) diff --git a/compiler/tests/05_dummy_array_test.py b/compiler/tests/05_dummy_array_test.py index de379a97..97ec8db5 100755 --- a/compiler/tests/05_dummy_array_test.py +++ b/compiler/tests/05_dummy_array_test.py @@ -16,7 +16,8 @@ import debug class dummy_row_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Testing dummy row for 6t_cell") a = factory.create(module_type="dummy_array", rows=1, cols=4) diff --git a/compiler/tests/05_pbitcell_array_test.py b/compiler/tests/05_pbitcell_array_test.py index 91bf7522..6c5e4729 100755 --- a/compiler/tests/05_pbitcell_array_test.py +++ b/compiler/tests/05_pbitcell_array_test.py @@ -19,7 +19,8 @@ import debug class pbitcell_array_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Testing 4x4 array for multiport bitcell, with read ports at the edge of the bit cell") OPTS.bitcell = "pbitcell" diff --git a/compiler/tests/06_hierarchical_decoder_1rw_1r_test.py b/compiler/tests/06_hierarchical_decoder_1rw_1r_test.py new file mode 100755 index 00000000..844160a6 --- /dev/null +++ b/compiler/tests/06_hierarchical_decoder_1rw_1r_test.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class hierarchical_decoder_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + # Checks 2x4 and 2-input NAND decoder + debug.info(1, "Testing 16 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=16) + self.local_check(a) + + # Checks 2x4 and 2-input NAND decoder with non-power-of-two + debug.info(1, "Testing 17 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=17) + self.local_check(a) + + # Checks 2x4 with 3x8 and 2-input NAND decoder + debug.info(1, "Testing 32 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=32) + self.local_check(a) + + # Checks 3 x 2x4 and 3-input NAND decoder + debug.info(1, "Testing 64 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=64) + self.local_check(a) + + # Checks 2x4 and 2 x 3x8 and 3-input NAND with non-power-of-two + debug.info(1, "Testing 132 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=132) + self.local_check(a) + + # Checks 3 x 3x8 and 3-input NAND decoder + debug.info(1, "Testing 512 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=512) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/06_hierarchical_decoder_pbitcell_test.py b/compiler/tests/06_hierarchical_decoder_pbitcell_test.py new file mode 100755 index 00000000..e1605067 --- /dev/null +++ b/compiler/tests/06_hierarchical_decoder_pbitcell_test.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class hierarchical_decoder_pbitcell_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + # check hierarchical decoder for multi-port + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + globals.setup_bitcell() + + factory.reset() + debug.info(1, "Testing 16 row sample for hierarchical_decoder (multi-port case)") + a = factory.create(module_type="hierarchical_decoder", num_outputs=16) + self.local_check(a) + + factory.reset() + debug.info(1, "Testing 17 row sample for hierarchical_decoder (multi-port case)") + a = factory.create(module_type="hierarchical_decoder", num_outputs=17) + self.local_check(a) + + factory.reset() + debug.info(1, "Testing 23 row sample for hierarchical_decoder (multi-port case)") + a = factory.create(module_type="hierarchical_decoder", num_outputs=23) + self.local_check(a) + + debug.info(1, "Testing 32 row sample for hierarchical_decoder (multi-port case)") + a = factory.create(module_type="hierarchical_decoder", num_outputs=32) + self.local_check(a) + + factory.reset() + debug.info(1, "Testing 65 row sample for hierarchical_decoder (multi-port case)") + a = factory.create(module_type="hierarchical_decoder", num_outputs=65) + self.local_check(a) + + debug.info(1, "Testing 128 row sample for hierarchical_decoder (multi-port case)") + a = factory.create(module_type="hierarchical_decoder", num_outputs=128) + self.local_check(a) + + factory.reset() + debug.info(1, "Testing 341 row sample for hierarchical_decoder (multi-port case)") + a = factory.create(module_type="hierarchical_decoder", num_outputs=341) + self.local_check(a) + + debug.info(1, "Testing 512 row sample for hierarchical_decoder (multi-port case)") + a = factory.create(module_type="hierarchical_decoder", num_outputs=512) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/06_hierarchical_decoder_test.py b/compiler/tests/06_hierarchical_decoder_test.py index c349e889..e2b34cba 100755 --- a/compiler/tests/06_hierarchical_decoder_test.py +++ b/compiler/tests/06_hierarchical_decoder_test.py @@ -18,57 +18,39 @@ import debug class hierarchical_decoder_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) - # Doesn't require hierarchical decoder - # debug.info(1, "Testing 4 row sample for hierarchical_decoder") - # a = hierarchical_decoder.hierarchical_decoder(name="hd1, rows=4) - # self.local_check(a) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) - # Doesn't require hierarchical decoder - # debug.info(1, "Testing 8 row sample for hierarchical_decoder") - # a = hierarchical_decoder.hierarchical_decoder(name="hd2", rows=8) - # self.local_check(a) - - # check hierarchical decoder for single port + # Checks 2x4 and 2-input NAND decoder debug.info(1, "Testing 16 row sample for hierarchical_decoder") - a = factory.create(module_type="hierarchical_decoder", rows=16) + a = factory.create(module_type="hierarchical_decoder", num_outputs=16) self.local_check(a) + # Checks 2x4 and 2-input NAND decoder with non-power-of-two + debug.info(1, "Testing 17 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=17) + self.local_check(a) + + # Checks 2x4 with 3x8 and 2-input NAND decoder debug.info(1, "Testing 32 row sample for hierarchical_decoder") - a = factory.create(module_type="hierarchical_decoder", rows=32) + a = factory.create(module_type="hierarchical_decoder", num_outputs=32) self.local_check(a) - debug.info(1, "Testing 128 row sample for hierarchical_decoder") - a = factory.create(module_type="hierarchical_decoder", rows=128) + # Checks 3 x 2x4 and 3-input NAND decoder + debug.info(1, "Testing 64 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=64) self.local_check(a) + # Checks 2x4 and 2 x 3x8 and 3-input NAND with non-power-of-two + debug.info(1, "Testing 132 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=132) + self.local_check(a) + + # Checks 3 x 3x8 and 3-input NAND decoder debug.info(1, "Testing 512 row sample for hierarchical_decoder") - a = factory.create(module_type="hierarchical_decoder", rows=512) + a = factory.create(module_type="hierarchical_decoder", num_outputs=512) self.local_check(a) - # check hierarchical decoder for multi-port - OPTS.bitcell = "pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - - factory.reset() - debug.info(1, "Testing 16 row sample for hierarchical_decoder (multi-port case)") - a = factory.create(module_type="hierarchical_decoder", rows=16) - self.local_check(a) - - debug.info(1, "Testing 32 row sample for hierarchical_decoder (multi-port case)") - a = factory.create(module_type="hierarchical_decoder", rows=32) - self.local_check(a) - - debug.info(1, "Testing 128 row sample for hierarchical_decoder (multi-port case)") - a = factory.create(module_type="hierarchical_decoder", rows=128) - self.local_check(a) - - debug.info(1, "Testing 512 row sample for hierarchical_decoder (multi-port case)") - a = factory.create(module_type="hierarchical_decoder", rows=512) - self.local_check(a) - globals.end_openram() # run the test from the command line diff --git a/compiler/tests/06_hierarchical_predecode2x4_1rw_1r_test.py b/compiler/tests/06_hierarchical_predecode2x4_1rw_1r_test.py new file mode 100755 index 00000000..a3ae2ed2 --- /dev/null +++ b/compiler/tests/06_hierarchical_predecode2x4_1rw_1r_test.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class hierarchical_predecode2x4_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(1, "Testing sample for hierarchy_predecode2x4") + a = factory.create(module_type="hierarchical_predecode2x4") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/06_hierarchical_predecode2x4_pbitcell_test.py b/compiler/tests/06_hierarchical_predecode2x4_pbitcell_test.py new file mode 100755 index 00000000..0f70ddb1 --- /dev/null +++ b/compiler/tests/06_hierarchical_predecode2x4_pbitcell_test.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class hierarchical_predecode2x4_pbitcell_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # checking hierarchical precode 2x4 for multi-port + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + globals.setup_bitcell() + + debug.info(1, "Testing sample for hierarchy_predecode2x4 (multi-port case)") + a = factory.create(module_type="hierarchical_predecode2x4") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/06_hierarchical_predecode2x4_test.py b/compiler/tests/06_hierarchical_predecode2x4_test.py index 0a5363ab..c2b51f10 100755 --- a/compiler/tests/06_hierarchical_predecode2x4_test.py +++ b/compiler/tests/06_hierarchical_predecode2x4_test.py @@ -18,25 +18,15 @@ import debug class hierarchical_predecode2x4_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) - # checking hierarchical precode 2x4 for single port debug.info(1, "Testing sample for hierarchy_predecode2x4") a = factory.create(module_type="hierarchical_predecode2x4") self.local_check(a) - # checking hierarchical precode 2x4 for multi-port - OPTS.bitcell = "pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - - debug.info(1, "Testing sample for hierarchy_predecode2x4 (multi-port case)") - a = factory.create(module_type="hierarchical_predecode2x4") - self.local_check(a) - globals.end_openram() - + # run the test from the command line if __name__ == "__main__": (OPTS, args) = globals.parse_args() diff --git a/compiler/tests/06_hierarchical_predecode3x8_1rw_1r_test.py b/compiler/tests/06_hierarchical_predecode3x8_1rw_1r_test.py new file mode 100755 index 00000000..c0653243 --- /dev/null +++ b/compiler/tests/06_hierarchical_predecode3x8_1rw_1r_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class hierarchical_predecode3x8_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(1, "Testing sample for hierarchy_predecode3x8") + a = factory.create(module_type="hierarchical_predecode3x8") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/06_hierarchical_predecode3x8_pbitcell_test.py b/compiler/tests/06_hierarchical_predecode3x8_pbitcell_test.py new file mode 100755 index 00000000..9170d5c0 --- /dev/null +++ b/compiler/tests/06_hierarchical_predecode3x8_pbitcell_test.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class hierarchical_predecode3x8_pbitcell_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # checking hierarchical precode 3x8 for multi-port + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + globals.setup_bitcell() + + debug.info(1, "Testing sample for hierarchy_predecode3x8 (multi-port case)") + a = factory.create(module_type="hierarchical_predecode3x8") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/06_hierarchical_predecode3x8_test.py b/compiler/tests/06_hierarchical_predecode3x8_test.py index b2a8d438..c1471a40 100755 --- a/compiler/tests/06_hierarchical_predecode3x8_test.py +++ b/compiler/tests/06_hierarchical_predecode3x8_test.py @@ -18,22 +18,12 @@ import debug class hierarchical_predecode3x8_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) - # checking hierarchical precode 3x8 for single port debug.info(1, "Testing sample for hierarchy_predecode3x8") a = factory.create(module_type="hierarchical_predecode3x8") self.local_check(a) - - # checking hierarchical precode 3x8 for multi-port - OPTS.bitcell = "pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - - debug.info(1, "Testing sample for hierarchy_predecode3x8 (multi-port case)") - a = factory.create(module_type="hierarchical_predecode3x8") - self.local_check(a) globals.end_openram() diff --git a/compiler/tests/06_hierarchical_predecode4x16_test.py b/compiler/tests/06_hierarchical_predecode4x16_test.py new file mode 100755 index 00000000..b4ebda38 --- /dev/null +++ b/compiler/tests/06_hierarchical_predecode4x16_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +@unittest.skip("SKIPPING hierarchical_predecode4x16_test") +class hierarchical_predecode4x16_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + debug.info(1, "Testing sample for hierarchy_predecode4x16") + a = factory.create(module_type="hierarchical_predecode4x16") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/07_single_level_column_mux_array_1rw_1r_test.py b/compiler/tests/07_single_level_column_mux_array_1rw_1r_test.py new file mode 100755 index 00000000..209133aa --- /dev/null +++ b/compiler/tests/07_single_level_column_mux_array_1rw_1r_test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class single_level_column_mux_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(1, "Testing sample for 2-way column_mux_array port 0") + a = factory.create(module_type="single_level_column_mux_array", columns=8, word_size=4, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(a) + + debug.info(1, "Testing sample for 2-way column_mux_array port 1") + a = factory.create(module_type="single_level_column_mux_array", columns=8, word_size=4, bitcell_bl="bl1", bitcell_br="br1") + self.local_check(a) + + debug.info(1, "Testing sample for 4-way column_mux_array port 0") + a = factory.create(module_type="single_level_column_mux_array", columns=8, word_size=2, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(a) + + debug.info(1, "Testing sample for 4-way column_mux_array port 1") + a = factory.create(module_type="single_level_column_mux_array", columns=8, word_size=2, bitcell_bl="bl1", bitcell_br="br1") + self.local_check(a) + + debug.info(1, "Testing sample for 8-way column_mux_array port 0") + a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=2, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(a) + + debug.info(1, "Testing sample for 8-way column_mux_array port 1") + a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=2, bitcell_bl="bl1", bitcell_br="br1") + self.local_check(a) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py b/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py new file mode 100755 index 00000000..663ff075 --- /dev/null +++ b/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class single_level_column_mux_pbitcell_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + import single_level_column_mux_array + + # check single level column mux array in multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 1 + + factory.reset() + debug.info(1, "Testing sample for 2-way column_mux_array in multi-port") + a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=8, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(a) + + debug.info(1, "Testing sample for 4-way column_mux_array in multi-port") + a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=4, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(a) + + debug.info(1, "Testing sample for 8-way column_mux_array in multi-port (innermost connections)") + a = factory.create(module_type="single_level_column_mux_array", columns=32, word_size=4, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(a) + + debug.info(1, "Testing sample for 8-way column_mux_array in multi-port (outermost connections)") + a = factory.create(module_type="single_level_column_mux_array", columns=32, word_size=4, bitcell_bl="bl2", bitcell_br="br2", column_offset=3) + self.local_check(a) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/07_single_level_column_mux_array_test.py b/compiler/tests/07_single_level_column_mux_array_test.py index c6cd7ed2..c0476a4f 100755 --- a/compiler/tests/07_single_level_column_mux_array_test.py +++ b/compiler/tests/07_single_level_column_mux_array_test.py @@ -7,7 +7,7 @@ # All rights reserved. # from testutils import * -import sys,os +import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS @@ -17,10 +17,9 @@ import debug class single_level_column_mux_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) - import single_level_column_mux_array - - # check single level column mux array in single port + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + debug.info(1, "Testing sample for 2-way column_mux_array") a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=8) self.local_check(a) @@ -32,32 +31,9 @@ class single_level_column_mux_test(openram_test): debug.info(1, "Testing sample for 8-way column_mux_array") a = factory.create(module_type="single_level_column_mux_array", columns=32, word_size=4) self.local_check(a) - - # check single level column mux array in multi-port - OPTS.bitcell = "pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_r_ports = 1 - OPTS.num_w_ports = 1 - - factory.reset() - debug.info(1, "Testing sample for 2-way column_mux_array in multi-port") - a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=8, bitcell_bl="bl0", bitcell_br="br0") - self.local_check(a) - - debug.info(1, "Testing sample for 4-way column_mux_array in multi-port") - a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=4, bitcell_bl="bl0", bitcell_br="br0") - self.local_check(a) - - debug.info(1, "Testing sample for 8-way column_mux_array in multi-port (innermost connections)") - a = factory.create(module_type="single_level_column_mux_array", columns=32, word_size=4, bitcell_bl="bl0", bitcell_br="br0") - self.local_check(a) - - debug.info(1, "Testing sample for 8-way column_mux_array in multi-port (outermost connections)") - a = factory.create(module_type="single_level_column_mux_array", columns=32, word_size=4, bitcell_bl="bl2", bitcell_br="br2") - self.local_check(a) globals.end_openram() - + # run the test from the command line if __name__ == "__main__": diff --git a/compiler/tests/08_precharge_array_1rw_1r_test.py b/compiler/tests/08_precharge_array_1rw_1r_test.py new file mode 100755 index 00000000..c5efb59b --- /dev/null +++ b/compiler/tests/08_precharge_array_1rw_1r_test.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class precharge_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check precharge array in multi-port + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + factory.reset() + debug.info(2, "Checking 3 column precharge array for 1RW/1R bitcell (port 0)") + pc = factory.create(module_type="precharge_array", columns=3, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(pc) + + debug.info(2, "Checking 3 column precharge array for 1RW/1R bitcell (port 1)") + pc = factory.create(module_type="precharge_array", columns=3, bitcell_bl="bl0", bitcell_br="br0", column_offset=1) + self.local_check(pc) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/08_precharge_array_test.py b/compiler/tests/08_precharge_array_test.py index ee29211b..47843ca3 100755 --- a/compiler/tests/08_precharge_array_test.py +++ b/compiler/tests/08_precharge_array_test.py @@ -18,31 +18,12 @@ import debug class precharge_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) - # check precharge array in single port debug.info(2, "Checking 3 column precharge") pc = factory.create(module_type="precharge_array", columns=3) self.local_check(pc) - - # check precharge array in multi-port - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.num_rw_ports = 1 - OPTS.num_r_ports = 1 - OPTS.num_w_ports = 0 - - factory.reset() - debug.info(2, "Checking 3 column precharge array for 1RW/1R bitcell") - pc = factory.create(module_type="precharge_array", columns=3, bitcell_bl="bl0", bitcell_br="br0") - self.local_check(pc) - - # debug.info(2, "Checking 3 column precharge array for pbitcell (innermost connections)") - # pc = precharge_array.precharge_array(name="pre3", columns=3, bitcell_bl="bl0", bitcell_br="br0") - # self.local_check(pc) - - # debug.info(2, "Checking 3 column precharge array for pbitcell (outermost connections)") - # pc = precharge_array.precharge_array(name="pre4", columns=3, bitcell_bl="bl2", bitcell_br="br2") - # self.local_check(pc) globals.end_openram() diff --git a/compiler/tests/08_wordline_driver_array_1rw_1r_test.py b/compiler/tests/08_wordline_driver_array_1rw_1r_test.py new file mode 100755 index 00000000..cf1810d8 --- /dev/null +++ b/compiler/tests/08_wordline_driver_array_1rw_1r_test.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class wordline_driver_array_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + # check wordline driver for single port + debug.info(2, "Checking driver") + tx = factory.create(module_type="wordline_driver_array", rows=8, cols=32) + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/08_wordline_driver_test.py b/compiler/tests/08_wordline_driver_array_pbitcell_test.py similarity index 70% rename from compiler/tests/08_wordline_driver_test.py rename to compiler/tests/08_wordline_driver_array_pbitcell_test.py index 31415a6c..267aaddf 100755 --- a/compiler/tests/08_wordline_driver_test.py +++ b/compiler/tests/08_wordline_driver_array_pbitcell_test.py @@ -15,17 +15,12 @@ from globals import OPTS from sram_factory import factory import debug -#@unittest.skip("SKIPPING 04_driver_test") -class wordline_driver_test(openram_test): +class wordline_driver_array_pbitcell_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) - - # check wordline driver for single port - debug.info(2, "Checking driver") - tx = factory.create(module_type="wordline_driver", rows=8, cols=32) - self.local_check(tx) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) # check wordline driver for multi-port OPTS.bitcell = "pbitcell" @@ -35,11 +30,11 @@ class wordline_driver_test(openram_test): factory.reset() debug.info(2, "Checking driver (multi-port case)") - tx = factory.create(module_type="wordline_driver", rows=8, cols=64) + tx = factory.create(module_type="wordline_driver_array", rows=8, cols=64) self.local_check(tx) globals.end_openram() - + # run the test from the command line if __name__ == "__main__": (OPTS, args) = globals.parse_args() diff --git a/compiler/tests/08_wordline_driver_array_test.py b/compiler/tests/08_wordline_driver_array_test.py new file mode 100755 index 00000000..3491cc4f --- /dev/null +++ b/compiler/tests/08_wordline_driver_array_test.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class wordline_driver_array_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check wordline driver for single port + debug.info(2, "Checking driver") + tx = factory.create(module_type="wordline_driver_array", rows=8, cols=32) + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/09_sense_amp_array_1rw_1r_test.py b/compiler/tests/09_sense_amp_array_1rw_1r_test.py new file mode 100755 index 00000000..a8ed4d2f --- /dev/null +++ b/compiler/tests/09_sense_amp_array_1rw_1r_test.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class sense_amp_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=1") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=1) + self.local_check(a) + + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=2") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=2) + self.local_check(a) + + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=4") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=4) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/09_sense_amp_array_spare_cols_test.py b/compiler/tests/09_sense_amp_array_spare_cols_test.py new file mode 100755 index 00000000..d8da1dc5 --- /dev/null +++ b/compiler/tests/09_sense_amp_array_spare_cols_test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class sense_amp_array_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check sense amp array for single port + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=2 and num_spare_cols=3") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=1, num_spare_cols=3) + self.local_check(a) + + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=4 and num_spare_cols=2") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=4, num_spare_cols=2) + self.local_check(a) + + # check sense amp array for multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + + factory.reset() + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=2, num_spare_cols=2 (multi-port case)") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=2, num_spare_cols=2) + self.local_check(a) + + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=4, num_spare_cols=3 (multi-port case)") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=4, num_spare_cols=3) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/09_sense_amp_array_test.py b/compiler/tests/09_sense_amp_array_test.py index e35ea3c3..c71a75e8 100755 --- a/compiler/tests/09_sense_amp_array_test.py +++ b/compiler/tests/09_sense_amp_array_test.py @@ -18,9 +18,13 @@ import debug class sense_amp_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=1") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=1) + self.local_check(a) - # check sense amp array for single port debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=2") a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=2) self.local_check(a) @@ -28,24 +32,9 @@ class sense_amp_test(openram_test): debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=4") a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=4) self.local_check(a) - - # check sense amp array for multi-port - OPTS.bitcell = "pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - factory.reset() - debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=2 (multi-port case)") - a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=2) - self.local_check(a) - - debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=4 (multi-port case)") - a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=4) - self.local_check(a) - globals.end_openram() - + # run the test from the command line if __name__ == "__main__": (OPTS, args) = globals.parse_args() diff --git a/compiler/tests/09_sense_amp_array_test_pbitcell.py b/compiler/tests/09_sense_amp_array_test_pbitcell.py new file mode 100755 index 00000000..a0fe256f --- /dev/null +++ b/compiler/tests/09_sense_amp_array_test_pbitcell.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class sense_amp_pbitcell_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + #check sense amp array for multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + + factory.reset() + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=2 (multi-port case)") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=2) + self.local_check(a) + + debug.info(2, "Testing sense_amp_array for word_size=4, words_per_row=4 (multi-port case)") + a = factory.create(module_type="sense_amp_array", word_size=4, words_per_row=4) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/10_write_driver_array_1rw_1r_test.py b/compiler/tests/10_write_driver_array_1rw_1r_test.py new file mode 100755 index 00000000..4acbf053 --- /dev/null +++ b/compiler/tests/10_write_driver_array_1rw_1r_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class write_driver_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing write_driver_array for columns=8, word_size=8") + a = factory.create(module_type="write_driver_array", columns=8, word_size=8) + self.local_check(a) + + debug.info(2, "Testing write_driver_array for columns=16, word_size=8") + a = factory.create(module_type="write_driver_array", columns=16, word_size=8) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/10_write_driver_array_pbitcell_test.py b/compiler/tests/10_write_driver_array_pbitcell_test.py new file mode 100755 index 00000000..397b3762 --- /dev/null +++ b/compiler/tests/10_write_driver_array_pbitcell_test.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class write_driver_pbitcell_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check write driver array for multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + + factory.reset() + debug.info(2, "Testing write_driver_array for columns=8, word_size=8 (multi-port case)") + a = factory.create(module_type="write_driver_array", columns=8, word_size=8) + self.local_check(a) + + debug.info(2, "Testing write_driver_array for columns=16, word_size=8 (multi-port case)") + a = factory.create(module_type="write_driver_array", columns=16, word_size=8) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/10_write_driver_array_spare_cols_test.py b/compiler/tests/10_write_driver_array_spare_cols_test.py new file mode 100755 index 00000000..8b1256d0 --- /dev/null +++ b/compiler/tests/10_write_driver_array_spare_cols_test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class write_driver_array_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check write driver array for single port + debug.info(2, "Testing write_driver_array for columns=8, word_size=8 and num_spare_cols=3") + a = factory.create(module_type="write_driver_array", columns=8, word_size=8, num_spare_cols=3) + self.local_check(a) + + debug.info(2, "Testing write_driver_array for columns=16, word_size=8 and num_spare_cols=3") + a = factory.create(module_type="write_driver_array", columns=16, word_size=8, num_spare_cols=3) + self.local_check(a) + + # check write driver array for multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + + factory.reset() + debug.info(2, "Testing write_driver_array for columns=8, word_size=8 (multi-port case and num_spare_cols=3") + a = factory.create(module_type="write_driver_array", columns=8, word_size=8, num_spare_cols=3) + self.local_check(a) + + debug.info(2, "Testing write_driver_array for columns=16, word_size=8 (multi-port case and num_spare_cols=3") + a = factory.create(module_type="write_driver_array", columns=16, word_size=8, num_spare_cols=3) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/10_write_driver_array_test.py b/compiler/tests/10_write_driver_array_test.py index 20dacca6..8db26a5a 100755 --- a/compiler/tests/10_write_driver_array_test.py +++ b/compiler/tests/10_write_driver_array_test.py @@ -18,7 +18,8 @@ import debug class write_driver_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) # check write driver array for single port debug.info(2, "Testing write_driver_array for columns=8, word_size=8") @@ -28,22 +29,7 @@ class write_driver_test(openram_test): debug.info(2, "Testing write_driver_array for columns=16, word_size=8") a = factory.create(module_type="write_driver_array", columns=16, word_size=8) self.local_check(a) - - # check write driver array for multi-port - OPTS.bitcell = "pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - factory.reset() - debug.info(2, "Testing write_driver_array for columns=8, word_size=8 (multi-port case)") - a = factory.create(module_type="write_driver_array", columns=8, word_size=8) - self.local_check(a) - - debug.info(2, "Testing write_driver_array for columns=16, word_size=8 (multi-port case)") - a = factory.create(module_type="write_driver_array", columns=16, word_size=8) - self.local_check(a) - globals.end_openram() # run the test from the command line diff --git a/compiler/tests/10_write_driver_array_wmask_pbitcell_test.py b/compiler/tests/10_write_driver_array_wmask_pbitcell_test.py new file mode 100755 index 00000000..b6593eb0 --- /dev/null +++ b/compiler/tests/10_write_driver_array_wmask_pbitcell_test.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class write_driver_pbitcell_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check write driver array for multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + + factory.reset() + debug.info(2, "Testing write_driver_array for columns=8, word_size=8, write_size=4 (multi-port case)") + a = factory.create(module_type="write_driver_array", columns=8, word_size=8, write_size=4) + self.local_check(a) + + debug.info(2, "Testing write_driver_array for columns=16, word_size=8, write_size=4 (multi-port case)") + a = factory.create(module_type="write_driver_array", columns=16, word_size=8, write_size=4) + self.local_check(a) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/10_write_driver_array_wmask_spare_cols_test.py b/compiler/tests/10_write_driver_array_wmask_spare_cols_test.py new file mode 100755 index 00000000..8a1e4788 --- /dev/null +++ b/compiler/tests/10_write_driver_array_wmask_spare_cols_test.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class write_driver_array_wmask_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check write driver array for single port + debug.info(2, "Testing write_driver_array for columns=8, word_size=8, write_size=4") + a = factory.create(module_type="write_driver_array", columns=8, word_size=8, write_size=4, num_spare_cols=3) + self.local_check(a) + + debug.info(2, "Testing write_driver_array for columns=16, word_size=16, write_size=2") + a = factory.create(module_type="write_driver_array", columns=16, word_size=16, write_size=2, num_spare_cols=2) + self.local_check(a) + + debug.info(2, "Testing write_driver_array for columns=16, word_size=8, write_size=4") + a = factory.create(module_type="write_driver_array", columns=16, word_size=8, write_size=4, num_spare_cols=3) + self.local_check(a) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/10_write_driver_array_wmask_test.py b/compiler/tests/10_write_driver_array_wmask_test.py index d09286b5..15e32b19 100755 --- a/compiler/tests/10_write_driver_array_wmask_test.py +++ b/compiler/tests/10_write_driver_array_wmask_test.py @@ -20,7 +20,8 @@ import debug class write_driver_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) # check write driver array for single port debug.info(2, "Testing write_driver_array for columns=8, word_size=8, write_size=4") @@ -35,21 +36,6 @@ class write_driver_test(openram_test): a = factory.create(module_type="write_driver_array", columns=16, word_size=8, write_size=4) self.local_check(a) - # check write driver array for multi-port - OPTS.bitcell = "pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - - factory.reset() - debug.info(2, "Testing write_driver_array for columns=8, word_size=8, write_size=4 (multi-port case)") - a = factory.create(module_type="write_driver_array", columns=8, word_size=8, write_size=4) - self.local_check(a) - - debug.info(2, "Testing write_driver_array for columns=16, word_size=8, write_size=4 (multi-port case)") - a = factory.create(module_type="write_driver_array", columns=16, word_size=8, write_size=4) - self.local_check(a) - globals.end_openram() @@ -58,4 +44,4 @@ if __name__ == "__main__": (OPTS, args) = globals.parse_args() del sys.argv[1:] header(__file__, OPTS.tech_name) - unittest.main(testRunner=debugTestRunner()) \ No newline at end of file + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/10_write_mask_and_array_1rw_1r_test.py b/compiler/tests/10_write_mask_and_array_1rw_1r_test.py new file mode 100755 index 00000000..73988db9 --- /dev/null +++ b/compiler/tests/10_write_mask_and_array_1rw_1r_test.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class write_mask_and_array_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing write_mask_and_array for columns=8, word_size=8, write_size=4") + a = factory.create(module_type="write_mask_and_array", columns=8, word_size=8, write_size=4) + self.local_check(a) + + debug.info(2, "Testing write_mask_and_array for columns=16, word_size=16, write_size=4") + a = factory.create(module_type="write_mask_and_array", columns=16, word_size=16, write_size=4) + self.local_check(a) + + debug.info(2, "Testing write_mask_and_array for columns=16, word_size=8, write_size=2") + a = factory.create(module_type="write_mask_and_array", columns=16, word_size=8, write_size=2) + self.local_check(a) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/10_write_mask_and_array_pbitcell_test.py b/compiler/tests/10_write_mask_and_array_pbitcell_test.py new file mode 100755 index 00000000..448ee8b1 --- /dev/null +++ b/compiler/tests/10_write_mask_and_array_pbitcell_test.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class write_mask_and_array_pbitcell_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check write driver array for multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + + factory.reset() + debug.info(2, "Testing write_mask_and_array for columns=8, word_size=8, write_size=4 (multi-port case)") + a = factory.create(module_type="write_mask_and_array", columns=8, word_size=8, write_size=4) + self.local_check(a) + + debug.info(2, "Testing write_mask_and_array for columns=16, word_size=8, write_size=2 (multi-port case)") + a = factory.create(module_type="write_mask_and_array", columns=16, word_size=8, write_size=2) + self.local_check(a) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/10_write_mask_and_array_test.py b/compiler/tests/10_write_mask_and_array_test.py index 91155467..42d80fa4 100755 --- a/compiler/tests/10_write_mask_and_array_test.py +++ b/compiler/tests/10_write_mask_and_array_test.py @@ -20,7 +20,8 @@ import debug class write_mask_and_array_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) # check write driver array for single port debug.info(2, "Testing write_mask_and_array for columns=8, word_size=8, write_size=4") @@ -35,21 +36,6 @@ class write_mask_and_array_test(openram_test): a = factory.create(module_type="write_mask_and_array", columns=16, word_size=8, write_size=2) self.local_check(a) - # check write driver array for multi-port - OPTS.bitcell = "pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - - factory.reset() - debug.info(2, "Testing write_mask_and_array for columns=8, word_size=8, write_size=4 (multi-port case)") - a = factory.create(module_type="write_mask_and_array", columns=8, word_size=8, write_size=4) - self.local_check(a) - - debug.info(2, "Testing write_mask_and_array for columns=16, word_size=8, write_size=2 (multi-port case)") - a = factory.create(module_type="write_mask_and_array", columns=16, word_size=8, write_size=2) - self.local_check(a) - globals.end_openram() diff --git a/compiler/tests/11_dff_array_test.py b/compiler/tests/11_dff_array_test.py index b843a6bb..9d8798c5 100755 --- a/compiler/tests/11_dff_array_test.py +++ b/compiler/tests/11_dff_array_test.py @@ -18,7 +18,8 @@ import debug class dff_array_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Testing dff_array for 3x3") a = factory.create(module_type="dff_array", rows=3, columns=3) diff --git a/compiler/tests/11_dff_buf_array_test.py b/compiler/tests/11_dff_buf_array_test.py index ec0e7742..6eb338a5 100755 --- a/compiler/tests/11_dff_buf_array_test.py +++ b/compiler/tests/11_dff_buf_array_test.py @@ -18,7 +18,8 @@ import debug class dff_buf_array_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Testing dff_buf_array for 3x3") a = factory.create(module_type="dff_buf_array", rows=3, columns=3) diff --git a/compiler/tests/12_tri_gate_array_test.py b/compiler/tests/12_tri_gate_array_test.py index 0bd5f60c..813feda0 100755 --- a/compiler/tests/12_tri_gate_array_test.py +++ b/compiler/tests/12_tri_gate_array_test.py @@ -18,7 +18,8 @@ import debug class tri_gate_array_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(1, "Testing tri_gate_array for columns=8, word_size=8") a = factory.create(module_type="tri_gate_array", columns=8, word_size=8) diff --git a/compiler/tests/13_delay_chain_test.py b/compiler/tests/13_delay_chain_test.py index 9dc8faeb..4b893a8a 100755 --- a/compiler/tests/13_delay_chain_test.py +++ b/compiler/tests/13_delay_chain_test.py @@ -18,7 +18,8 @@ import debug class delay_chain_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Testing delay_chain") a = factory.create(module_type="delay_chain", fanout_list=[4, 4, 4, 4]) diff --git a/compiler/tests/14_replica_bitcell_1rw_1r_array_test.py b/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py similarity index 59% rename from compiler/tests/14_replica_bitcell_1rw_1r_array_test.py rename to compiler/tests/14_replica_bitcell_array_1rw_1r_test.py index bae7edde..626725c4 100755 --- a/compiler/tests/14_replica_bitcell_1rw_1r_array_test.py +++ b/compiler/tests/14_replica_bitcell_array_1rw_1r_test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # See LICENSE for licensing information. # -# Copyright (c) 2016-2019 Regents of the University of California +# Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # import unittest @@ -13,25 +13,26 @@ from globals import OPTS from sram_factory import factory import debug -class replica_bitcell_array_test(openram_test): +class replica_bitcell_array_1rw_1r_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell="dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 + globals.setup_bitcell() debug.info(2, "Testing 4x4 array for cell_1rw_1r") - a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=2, right_rbl=0, bitcell_ports=[0,1]) + a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=1, right_rbl=1, bitcell_ports=[0, 1]) self.local_check(a) - debug.info(2, "Testing 4x4 array for cell_1rw_1r") - a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=1, right_rbl=1, bitcell_ports=[0,1]) - self.local_check(a) + # Sky 130 has restrictions on the symmetries + if OPTS.tech_name != "sky130": + debug.info(2, "Testing 4x4 array for cell_1rw_1r") + a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=2, right_rbl=0, bitcell_ports=[0, 1]) + self.local_check(a) globals.end_openram() diff --git a/compiler/tests/14_replica_bitcell_array_test.py b/compiler/tests/14_replica_bitcell_array_test.py index 2b446758..d229b99c 100755 --- a/compiler/tests/14_replica_bitcell_array_test.py +++ b/compiler/tests/14_replica_bitcell_array_test.py @@ -16,12 +16,18 @@ import debug class replica_bitcell_array_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) - debug.info(2, "Testing 4x4 array for 6t_cell") + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 0 + OPTS.num_w_ports = 0 + + factory.reset() + debug.info(2, "Testing 4x4 array for bitcell") a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=1, right_rbl=0, bitcell_ports=[0]) self.local_check(a) - + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/14_replica_column_1rw_1r_test.py b/compiler/tests/14_replica_column_1rw_1r_test.py new file mode 100755 index 00000000..3c1a0e1e --- /dev/null +++ b/compiler/tests/14_replica_column_1rw_1r_test.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class replica_column_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(2, "Testing replica column for 6t_cell") + a = factory.create(module_type="replica_column", rows=4, left_rbl=1, right_rbl=0, replica_bit=1) + self.local_check(a) + + debug.info(2, "Testing replica column for 6t_cell") + a = factory.create(module_type="replica_column", rows=4, left_rbl=1, right_rbl=1, replica_bit=6) + self.local_check(a) + + debug.info(2, "Testing replica column for 6t_cell") + a = factory.create(module_type="replica_column", rows=4, left_rbl=2, right_rbl=0, replica_bit=2) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/14_replica_column_test.py b/compiler/tests/14_replica_column_test.py index c0db4d17..57c92e84 100755 --- a/compiler/tests/14_replica_column_test.py +++ b/compiler/tests/14_replica_column_test.py @@ -16,7 +16,8 @@ import debug class replica_column_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(2, "Testing replica column for 6t_cell") a = factory.create(module_type="replica_column", rows=4, left_rbl=1, right_rbl=0, replica_bit=1) diff --git a/compiler/tests/05_replica_pbitcell_array_test.py b/compiler/tests/14_replica_pbitcell_array_test.py similarity index 89% rename from compiler/tests/05_replica_pbitcell_array_test.py rename to compiler/tests/14_replica_pbitcell_array_test.py index 2bc4a0d2..6a87f408 100755 --- a/compiler/tests/05_replica_pbitcell_array_test.py +++ b/compiler/tests/14_replica_pbitcell_array_test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # See LICENSE for licensing information. # -# Copyright (c) 2016-2019 Regents of the University of California +# Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # import unittest @@ -13,10 +13,11 @@ from globals import OPTS from sram_factory import factory import debug -class replica_bitcell_array_test(openram_test): +class replica_pbitcell_array_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.bitcell = "pbitcell" OPTS.replica_bitcell = "replica_pbitcell" @@ -40,7 +41,7 @@ class replica_bitcell_array_test(openram_test): debug.info(2, "Testing 4x4 array for pbitcell") a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=1, right_rbl=0, bitcell_ports=[0]) self.local_check(a) - + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/16_control_logic_multiport_test.py b/compiler/tests/16_control_logic_multiport_test.py index 66c34d24..919e2335 100755 --- a/compiler/tests/16_control_logic_multiport_test.py +++ b/compiler/tests/16_control_logic_multiport_test.py @@ -22,7 +22,8 @@ import debug class control_logic_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import control_logic import tech diff --git a/compiler/tests/16_control_logic_r_test.py b/compiler/tests/16_control_logic_r_test.py new file mode 100755 index 00000000..b695f9c9 --- /dev/null +++ b/compiler/tests/16_control_logic_r_test.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class control_logic_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + debug.info(1, "Testing sample for control_logic_r") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32, port_type="r") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/16_control_logic_rw_test.py b/compiler/tests/16_control_logic_rw_test.py new file mode 100755 index 00000000..8d12085f --- /dev/null +++ b/compiler/tests/16_control_logic_rw_test.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class control_logic_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + debug.info(1, "Testing sample for control_logic_rw") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/16_control_logic_test.py b/compiler/tests/16_control_logic_w_test.py similarity index 65% rename from compiler/tests/16_control_logic_test.py rename to compiler/tests/16_control_logic_w_test.py index 92d5c94b..8407d1e8 100755 --- a/compiler/tests/16_control_logic_test.py +++ b/compiler/tests/16_control_logic_w_test.py @@ -18,21 +18,14 @@ import debug class control_logic_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) - import control_logic - import tech - - debug.info(1, "Testing sample for control_logic_rw") - a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32) - self.local_check(a) - - debug.info(1, "Testing sample for control_logic_r") - a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32, port_type="r") - self.local_check(a) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(1, "Testing sample for control_logic_w") a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32, port_type="w") self.local_check(a) + + globals.end_openram() # run the test from the command line if __name__ == "__main__": diff --git a/compiler/tests/18_port_address_1rw_1r_test.py b/compiler/tests/18_port_address_1rw_1r_test.py new file mode 100755 index 00000000..caf2cb96 --- /dev/null +++ b/compiler/tests/18_port_address_1rw_1r_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class port_address_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + debug.info(1, "Port address 16 rows") + a = factory.create("port_address", cols=16, rows=16) + self.local_check(a) + + debug.info(1, "Port address 256 rows") + a = factory.create("port_address", cols=256, rows=256) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/18_port_address_test.py b/compiler/tests/18_port_address_test.py index c8db6ec2..11da333e 100755 --- a/compiler/tests/18_port_address_test.py +++ b/compiler/tests/18_port_address_test.py @@ -13,15 +13,21 @@ from globals import OPTS from sram_factory import factory import debug + class port_address_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(1, "Port address 16 rows") a = factory.create("port_address", cols=16, rows=16) self.local_check(a) + debug.info(1, "Port address 512 rows") + a = factory.create("port_address", cols=256, rows=512) + self.local_check(a) + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/18_port_data_1rw_1r_test.py b/compiler/tests/18_port_data_1rw_1r_test.py new file mode 100755 index 00000000..3a7687d6 --- /dev/null +++ b/compiler/tests/18_port_data_1rw_1r_test.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class port_data_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + c = sram_config(word_size=4, + num_words=16) + + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.num_words=64 + c.words_per_row=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.word_size=2 + c.num_words=128 + c.words_per_row=8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/18_port_data_spare_cols_test.py b/compiler/tests/18_port_data_spare_cols_test.py new file mode 100755 index 00000000..8c08ad0a --- /dev/null +++ b/compiler/tests/18_port_data_spare_cols_test.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class port_data_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + c = sram_config(word_size=8, + num_words=16, + num_spare_cols=3) + + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + c.num_words=64 + c.words_per_row=4 + c.num_spare_cols=3 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + c.word_size=2 + c.num_words=128 + c.words_per_row=8 + c.num_spare_cols=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + OPTS.bitcell = "bitcell_1w_1r" + OPTS.num_rw_ports = 0 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 1 + + c.num_words=16 + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.num_words=64 + c.words_per_row=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.word_size=2 + c.num_words=128 + c.words_per_row=8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/18_port_data_test.py b/compiler/tests/18_port_data_test.py index e5f94329..57e19846 100755 --- a/compiler/tests/18_port_data_test.py +++ b/compiler/tests/18_port_data_test.py @@ -16,7 +16,8 @@ import debug class port_data_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config c = sram_config(word_size=4, @@ -54,52 +55,6 @@ class port_data_test(openram_test): a = factory.create("port_data", sram_config=c, port=0) self.local_check(a) - OPTS.bitcell = "bitcell_1w_1r" - OPTS.num_rw_ports = 0 - OPTS.num_r_ports = 1 - OPTS.num_w_ports = 1 - - c.num_words=16 - c.words_per_row=1 - factory.reset() - c.recompute_sizes() - debug.info(1, "No column mux") - a = factory.create("port_data", sram_config=c, port=0) - self.local_check(a) - a = factory.create("port_data", sram_config=c, port=1) - self.local_check(a) - - c.num_words=32 - c.words_per_row=2 - factory.reset() - c.recompute_sizes() - debug.info(1, "Two way column mux") - a = factory.create("port_data", sram_config=c, port=0) - self.local_check(a) - a = factory.create("port_data", sram_config=c, port=1) - self.local_check(a) - - c.num_words=64 - c.words_per_row=4 - factory.reset() - c.recompute_sizes() - debug.info(1, "Four way column mux") - a = factory.create("port_data", sram_config=c, port=0) - self.local_check(a) - a = factory.create("port_data", sram_config=c, port=1) - self.local_check(a) - - c.word_size=2 - c.num_words=128 - c.words_per_row=8 - factory.reset() - c.recompute_sizes() - debug.info(1, "Eight way column mux") - a = factory.create("port_data", sram_config=c, port=0) - self.local_check(a) - a = factory.create("port_data", sram_config=c, port=1) - self.local_check(a) - globals.end_openram() # run the test from the command line diff --git a/compiler/tests/18_port_data_wmask_1rw_1r_test.py b/compiler/tests/18_port_data_wmask_1rw_1r_test.py new file mode 100755 index 00000000..74aa7fc0 --- /dev/null +++ b/compiler/tests/18_port_data_wmask_1rw_1r_test.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +import unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class port_data_wmask_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + c = sram_config(word_size=16, + write_size=4, + num_words=16) + + c.words_per_row = 1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + c.num_words = 32 + c.words_per_row = 2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + c.num_words = 64 + c.words_per_row = 4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + c.num_words = 128 + c.words_per_row = 8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + + OPTS.bitcell = "bitcell_1w_1r" + OPTS.num_rw_ports = 0 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 1 + + c.num_words = 16 + c.words_per_row = 1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + # + c.num_words = 32 + c.words_per_row = 2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.num_words = 64 + c.words_per_row = 4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.word_size = 8 + c.num_words = 128 + c.words_per_row = 8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/18_port_data_wmask_test.py b/compiler/tests/18_port_data_wmask_test.py index e9b70337..1c650c74 100755 --- a/compiler/tests/18_port_data_wmask_test.py +++ b/compiler/tests/18_port_data_wmask_test.py @@ -15,10 +15,11 @@ from sram_factory import factory import debug -class port_data_test(openram_test): +class port_data_wmask_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config c = sram_config(word_size=16, diff --git a/compiler/tests/19_bank_select_pbitcell_test.py b/compiler/tests/19_bank_select_pbitcell_test.py new file mode 100755 index 00000000..6bf5929e --- /dev/null +++ b/compiler/tests/19_bank_select_pbitcell_test.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class bank_select_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.bitcell = "pbitcell" + debug.info(1, "No column mux, rw control logic") + a = factory.create(module_type="bank_select", port="rw") + self.local_check(a) + + OPTS.num_rw_ports = 0 + OPTS.num_w_ports = 1 + OPTS.num_r_ports = 1 + + debug.info(1, "No column mux, w control logic") + a = factory.create(module_type="bank_select", port="w") + self.local_check(a) + + debug.info(1, "No column mux, r control logic") + a = factory.create(module_type="bank_select", port="r") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/19_bank_select_test.py b/compiler/tests/19_bank_select_test.py index e2f5a9a8..afec4c3c 100755 --- a/compiler/tests/19_bank_select_test.py +++ b/compiler/tests/19_bank_select_test.py @@ -18,31 +18,15 @@ import debug class bank_select_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(1, "No column mux, rw control logic") a = factory.create(module_type="bank_select", port="rw") self.local_check(a) - - OPTS.bitcell = "pbitcell" - debug.info(1, "No column mux, rw control logic") - a = factory.create(module_type="bank_select", port="rw") - self.local_check(a) - - OPTS.num_rw_ports = 0 - OPTS.num_w_ports = 1 - OPTS.num_r_ports = 1 - debug.info(1, "No column mux, w control logic") - a = factory.create(module_type="bank_select", port="w") - self.local_check(a) - - debug.info(1, "No column mux, r control logic") - a = factory.create(module_type="bank_select", port="r") - self.local_check(a) - globals.end_openram() - + # run the test from the command line if __name__ == "__main__": (OPTS, args) = globals.parse_args() diff --git a/compiler/tests/19_multi_bank_test.py b/compiler/tests/19_multi_bank_test.py index 1816cd61..f4c622c7 100755 --- a/compiler/tests/19_multi_bank_test.py +++ b/compiler/tests/19_multi_bank_test.py @@ -19,7 +19,8 @@ import debug class multi_bank_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config c = sram_config(word_size=4, diff --git a/compiler/tests/19_pmulti_bank_test.py b/compiler/tests/19_pmulti_bank_test.py index 749460fa..7c3da444 100755 --- a/compiler/tests/19_pmulti_bank_test.py +++ b/compiler/tests/19_pmulti_bank_test.py @@ -19,7 +19,8 @@ import debug class multi_bank_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config OPTS.bitcell = "pbitcell" diff --git a/compiler/tests/19_psingle_bank_test.py b/compiler/tests/19_psingle_bank_test.py index 90e886f4..8c6ddb12 100755 --- a/compiler/tests/19_psingle_bank_test.py +++ b/compiler/tests/19_psingle_bank_test.py @@ -19,7 +19,8 @@ import debug class psingle_bank_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config OPTS.bitcell = "pbitcell" diff --git a/compiler/tests/19_single_bank_1rw_1r_test.py b/compiler/tests/19_single_bank_1rw_1r_test.py index ab5ce041..22f83f29 100755 --- a/compiler/tests/19_single_bank_1rw_1r_test.py +++ b/compiler/tests/19_single_bank_1rw_1r_test.py @@ -18,15 +18,14 @@ import debug class single_bank_1rw_1r_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell="dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=16) diff --git a/compiler/tests/19_single_bank_1w_1r_test.py b/compiler/tests/19_single_bank_1w_1r_test.py index 12b9f3a0..e3a2d886 100755 --- a/compiler/tests/19_single_bank_1w_1r_test.py +++ b/compiler/tests/19_single_bank_1w_1r_test.py @@ -18,16 +18,14 @@ import debug class single_bank_1w_1r_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1w_1r" - OPTS.replica_bitcell = "replica_bitcell_1w_1r" - OPTS.dummy_bitcell="dummy_bitcell_1w_1r" - OPTS.num_rw_ports = 0 OPTS.num_r_ports = 1 OPTS.num_w_ports = 1 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=16) diff --git a/compiler/tests/19_single_bank_spare_cols_test.py b/compiler/tests/19_single_bank_spare_cols_test.py new file mode 100755 index 00000000..52eeea52 --- /dev/null +++ b/compiler/tests/19_single_bank_spare_cols_test.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class single_bank_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + c = sram_config(word_size=4, + num_words=16, + num_spare_cols=3) + + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.num_words=64 + c.words_per_row=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.word_size=2 + c.num_words=128 + c.words_per_row=8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/19_single_bank_test.py b/compiler/tests/19_single_bank_test.py index 1d010db5..6cff481b 100755 --- a/compiler/tests/19_single_bank_test.py +++ b/compiler/tests/19_single_bank_test.py @@ -18,7 +18,8 @@ import debug class single_bank_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config c = sram_config(word_size=4, diff --git a/compiler/tests/19_single_bank_wmask_1rw_1r_test.py b/compiler/tests/19_single_bank_wmask_1rw_1r_test.py new file mode 100755 index 00000000..ddb97905 --- /dev/null +++ b/compiler/tests/19_single_bank_wmask_1rw_1r_test.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class single_bank_wmask_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + c = sram_config(word_size=8, + write_size=4, + num_words=16, + num_banks=1) + + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.num_words=64 + c.words_per_row=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.num_words=128 + c.words_per_row=8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/19_single_bank_wmask_test.py b/compiler/tests/19_single_bank_wmask_test.py index 439ffeba..50567ebb 100755 --- a/compiler/tests/19_single_bank_wmask_test.py +++ b/compiler/tests/19_single_bank_wmask_test.py @@ -18,7 +18,8 @@ import debug class single_bank_wmask_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config diff --git a/compiler/tests/20_psram_1bank_2mux_1rw_1w_test.py b/compiler/tests/20_psram_1bank_2mux_1rw_1w_test.py index fdeae56f..f521851b 100755 --- a/compiler/tests/20_psram_1bank_2mux_1rw_1w_test.py +++ b/compiler/tests/20_psram_1bank_2mux_1rw_1w_test.py @@ -18,16 +18,16 @@ import debug #@unittest.skip("SKIPPING 20_psram_1bank_test, multiport layout not complete") class psram_1bank_2mux_1rw_1w_test(openram_test): - def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell="replica_pbitcell" - OPTS.dummy_bitcell="dummy_pbitcell" OPTS.num_rw_ports = 1 OPTS.num_w_ports = 1 OPTS.num_r_ports = 0 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=32, diff --git a/compiler/tests/20_psram_1bank_2mux_1rw_1w_wmask_test.py b/compiler/tests/20_psram_1bank_2mux_1rw_1w_wmask_test.py index 20fbd8e6..0d236bc2 100755 --- a/compiler/tests/20_psram_1bank_2mux_1rw_1w_wmask_test.py +++ b/compiler/tests/20_psram_1bank_2mux_1rw_1w_wmask_test.py @@ -21,7 +21,8 @@ import debug class psram_1bank_2mux_1rw_1w_wmask_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config OPTS.bitcell = "pbitcell" @@ -58,4 +59,4 @@ if __name__ == "__main__": (OPTS, args) = globals.parse_args() del sys.argv[1:] header(__file__, OPTS.tech_name) - unittest.main(testRunner=debugTestRunner()) \ No newline at end of file + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_psram_1bank_2mux_1w_1r_test.py b/compiler/tests/20_psram_1bank_2mux_1w_1r_test.py index a5c01d8f..35912823 100755 --- a/compiler/tests/20_psram_1bank_2mux_1w_1r_test.py +++ b/compiler/tests/20_psram_1bank_2mux_1w_1r_test.py @@ -18,16 +18,16 @@ import debug #@unittest.skip("SKIPPING 20_psram_1bank_2mux_1w_1r_test, odd supply routing error") class psram_1bank_2mux_1w_1r_test(openram_test): - def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell="replica_pbitcell" - OPTS.dummy_bitcell="dummy_pbitcell" OPTS.num_rw_ports = 0 OPTS.num_w_ports = 1 OPTS.num_r_ports = 1 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=32, diff --git a/compiler/tests/20_psram_1bank_2mux_test.py b/compiler/tests/20_psram_1bank_2mux_test.py index 64fa72ca..92403cd1 100755 --- a/compiler/tests/20_psram_1bank_2mux_test.py +++ b/compiler/tests/20_psram_1bank_2mux_test.py @@ -18,17 +18,16 @@ import debug #@unittest.skip("SKIPPING 20_psram_1bank_2mux_test, wide metal supply routing error") class psram_1bank_2mux_test(openram_test): - def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell="replica_pbitcell" - OPTS.dummy_bitcell="dummy_pbitcell" - # testing layout of sram using pbitcell with 1 RW port (a 6T-cell equivalent) + OPTS.bitcell = "pbitcell" OPTS.num_rw_ports = 1 OPTS.num_w_ports = 0 OPTS.num_r_ports = 0 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=32, diff --git a/compiler/tests/20_psram_1bank_4mux_1rw_1r_test.py b/compiler/tests/20_psram_1bank_4mux_1rw_1r_test.py index 7779b794..ecbd0863 100755 --- a/compiler/tests/20_psram_1bank_4mux_1rw_1r_test.py +++ b/compiler/tests/20_psram_1bank_4mux_1rw_1r_test.py @@ -17,16 +17,16 @@ import debug class psram_1bank_4mux_1rw_1r_test(openram_test): - def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell="replica_pbitcell" - OPTS.dummy_bitcell="dummy_pbitcell" OPTS.num_rw_ports = 1 OPTS.num_w_ports = 0 OPTS.num_r_ports = 1 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=64, diff --git a/compiler/tests/20_sram_1bank_2mux_1rw_1r_spare_cols_test.py b/compiler/tests/20_sram_1bank_2mux_1rw_1r_spare_cols_test.py new file mode 100755 index 00000000..6adb3e8e --- /dev/null +++ b/compiler/tests/20_sram_1bank_2mux_1rw_1r_spare_cols_test.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class sram_1bank_2mux_1rw_1r_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + c = sram_config(word_size=4, + num_words=32, + num_spare_cols=3, + num_banks=1) + + c.words_per_row=2 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} words per " + "row, {} spare columns, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_spare_cols, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py index 60192759..0a2b7d32 100755 --- a/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py +++ b/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py @@ -18,15 +18,14 @@ import debug class sram_1bank_2mux_1rw_1r_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell="dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=32, diff --git a/compiler/tests/20_sram_1bank_2mux_1w_1r_spare_cols_test.py b/compiler/tests/20_sram_1bank_2mux_1w_1r_spare_cols_test.py new file mode 100755 index 00000000..987a297e --- /dev/null +++ b/compiler/tests/20_sram_1bank_2mux_1w_1r_spare_cols_test.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +#@unittest.skip("SKIPPING 20_sram_1bank_2mux_1w_1r_spare_cols_test, odd supply routing error") +class sram_1bank_2mux_1w_1r_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 0 + OPTS.num_w_ports = 1 + OPTS.num_r_ports = 1 + globals.setup_bitcell() + + c = sram_config(word_size=4, + num_words=32, + num_spare_cols=3, + num_banks=1) + c.num_words=32 + c.words_per_row=2 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} words per " + "row, {} spare columns, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_spare_cols, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_2mux_1w_1r_test.py b/compiler/tests/20_sram_1bank_2mux_1w_1r_test.py index 2e1e848f..2c4e29e6 100755 --- a/compiler/tests/20_sram_1bank_2mux_1w_1r_test.py +++ b/compiler/tests/20_sram_1bank_2mux_1w_1r_test.py @@ -18,16 +18,15 @@ import debug #@unittest.skip("SKIPPING 20_psram_1bank_2mux_1w_1r_test, odd supply routing error") class psram_1bank_2mux_1w_1r_test(openram_test): - def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1w_1r" - OPTS.replica_bitcell="replica_bitcell_1w_1r" - OPTS.dummy_bitcell="dummy_bitcell_1w_1r" OPTS.num_rw_ports = 0 OPTS.num_w_ports = 1 OPTS.num_r_ports = 1 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=32, diff --git a/compiler/tests/20_sram_1bank_2mux_test.py b/compiler/tests/20_sram_1bank_2mux_test.py index 7e5a4f3a..582f5217 100755 --- a/compiler/tests/20_sram_1bank_2mux_test.py +++ b/compiler/tests/20_sram_1bank_2mux_test.py @@ -19,7 +19,8 @@ import debug class sram_1bank_2mux_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config c = sram_config(word_size=4, num_words=32, diff --git a/compiler/tests/20_sram_1bank_2mux_wmask_spare_cols_test.py b/compiler/tests/20_sram_1bank_2mux_wmask_spare_cols_test.py new file mode 100755 index 00000000..0488b93e --- /dev/null +++ b/compiler/tests/20_sram_1bank_2mux_wmask_spare_cols_test.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +# @unittest.skip("SKIPPING 20_sram_1bank_2mux_wmask_spare_cols_test") +class sram_1bank_2mux_wmask_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + c = sram_config(word_size=8, + write_size=4, + num_spare_cols=3, + num_words=64, + num_banks=1) + + c.words_per_row = 2 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} bit writes, {} words per " + "row, {} spare columns, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.write_size, + c.words_per_row, + c.num_spare_cols, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_2mux_wmask_test.py b/compiler/tests/20_sram_1bank_2mux_wmask_test.py index 65f025a7..50bd41dc 100755 --- a/compiler/tests/20_sram_1bank_2mux_wmask_test.py +++ b/compiler/tests/20_sram_1bank_2mux_wmask_test.py @@ -21,7 +21,8 @@ import debug class sram_1bank_2mux_wmask_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config c = sram_config(word_size=8, write_size=4, @@ -51,4 +52,4 @@ if __name__ == "__main__": (OPTS, args) = globals.parse_args() del sys.argv[1:] header(__file__, OPTS.tech_name) - unittest.main(testRunner=debugTestRunner()) \ No newline at end of file + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_32b_1024_wmask_test.py b/compiler/tests/20_sram_1bank_32b_1024_wmask_test.py index a5232267..05cb7c0d 100755 --- a/compiler/tests/20_sram_1bank_32b_1024_wmask_test.py +++ b/compiler/tests/20_sram_1bank_32b_1024_wmask_test.py @@ -21,7 +21,8 @@ import debug class sram_1bank_32b_1024_wmask_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config c = sram_config(word_size=32, write_size=8, @@ -50,4 +51,4 @@ if __name__ == "__main__": (OPTS, args) = globals.parse_args() del sys.argv[1:] header(__file__, OPTS.tech_name) - unittest.main(testRunner=debugTestRunner()) \ No newline at end of file + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_4mux_test.py b/compiler/tests/20_sram_1bank_4mux_test.py index 34af86a1..e38ef7c4 100755 --- a/compiler/tests/20_sram_1bank_4mux_test.py +++ b/compiler/tests/20_sram_1bank_4mux_test.py @@ -19,7 +19,8 @@ import debug class sram_1bank_4mux_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config c = sram_config(word_size=4, num_words=64, diff --git a/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py index 48a42106..1e4df34d 100755 --- a/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py +++ b/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py @@ -18,16 +18,15 @@ import debug class sram_1bank_8mux_1rw_1r_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell="dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 - + globals.setup_bitcell() + c = sram_config(word_size=2, num_words=128, num_banks=1) diff --git a/compiler/tests/20_sram_1bank_8mux_test.py b/compiler/tests/20_sram_1bank_8mux_test.py index c5eaea75..dfdcd5b7 100755 --- a/compiler/tests/20_sram_1bank_8mux_test.py +++ b/compiler/tests/20_sram_1bank_8mux_test.py @@ -19,7 +19,8 @@ import debug class sram_1bank_8mux_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config c = sram_config(word_size=2, num_words=128, diff --git a/compiler/tests/20_sram_1bank_nomux_1rw_1r_spare_cols_test.py b/compiler/tests/20_sram_1bank_nomux_1rw_1r_spare_cols_test.py new file mode 100755 index 00000000..dbeca8aa --- /dev/null +++ b/compiler/tests/20_sram_1bank_nomux_1rw_1r_spare_cols_test.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class sram_1bank_nomux_1rw_1r_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + + c = sram_config(word_size=4, + num_words=16, + num_spare_cols=4, + num_banks=1) + + c.words_per_row=1 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} words per " + "row, {} spare columns, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_spare_cols, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py index f6bccc13..a516b4f0 100755 --- a/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py +++ b/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py @@ -18,15 +18,14 @@ import debug class sram_1bank_nomux_1rw_1r_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell = "dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 + globals.setup_bitcell() c = sram_config(word_size=4, num_words=16, diff --git a/compiler/tests/20_sram_1bank_nomux_spare_cols_test.py b/compiler/tests/20_sram_1bank_nomux_spare_cols_test.py new file mode 100755 index 00000000..135518e3 --- /dev/null +++ b/compiler/tests/20_sram_1bank_nomux_spare_cols_test.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +# @unittest.skip("SKIPPING 20_sram_1bank_nomux_spare_cols_test") +class sram_1bank_nomux_spare_cols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + c = sram_config(word_size=8, + num_spare_cols=3, + num_words=16, + num_banks=1) + + c.words_per_row = 1 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} spare cols, {} words per " + "row, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.num_spare_cols, + c.words_per_row, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_nomux_test.py b/compiler/tests/20_sram_1bank_nomux_test.py index 650d2ac2..c79d8552 100755 --- a/compiler/tests/20_sram_1bank_nomux_test.py +++ b/compiler/tests/20_sram_1bank_nomux_test.py @@ -19,7 +19,8 @@ import debug class sram_1bank_nomux_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config c = sram_config(word_size=4, num_words=16, diff --git a/compiler/tests/20_sram_1bank_nomux_wmask_sparecols_test.py b/compiler/tests/20_sram_1bank_nomux_wmask_sparecols_test.py new file mode 100755 index 00000000..e22122bd --- /dev/null +++ b/compiler/tests/20_sram_1bank_nomux_wmask_sparecols_test.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys, os + +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +@unittest.skip("SKIPPING 20_sram_1bank_nomux_wmask_sparecols_test, not working yet") +class sram_1bank_nomux_wmask_sparecols_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + c = sram_config(word_size=8, + write_size=4, + num_words=16, + num_spare_cols=3, + num_banks=1) + + c.words_per_row = 1 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} bit writes, {} words per " + "row, {} spare columns, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.write_size, + c.words_per_row, + c.num_spare_cols, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_1bank_nomux_wmask_test.py b/compiler/tests/20_sram_1bank_nomux_wmask_test.py index e0292e95..24d7c97d 100755 --- a/compiler/tests/20_sram_1bank_nomux_wmask_test.py +++ b/compiler/tests/20_sram_1bank_nomux_wmask_test.py @@ -21,7 +21,8 @@ import debug class sram_1bank_nomux_wmask_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config c = sram_config(word_size=8, write_size=4, @@ -51,4 +52,4 @@ if __name__ == "__main__": (OPTS, args) = globals.parse_args() del sys.argv[1:] header(__file__, OPTS.tech_name) - unittest.main(testRunner=debugTestRunner()) \ No newline at end of file + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/20_sram_2bank_test.py b/compiler/tests/20_sram_2bank_test.py index c5d9d3d0..5fd4bf29 100755 --- a/compiler/tests/20_sram_2bank_test.py +++ b/compiler/tests/20_sram_2bank_test.py @@ -19,7 +19,8 @@ import debug class sram_2bank_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram_config import sram_config c = sram_config(word_size=16, num_words=32, diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index ebb424aa..a602f9d2 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -18,7 +18,8 @@ import debug class timing_sram_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.spice_name="hspice" OPTS.analytical_delay = False OPTS.netlist_only = True @@ -59,29 +60,36 @@ class timing_sram_test(openram_test): data, port_data = d.analyze(probe_address, probe_data, slews, loads) #Combine info about port into all data data.update(port_data[0]) - + if OPTS.tech_name == "freepdk45": - golden_data = {'delay_hl': [0.2383338], - 'delay_lh': [0.2383338], - 'leakage_power': 0.0014532999999999998, - 'min_period': 0.898, - 'read0_power': [0.30059800000000003], - 'read1_power': [0.30061810000000005], - 'slew_hl': [0.25358420000000004], - 'slew_lh': [0.25358420000000004], - 'write0_power': [0.34616749999999996], - 'write1_power': [0.2792924]} + golden_data = {'min_period': 0.898, + 'write1_power': [0.2659137999999999], + 'disabled_write0_power': [0.1782495], + 'disabled_read0_power': [0.14490679999999997], + 'write0_power': [0.3330119], + 'disabled_write1_power': [0.1865223], + 'leakage_power': 0.0014532, + 'disabled_read1_power': [0.1627516], + 'slew_lh': [0.25367799999999996], + 'slew_hl': [0.25367799999999996], + 'delay_lh': [0.23820930000000004], + 'delay_hl': [0.23820930000000004], + 'read1_power': [0.3005756], + 'read0_power': [0.3005888]} elif OPTS.tech_name == "scn4m_subm": - golden_data = {'delay_hl': [1.7448], - 'delay_lh': [1.7448], - 'leakage_power': 0.0006356744000000001, + golden_data = {'leakage_power': 0.0006356576000000001, + 'write1_power': [11.292700000000002], + 'read0_power': [12.98], + 'disabled_write1_power': [8.3707], + 'write0_power': [14.4447], 'delay_hl': [1.7445000000000002], + 'disabled_read0_power': [6.4325], + 'slew_hl': [1.7437], + 'disabled_write0_power': [8.1307], + 'slew_lh': [1.7437], + 'read1_power': [12.9869], + 'disabled_read1_power': [7.706], 'min_period': 6.25, - 'read0_power': [12.9846], - 'read1_power': [12.9722], - 'slew_hl': [1.7433], - 'slew_lh': [1.7433], - 'write0_power': [14.8772], - 'write1_power': [11.7217]} + 'delay_lh': [1.7445000000000002]} else: self.assertTrue(False) # other techs fail # Check if no too many or too few results diff --git a/compiler/tests/21_hspice_setuphold_test.py b/compiler/tests/21_hspice_setuphold_test.py index 83bd5509..1844fd39 100755 --- a/compiler/tests/21_hspice_setuphold_test.py +++ b/compiler/tests/21_hspice_setuphold_test.py @@ -18,7 +18,8 @@ import debug class timing_setup_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.spice_name="hspice" OPTS.analytical_delay = False OPTS.netlist_only = True diff --git a/compiler/tests/21_model_delay_test.py b/compiler/tests/21_model_delay_test.py index a4de4c2a..e21d658e 100755 --- a/compiler/tests/21_model_delay_test.py +++ b/compiler/tests/21_model_delay_test.py @@ -20,7 +20,8 @@ class model_delay_test(openram_test): """ Compare the accuracy of the analytical model with a spice simulation. """ def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True diff --git a/compiler/tests/21_ngspice_delay_extra_rows_test.py b/compiler/tests/21_ngspice_delay_extra_rows_test.py new file mode 100755 index 00000000..22812d52 --- /dev/null +++ b/compiler/tests/21_ngspice_delay_extra_rows_test.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class timing_sram_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.spice_name="ngspice" + OPTS.analytical_delay = False + OPTS.netlist_only = True + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import delay + from sram_config import sram_config + c = sram_config(word_size=1, + num_words=16, + num_banks=1, + num_spare_rows=3) + c.words_per_row=1 + c.recompute_sizes() + debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") + s = factory.create(module_type="sram", sram_config=c) + + tempspice = OPTS.openram_temp + "temp.sp" + s.sp_write(tempspice) + + probe_address = "0" + ("1" * (s.s.addr_size - 1)) + probe_data = s.s.word_size - 1 + debug.info(1, "Probe address {0} probe data bit {1}".format(probe_address, probe_data)) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + d = delay(s.s, tempspice, corner) + import tech + loads = [tech.spice["dff_in_cap"]*4] + slews = [tech.spice["rise_time"]*2] + data, port_data = d.analyze(probe_address, probe_data, slews, loads) + #Combine info about port into all data + data.update(port_data[0]) + + if OPTS.tech_name == "freepdk45": + golden_data = {'slew_lh': [0.2592187], + 'slew_hl': [0.2592187], + 'delay_lh': [0.2465583], + 'disabled_write0_power': [0.1924678], + 'disabled_read0_power': [0.152483], + 'write0_power': [0.3409064], + 'disabled_read1_power': [0.1737818], + 'read0_power': [0.3096708], + 'read1_power': [0.3107916], + 'delay_hl': [0.2465583], + 'write1_power': [0.26915849999999997], + 'leakage_power': 0.002044307, + 'min_period': 0.898, + 'disabled_write1_power': [0.201411]} + elif OPTS.tech_name == "scn4m_subm": + golden_data = {'read1_power': [12.11658], + 'write1_power': [10.52653], + 'read0_power': [11.956710000000001], + 'disabled_write0_power': [7.673665], + 'disabled_write1_power': [7.981922000000001], + 'slew_lh': [1.868836], + 'slew_hl': [1.868836], + 'delay_hl': [1.8598510000000001], + 'delay_lh': [1.8598510000000001], + 'leakage_power': 0.005457728, + 'disabled_read0_power': [5.904712], + 'min_period': 6.875, + 'disabled_read1_power': [7.132159], + 'write0_power': [13.406400000000001]} + else: + self.assertTrue(False) # other techs fail + + # Check if no too many or too few results + self.assertTrue(len(data.keys())==len(golden_data.keys())) + + self.assertTrue(self.check_golden_data(data,golden_data,0.25)) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/21_ngspice_delay_test.py b/compiler/tests/21_ngspice_delay_test.py index a5eb67fa..92b8eac0 100755 --- a/compiler/tests/21_ngspice_delay_test.py +++ b/compiler/tests/21_ngspice_delay_test.py @@ -18,7 +18,8 @@ import debug class timing_sram_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.spice_name="ngspice" OPTS.analytical_delay = False OPTS.netlist_only = True @@ -54,27 +55,35 @@ class timing_sram_test(openram_test): data.update(port_data[0]) if OPTS.tech_name == "freepdk45": - golden_data = {'delay_hl': [0.2264205], - 'delay_lh': [0.2264205], - 'leakage_power': 0.0021017429999999997, - 'min_period': 0.859, - 'read0_power': [0.3339161], - 'read1_power': [0.31329440000000003], - 'slew_hl': [0.2590786], - 'slew_lh': [0.2590786], - 'write0_power': [0.36360849999999995], - 'write1_power': [0.3486931]} + golden_data = {'slew_lh': [0.2592187], + 'slew_hl': [0.2592187], + 'delay_lh': [0.2465583], + 'disabled_write0_power': [0.1924678], + 'disabled_read0_power': [0.152483], + 'write0_power': [0.3409064], + 'disabled_read1_power': [0.1737818], + 'read0_power': [0.3096708], + 'read1_power': [0.3107916], + 'delay_hl': [0.2465583], + 'write1_power': [0.26915849999999997], + 'leakage_power': 0.002044307, + 'min_period': 0.898, + 'disabled_write1_power': [0.201411]} elif OPTS.tech_name == "scn4m_subm": - golden_data = {'delay_hl': [1.85985], - 'delay_lh': [1.85985], - 'leakage_power': 0.008613619, - 'min_period': 6.875, - 'read0_power': [12.656310000000001], - 'read1_power': [12.11682], - 'slew_hl': [1.868942], - 'slew_lh': [1.868942], - 'write0_power': [13.978110000000001], - 'write1_power': [11.437930000000001]} + golden_data = {'delay_hl': [1.8435739999999998], + 'delay_lh': [1.8435739999999998], + 'disabled_read0_power': [5.917947], + 'disabled_read1_power': [7.154297], + 'disabled_write0_power': [7.696351], + 'disabled_write1_power': [7.999409000000001], + 'leakage_power': 0.004809726, + 'min_period': 6.875, + 'read0_power': [11.833079999999999], + 'read1_power': [11.99236], + 'slew_hl': [1.8668490000000002], + 'slew_lh': [1.8668490000000002], + 'write0_power': [13.287510000000001], + 'write1_power': [10.416369999999999]} else: self.assertTrue(False) # other techs fail diff --git a/compiler/tests/21_ngspice_setuphold_test.py b/compiler/tests/21_ngspice_setuphold_test.py index 0d160943..246bdff9 100755 --- a/compiler/tests/21_ngspice_setuphold_test.py +++ b/compiler/tests/21_ngspice_setuphold_test.py @@ -18,7 +18,8 @@ import debug class timing_setup_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.spice_name="ngspice" OPTS.analytical_delay = False OPTS.netlist_only = True diff --git a/compiler/tests/22_psram_1bank_2mux_func_test.py b/compiler/tests/22_psram_1bank_2mux_func_test.py index f986c3e7..7a6da149 100755 --- a/compiler/tests/22_psram_1bank_2mux_func_test.py +++ b/compiler/tests/22_psram_1bank_2mux_func_test.py @@ -18,7 +18,8 @@ import debug class psram_1bank_2mux_func_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False diff --git a/compiler/tests/22_psram_1bank_4mux_func_test.py b/compiler/tests/22_psram_1bank_4mux_func_test.py index c5fd8945..facd3874 100755 --- a/compiler/tests/22_psram_1bank_4mux_func_test.py +++ b/compiler/tests/22_psram_1bank_4mux_func_test.py @@ -19,7 +19,8 @@ import debug class psram_1bank_4mux_func_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False diff --git a/compiler/tests/22_psram_1bank_8mux_func_test.py b/compiler/tests/22_psram_1bank_8mux_func_test.py index acb168c0..acf0c3a4 100755 --- a/compiler/tests/22_psram_1bank_8mux_func_test.py +++ b/compiler/tests/22_psram_1bank_8mux_func_test.py @@ -19,7 +19,8 @@ import debug class psram_1bank_8mux_func_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False diff --git a/compiler/tests/22_psram_1bank_nomux_func_test.py b/compiler/tests/22_psram_1bank_nomux_func_test.py index a2a2b41c..b7d6cf78 100755 --- a/compiler/tests/22_psram_1bank_nomux_func_test.py +++ b/compiler/tests/22_psram_1bank_nomux_func_test.py @@ -19,7 +19,8 @@ import debug class psram_1bank_nomux_func_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False diff --git a/compiler/tests/22_sram_1bank_2mux_func_test.py b/compiler/tests/22_sram_1bank_2mux_func_test.py index 2037169e..10d19c1c 100755 --- a/compiler/tests/22_sram_1bank_2mux_func_test.py +++ b/compiler/tests/22_sram_1bank_2mux_func_test.py @@ -19,7 +19,8 @@ import debug class sram_1bank_2mux_func_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False diff --git a/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py b/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py new file mode 100755 index 00000000..902cacdd --- /dev/null +++ b/compiler/tests/22_sram_1bank_2mux_sparecols_func_test.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +#@unittest.skip("SKIPPING 22_sram_1bank_2mux_sparecols_func_test") +class sram_1bank_2mux_sparecols_func_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.analytical_delay = False + OPTS.netlist_only = True + OPTS.trim_netlist = False + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import functional, delay + from sram_config import sram_config + c = sram_config(word_size=4, + num_words=32, + num_spare_cols=3, + num_banks=1) + c.words_per_row=2 + c.recompute_sizes() + debug.info(1, "Functional test for sram with " + "{} bit words, {} words, {} words per row, {} spare columns, {} banks".format(c.word_size, + c.num_words, + c.words_per_row, + c.num_spare_cols, + c.num_banks)) + s = factory.create(module_type="sram", sram_config=c) + tempspice = OPTS.openram_temp + "sram.sp" + s.sp_write(tempspice) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + f = functional(s.s, tempspice, corner) + (fail, error) = f.run() + self.assertTrue(fail,error) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/22_sram_1bank_4mux_func_test.py b/compiler/tests/22_sram_1bank_4mux_func_test.py index 178f955b..d2bf7886 100755 --- a/compiler/tests/22_sram_1bank_4mux_func_test.py +++ b/compiler/tests/22_sram_1bank_4mux_func_test.py @@ -19,7 +19,8 @@ import debug class sram_1bank_4mux_func_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False diff --git a/compiler/tests/22_sram_1bank_8mux_func_test.py b/compiler/tests/22_sram_1bank_8mux_func_test.py index d531163a..3f6ff55f 100755 --- a/compiler/tests/22_sram_1bank_8mux_func_test.py +++ b/compiler/tests/22_sram_1bank_8mux_func_test.py @@ -19,7 +19,8 @@ import debug class sram_1bank_8mux_func_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False diff --git a/compiler/tests/22_sram_1rw_1r_1bank_nomux_func_test.py b/compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py similarity index 91% rename from compiler/tests/22_sram_1rw_1r_1bank_nomux_func_test.py rename to compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py index 169e34d0..f2958f9f 100755 --- a/compiler/tests/22_sram_1rw_1r_1bank_nomux_func_test.py +++ b/compiler/tests/22_sram_1bank_nomux_1rw_1r_func_test.py @@ -19,16 +19,15 @@ import debug class psram_1bank_nomux_func_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.replica_bitcell = "replica_bitcell_1rw_1r" - OPTS.dummy_bitcell="dummy_bitcell_1rw_1r" OPTS.num_rw_ports = 1 OPTS.num_w_ports = 0 OPTS.num_r_ports = 1 + globals.setup_bitcell() # This is a hack to reload the characterizer __init__ with the spice version from importlib import reload diff --git a/compiler/tests/22_sram_1bank_nomux_func_test.py b/compiler/tests/22_sram_1bank_nomux_func_test.py index eb6d2412..2aa20e99 100755 --- a/compiler/tests/22_sram_1bank_nomux_func_test.py +++ b/compiler/tests/22_sram_1bank_nomux_func_test.py @@ -19,7 +19,8 @@ import debug class sram_1bank_nomux_func_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True diff --git a/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py b/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py new file mode 100755 index 00000000..347d15d0 --- /dev/null +++ b/compiler/tests/22_sram_1bank_nomux_sparecols_func_test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +#@unittest.skip("SKIPPING 22_sram_func_test") +class sram_1bank_nomux_sparecols_func_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.analytical_delay = False + OPTS.netlist_only = True + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import functional + from sram_config import sram_config + c = sram_config(word_size=4, + num_words=16, + num_spare_cols=3, + num_banks=1) + c.words_per_row=1 + c.recompute_sizes() + debug.info(1, "Functional test for sram with " + "{} bit words, {} words, {} words per row, {} banks".format(c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) + s = factory.create(module_type="sram", sram_config=c) + tempspice = OPTS.openram_temp + "sram.sp" + s.sp_write(tempspice) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + f = functional(s.s, tempspice, corner) + (fail, error) = f.run() + self.assertTrue(fail,error) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/22_sram_wmask_1w_1r_func_test.py b/compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py similarity index 88% rename from compiler/tests/22_sram_wmask_1w_1r_func_test.py rename to compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py index 2ee79750..07cff70e 100755 --- a/compiler/tests/22_sram_wmask_1w_1r_func_test.py +++ b/compiler/tests/22_sram_1bank_wmask_1rw_1r_func_test.py @@ -21,18 +21,16 @@ import debug class sram_wmask_1w_1r_func_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False - OPTS.bitcell = "bitcell_1w_1r" - OPTS.replica_bitcell = "replica_bitcell_1w_1r" - OPTS.dummy_bitcell = "dummy_bitcell_1w_1r" - - OPTS.num_rw_ports = 0 - OPTS.num_w_ports = 1 + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 OPTS.num_r_ports = 1 - + globals.setup_bitcell() + # This is a hack to reload the characterizer __init__ with the spice version from importlib import reload import characterizer diff --git a/compiler/tests/22_sram_wmask_func_test.py b/compiler/tests/22_sram_wmask_func_test.py index c390f030..d29795a9 100755 --- a/compiler/tests/22_sram_wmask_func_test.py +++ b/compiler/tests/22_sram_wmask_func_test.py @@ -19,7 +19,8 @@ import debug class sram_wmask_func_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False OPTS.netlist_only = True OPTS.trim_netlist = False diff --git a/compiler/tests/23_lib_sram_model_corners_test.py b/compiler/tests/23_lib_sram_model_corners_test.py index 5840c05d..84de2b3f 100755 --- a/compiler/tests/23_lib_sram_model_corners_test.py +++ b/compiler/tests/23_lib_sram_model_corners_test.py @@ -18,7 +18,9 @@ import debug class lib_model_corners_lib_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.nominal_corner_only = False OPTS.netlist_only = True from characterizer import lib diff --git a/compiler/tests/23_lib_sram_model_test.py b/compiler/tests/23_lib_sram_model_test.py index 75e73f71..1ea8fc9e 100755 --- a/compiler/tests/23_lib_sram_model_test.py +++ b/compiler/tests/23_lib_sram_model_test.py @@ -18,7 +18,9 @@ import debug class lib_sram_model_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.nominal_corner_only = False OPTS.netlist_only = True from characterizer import lib diff --git a/compiler/tests/23_lib_sram_prune_test.py b/compiler/tests/23_lib_sram_prune_test.py index 1fc5a66b..e9c727c6 100755 --- a/compiler/tests/23_lib_sram_prune_test.py +++ b/compiler/tests/23_lib_sram_prune_test.py @@ -18,8 +18,10 @@ import debug class lib_sram_prune_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False + OPTS.netlist_only = True OPTS.trim_netlist = True # This is a hack to reload the characterizer __init__ with the spice version diff --git a/compiler/tests/23_lib_sram_test.py b/compiler/tests/23_lib_sram_test.py index 0ababf32..91f410c3 100755 --- a/compiler/tests/23_lib_sram_test.py +++ b/compiler/tests/23_lib_sram_test.py @@ -17,8 +17,10 @@ import debug class lib_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.analytical_delay = False + OPTS.netlist_only = True OPTS.trim_netlist = False # This is a hack to reload the characterizer __init__ with the spice version diff --git a/compiler/tests/24_lef_sram_test.py b/compiler/tests/24_lef_sram_test.py index 5f7fdc60..14ad551f 100755 --- a/compiler/tests/24_lef_sram_test.py +++ b/compiler/tests/24_lef_sram_test.py @@ -18,7 +18,8 @@ import debug class lef_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) from sram import sram from sram_config import sram_config diff --git a/compiler/tests/25_verilog_sram_test.py b/compiler/tests/25_verilog_sram_test.py index da6a2682..120974ff 100755 --- a/compiler/tests/25_verilog_sram_test.py +++ b/compiler/tests/25_verilog_sram_test.py @@ -17,8 +17,11 @@ import debug class verilog_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) - + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.route_supplies=False + OPTS.check_lvsdrc=False + OPTS.netlist_only=True from sram import sram from sram_config import sram_config c = sram_config(word_size=2, diff --git a/compiler/tests/26_hspice_pex_pinv_test.py b/compiler/tests/26_hspice_pex_pinv_test.py index f0cccba3..b4b55cdb 100755 --- a/compiler/tests/26_hspice_pex_pinv_test.py +++ b/compiler/tests/26_hspice_pex_pinv_test.py @@ -20,7 +20,8 @@ import debug class hspice_pex_pinv_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import pinv # load the hspice diff --git a/compiler/tests/26_ngspice_pex_pinv_test.py b/compiler/tests/26_ngspice_pex_pinv_test.py index 2eb95948..e6e0cfb2 100755 --- a/compiler/tests/26_ngspice_pex_pinv_test.py +++ b/compiler/tests/26_ngspice_pex_pinv_test.py @@ -19,7 +19,8 @@ import debug class ngspice_pex_pinv_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) import pinv # load the ngspice diff --git a/compiler/tests/26_pex_test.py b/compiler/tests/26_pex_test.py index 78409249..4eff7db5 100755 --- a/compiler/tests/26_pex_test.py +++ b/compiler/tests/26_pex_test.py @@ -19,7 +19,8 @@ import debug class sram_func_test(openram_test): def runTest(self): - globals.init_openram("config_{0}".format(OPTS.tech_name)) + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) OPTS.use_pex = True diff --git a/compiler/tests/30_openram_back_end_test.py b/compiler/tests/30_openram_back_end_test.py index de1bec05..591d03ac 100755 --- a/compiler/tests/30_openram_back_end_test.py +++ b/compiler/tests/30_openram_back_end_test.py @@ -20,7 +20,8 @@ class openram_back_end_test(openram_test): def runTest(self): OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME")) - globals.init_openram("{0}/tests/config_{1}".format(OPENRAM_HOME,OPTS.tech_name)) + config_file = "{}/tests/configs/config_back_end".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(1, "Testing top-level back-end openram.py with 2-bit, 16 word SRAM.") out_file = "testsram" @@ -50,8 +51,8 @@ class openram_back_end_test(openram_test): debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage") exe_name = "{0}/openram.py ".format(OPENRAM_HOME) else: - exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME) - config_name = "{0}config_{1}_back_end.py".format(OPENRAM_HOME + "/tests/",OPTS.tech_name) + exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME) + config_name = "{0}/tests/configs/config_back_end.py".format(OPENRAM_HOME) cmd = "{0} -n -o {1} -p {2} {3} {4} 2>&1 > {5}/output.log".format(exe_name, out_file, out_path, @@ -63,33 +64,32 @@ class openram_back_end_test(openram_test): # assert an error until we actually check a resul for extension in ["gds", "v", "lef", "sp"]: - filename = "{0}/{1}.{2}".format(out_path,out_file,extension) - debug.info(1,"Checking for file: " + filename) - self.assertEqual(os.path.exists(filename),True) + filename = "{0}/{1}.{2}".format(out_path, out_file, extension) + debug.info(1, "Checking for file: " + filename) + self.assertEqual(os.path.exists(filename), True) # Make sure there is any .lib file import glob files = glob.glob('{0}/*.lib'.format(out_path)) self.assertTrue(len(files)>0) - # Make sure there is any .html file + # Make sure there is any .html file if os.path.exists(out_path): datasheets = glob.glob('{0}/*html'.format(out_path)) self.assertTrue(len(datasheets)>0) # grep any errors from the output - output_log = open("{0}/output.log".format(out_path),"r") + output_log = open("{0}/output.log".format(out_path), "r") output = output_log.read() output_log.close() - self.assertEqual(len(re.findall('ERROR',output)),0) - self.assertEqual(len(re.findall('WARNING',output)),0) - + self.assertEqual(len(re.findall('ERROR', output)), 0) + self.assertEqual(len(re.findall('WARNING', output)), 0) # now clean up the directory if OPTS.purge_temp: if os.path.exists(out_path): shutil.rmtree(out_path, ignore_errors=True) - self.assertEqual(os.path.exists(out_path),False) + self.assertEqual(os.path.exists(out_path), False) globals.end_openram() diff --git a/compiler/tests/30_openram_front_end_test.py b/compiler/tests/30_openram_front_end_test.py index dbe7fcb9..127df6c2 100755 --- a/compiler/tests/30_openram_front_end_test.py +++ b/compiler/tests/30_openram_front_end_test.py @@ -16,12 +16,12 @@ from sram_factory import factory import debug import getpass -#@unittest.skip("SKIPPING 30_openram_front_end_test") class openram_front_end_test(openram_test): def runTest(self): OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME")) - globals.init_openram("{0}/tests/config_{1}".format(OPENRAM_HOME,OPTS.tech_name)) + config_file = "{}/tests/configs/config_front_end".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) debug.info(1, "Testing top-level front-end openram.py with 2-bit, 16 word SRAM.") out_file = "testsram" @@ -51,8 +51,8 @@ class openram_front_end_test(openram_test): debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage") exe_name = "{0}/openram.py ".format(OPENRAM_HOME) else: - exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME) - config_name = "{0}config_{1}_front_end.py".format(OPENRAM_HOME + "/tests/",OPTS.tech_name) + exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME) + config_name = "{0}/tests/configs/config_front_end.py".format(OPENRAM_HOME) cmd = "{0} -n -o {1} -p {2} {3} {4} 2>&1 > {5}/output.log".format(exe_name, out_file, out_path, @@ -64,33 +64,32 @@ class openram_front_end_test(openram_test): # assert an error until we actually check a result for extension in ["v", "lef", "sp", "gds"]: - filename = "{0}/{1}.{2}".format(out_path,out_file,extension) - debug.info(1,"Checking for file: " + filename) - self.assertEqual(os.path.exists(filename),True) + filename = "{0}/{1}.{2}".format(out_path, out_file, extension) + debug.info(1, "Checking for file: " + filename) + self.assertEqual(os.path.exists(filename), True) # Make sure there is any .lib file import glob files = glob.glob('{0}/*.lib'.format(out_path)) self.assertTrue(len(files)>0) - # Make sure there is any .html file + # Make sure there is any .html file if os.path.exists(out_path): datasheets = glob.glob('{0}/*html'.format(out_path)) self.assertTrue(len(datasheets)>0) # grep any errors from the output - output_log = open("{0}/output.log".format(out_path),"r") + output_log = open("{0}/output.log".format(out_path), "r") output = output_log.read() output_log.close() - self.assertEqual(len(re.findall('ERROR',output)),0) - self.assertEqual(len(re.findall('WARNING',output)),0) + self.assertEqual(len(re.findall('ERROR', output)), 0) + self.assertEqual(len(re.findall('WARNING', output)), 0) - - # now clean up the directory + # now clean up the directory if OPTS.purge_temp: if os.path.exists(out_path): shutil.rmtree(out_path, ignore_errors=True) - self.assertEqual(os.path.exists(out_path),False) + self.assertEqual(os.path.exists(out_path), False) globals.end_openram() diff --git a/compiler/tests/50_riscv_func_test.py b/compiler/tests/50_riscv_func_test.py new file mode 100755 index 00000000..1d5720f7 --- /dev/null +++ b/compiler/tests/50_riscv_func_test.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +@unittest.skip("SKIPPING 50_riscv_func_test") +class riscv_func_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.analytical_delay = False + OPTS.netlist_only = True + OPTS.trim_netlist = False + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 1 + globals.setup_bitcell() + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import functional, delay + from sram_config import sram_config + c = sram_config(word_size=32, + write_size=8, + num_words=256, + num_banks=1) + c.words_per_row=1 + c.recompute_sizes() + debug.info(1, "Functional test RISC-V memory" + "{} bit words, {} words, {} words per row, {} banks".format(c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) + s = factory.create(module_type="sram", sram_config=c) + tempspice = OPTS.openram_temp + "sram.sp" + s.sp_write(tempspice) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + f = functional(s.s, tempspice, corner) + (fail, error) = f.run() + self.assertTrue(fail,error) + + globals.end_openram() + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/50_riscv_phys_test.py b/compiler/tests/50_riscv_phys_test.py new file mode 100755 index 00000000..0ae11025 --- /dev/null +++ b/compiler/tests/50_riscv_phys_test.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +@unittest.skip("SKIPPING 50_riscv_phys_test") +class riscv_phys_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + globals.setup_bitcell() + OPTS.route_supplies=False + OPTS.perimeter_pins=False + + c = sram_config(word_size=32, + write_size=8, + num_words=256, + num_banks=1) + + c.words_per_row=2 + c.recompute_sizes() + debug.info(1, "Layout test for {}rw,{}r,{}w sram " + "with {} bit words, {} words, {} words per " + "row, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) + a = factory.create(module_type="sram", sram_config=c) + self.local_check(a, final_verification=True) + + globals.end_openram() + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/config_scn3me_subm.py b/compiler/tests/config_scn3me_subm.py deleted file mode 100644 index 7b5b5e15..00000000 --- a/compiler/tests/config_scn3me_subm.py +++ /dev/null @@ -1,22 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2019 Regents of the University of California and The Board -# of Regents for the Oklahoma Agricultural and Mechanical College -# (acting for and on behalf of Oklahoma State University) -# All rights reserved. -# -word_size = 1 -num_words = 16 - -tech_name = "scn3me_subm" -process_corners = ["TT"] -supply_voltages = [5.0] -temperatures = [25] - -route_supplies = True -check_lvsdrc = True - -drc_name = "magic" -lvs_name = "netgen" -pex_name = "magic" - diff --git a/compiler/tests/config_scn3me_subm_back_end.py b/compiler/tests/config_scn3me_subm_back_end.py deleted file mode 100644 index f9c23417..00000000 --- a/compiler/tests/config_scn3me_subm_back_end.py +++ /dev/null @@ -1,23 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2019 Regents of the University of California and The Board -# of Regents for the Oklahoma Agricultural and Mechanical College -# (acting for and on behalf of Oklahoma State University) -# All rights reserved. -# -word_size = 1 -num_words = 16 - -tech_name = "scn3me_subm" -process_corners = ["TT"] -supply_voltages = [5.0] -temperatures = [25] - -route_supplies = True -check_lvsdrc = True -inline_lvsdrc = True -analytical_delay = False - -drc_name = "magic" -lvs_name = "netgen" -pex_name = "magic" diff --git a/compiler/tests/config_scn3me_subm_front_end.py b/compiler/tests/config_scn3me_subm_front_end.py deleted file mode 100644 index 40504a18..00000000 --- a/compiler/tests/config_scn3me_subm_front_end.py +++ /dev/null @@ -1,21 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2019 Regents of the University of California and The Board -# of Regents for the Oklahoma Agricultural and Mechanical College -# (acting for and on behalf of Oklahoma State University) -# All rights reserved. -# -word_size = 1 -num_words = 16 - -tech_name = "scn3me_subm" -process_corners = ["TT"] -supply_voltages = [5.0] -temperatures = [25] - -analytical_delay = False - -drc_name = "magic" -lvs_name = "netgen" -pex_name = "magic" - diff --git a/compiler/tests/config_scn4m_subm.py b/compiler/tests/config_scn4m_subm.py deleted file mode 100644 index abb31435..00000000 --- a/compiler/tests/config_scn4m_subm.py +++ /dev/null @@ -1,22 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2019 Regents of the University of California and The Board -# of Regents for the Oklahoma Agricultural and Mechanical College -# (acting for and on behalf of Oklahoma State University) -# All rights reserved. -# -word_size = 1 -num_words = 16 - -tech_name = "scn4m_subm" -process_corners = ["TT"] -supply_voltages = [5.0] -temperatures = [25] - -route_supplies = True -check_lvsdrc = True - -drc_name = "magic" -lvs_name = "netgen" -pex_name = "magic" - diff --git a/compiler/tests/config_scn4m_subm_back_end.py b/compiler/tests/config_scn4m_subm_back_end.py deleted file mode 100644 index 35e4cd91..00000000 --- a/compiler/tests/config_scn4m_subm_back_end.py +++ /dev/null @@ -1,23 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2019 Regents of the University of California and The Board -# of Regents for the Oklahoma Agricultural and Mechanical College -# (acting for and on behalf of Oklahoma State University) -# All rights reserved. -# -word_size = 1 -num_words = 16 - -tech_name = "scn4m_subm" -process_corners = ["TT"] -supply_voltages = [5.0] -temperatures = [25] - -route_supplies = True -check_lvsdrc = True -inline_lvsdrc = True -analytical_delay = False - -drc_name = "magic" -lvs_name = "netgen" -pex_name = "magic" diff --git a/compiler/tests/config_scn4m_subm_front_end.py b/compiler/tests/config_scn4m_subm_front_end.py deleted file mode 100644 index 142191a0..00000000 --- a/compiler/tests/config_scn4m_subm_front_end.py +++ /dev/null @@ -1,19 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2019 Regents of the University of California and The Board -# of Regents for the Oklahoma Agricultural and Mechanical College -# (acting for and on behalf of Oklahoma State University) -# All rights reserved. -# -word_size = 1 -num_words = 16 - -tech_name = "scn4m_subm" -process_corners = ["TT"] -supply_voltages = [5.0] -temperatures = [25] - -drc_name = "magic" -lvs_name = "netgen" -pex_name = "magic" - diff --git a/compiler/tests/config_freepdk45.py b/compiler/tests/configs/config.py similarity index 78% rename from compiler/tests/config_freepdk45.py rename to compiler/tests/configs/config.py index 3103217f..d19ae02e 100644 --- a/compiler/tests/config_freepdk45.py +++ b/compiler/tests/configs/config.py @@ -5,14 +5,13 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # +from globals import OPTS word_size = 1 num_words = 16 -tech_name = "freepdk45" -process_corners = ["TT"] -supply_voltages = [1.0] -temperatures = [25] +tech_name = OPTS.tech_name +nominal_corner_only = True route_supplies = True check_lvsdrc = True diff --git a/compiler/tests/config_freepdk45_back_end.py b/compiler/tests/configs/config_back_end.py similarity index 80% rename from compiler/tests/config_freepdk45_back_end.py rename to compiler/tests/configs/config_back_end.py index 68417a3b..3ba6492e 100644 --- a/compiler/tests/config_freepdk45_back_end.py +++ b/compiler/tests/configs/config_back_end.py @@ -5,16 +5,16 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # +from globals import OPTS word_size = 1 num_words = 16 -tech_name = "freepdk45" -process_corners = ["TT"] -supply_voltages = [1.0] -temperatures = [25] +tech_name = OPTS.tech_name -inline_lvsdrc = True +nominal_corner_only = True route_supplies = True check_lvsdrc = True +inline_lvsdrc = True analytical_delay = False + diff --git a/compiler/tests/config_freepdk45_front_end.py b/compiler/tests/configs/config_front_end.py similarity index 71% rename from compiler/tests/config_freepdk45_front_end.py rename to compiler/tests/configs/config_front_end.py index 1886d808..cf872ce6 100644 --- a/compiler/tests/config_freepdk45_front_end.py +++ b/compiler/tests/configs/config_front_end.py @@ -5,14 +5,9 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # +from globals import OPTS word_size = 1 num_words = 16 -tech_name = "freepdk45" -process_corners = ["TT"] -supply_voltages = [1.0] -temperatures = [25] - -analytical_delay = False - +tech_name = OPTS.tech_name diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_FF_1p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_FF_1p0V_25C_analytical.lib index a1c5a04b..b3ef0e0a 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_FF_1p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_FF_1p0V_25C_analytical.lib @@ -35,11 +35,14 @@ library (sram_2_16_1_freepdk45_FF_1p0V_25C_lib){ default_max_fanout : 4.0 ; default_connection_class : universal ; + voltage_map ( VDD, 1.0 ); + voltage_map ( GND, 0 ); + lu_table_template(CELL_TABLE){ variable_1 : input_net_transition; variable_2 : total_output_net_capacitance; index_1("0.00125, 0.005, 0.04"); - index_2("0.052275, 0.2091, 1.6728"); + index_2("5.2275e-05, 0.0002091, 0.0008364"); } lu_table_template(CONSTRAINT_TABLE){ @@ -78,17 +81,26 @@ cell (sram_2_16_1_freepdk45){ dont_use : true; map_only : true; dont_touch : true; - area : 1124.88; + area : 0; + + pg_pin(vdd) { + voltage_name : VDD; + pg_type : primary_power; + } + + pg_pin(gnd) { + voltage_name : GND; + pg_type : primary_ground; + } leakage_power () { - when : "csb0"; - value : 0.000167; + value : 0.000198; } - cell_leakage_power : 0; + cell_leakage_power : 0.000198; bus(din0){ bus_type : data; direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; memory_write(){ address : addr0; clocked_on : clk0; @@ -127,8 +139,8 @@ cell (sram_2_16_1_freepdk45){ bus(dout0){ bus_type : data; direction : output; - max_capacitance : 1.6728; - min_capacitance : 0.052275; + max_capacitance : 0.0008364000000000001; + min_capacitance : 5.2275000000000003e-05; memory_read(){ address : addr0; } @@ -138,14 +150,14 @@ cell (sram_2_16_1_freepdk45){ related_pin : "clk0"; timing_type : falling_edge; cell_rise(CELL_TABLE) { - values("0.088, 0.088, 0.088",\ - "0.088, 0.088, 0.088",\ - "0.088, 0.088, 0.088"); + values("0.193, 0.193, 0.194",\ + "0.193, 0.193, 0.194",\ + "0.193, 0.193, 0.194"); } cell_fall(CELL_TABLE) { - values("0.088, 0.088, 0.088",\ - "0.088, 0.088, 0.088",\ - "0.088, 0.088, 0.088"); + values("0.193, 0.193, 0.194",\ + "0.193, 0.193, 0.194",\ + "0.193, 0.193, 0.194"); } rise_transition(CELL_TABLE) { values("0.001, 0.001, 0.001",\ @@ -164,7 +176,7 @@ cell (sram_2_16_1_freepdk45){ bus(addr0){ bus_type : addr; direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; max_transition : 0.04; pin(addr0[3:0]){ timing(){ @@ -200,7 +212,7 @@ cell (sram_2_16_1_freepdk45){ pin(csb0){ direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; timing(){ timing_type : setup_rising; related_pin : "clk0"; @@ -233,7 +245,7 @@ cell (sram_2_16_1_freepdk45){ pin(web0){ direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; timing(){ timing_type : setup_rising; related_pin : "clk0"; @@ -267,52 +279,61 @@ cell (sram_2_16_1_freepdk45){ pin(clk0){ clock : true; direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; internal_power(){ - when : "!csb0 & clk0 & !web0"; + when : "!csb0 & !web0"; rise_power(scalar){ - values("0.033101244168888884"); + values("9.240667e-02"); } fall_power(scalar){ - values("0.033101244168888884"); + values("9.240667e-02"); } } internal_power(){ - when : "!csb0 & !clk0 & web0"; + when : "csb0 & !web0"; rise_power(scalar){ - values("0.033101244168888884"); + values("9.240667e-02"); } fall_power(scalar){ - values("0.033101244168888884"); + values("9.240667e-02"); } } internal_power(){ - when : "csb0"; + when : "!csb0 & web0"; rise_power(scalar){ - values("0"); + values("9.240667e-02"); } fall_power(scalar){ - values("0"); + values("9.240667e-02"); + } + } + internal_power(){ + when : "csb0 & web0"; + rise_power(scalar){ + values("9.240667e-02"); + } + fall_power(scalar){ + values("9.240667e-02"); } } timing(){ timing_type :"min_pulse_width"; related_pin : clk0; rise_constraint(scalar) { - values("0.009"); + values("0.0195"); } fall_constraint(scalar) { - values("0.009"); + values("0.0195"); } } timing(){ timing_type :"minimum_period"; related_pin : clk0; rise_constraint(scalar) { - values("0.018"); + values("0.039"); } fall_constraint(scalar) { - values("0.018"); + values("0.039"); } } } diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_SS_1p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_SS_1p0V_25C_analytical.lib index 865daada..34be4fe4 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_SS_1p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_SS_1p0V_25C_analytical.lib @@ -35,11 +35,14 @@ library (sram_2_16_1_freepdk45_SS_1p0V_25C_lib){ default_max_fanout : 4.0 ; default_connection_class : universal ; + voltage_map ( VDD, 1.0 ); + voltage_map ( GND, 0 ); + lu_table_template(CELL_TABLE){ variable_1 : input_net_transition; variable_2 : total_output_net_capacitance; index_1("0.00125, 0.005, 0.04"); - index_2("0.052275, 0.2091, 1.6728"); + index_2("5.2275e-05, 0.0002091, 0.0008364"); } lu_table_template(CONSTRAINT_TABLE){ @@ -78,17 +81,26 @@ cell (sram_2_16_1_freepdk45){ dont_use : true; map_only : true; dont_touch : true; - area : 1124.88; + area : 0; + + pg_pin(vdd) { + voltage_name : VDD; + pg_type : primary_power; + } + + pg_pin(gnd) { + voltage_name : GND; + pg_type : primary_ground; + } leakage_power () { - when : "csb0"; - value : 0.000167; + value : 0.000198; } - cell_leakage_power : 0; + cell_leakage_power : 0.000198; bus(din0){ bus_type : data; direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; memory_write(){ address : addr0; clocked_on : clk0; @@ -127,8 +139,8 @@ cell (sram_2_16_1_freepdk45){ bus(dout0){ bus_type : data; direction : output; - max_capacitance : 1.6728; - min_capacitance : 0.052275; + max_capacitance : 0.0008364000000000001; + min_capacitance : 5.2275000000000003e-05; memory_read(){ address : addr0; } @@ -138,14 +150,14 @@ cell (sram_2_16_1_freepdk45){ related_pin : "clk0"; timing_type : falling_edge; cell_rise(CELL_TABLE) { - values("0.107, 0.107, 0.107",\ - "0.107, 0.107, 0.107",\ - "0.107, 0.107, 0.107"); + values("0.236, 0.236, 0.237",\ + "0.236, 0.236, 0.237",\ + "0.236, 0.236, 0.237"); } cell_fall(CELL_TABLE) { - values("0.107, 0.107, 0.107",\ - "0.107, 0.107, 0.107",\ - "0.107, 0.107, 0.107"); + values("0.236, 0.236, 0.237",\ + "0.236, 0.236, 0.237",\ + "0.236, 0.236, 0.237"); } rise_transition(CELL_TABLE) { values("0.001, 0.001, 0.001",\ @@ -164,7 +176,7 @@ cell (sram_2_16_1_freepdk45){ bus(addr0){ bus_type : addr; direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; max_transition : 0.04; pin(addr0[3:0]){ timing(){ @@ -200,7 +212,7 @@ cell (sram_2_16_1_freepdk45){ pin(csb0){ direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; timing(){ timing_type : setup_rising; related_pin : "clk0"; @@ -233,7 +245,7 @@ cell (sram_2_16_1_freepdk45){ pin(web0){ direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; timing(){ timing_type : setup_rising; related_pin : "clk0"; @@ -267,52 +279,61 @@ cell (sram_2_16_1_freepdk45){ pin(clk0){ clock : true; direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; internal_power(){ - when : "!csb0 & clk0 & !web0"; + when : "!csb0 & !web0"; rise_power(scalar){ - values("0.033101244168888884"); + values("7.560546e-02"); } fall_power(scalar){ - values("0.033101244168888884"); + values("7.560546e-02"); } } internal_power(){ - when : "!csb0 & !clk0 & web0"; + when : "csb0 & !web0"; rise_power(scalar){ - values("0.033101244168888884"); + values("7.560546e-02"); } fall_power(scalar){ - values("0.033101244168888884"); + values("7.560546e-02"); } } internal_power(){ - when : "csb0"; + when : "!csb0 & web0"; rise_power(scalar){ - values("0"); + values("7.560546e-02"); } fall_power(scalar){ - values("0"); + values("7.560546e-02"); + } + } + internal_power(){ + when : "csb0 & web0"; + rise_power(scalar){ + values("7.560546e-02"); + } + fall_power(scalar){ + values("7.560546e-02"); } } timing(){ timing_type :"min_pulse_width"; related_pin : clk0; rise_constraint(scalar) { - values("0.0105"); + values("0.0235"); } fall_constraint(scalar) { - values("0.0105"); + values("0.0235"); } } timing(){ timing_type :"minimum_period"; related_pin : clk0; rise_constraint(scalar) { - values("0.021"); + values("0.047"); } fall_constraint(scalar) { - values("0.021"); + values("0.047"); } } } diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib index 72d01a0f..cca9c1ed 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib @@ -35,11 +35,14 @@ library (sram_2_16_1_freepdk45_TT_1p0V_25C_lib){ default_max_fanout : 4.0 ; default_connection_class : universal ; + voltage_map ( VDD, 1.0 ); + voltage_map ( GND, 0 ); + lu_table_template(CELL_TABLE){ variable_1 : input_net_transition; variable_2 : total_output_net_capacitance; index_1("0.00125, 0.005, 0.04"); - index_2("0.052275, 0.2091, 1.6728"); + index_2("5.2275e-05, 0.0002091, 0.0008364"); } lu_table_template(CONSTRAINT_TABLE){ @@ -78,17 +81,26 @@ cell (sram_2_16_1_freepdk45){ dont_use : true; map_only : true; dont_touch : true; - area : 977.4951374999999; + area : 0; + + pg_pin(vdd) { + voltage_name : VDD; + pg_type : primary_power; + } + + pg_pin(gnd) { + voltage_name : GND; + pg_type : primary_ground; + } leakage_power () { - when : "csb0"; - value : 0.0011164579999999999; + value : 0.00163; } - cell_leakage_power : 0; + cell_leakage_power : 0.00163; bus(din0){ bus_type : data; direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; memory_write(){ address : addr0; clocked_on : clk0; @@ -98,9 +110,9 @@ cell (sram_2_16_1_freepdk45){ timing_type : setup_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039"); + values("0.033, 0.033, 0.033",\ + "0.033, 0.033, 0.033",\ + "0.033, 0.033, 0.033"); } fall_constraint(CONSTRAINT_TABLE) { values("0.027, 0.027, 0.033",\ @@ -112,14 +124,14 @@ cell (sram_2_16_1_freepdk45){ timing_type : hold_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022"); + values("-0.01, -0.01, 0.021",\ + "-0.01, -0.01, 0.021",\ + "-0.01, -0.01, 0.021"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016"); + values("-0.016, -0.01, -0.016",\ + "-0.016, -0.01, -0.016",\ + "-0.016, -0.01, -0.016"); } } } @@ -127,8 +139,8 @@ cell (sram_2_16_1_freepdk45){ bus(dout0){ bus_type : data; direction : output; - max_capacitance : 1.6728; - min_capacitance : 0.052275; + max_capacitance : 0.0008364000000000001; + min_capacitance : 5.2275000000000003e-05; memory_read(){ address : addr0; } @@ -138,24 +150,24 @@ cell (sram_2_16_1_freepdk45){ related_pin : "clk0"; timing_type : falling_edge; cell_rise(CELL_TABLE) { - values("0.235, 0.235, 0.239",\ - "0.235, 0.236, 0.24",\ - "0.241, 0.242, 0.246"); + values("0.226, 0.227, 0.232",\ + "0.227, 0.228, 0.233",\ + "0.232, 0.234, 0.238"); } cell_fall(CELL_TABLE) { - values("2.583, 2.585, 2.612",\ - "2.584, 2.585, 2.613",\ - "2.59, 2.592, 2.62"); + values("0.226, 0.227, 0.232",\ + "0.227, 0.228, 0.233",\ + "0.232, 0.234, 0.238"); } rise_transition(CELL_TABLE) { - values("0.022, 0.022, 0.03",\ - "0.022, 0.023, 0.03",\ - "0.022, 0.022, 0.03"); + values("0.256, 0.256, 0.257",\ + "0.256, 0.256, 0.257",\ + "0.256, 0.256, 0.257"); } fall_transition(CELL_TABLE) { - values("0.078, 0.079, 0.083",\ - "0.078, 0.079, 0.083",\ - "0.079, 0.079, 0.083"); + values("0.256, 0.256, 0.257",\ + "0.256, 0.256, 0.257",\ + "0.256, 0.256, 0.257"); } } } @@ -164,16 +176,16 @@ cell (sram_2_16_1_freepdk45){ bus(addr0){ bus_type : addr; direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; max_transition : 0.04; pin(addr0[3:0]){ timing(){ timing_type : setup_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039"); + values("0.033, 0.033, 0.033",\ + "0.033, 0.033, 0.033",\ + "0.033, 0.033, 0.033"); } fall_constraint(CONSTRAINT_TABLE) { values("0.027, 0.027, 0.033",\ @@ -185,14 +197,14 @@ cell (sram_2_16_1_freepdk45){ timing_type : hold_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022"); + values("-0.01, -0.01, 0.021",\ + "-0.01, -0.01, 0.021",\ + "-0.01, -0.01, 0.021"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016"); + values("-0.016, -0.01, -0.016",\ + "-0.016, -0.01, -0.016",\ + "-0.016, -0.01, -0.016"); } } } @@ -200,14 +212,14 @@ cell (sram_2_16_1_freepdk45){ pin(csb0){ direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; timing(){ timing_type : setup_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039"); + values("0.033, 0.033, 0.033",\ + "0.033, 0.033, 0.033",\ + "0.033, 0.033, 0.033"); } fall_constraint(CONSTRAINT_TABLE) { values("0.027, 0.027, 0.033",\ @@ -219,28 +231,28 @@ cell (sram_2_16_1_freepdk45){ timing_type : hold_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022"); + values("-0.01, -0.01, 0.021",\ + "-0.01, -0.01, 0.021",\ + "-0.01, -0.01, 0.021"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016"); + values("-0.016, -0.01, -0.016",\ + "-0.016, -0.01, -0.016",\ + "-0.016, -0.01, -0.016"); } } } pin(web0){ direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; timing(){ timing_type : setup_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039"); + values("0.033, 0.033, 0.033",\ + "0.033, 0.033, 0.033",\ + "0.033, 0.033, 0.033"); } fall_constraint(CONSTRAINT_TABLE) { values("0.027, 0.027, 0.033",\ @@ -252,14 +264,14 @@ cell (sram_2_16_1_freepdk45){ timing_type : hold_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022"); + values("-0.01, -0.01, 0.021",\ + "-0.01, -0.01, 0.021",\ + "-0.01, -0.01, 0.021"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016"); + values("-0.016, -0.01, -0.016",\ + "-0.016, -0.01, -0.016",\ + "-0.016, -0.01, -0.016"); } } } @@ -267,52 +279,61 @@ cell (sram_2_16_1_freepdk45){ pin(clk0){ clock : true; direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; internal_power(){ - when : "!csb0 & clk0 & !web0"; + when : "!csb0 & !web0"; rise_power(scalar){ - values("0.03599689694444445"); + values("3.069977e-01"); } fall_power(scalar){ - values("0.03599689694444445"); + values("3.686680e-01"); } } internal_power(){ - when : "!csb0 & !clk0 & web0"; + when : "csb0 & !web0"; rise_power(scalar){ - values("0.029906643888888886"); + values("2.055845e-01"); } fall_power(scalar){ - values("0.029906643888888886"); + values("1.933561e-01"); } } internal_power(){ - when : "csb0"; + when : "!csb0 & web0"; rise_power(scalar){ - values("0"); + values("3.315565e-01"); } fall_power(scalar){ - values("0"); + values("3.314553e-01"); + } + } + internal_power(){ + when : "csb0 & web0"; + rise_power(scalar){ + values("1.777355e-01"); + } + fall_power(scalar){ + values("1.615044e-01"); } } timing(){ timing_type :"min_pulse_width"; related_pin : clk0; rise_constraint(scalar) { - values("2.422"); + values("0.449"); } fall_constraint(scalar) { - values("2.422"); + values("0.449"); } } timing(){ timing_type :"minimum_period"; related_pin : clk0; rise_constraint(scalar) { - values("4.844"); + values("0.898"); } fall_constraint(scalar) { - values("4.844"); + values("0.898"); } } } diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib index 33587063..26028892 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib @@ -35,11 +35,14 @@ library (sram_2_16_1_freepdk45_TT_1p0V_25C_lib){ default_max_fanout : 4.0 ; default_connection_class : universal ; + voltage_map ( VDD, 1.0 ); + voltage_map ( GND, 0 ); + lu_table_template(CELL_TABLE){ variable_1 : input_net_transition; variable_2 : total_output_net_capacitance; index_1("0.00125, 0.005, 0.04"); - index_2("0.052275, 0.2091, 1.6728"); + index_2("5.2275e-05, 0.0002091, 0.0008364"); } lu_table_template(CONSTRAINT_TABLE){ @@ -78,17 +81,26 @@ cell (sram_2_16_1_freepdk45){ dont_use : true; map_only : true; dont_touch : true; - area : 977.4951374999999; + area : 0; + + pg_pin(vdd) { + voltage_name : VDD; + pg_type : primary_power; + } + + pg_pin(gnd) { + voltage_name : GND; + pg_type : primary_ground; + } leakage_power () { - when : "csb0"; - value : 0.000179; + value : 0.000198; } - cell_leakage_power : 0; + cell_leakage_power : 0.000198; bus(din0){ bus_type : data; direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; memory_write(){ address : addr0; clocked_on : clk0; @@ -127,8 +139,8 @@ cell (sram_2_16_1_freepdk45){ bus(dout0){ bus_type : data; direction : output; - max_capacitance : 1.6728; - min_capacitance : 0.052275; + max_capacitance : 0.0008364000000000001; + min_capacitance : 5.2275000000000003e-05; memory_read(){ address : addr0; } @@ -138,14 +150,14 @@ cell (sram_2_16_1_freepdk45){ related_pin : "clk0"; timing_type : falling_edge; cell_rise(CELL_TABLE) { - values("0.098, 0.098, 0.098",\ - "0.098, 0.098, 0.098",\ - "0.098, 0.098, 0.098"); + values("0.215, 0.215, 0.216",\ + "0.215, 0.215, 0.216",\ + "0.215, 0.215, 0.216"); } cell_fall(CELL_TABLE) { - values("0.098, 0.098, 0.098",\ - "0.098, 0.098, 0.098",\ - "0.098, 0.098, 0.098"); + values("0.215, 0.215, 0.216",\ + "0.215, 0.215, 0.216",\ + "0.215, 0.215, 0.216"); } rise_transition(CELL_TABLE) { values("0.001, 0.001, 0.001",\ @@ -164,7 +176,7 @@ cell (sram_2_16_1_freepdk45){ bus(addr0){ bus_type : addr; direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; max_transition : 0.04; pin(addr0[3:0]){ timing(){ @@ -200,7 +212,7 @@ cell (sram_2_16_1_freepdk45){ pin(csb0){ direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; timing(){ timing_type : setup_rising; related_pin : "clk0"; @@ -233,7 +245,7 @@ cell (sram_2_16_1_freepdk45){ pin(web0){ direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; timing(){ timing_type : setup_rising; related_pin : "clk0"; @@ -267,52 +279,61 @@ cell (sram_2_16_1_freepdk45){ pin(clk0){ clock : true; direction : input; - capacitance : 0.2091; + capacitance : 0.00020910000000000001; internal_power(){ - when : "!csb0 & clk0 & !web0"; + when : "!csb0 & !web0"; rise_power(scalar){ - values("0.0747594982142222"); + values("8.316600e-02"); } fall_power(scalar){ - values("0.0747594982142222"); + values("8.316600e-02"); } } internal_power(){ - when : "!csb0 & !clk0 & web0"; + when : "csb0 & !web0"; rise_power(scalar){ - values("0.0747594982142222"); + values("8.316600e-02"); } fall_power(scalar){ - values("0.0747594982142222"); + values("8.316600e-02"); } } internal_power(){ - when : "csb0"; + when : "!csb0 & web0"; rise_power(scalar){ - values("0"); + values("8.316600e-02"); } fall_power(scalar){ - values("0"); + values("8.316600e-02"); + } + } + internal_power(){ + when : "csb0 & web0"; + rise_power(scalar){ + values("8.316600e-02"); + } + fall_power(scalar){ + values("8.316600e-02"); } } timing(){ timing_type :"min_pulse_width"; related_pin : clk0; rise_constraint(scalar) { - values("0.0"); + values("0.0215"); } fall_constraint(scalar) { - values("0.0"); + values("0.0215"); } } timing(){ timing_type :"minimum_period"; related_pin : clk0; rise_constraint(scalar) { - values("0"); + values("0.043"); } fall_constraint(scalar) { - values("0"); + values("0.043"); } } } diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_FF_5p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_FF_5p0V_25C_analytical.lib index c370f993..6912aec7 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_FF_5p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_FF_5p0V_25C_analytical.lib @@ -35,11 +35,14 @@ library (sram_2_16_1_scn4m_subm_FF_5p0V_25C_lib){ default_max_fanout : 4.0 ; default_connection_class : universal ; + voltage_map ( VDD, 5.0 ); + voltage_map ( GND, 0 ); + lu_table_template(CELL_TABLE){ variable_1 : input_net_transition; variable_2 : total_output_net_capacitance; index_1("0.0125, 0.05, 0.4"); - index_2("2.45605, 9.8242, 78.5936"); + index_2("0.00245605, 0.0098242, 0.0392968"); } lu_table_template(CONSTRAINT_TABLE){ @@ -78,17 +81,26 @@ cell (sram_2_16_1_scn4m_subm){ dont_use : true; map_only : true; dont_touch : true; - area : 73068.14000000001; + area : 0; + + pg_pin(vdd) { + voltage_name : VDD; + pg_type : primary_power; + } + + pg_pin(gnd) { + voltage_name : GND; + pg_type : primary_ground; + } leakage_power () { - when : "csb0"; - value : 0.000167; + value : 0.000198; } - cell_leakage_power : 0; + cell_leakage_power : 0.000198; bus(din0){ bus_type : data; direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; memory_write(){ address : addr0; clocked_on : clk0; @@ -127,8 +139,8 @@ cell (sram_2_16_1_scn4m_subm){ bus(dout0){ bus_type : data; direction : output; - max_capacitance : 78.5936; - min_capacitance : 2.45605; + max_capacitance : 0.0392968; + min_capacitance : 0.00245605; memory_read(){ address : addr0; } @@ -138,24 +150,24 @@ cell (sram_2_16_1_scn4m_subm){ related_pin : "clk0"; timing_type : falling_edge; cell_rise(CELL_TABLE) { - values("0.241, 0.241, 0.241",\ - "0.241, 0.241, 0.241",\ - "0.241, 0.241, 0.241"); + values("1.183, 1.199, 1.264",\ + "1.183, 1.199, 1.264",\ + "1.183, 1.199, 1.264"); } cell_fall(CELL_TABLE) { - values("0.241, 0.241, 0.241",\ - "0.241, 0.241, 0.241",\ - "0.241, 0.241, 0.241"); + values("1.183, 1.199, 1.264",\ + "1.183, 1.199, 1.264",\ + "1.183, 1.199, 1.264"); } rise_transition(CELL_TABLE) { - values("0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004"); + values("0.006, 0.007, 0.014",\ + "0.006, 0.007, 0.014",\ + "0.006, 0.007, 0.014"); } fall_transition(CELL_TABLE) { - values("0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004"); + values("0.006, 0.007, 0.014",\ + "0.006, 0.007, 0.014",\ + "0.006, 0.007, 0.014"); } } } @@ -164,7 +176,7 @@ cell (sram_2_16_1_scn4m_subm){ bus(addr0){ bus_type : addr; direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; max_transition : 0.4; pin(addr0[3:0]){ timing(){ @@ -200,7 +212,7 @@ cell (sram_2_16_1_scn4m_subm){ pin(csb0){ direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; timing(){ timing_type : setup_rising; related_pin : "clk0"; @@ -233,7 +245,7 @@ cell (sram_2_16_1_scn4m_subm){ pin(web0){ direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; timing(){ timing_type : setup_rising; related_pin : "clk0"; @@ -267,52 +279,61 @@ cell (sram_2_16_1_scn4m_subm){ pin(clk0){ clock : true; direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; internal_power(){ - when : "!csb0 & clk0 & !web0"; + when : "!csb0 & !web0"; rise_power(scalar){ - values("4.99880645"); + values("7.797263e+00"); } fall_power(scalar){ - values("4.99880645"); + values("7.797263e+00"); } } internal_power(){ - when : "!csb0 & !clk0 & web0"; + when : "csb0 & !web0"; rise_power(scalar){ - values("4.99880645"); + values("7.797263e+00"); } fall_power(scalar){ - values("4.99880645"); + values("7.797263e+00"); } } internal_power(){ - when : "csb0"; + when : "!csb0 & web0"; rise_power(scalar){ - values("0"); + values("7.797263e+00"); } fall_power(scalar){ - values("0"); + values("7.797263e+00"); + } + } + internal_power(){ + when : "csb0 & web0"; + rise_power(scalar){ + values("7.797263e+00"); + } + fall_power(scalar){ + values("7.797263e+00"); } } timing(){ timing_type :"min_pulse_width"; related_pin : clk0; rise_constraint(scalar) { - values("0.024"); + values("0.1265"); } fall_constraint(scalar) { - values("0.024"); + values("0.1265"); } } timing(){ timing_type :"minimum_period"; related_pin : clk0; rise_constraint(scalar) { - values("0.048"); + values("0.253"); } fall_constraint(scalar) { - values("0.048"); + values("0.253"); } } } diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_SS_5p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_SS_5p0V_25C_analytical.lib index f52de676..a7605cb3 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_SS_5p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_SS_5p0V_25C_analytical.lib @@ -35,11 +35,14 @@ library (sram_2_16_1_scn4m_subm_SS_5p0V_25C_lib){ default_max_fanout : 4.0 ; default_connection_class : universal ; + voltage_map ( VDD, 5.0 ); + voltage_map ( GND, 0 ); + lu_table_template(CELL_TABLE){ variable_1 : input_net_transition; variable_2 : total_output_net_capacitance; index_1("0.0125, 0.05, 0.4"); - index_2("2.45605, 9.8242, 78.5936"); + index_2("0.00245605, 0.0098242, 0.0392968"); } lu_table_template(CONSTRAINT_TABLE){ @@ -78,17 +81,26 @@ cell (sram_2_16_1_scn4m_subm){ dont_use : true; map_only : true; dont_touch : true; - area : 73068.14000000001; + area : 0; + + pg_pin(vdd) { + voltage_name : VDD; + pg_type : primary_power; + } + + pg_pin(gnd) { + voltage_name : GND; + pg_type : primary_ground; + } leakage_power () { - when : "csb0"; - value : 0.000167; + value : 0.000198; } - cell_leakage_power : 0; + cell_leakage_power : 0.000198; bus(din0){ bus_type : data; direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; memory_write(){ address : addr0; clocked_on : clk0; @@ -127,8 +139,8 @@ cell (sram_2_16_1_scn4m_subm){ bus(dout0){ bus_type : data; direction : output; - max_capacitance : 78.5936; - min_capacitance : 2.45605; + max_capacitance : 0.0392968; + min_capacitance : 0.00245605; memory_read(){ address : addr0; } @@ -138,24 +150,24 @@ cell (sram_2_16_1_scn4m_subm){ related_pin : "clk0"; timing_type : falling_edge; cell_rise(CELL_TABLE) { - values("0.294, 0.294, 0.294",\ - "0.294, 0.294, 0.294",\ - "0.294, 0.294, 0.294"); + values("1.446, 1.466, 1.545",\ + "1.446, 1.466, 1.545",\ + "1.446, 1.466, 1.545"); } cell_fall(CELL_TABLE) { - values("0.294, 0.294, 0.294",\ - "0.294, 0.294, 0.294",\ - "0.294, 0.294, 0.294"); + values("1.446, 1.466, 1.545",\ + "1.446, 1.466, 1.545",\ + "1.446, 1.466, 1.545"); } rise_transition(CELL_TABLE) { - values("0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004"); + values("0.007, 0.009, 0.017",\ + "0.007, 0.009, 0.017",\ + "0.007, 0.009, 0.017"); } fall_transition(CELL_TABLE) { - values("0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004"); + values("0.007, 0.009, 0.017",\ + "0.007, 0.009, 0.017",\ + "0.007, 0.009, 0.017"); } } } @@ -164,7 +176,7 @@ cell (sram_2_16_1_scn4m_subm){ bus(addr0){ bus_type : addr; direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; max_transition : 0.4; pin(addr0[3:0]){ timing(){ @@ -200,7 +212,7 @@ cell (sram_2_16_1_scn4m_subm){ pin(csb0){ direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; timing(){ timing_type : setup_rising; related_pin : "clk0"; @@ -233,7 +245,7 @@ cell (sram_2_16_1_scn4m_subm){ pin(web0){ direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; timing(){ timing_type : setup_rising; related_pin : "clk0"; @@ -267,52 +279,61 @@ cell (sram_2_16_1_scn4m_subm){ pin(clk0){ clock : true; direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; internal_power(){ - when : "!csb0 & clk0 & !web0"; + when : "!csb0 & !web0"; rise_power(scalar){ - values("4.99880645"); + values("6.379579e+00"); } fall_power(scalar){ - values("4.99880645"); + values("6.379579e+00"); } } internal_power(){ - when : "!csb0 & !clk0 & web0"; + when : "csb0 & !web0"; rise_power(scalar){ - values("4.99880645"); + values("6.379579e+00"); } fall_power(scalar){ - values("4.99880645"); + values("6.379579e+00"); } } internal_power(){ - when : "csb0"; + when : "!csb0 & web0"; rise_power(scalar){ - values("0"); + values("6.379579e+00"); } fall_power(scalar){ - values("0"); + values("6.379579e+00"); + } + } + internal_power(){ + when : "csb0 & web0"; + rise_power(scalar){ + values("6.379579e+00"); + } + fall_power(scalar){ + values("6.379579e+00"); } } timing(){ timing_type :"min_pulse_width"; related_pin : clk0; rise_constraint(scalar) { - values("0.0295"); + values("0.1545"); } fall_constraint(scalar) { - values("0.0295"); + values("0.1545"); } } timing(){ timing_type :"minimum_period"; related_pin : clk0; rise_constraint(scalar) { - values("0.059"); + values("0.309"); } fall_constraint(scalar) { - values("0.059"); + values("0.309"); } } } diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib index 7447a1e2..8bec74c3 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib @@ -35,11 +35,14 @@ library (sram_2_16_1_scn4m_subm_TT_5p0V_25C_lib){ default_max_fanout : 4.0 ; default_connection_class : universal ; + voltage_map ( VDD, 5.0 ); + voltage_map ( GND, 0 ); + lu_table_template(CELL_TABLE){ variable_1 : input_net_transition; variable_2 : total_output_net_capacitance; index_1("0.0125, 0.05, 0.4"); - index_2("2.45605, 9.8242, 78.5936"); + index_2("0.00245605, 0.0098242, 0.0392968"); } lu_table_template(CONSTRAINT_TABLE){ @@ -78,17 +81,26 @@ cell (sram_2_16_1_scn4m_subm){ dont_use : true; map_only : true; dont_touch : true; - area : 60774.3; + area : 0; + + pg_pin(vdd) { + voltage_name : VDD; + pg_type : primary_power; + } + + pg_pin(gnd) { + voltage_name : GND; + pg_type : primary_ground; + } leakage_power () { - when : "csb0"; - value : 0.0009813788999999999; + value : 0.000198; } - cell_leakage_power : 0; + cell_leakage_power : 0.000198; bus(din0){ bus_type : data; direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; memory_write(){ address : addr0; clocked_on : clk0; @@ -98,28 +110,28 @@ cell (sram_2_16_1_scn4m_subm){ timing_type : setup_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.167, 0.167, 0.228",\ - "0.167, 0.167, 0.228",\ - "0.167, 0.167, 0.228"); + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.131, 0.125, 0.137",\ - "0.131, 0.125, 0.137",\ - "0.131, 0.125, 0.137"); + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); } } timing(){ timing_type : hold_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114"); + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.089, -0.089, -0.089",\ - "-0.089, -0.089, -0.089",\ - "-0.089, -0.089, -0.089"); + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); } } } @@ -127,8 +139,8 @@ cell (sram_2_16_1_scn4m_subm){ bus(dout0){ bus_type : data; direction : output; - max_capacitance : 78.5936; - min_capacitance : 2.45605; + max_capacitance : 0.0392968; + min_capacitance : 0.00245605; memory_read(){ address : addr0; } @@ -138,24 +150,24 @@ cell (sram_2_16_1_scn4m_subm){ related_pin : "clk0"; timing_type : falling_edge; cell_rise(CELL_TABLE) { - values("1.556, 1.576, 1.751",\ - "1.559, 1.579, 1.754",\ - "1.624, 1.643, 1.819"); + values("1.314, 1.332, 1.404",\ + "1.314, 1.332, 1.404",\ + "1.314, 1.332, 1.404"); } cell_fall(CELL_TABLE) { - values("3.445, 3.504, 3.926",\ - "3.448, 3.507, 3.93",\ - "3.49, 3.549, 3.972"); + values("1.314, 1.332, 1.404",\ + "1.314, 1.332, 1.404",\ + "1.314, 1.332, 1.404"); } rise_transition(CELL_TABLE) { - values("0.13, 0.169, 0.574",\ - "0.13, 0.169, 0.574",\ - "0.13, 0.169, 0.574"); + values("0.006, 0.008, 0.015",\ + "0.006, 0.008, 0.015",\ + "0.006, 0.008, 0.015"); } fall_transition(CELL_TABLE) { - values("0.467, 0.49, 0.959",\ - "0.467, 0.49, 0.959",\ - "0.47, 0.493, 0.96"); + values("0.006, 0.008, 0.015",\ + "0.006, 0.008, 0.015",\ + "0.006, 0.008, 0.015"); } } } @@ -164,35 +176,35 @@ cell (sram_2_16_1_scn4m_subm){ bus(addr0){ bus_type : addr; direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; max_transition : 0.4; pin(addr0[3:0]){ timing(){ timing_type : setup_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.167, 0.167, 0.228",\ - "0.167, 0.167, 0.228",\ - "0.167, 0.167, 0.228"); + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.131, 0.125, 0.137",\ - "0.131, 0.125, 0.137",\ - "0.131, 0.125, 0.137"); + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); } } timing(){ timing_type : hold_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114"); + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.089, -0.089, -0.089",\ - "-0.089, -0.089, -0.089",\ - "-0.089, -0.089, -0.089"); + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); } } } @@ -200,66 +212,66 @@ cell (sram_2_16_1_scn4m_subm){ pin(csb0){ direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; timing(){ timing_type : setup_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.167, 0.167, 0.228",\ - "0.167, 0.167, 0.228",\ - "0.167, 0.167, 0.228"); + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.131, 0.125, 0.137",\ - "0.131, 0.125, 0.137",\ - "0.131, 0.125, 0.137"); + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); } } timing(){ timing_type : hold_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114"); + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.089, -0.089, -0.089",\ - "-0.089, -0.089, -0.089",\ - "-0.089, -0.089, -0.089"); + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); } } } pin(web0){ direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; timing(){ timing_type : setup_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.167, 0.167, 0.228",\ - "0.167, 0.167, 0.228",\ - "0.167, 0.167, 0.228"); + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.131, 0.125, 0.137",\ - "0.131, 0.125, 0.137",\ - "0.131, 0.125, 0.137"); + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); } } timing(){ timing_type : hold_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114"); + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.089, -0.089, -0.089",\ - "-0.089, -0.089, -0.089",\ - "-0.089, -0.089, -0.089"); + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); } } } @@ -267,52 +279,61 @@ cell (sram_2_16_1_scn4m_subm){ pin(clk0){ clock : true; direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; internal_power(){ - when : "!csb0 & clk0 & !web0"; + when : "!csb0 & !web0"; rise_power(scalar){ - values("9.972790277777777"); + values("7.017537e+00"); } fall_power(scalar){ - values("9.972790277777777"); + values("7.017537e+00"); } } internal_power(){ - when : "!csb0 & !clk0 & web0"; + when : "csb0 & !web0"; rise_power(scalar){ - values("8.899322499999998"); + values("7.017537e+00"); } fall_power(scalar){ - values("8.899322499999998"); + values("7.017537e+00"); } } internal_power(){ - when : "csb0"; + when : "!csb0 & web0"; rise_power(scalar){ - values("0"); + values("7.017537e+00"); } fall_power(scalar){ - values("0"); + values("7.017537e+00"); + } + } + internal_power(){ + when : "csb0 & web0"; + rise_power(scalar){ + values("7.017537e+00"); + } + fall_power(scalar){ + values("7.017537e+00"); } } timing(){ timing_type :"min_pulse_width"; related_pin : clk0; rise_constraint(scalar) { - values("2.344"); + values("0.1405"); } fall_constraint(scalar) { - values("2.344"); + values("0.1405"); } } timing(){ timing_type :"minimum_period"; related_pin : clk0; rise_constraint(scalar) { - values("4.688"); + values("0.281"); } fall_constraint(scalar) { - values("4.688"); + values("0.281"); } } } diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib index 4248b986..8bec74c3 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib @@ -35,11 +35,14 @@ library (sram_2_16_1_scn4m_subm_TT_5p0V_25C_lib){ default_max_fanout : 4.0 ; default_connection_class : universal ; + voltage_map ( VDD, 5.0 ); + voltage_map ( GND, 0 ); + lu_table_template(CELL_TABLE){ variable_1 : input_net_transition; variable_2 : total_output_net_capacitance; index_1("0.0125, 0.05, 0.4"); - index_2("2.45605, 9.8242, 78.5936"); + index_2("0.00245605, 0.0098242, 0.0392968"); } lu_table_template(CONSTRAINT_TABLE){ @@ -78,17 +81,26 @@ cell (sram_2_16_1_scn4m_subm){ dont_use : true; map_only : true; dont_touch : true; - area : 60774.3; + area : 0; + + pg_pin(vdd) { + voltage_name : VDD; + pg_type : primary_power; + } + + pg_pin(gnd) { + voltage_name : GND; + pg_type : primary_ground; + } leakage_power () { - when : "csb0"; - value : 0.000179; + value : 0.000198; } - cell_leakage_power : 0; + cell_leakage_power : 0.000198; bus(din0){ bus_type : data; direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; memory_write(){ address : addr0; clocked_on : clk0; @@ -127,8 +139,8 @@ cell (sram_2_16_1_scn4m_subm){ bus(dout0){ bus_type : data; direction : output; - max_capacitance : 78.5936; - min_capacitance : 2.45605; + max_capacitance : 0.0392968; + min_capacitance : 0.00245605; memory_read(){ address : addr0; } @@ -138,24 +150,24 @@ cell (sram_2_16_1_scn4m_subm){ related_pin : "clk0"; timing_type : falling_edge; cell_rise(CELL_TABLE) { - values("0.268, 0.268, 0.268",\ - "0.268, 0.268, 0.268",\ - "0.268, 0.268, 0.268"); + values("1.314, 1.332, 1.404",\ + "1.314, 1.332, 1.404",\ + "1.314, 1.332, 1.404"); } cell_fall(CELL_TABLE) { - values("0.268, 0.268, 0.268",\ - "0.268, 0.268, 0.268",\ - "0.268, 0.268, 0.268"); + values("1.314, 1.332, 1.404",\ + "1.314, 1.332, 1.404",\ + "1.314, 1.332, 1.404"); } rise_transition(CELL_TABLE) { - values("0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004"); + values("0.006, 0.008, 0.015",\ + "0.006, 0.008, 0.015",\ + "0.006, 0.008, 0.015"); } fall_transition(CELL_TABLE) { - values("0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004"); + values("0.006, 0.008, 0.015",\ + "0.006, 0.008, 0.015",\ + "0.006, 0.008, 0.015"); } } } @@ -164,7 +176,7 @@ cell (sram_2_16_1_scn4m_subm){ bus(addr0){ bus_type : addr; direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; max_transition : 0.4; pin(addr0[3:0]){ timing(){ @@ -200,7 +212,7 @@ cell (sram_2_16_1_scn4m_subm){ pin(csb0){ direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; timing(){ timing_type : setup_rising; related_pin : "clk0"; @@ -233,7 +245,7 @@ cell (sram_2_16_1_scn4m_subm){ pin(web0){ direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; timing(){ timing_type : setup_rising; related_pin : "clk0"; @@ -267,52 +279,61 @@ cell (sram_2_16_1_scn4m_subm){ pin(clk0){ clock : true; direction : input; - capacitance : 9.8242; + capacitance : 0.0098242; internal_power(){ - when : "!csb0 & clk0 & !web0"; + when : "!csb0 & !web0"; rise_power(scalar){ - values("11.3049604371"); + values("7.017537e+00"); } fall_power(scalar){ - values("11.3049604371"); + values("7.017537e+00"); } } internal_power(){ - when : "!csb0 & !clk0 & web0"; + when : "csb0 & !web0"; rise_power(scalar){ - values("11.3049604371"); + values("7.017537e+00"); } fall_power(scalar){ - values("11.3049604371"); + values("7.017537e+00"); } } internal_power(){ - when : "csb0"; + when : "!csb0 & web0"; rise_power(scalar){ - values("0"); + values("7.017537e+00"); } fall_power(scalar){ - values("0"); + values("7.017537e+00"); + } + } + internal_power(){ + when : "csb0 & web0"; + rise_power(scalar){ + values("7.017537e+00"); + } + fall_power(scalar){ + values("7.017537e+00"); } } timing(){ timing_type :"min_pulse_width"; related_pin : clk0; rise_constraint(scalar) { - values("0.0"); + values("0.1405"); } fall_constraint(scalar) { - values("0.0"); + values("0.1405"); } } timing(){ timing_type :"minimum_period"; related_pin : clk0; rise_constraint(scalar) { - values("0"); + values("0.281"); } fall_constraint(scalar) { - values("0"); + values("0.281"); } } } diff --git a/compiler/tests/regress.py b/compiler/tests/regress.py index e60b010d..3fd5d6eb 100755 --- a/compiler/tests/regress.py +++ b/compiler/tests/regress.py @@ -22,14 +22,26 @@ header(__file__, OPTS.tech_name) # get a list of all files in the tests directory files = os.listdir(sys.path[0]) +# load a file with all tests to skip in a given technology +# since tech_name is dynamically loaded, we can't use @skip directives +try: + skip_file_name = "{0}/tests/skip_tests_{1}.txt".format(os.getenv("OPENRAM_HOME"), OPTS.tech_name) + skip_file = open(skip_file_name, "r") + skip_tests = skip_file.read().splitlines() + for st in skip_tests: + debug.warning("Skipping: " + st) +except FileNotFoundError: + skip_tests = [] + # assume any file that ends in "test.py" in it is a regression test nametest = re.compile("test\.py$", re.IGNORECASE) -tests = list(filter(nametest.search, files)) -tests.sort() +all_tests = list(filter(nametest.search, files)) +filtered_tests = list(filter(lambda i: i not in skip_tests, all_tests)) +filtered_tests.sort() # import all of the modules filenameToModuleName = lambda f: os.path.splitext(f)[0] -moduleNames = map(filenameToModuleName, tests) +moduleNames = map(filenameToModuleName, filtered_tests) modules = map(__import__, moduleNames) suite = unittest.TestSuite() load = unittest.defaultTestLoader.loadTestsFromModule diff --git a/compiler/tests/skip_tests_sky130.txt b/compiler/tests/skip_tests_sky130.txt new file mode 100644 index 00000000..68a6549b --- /dev/null +++ b/compiler/tests/skip_tests_sky130.txt @@ -0,0 +1,87 @@ +04_dummy_pbitcell_test.py +04_pbitcell_test.py +04_precharge_pbitcell_test.py +04_replica_pbitcell_test.py +04_single_level_column_mux_pbitcell_test.py +05_bitcell_1rw_1r_array_test.py +05_bitcell_array_test.py +05_dummy_array_test.py +05_pbitcell_array_test.py +06_hierarchical_decoder_pbitcell_test.py +06_hierarchical_decoder_test.py +06_hierarchical_predecode2x4_pbitcell_test.py +06_hierarchical_predecode2x4_test.py +06_hierarchical_predecode3x8_pbitcell_test.py +06_hierarchical_predecode3x8_test.py +06_hierarchical_predecode4x16_test.py +07_single_level_column_mux_array_pbitcell_test.py +08_wordline_driver_array_pbitcell_test.py +08_wordline_driver_array_test.py +09_sense_amp_array_test_pbitcell.py +09_sense_amp_array_test.py +10_write_driver_array_pbitcell_test.py +10_write_driver_array_test.py +10_write_driver_array_wmask_pbitcell_test.py +10_write_driver_array_wmask_test.py +10_write_mask_and_array_pbitcell_test.py +10_write_mask_and_array_test.py +12_tri_gate_array_test.py +14_replica_pbitcell_array_test.py +14_replica_bitcell_array_test.py +14_replica_column_test.py +14_replica_column_1rw_1r_test.py +18_port_address_test.py +18_port_data_test.py +18_port_data_wmask_test.py +19_bank_select_pbitcell_test.py +19_bank_select_test.py +19_psingle_bank_test.py +19_bank_select_pbitcell_test.py +19_pmulti_bank_test.py +19_multi_bank_test.py +19_psingle_bank_test.py +19_single_bank_1w_1r_test.py +19_single_bank_wmask_1rw_1r_test.py +19_single_bank_1rw_1r_test.py +19_single_bank_test.py +19_single_bank_wmask_test.py +20_psram_1bank_2mux_1rw_1w_test.py +20_psram_1bank_2mux_1rw_1w_wmask_test.py +20_psram_1bank_2mux_1w_1r_test.py +20_psram_1bank_2mux_test.py +20_psram_1bank_4mux_1rw_1r_test.py +20_sram_1bank_2mux_1w_1r_test.py +20_sram_1bank_2mux_test.py +20_sram_1bank_2mux_wmask_test.py +20_sram_1bank_32b_1024_wmask_test.py +20_sram_1bank_4mux_test.py +20_sram_1bank_8mux_test.py +20_sram_1bank_nomux_test.py +20_sram_1bank_nomux_wmask_test.py +20_sram_2bank_test.py +21_hspice_delay_test.py +21_hspice_setuphold_test.py +21_model_delay_test.py +21_ngspice_delay_test.py +21_ngspice_setuphold_test.py +22_psram_1bank_2mux_func_test.py +22_psram_1bank_4mux_func_test.py +22_psram_1bank_8mux_func_test.py +22_psram_1bank_nomux_func_test.py +22_sram_1bank_2mux_func_test.py +22_sram_1bank_4mux_func_test.py +22_sram_1bank_8mux_func_test.py +22_sram_1bank_nomux_func_test.py +22_sram_1rw_1r_1bank_nomux_func_test.py +22_sram_wmask_func_test.py +23_lib_sram_model_corners_test.py +23_lib_sram_model_test.py +23_lib_sram_prune_test.py +23_lib_sram_test.py +24_lef_sram_test.py +25_verilog_sram_test.py +26_hspice_pex_pinv_test.py +26_ngspice_pex_pinv_test.py +26_pex_test.py +30_openram_back_end_test.py +30_openram_front_end_test.py diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index 95675c38..a8da9fb4 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -5,23 +5,21 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import unittest,warnings -import pdb,traceback -import sys,os,glob,copy -import shutil +import unittest +import sys, os, glob sys.path.append(os.getenv("OPENRAM_HOME")) from globals import OPTS import debug + class openram_test(unittest.TestCase): """ Base unit test that we have some shared classes in. """ - def local_drc_check(self, w): self.reset() - tempgds = "{0}{1}.gds".format(OPTS.openram_temp,w.name) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp, w.name) w.gds_write(tempgds) import verify @@ -36,40 +34,54 @@ class openram_test(unittest.TestCase): self.reset() - tempspice = "{0}{1}.sp".format(OPTS.openram_temp,a.name) - tempgds = "{0}{1}.gds".format(OPTS.openram_temp,a.name) + tempspice = "{0}{1}.sp".format(OPTS.openram_temp, a.name) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp, a.name) - a.sp_write(tempspice) + a.lvs_write(tempspice) # cannot write gds in netlist_only mode if not OPTS.netlist_only: a.gds_write(tempgds) import verify - result=verify.run_drc(a.name, tempgds, extract=True, final_verification=final_verification) - if result != 0: - #zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid()) - #debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) - #shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) + # Run both DRC and LVS even if DRC might fail + # Magic can still extract despite DRC failing, so it might be ok in some techs + # if we ignore things like minimum metal area of pins + drc_result=verify.run_drc(a.name, tempgds, extract=True, final_verification=final_verification) + + # We can still run LVS even if DRC fails in Magic OR Calibre + lvs_result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification) + + # Only allow DRC to fail and LVS to pass if we are using magic + if lvs_result == 0 and drc_result != 0: + # import shutil + # zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid()) + # debug.info(0, "Archiving failed files to {}.zip".format(zip_file)) + # shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) + self.fail("DRC failed but LVS passed: {}".format(a.name)) + elif drc_result != 0: + # import shutil + # zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid()) + # debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) + # shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) self.fail("DRC failed: {}".format(a.name)) - - - result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification) - if result != 0: - #zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid()) - #debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) - #shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) + + if lvs_result != 0: + # import shutil + # zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid()) + # debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) + # shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) self.fail("LVS mismatch: {}".format(a.name)) # For debug... - #import pdb; pdb.set_trace() + # import pdb; pdb.set_trace() if OPTS.purge_temp: self.cleanup() def run_pex(self, a, output=None): if output == None: output = OPTS.openram_temp + a.name + ".pex.netlist" - tempspice = "{0}{1}.sp".format(OPTS.openram_temp,a.name) - tempgds = "{0}{1}.gds".format(OPTS.openram_temp,a.name) + tempspice = "{0}{1}.sp".format(OPTS.openram_temp, a.name) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp, a.name) import verify result=verify.run_pex(a.name, tempgds, tempspice, output=output, final_verification=False) @@ -83,8 +95,8 @@ class openram_test(unittest.TestCase): """ debug.info(1, "Finding feasible period for current test.") delay_obj.set_load_slew(load, slew) - test_port = delay_obj.read_ports[0] #Only test one port, assumes other ports have similar period. - delay_obj.analysis_init(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1)) + test_port = delay_obj.read_ports[0] # Only test one port, assumes other ports have similar period. + delay_obj.analysis_init(probe_address="1" * sram.addr_size, probe_data=sram.word_size - 1) delay_obj.find_feasible_period_one_port(test_port) return delay_obj.period @@ -116,29 +128,27 @@ class openram_test(unittest.TestCase): for k in data.keys(): if type(data[k])==list: for i in range(len(data[k])): - if not self.isclose(k,data[k][i],golden_data[k][i],error_tolerance): + if not self.isclose(k, data[k][i], golden_data[k][i], error_tolerance): data_matches = False else: - if not self.isclose(k,data[k],golden_data[k],error_tolerance): + if not self.isclose(k, data[k], golden_data[k], error_tolerance): data_matches = False if not data_matches: import pprint data_string=pprint.pformat(data) - debug.error("Results exceeded {:.1f}% tolerance compared to golden results:\n".format(error_tolerance*100)+data_string) + debug.error("Results exceeded {:.1f}% tolerance compared to golden results:\n".format(error_tolerance * 100) + data_string) return data_matches - - - def isclose(self,key,value,actual_value,error_tolerance=1e-2): + def isclose(self, key, value, actual_value, error_tolerance=1e-2): """ This is used to compare relative values. """ import debug - relative_diff = self.relative_diff(value,actual_value) + relative_diff = self.relative_diff(value, actual_value) check = relative_diff <= error_tolerance if check: - debug.info(2,"CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key,value,actual_value,relative_diff*100)) + debug.info(2, "CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key, value, actual_value, relative_diff * 100)) return True else: - debug.error("NOT CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key,value,actual_value,relative_diff*100)) + debug.error("NOT CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key, value, actual_value, relative_diff * 100)) return False def relative_diff(self, value1, value2): @@ -155,18 +165,14 @@ class openram_test(unittest.TestCase): # Get normalization value norm_value = abs(max(value1, value2)) - # Edge case where greater is a zero - if norm_value == 0: - min_value = abs(min(value1, value2)) return abs(value1 - value2) / norm_value - - def relative_compare(self, value,actual_value,error_tolerance): + def relative_compare(self, value, actual_value, error_tolerance): """ This is used to compare relative values. """ if (value==actual_value): # if we don't need a relative comparison! return True - return (abs(value - actual_value) / max(value,actual_value) <= error_tolerance) + return (abs(value - actual_value) / max(value, actual_value) <= error_tolerance) def isapproxdiff(self, filename1, filename2, error_tolerance=0.001): """Compare two files. @@ -204,23 +210,22 @@ class openram_test(unittest.TestCase): line_num+=1 line1 = fp1.readline().decode('utf-8') line2 = fp2.readline().decode('utf-8') - #print("line1:",line1) - #print("line2:",line2) + # print("line1:", line1) + # print("line2:", line2) # 1. Find all of the floats using a regex line1_floats=rx.findall(line1) line2_floats=rx.findall(line2) - debug.info(3,"line1_floats: "+str(line1_floats)) - debug.info(3,"line2_floats: "+str(line2_floats)) - + debug.info(3, "line1_floats: " + str(line1_floats)) + debug.info(3, "line2_floats: " + str(line2_floats)) # 2. Remove the floats from the string for f in line1_floats: - line1=line1.replace(f,"",1) + line1=line1.replace(f, "", 1) for f in line2_floats: - line2=line2.replace(f,"",1) - #print("line1:",line1) - #print("line2:",line2) + line2=line2.replace(f, "", 1) + # print("line1:", line1) + # print("line2:", line2) # 3. Convert to floats rather than strings line1_floats = [float(x) for x in line1_floats] @@ -228,29 +233,29 @@ class openram_test(unittest.TestCase): # 4. Check if remaining string matches if line1 != line2: - #Uncomment if you want to see all the individual chars of the two lines - #print(str([i for i in line1])) - #print(str([i for i in line2])) + # Uncomment if you want to see all the individual chars of the two lines + # print(str([i for i in line1])) + # print(str([i for i in line2])) if mismatches==0: - debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2)) + debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1, filename2)) mismatches += 1 - debug.error("MISMATCH Line ({0}):\n{1}\n!=\n{2}".format(line_num,line1.rstrip('\n'),line2.rstrip('\n'))) + debug.error("MISMATCH Line ({0}):\n{1}\n!=\n{2}".format(line_num, line1.rstrip('\n'), line2.rstrip('\n'))) # 5. Now compare that the floats match elif len(line1_floats)!=len(line2_floats): if mismatches==0: - debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2)) + debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1, filename2)) mismatches += 1 - debug.error("MISMATCH Line ({0}) Length {1} != {2}".format(line_num,len(line1_floats),len(line2_floats))) + debug.error("MISMATCH Line ({0}) Length {1} != {2}".format(line_num, len(line1_floats), len(line2_floats))) else: - for (float1,float2) in zip(line1_floats,line2_floats): - relative_diff = self.relative_diff(float1,float2) + for (float1, float2) in zip(line1_floats, line2_floats): + relative_diff = self.relative_diff(float1, float2) check = relative_diff <= error_tolerance if not check: if mismatches==0: - debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2)) + debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1, filename2)) mismatches += 1 - debug.error("MISMATCH Line ({0}) Float {1} != {2} diff: {3:.1f}%".format(line_num,float1,float2,relative_diff*100)) + debug.error("MISMATCH Line ({0}) Float {1} != {2} diff: {3:.1f}%".format(line_num, float1, float2, relative_diff * 100)) # Only show the first 10 mismatch lines if not line1 and not line2 or mismatches>10: @@ -261,19 +266,18 @@ class openram_test(unittest.TestCase): # Never reached return False - - def isdiff(self,filename1,filename2): + def isdiff(self, filename1, filename2): """ This is used to compare two files and display the diff if they are different.. """ import debug import filecmp import difflib - check = filecmp.cmp(filename1,filename2) + check = filecmp.cmp(filename1, filename2) if not check: - debug.error("MISMATCH file1={0} file2={1}".format(filename1,filename2)) - f1 = open(filename1,mode="r",encoding='utf-8') + debug.error("MISMATCH file1={0} file2={1}".format(filename1, filename2)) + f1 = open(filename1, mode="r", encoding='utf-8') s1 = f1.readlines() f1.close() - f2 = open(filename2,mode="r",encoding='utf-8') + f2 = open(filename2, mode="r", encoding='utf-8') s2 = f2.readlines() f2.close() mismatches=0 @@ -288,10 +292,13 @@ class openram_test(unittest.TestCase): return False return False else: - debug.info(2,"MATCH {0} {1}".format(filename1,filename2)) + debug.info(2, "MATCH {0} {1}".format(filename1, filename2)) return True + def dbg(): + import pdb; pdb.set_trace() + def header(filename, technology): # Skip the header for gitlab regression import getpass @@ -305,14 +312,18 @@ def header(filename, technology): print("|=========" + tst.center(60) + "=========|") print("|=========" + technology.center(60) + "=========|") print("|=========" + filename.center(60) + "=========|") - from globals import OPTS + from globals import OPTS print("|=========" + OPTS.openram_temp.center(60) + "=========|") print("|==============================================================================|") + def debugTestRunner(post_mortem=None): """unittest runner doing post mortem debugging on failing tests""" + import pdb + import traceback if post_mortem is None and not OPTS.purge_temp: post_mortem = pdb.post_mortem + class DebugTestResult(unittest.TextTestResult): def addError(self, test, err): # called before tearDown() @@ -320,9 +331,10 @@ def debugTestRunner(post_mortem=None): if post_mortem: post_mortem(err[2]) super(DebugTestResult, self).addError(test, err) + def addFailure(self, test, err): traceback.print_exception(*err) - if post_mortem: + if post_mortem: post_mortem(err[2]) super(DebugTestResult, self).addFailure(test, err) return unittest.TextTestRunner(resultclass=DebugTestResult) diff --git a/compiler/verify/__init__.py b/compiler/verify/__init__.py index 042fecff..4d9eb151 100644 --- a/compiler/verify/__init__.py +++ b/compiler/verify/__init__.py @@ -17,46 +17,53 @@ If not, OpenRAM will continue as if nothing happened! import os import debug -from globals import OPTS,find_exe,get_tool -import sys +from globals import OPTS +from globals import get_tool +from tech import drc_name +from tech import lvs_name +from tech import pex_name -debug.info(1,"Initializing verify...") +debug.info(1, "Initializing verify...") if not OPTS.check_lvsdrc: - debug.info(1,"LVS/DRC/PEX disabled.") + debug.info(1, "LVS/DRC/PEX disabled.") OPTS.drc_exe = None OPTS.lvs_exe = None OPTS.pex_exe = None + if OPTS.tech_name == "sky130": + OPTS.magic_exe = None else: - debug.info(1, "Finding DRC/LVS/PEX tools.") - OPTS.drc_exe = get_tool("DRC", ["calibre","assura","magic"], OPTS.drc_name) - OPTS.lvs_exe = get_tool("LVS", ["calibre","assura","netgen"], OPTS.lvs_name) - OPTS.pex_exe = get_tool("PEX", ["calibre","magic"], OPTS.pex_name) + debug.info(1, "Finding DRC/LVS/PEX tools.") + OPTS.drc_exe = get_tool("DRC", ["calibre", "assura", "magic"], drc_name) + OPTS.lvs_exe = get_tool("LVS", ["calibre", "assura", "netgen"], lvs_name) + OPTS.pex_exe = get_tool("PEX", ["calibre", "magic"], pex_name) + if OPTS.tech_name == "sky130": + OPTS.magic_exe = get_tool("GDS", ["magic"]) -if OPTS.drc_exe == None: - from .none import run_drc,print_drc_stats +if not OPTS.drc_exe: + from .none import run_drc, print_drc_stats elif "calibre"==OPTS.drc_exe[0]: - from .calibre import run_drc,print_drc_stats + from .calibre import run_drc, print_drc_stats elif "assura"==OPTS.drc_exe[0]: - from .assura import run_drc,print_drc_stats + from .assura import run_drc, print_drc_stats elif "magic"==OPTS.drc_exe[0]: - from .magic import run_drc,print_drc_stats + from .magic import run_drc, print_drc_stats else: debug.warning("Did not find a supported DRC tool.") -if OPTS.lvs_exe == None: - from .none import run_lvs,print_lvs_stats +if not OPTS.lvs_exe: + from .none import run_lvs, print_lvs_stats elif "calibre"==OPTS.lvs_exe[0]: - from .calibre import run_lvs,print_lvs_stats + from .calibre import run_lvs, print_lvs_stats elif "assura"==OPTS.lvs_exe[0]: - from .assura import run_lvs,print_lvs_stats + from .assura import run_lvs, print_lvs_stats elif "netgen"==OPTS.lvs_exe[0]: - from .magic import run_lvs,print_lvs_stats + from .magic import run_lvs, print_lvs_stats else: debug.warning("Did not find a supported LVS tool.") -if OPTS.pex_exe == None: +if not OPTS.pex_exe: from .none import run_pex,print_pex_stats elif "calibre"==OPTS.pex_exe[0]: from .calibre import run_pex,print_pex_stats @@ -64,4 +71,10 @@ elif "magic"==OPTS.pex_exe[0]: from .magic import run_pex,print_pex_stats else: debug.warning("Did not find a supported PEX tool.") - + +if OPTS.tech_name == "sky130": + if OPTS.magic_exe and "magic"==OPTS.magic_exe[0]: + from .magic import filter_gds + else: + debug.warning("Did not find Magic.") + diff --git a/compiler/verify/calibre.py b/compiler/verify/calibre.py index 8abca448..443c91ca 100644 --- a/compiler/verify/calibre.py +++ b/compiler/verify/calibre.py @@ -20,16 +20,16 @@ Calibre means pointing the code to the proper DRC and LVS rule files. import os import shutil import re -import time import debug from globals import OPTS -from run_script import * +from run_script import run_script # Keep track of statistics num_drc_runs = 0 num_lvs_runs = 0 num_pex_runs = 0 + def write_calibre_drc_script(cell_name, extract, final_verification): """ Write a Calibre runset file and script to run DRC """ # the runset file contains all the options to run calibre @@ -67,6 +67,7 @@ def write_calibre_drc_script(cell_name, extract, final_verification): os.system("chmod u+x {}".format(run_file)) return drc_runset + def write_calibre_lvs_script(cell_name, final_verification): """ Write a Calibre runset file and script to run LVS """ @@ -80,7 +81,7 @@ def write_calibre_lvs_script(cell_name, final_verification): 'lvsSourcePath': cell_name + ".sp", 'lvsSourcePrimary': cell_name, 'lvsSourceSystem': 'SPICE', - 'lvsSpiceFile': "extracted.sp", + 'lvsSpiceFile': "{}.spice".format(cell_name), 'lvsPowerNames': 'vdd', 'lvsGroundNames': 'gnd', 'lvsIncludeSVRFCmds': 1, @@ -130,8 +131,9 @@ def write_calibre_lvs_script(cell_name, final_verification): return lvs_runset -def write_calibre_pex_script(cell_name, extract, output, final_verification): +def write_calibre_pex_script(cell_name, extract, output, final_verification): + """ Write a pex script that can either just extract the netlist or the netlist+parasitics """ if output == None: output = name + ".pex.netlist" @@ -150,10 +152,9 @@ def write_calibre_pex_script(cell_name, extract, output, final_verification): 'pexRunDir': OPTS.openram_temp, 'pexLayoutPaths': cell_name + ".gds", 'pexLayoutPrimary': cell_name, - #'pexSourcePath' : OPTS.openram_temp+"extracted.sp", 'pexSourcePath': cell_name + ".sp", 'pexSourcePrimary': cell_name, - 'pexReportFile': cell_name + ".lvs.report", + 'pexReportFile': cell_name + ".pex.report", 'pexPexNetlistFile': cell_name + ".pex.netlist", 'pexPexReportFile': cell_name + ".pex.report", 'pexMaskDBFile': cell_name + ".maskdb", @@ -179,6 +180,7 @@ def write_calibre_pex_script(cell_name, extract, output, final_verification): return pex_runset + def run_drc(cell_name, gds_name, extract=False, final_verification=False): """Run DRC check on a given top-level name which is implemented in gds_name.""" @@ -186,9 +188,15 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False): global num_drc_runs num_drc_runs += 1 - # Copy file to local dir if it isn't already - if os.path.dirname(gds_name)!=OPTS.openram_temp.rstrip('/'): - shutil.copy(gds_name, OPTS.openram_temp) + # Filter the layouts through magic as a GDS filter for nsdm/psdm/nwell merging + if OPTS.tech_name == "sky130" and False: + shutil.copy(gds_name, OPTS.openram_temp + "temp.gds") + from magic import filter_gds + filter_gds(cell_name, OPTS.openram_temp + "temp.gds", OPTS.openram_temp + cell_name + ".gds") + else: + # Copy file to local dir if it isn't already + if os.path.dirname(gds_name)!=OPTS.openram_temp.rstrip('/'): + shutil.copy(gds_name, OPTS.openram_temp) drc_runset = write_calibre_drc_script(cell_name, extract, final_verification) @@ -211,16 +219,14 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False): errors = int(re.split(r'\W+', results[2])[5]) # always display this summary - if errors > 0: - debug.error("{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, + result_str = "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, geometries, rulechecks, - errors)) + errors) + if errors > 0: + debug.warning(result_str) else: - debug.info(1, "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, - geometries, - rulechecks, - errors)) + debug.info(1, result_str) return errors @@ -299,16 +305,15 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): out_errors = len(stdouterrors) total_errors = summary_errors + out_errors + ext_errors - if total_errors > 0: - debug.error("{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, + # always display this summary + result_str = "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, summary_errors, out_errors, - ext_errors)) + ext_errors) + if total_errors > 0: + debug.warning(result_str) else: - debug.info(1, "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, - summary_errors, - out_errors, - ext_errors)) + debug.info(1, result_str) return total_errors diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py index c2960aa6..9695591d 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -22,7 +22,6 @@ and include its appropriate license. import os import re -import time import shutil import debug from globals import OPTS @@ -34,6 +33,39 @@ num_lvs_runs = 0 num_pex_runs = 0 +def filter_gds(cell_name, input_gds, output_gds): + """ Run the gds through magic for any layer processing """ + global OPTS + + # Copy .magicrc file into temp dir + magic_file = OPTS.openram_tech + "tech/.magicrc" + if os.path.exists(magic_file): + shutil.copy(magic_file, OPTS.openram_temp) + else: + debug.warning("Could not locate .magicrc file: {}".format(magic_file)) + + + run_file = OPTS.openram_temp + "run_filter.sh" + f = open(run_file, "w") + f.write("#!/bin/sh\n") + f.write("{} -dnull -noconsole << EOF\n".format(OPTS.magic_exe[1])) + f.write("gds polygon subcell true\n") + f.write("gds warning default\n") + f.write("gds read {}\n".format(input_gds)) + f.write("load {}\n".format(cell_name)) + f.write("cellname delete \\(UNNAMED\\)\n") + #f.write("writeall force\n") + f.write("select top cell\n") + f.write("gds write {}\n".format(output_gds)) + f.write("quit -noprompt\n") + f.write("EOF\n") + + f.close() + os.system("chmod u+x {}".format(run_file)) + + (outfile, errfile, resultsfile) = run_script(cell_name, "filter") + + def write_magic_script(cell_name, extract=False, final_verification=False): """ Write a magic script to perform DRC and optionally extraction. """ @@ -67,11 +99,15 @@ def write_magic_script(cell_name, extract=False, final_verification=False): else: pre = "" if final_verification: - f.write(pre+"extract unique all\n".format(cell_name)) - f.write(pre+"extract\n".format(cell_name)) - #f.write(pre+"ext2spice hierarchy on\n") - #f.write(pre+"ext2spice scale off\n") + f.write(pre + "extract unique all\n".format(cell_name)) + # Hack to work around unit scales in SkyWater + if OPTS.tech_name=="sky130": + f.write(pre + "extract style ngspice(si)\n") + f.write(pre + "extract\n".format(cell_name)) + # f.write(pre + "ext2spice hierarchy on\n") + # f.write(pre + "ext2spice scale off\n") # lvs exists in 8.2.79, but be backword compatible for now +<<<<<<< HEAD #f.write(pre+"ext2spice lvs\n") f.write(pre+"ext2spice hierarchy on\n") f.write(pre+"ext2spice format ngspice\n") @@ -87,6 +123,23 @@ def write_magic_script(cell_name, extract=False, final_verification=False): # but they all seem compatible enough. f.write(pre+"ext2spice format ngspice\n") f.write(pre+"ext2spice {}\n".format(cell_name)) +======= + # f.write(pre + "ext2spice lvs\n") + f.write(pre + "ext2spice hierarchy on\n") + f.write(pre + "ext2spice format ngspice\n") + f.write(pre + "ext2spice cthresh infinite\n") + f.write(pre + "ext2spice rthresh infinite\n") + f.write(pre + "ext2spice renumber off\n") + f.write(pre + "ext2spice scale off\n") + f.write(pre + "ext2spice blackbox on\n") + f.write(pre + "ext2spice subcircuit top auto\n") + f.write(pre + "ext2spice global off\n") + + # Can choose hspice, ngspice, or spice3, + # but they all seem compatible enough. + #f.write(pre + "ext2spice format ngspice\n") + f.write(pre + "ext2spice {}\n".format(cell_name)) +>>>>>>> dev f.write("quit -noprompt\n") f.write("EOF\n") @@ -100,7 +153,7 @@ def write_netgen_script(cell_name): global OPTS setup_file = "setup.tcl" - full_setup_file = OPTS.openram_tech + "mag_lib/" + setup_file + full_setup_file = OPTS.openram_tech + "tech/" + setup_file if os.path.exists(full_setup_file): # Copy setup.tcl file into temp dir shutil.copy(full_setup_file, OPTS.openram_temp) @@ -131,7 +184,7 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False): shutil.copy(gds_name, OPTS.openram_temp) # Copy .magicrc file into temp dir - magic_file = OPTS.openram_tech + "mag_lib/.magicrc" + magic_file = OPTS.openram_tech + "tech/.magicrc" if os.path.exists(magic_file): shutil.copy(magic_file, OPTS.openram_temp) else: @@ -165,13 +218,14 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False): # always display this summary + result_str = "DRC Errors {0}\t{1}".format(cell_name, errors) if errors > 0: for line in results: if "error tiles" in line: debug.info(1,line.rstrip("\n")) - debug.error("DRC Errors {0}\t{1}".format(cell_name, errors)) + debug.warning(result_str) else: - debug.info(1, "DRC Errors {0}\t{1}".format(cell_name, errors)) + debug.info(1, result_str) return errors diff --git a/compiler/verify/none.py b/compiler/verify/none.py index 9d7ac938..f82d59ae 100644 --- a/compiler/verify/none.py +++ b/compiler/verify/none.py @@ -16,33 +16,41 @@ drc_warned = False lvs_warned = False pex_warned = False + def run_drc(cell_name, gds_name, extract=False, final_verification=False): global drc_warned if not drc_warned: - debug.warning("DRC unable to run.") + debug.error("DRC unable to run.", -1) drc_warned=True - # Since we warned, return a failing test. + # Since we warned, return a failing test. return 1 - + + def run_lvs(cell_name, gds_name, sp_name, final_verification=False): global lvs_warned if not lvs_warned: - debug.warning("LVS unable to run.") + debug.error("LVS unable to run.", -1) lvs_warned=True # Since we warned, return a failing test. return 1 + def run_pex(name, gds_name, sp_name, output=None, final_verification=False): - global pex_warned + global pex_warned if not pex_warned: - debug.warning("PEX unable to run.") + debug.error("PEX unable to run.", -1) pex_warned=True - # Since we warned, return a failing test. + # Since we warned, return a failing test. return 1 + def print_drc_stats(): pass + + def print_lvs_stats(): pass + + def print_pex_stats(): pass diff --git a/technology/freepdk45/gds_lib/dff.gds b/technology/freepdk45/gds_lib/dff.gds index 526a1861..2a6004d1 100644 Binary files a/technology/freepdk45/gds_lib/dff.gds and b/technology/freepdk45/gds_lib/dff.gds differ diff --git a/technology/freepdk45/gds_lib/sense_amp.gds b/technology/freepdk45/gds_lib/sense_amp.gds index fecbfcb8..53c499f9 100644 Binary files a/technology/freepdk45/gds_lib/sense_amp.gds and b/technology/freepdk45/gds_lib/sense_amp.gds differ diff --git a/technology/freepdk45/gds_lib/write_driver.gds b/technology/freepdk45/gds_lib/write_driver.gds index 86015e7a..5e4fb15a 100644 Binary files a/technology/freepdk45/gds_lib/write_driver.gds and b/technology/freepdk45/gds_lib/write_driver.gds differ diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index caa3c748..6de01254 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -7,12 +7,37 @@ # import os from design_rules import * +from module_type import * +from custom_cell_properties import cell_properties """ File containing the process technology parameters for FreePDK 45nm. """ -#GDS file info +################################################### +# Custom modules +################################################### + +# This uses the default classes to instantiate module from +# '$OPENRAM_HOME/compiler/modules'. +# Using tech_modules['cellname'] you can override each class by providing a custom +# implementation in '$OPENRAM_TECHDIR/modules/' +# For example: tech_modules['contact'] = 'contact_freepdk45' +tech_modules = module_type() + + +################################################### +# Custom cell properties +################################################### +cell_properties = cell_properties() +cell_properties.bitcell.mirror.x = True +cell_properties.bitcell.mirror.y = False +cell_properties.bitcell_power_pin_directions = ("V", "V") + +################################################### +# GDS file info +################################################### + GDS = {} # gds units # From http://www.cnf.cornell.edu/cnf_spie9.html: "The first @@ -29,52 +54,86 @@ GDS["unit"] = (0.0005,1e-9) GDS["zoom"] = 0.05 ################################################### -##GDS Layer Map +# Interconnect stacks +################################################### + +poly_stack = ("poly", "contact", "m1") +active_stack = ("active", "contact", "m1") +m1_stack = ("m1", "via1", "m2") +m2_stack = ("m2", "via2", "m3") +m3_stack = ("m3", "via3", "m4") + +layer_indices = {"poly": 0, + "active": 0, + "m1": 1, + "m2": 2, + "m3": 3, + "m4": 4} + +# The FEOL stacks get us up to m1 +feol_stacks = [poly_stack, + active_stack] + +# The BEOL stacks are m1 and up +beol_stacks = [m1_stack, + m2_stack, + m3_stack] + +layer_stacks = feol_stacks + beol_stacks + +preferred_directions = {"poly": "V", + "active": "V", + "m1": "H", + "m2": "V", + "m3": "H", + "m4": "V"} +################################################### +# Power grid +################################################### +# Use M3/M4 +power_grid = m3_stack + +################################################### +# GDS Layer Map ################################################### # create the GDS layer map # FIXME: parse the gds layer map from the cadence map? layer = {} -layer["active"] = 1 -layer["pwell"] = 2 -layer["nwell"] = 3 -layer["nimplant"]= 4 -layer["pimplant"]= 5 -layer["vtg"] = 6 -layer["vth"] = 7 -layer["thkox"] = 8 -layer["poly"] = 9 -layer["contact"] = 10 -layer["active_contact"] = 10 -layer["metal1"] = 11 -layer["via1"] = 12 -layer["metal2"] = 13 -layer["via2"] = 14 -layer["metal3"] = 15 -layer["via3"] = 16 -layer["metal4"] = 17 -layer["via4"] = 18 -layer["metal5"] = 19 -layer["via5"] = 20 -layer["metal6"] = 21 -layer["via6"] = 22 -layer["metal7"] = 23 -layer["via7"] = 24 -layer["metal8"] = 25 -layer["via8"] = 26 -layer["metal9"] = 27 -layer["via9"] = 28 -layer["metal10"] = 29 -layer["text"] = 239 -layer["boundary"]= 239 -layer["blockage"]= 239 +layer["active"] = (1, 0) +layer["pwell"] = (2, 0) +layer["nwell"] = (3, 0) +layer["nimplant"]= (4, 0) +layer["pimplant"]= (5, 0) +layer["vtg"] = (6, 0) +layer["vth"] = (7, 0) +layer["thkox"] = (8, 0) +layer["poly"] = (9, 0) +layer["contact"] = (10, 0) +layer["m1"] = (11, 0) +layer["via1"] = (12, 0) +layer["m2"] = (13, 0) +layer["via2"] = (14, 0) +layer["m3"] = (15, 0) +layer["via3"] = (16, 0) +layer["m4"] = (17, 0) +layer["via4"] = (18, 0) +layer["m5"] = (19, 0) +layer["via5"] = (20, 0) +layer["m6"] = (21, 0) +layer["via6"] = (22, 0) +layer["m7"] = (23, 0) +layer["via7"] = (24, 0) +layer["m8"] = (25, 0) +layer["via8"] = (26, 0) +layer["m9"] = (27, 0) +layer["via9"] = (28, 0) +layer["m10"] = (29, 0) +layer["text"] = (239, 0) +layer["boundary"]= (239, 0) ################################################### -##END GDS Layer Map -################################################### - -################################################### -##DRC/LVS Rules Setup +# DRC/LVS Rules Setup ################################################### #technology parameter @@ -90,10 +149,6 @@ drclvs_home=os.environ.get("DRCLVS_HOME") drc = design_rules("freepdk45") -drc["body_tie_down"] = 0 -drc["has_pwell"] = True -drc["has_nwell"] = True - #grid size drc["grid"] = 0.0025 @@ -110,20 +165,26 @@ drc["minlength_channel"] = 0.05 # WELL.2 Minimum spacing of nwell/pwell at different potential drc["pwell_to_nwell"] = 0.225 # WELL.3 Minimum spacing of nwell/pwell at the same potential -drc["well_to_well"] = 0.135 # WELL.4 Minimum width of nwell/pwell -drc["minwidth_well"] = 0.2 +drc.add_layer("nwell", + width = 0.2, + spacing = 0.135) +drc.add_layer("pwell", + width = 0.2, + spacing = 0.135) # POLY.1 Minimum width of poly -drc["minwidth_poly"] = 0.05 # POLY.2 Minimum spacing of poly AND active -drc["poly_to_poly"] = 0.14 +drc.add_layer("poly", + width = 0.05, + spacing = 0.14) + # POLY.3 Minimum poly extension beyond active drc["poly_extend_active"] = 0.055 # Not a rule -drc["poly_to_polycontact"] = 0.075 +drc["poly_to_contact"] = 0.075 # POLY.4 Minimum enclosure of active around gate -drc["active_enclosure_gate"] = 0.07 +drc["active_enclose_gate"] = 0.07 # POLY.5 Minimum spacing of field poly to active drc["poly_to_active"] = 0.05 # POLY.6 Minimum Minimum spacing of field poly @@ -131,150 +192,169 @@ drc["poly_to_field_poly"] = 0.075 # Not a rule drc["minarea_poly"] = 0.0 -# ACTIVE.2 Minimum spacing of active -drc["active_to_body_active"] = 0.08 # ACTIVE.1 Minimum width of active -drc["minwidth_active"] = 0.09 -# Not a rule -drc["active_to_active"] = 0 +# ACTIVE.2 Minimum spacing of active +drc.add_layer("active", + width = 0.09, + spacing = 0.08) # ACTIVE.3 Minimum enclosure/spacing of nwell/pwell to active -drc["well_enclosure_active"] = 0.055 -# Reserved for asymmetric enclosures -drc["well_extend_active"] = 0.055 -# Not a rule -drc["minarea_active"] = 0 +drc.add_enclosure("nwell", + layer = "active", + enclosure = 0.055) +drc.add_enclosure("pwell", + layer = "active", + enclosure = 0.055) # IMPLANT.1 Minimum spacing of nimplant/ pimplant to channel drc["implant_to_channel"] = 0.07 # Not a rule -drc["implant_enclosure_active"] = 0 +drc.add_enclosure("implant", + layer = "active", + enclosure = 0) # Not a rule -drc["implant_enclosure_contact"] = 0 +drc.add_enclosure("implant", + layer = "contact", + enclosure = 0) # IMPLANT.2 Minimum spacing of nimplant/ pimplant to contact drc["implant_to_contact"] = 0.025 # IMPLANT.3 Minimum width/ spacing of nimplant/ pimplant -drc["implant_to_implant"] = 0.045 # IMPLANT.4 Minimum width/ spacing of nimplant/ pimplant -drc["minwidth_implant"] = 0.045 +drc.add_layer("implant", + width = 0.045, + spacing = 0.045) # CONTACT.1 Minimum width of contact -drc["minwidth_contact"] = 0.065 # CONTACT.2 Minimum spacing of contact -drc["contact_to_contact"] = 0.075 +drc.add_layer("contact", + width = 0.065, + spacing = 0.075) # CONTACT.4 Minimum enclosure of active around contact -drc["active_enclosure_contact"] = 0.005 -# Reserved for asymmetric enclosures -drc["active_extend_contact"] = 0.005 +drc.add_enclosure("active", + layer = "contact", + enclosure = 0.005) + +# CONTACT.6 Minimum spacing of contact and gate +drc["active_contact_to_gate"] = 0.0375 #changed from 0.035 +# CONTACT.7 Minimum spacing of contact and poly +drc["poly_contact_to_gate"] = 0.090 + +# CONTACT.1 Minimum width of contact +# CONTACT.2 Minimum spacing of contact +drc.add_layer("contact", + width = 0.065, + spacing = 0.075) # CONTACT.5 Minimum enclosure of poly around contact -drc["poly_enclosure_contact"] = 0.005 -# Reserved for asymmetric enclosures -drc["poly_extend_contact"] = 0.005 +drc.add_enclosure("poly", + layer = "contact", + enclosure = 0.005) # CONTACT.6 Minimum spacing of contact and gate drc["contact_to_gate"] = 0.0375 #changed from 0.035 # CONTACT.7 Minimum spacing of contact and poly drc["contact_to_poly"] = 0.090 # METAL1.1 Minimum width of metal1 -drc["minwidth_metal1"] = 0.065 # METAL1.2 Minimum spacing of metal1 -drc["metal1_to_metal1"] = 0.065 +drc.add_layer("m1", + width = 0.065, + spacing = 0.065) + # METAL1.3 Minimum enclosure around contact on two opposite sides -drc["metal1_enclosure_contact"] = 0 -# Reserved for asymmetric enclosures -drc["metal1_extend_contact"] = 0.035 +drc.add_enclosure("m1", + layer = "contact", + enclosure = 0, + extension = 0.035) # METAL1.4 inimum enclosure around via1 on two opposite sides -drc["metal1_extend_via1"] = 0.035 -# Reserved for asymmetric enclosures -drc["metal1_enclosure_via1"] = 0 -# Not a rule -drc["minarea_metal1"] = 0 +drc.add_enclosure("m1", + layer = "via1", + enclosure = 0, + extension = 0.035) # VIA1.1 Minimum width of via1 -drc["minwidth_via1"] = 0.065 # VIA1.2 Minimum spacing of via1 -drc["via1_to_via1"] = 0.075 +drc.add_layer("via1", + width = 0.065, + spacing = 0.075) + # METALINT.1 Minimum width of intermediate metal -drc["minwidth_metal2"] = 0.07 # METALINT.2 Minimum spacing of intermediate metal -drc["metal2_to_metal2"] = 0.07 +drc.add_layer("m2", + width = 0.07, + spacing = 0.07) + # METALINT.3 Minimum enclosure around via1 on two opposite sides -drc["metal2_extend_via1"] = 0.035 -# Reserved for asymmetric enclosures -drc["metal2_enclosure_via1"] = 0 +drc.add_enclosure("m2", + layer = "via1", + enclosure = 0, + extension = 0.035) + # METALINT.4 Minimum enclosure around via[2-3] on two opposite sides -drc["metal2_extend_via2"] = 0.035 -# Reserved for asymmetric enclosures -drc["metal2_enclosure_via2"] = 0 -# Not a rule -drc["minarea_metal2"] = 0 +drc.add_enclosure("m2", + layer = "via2", + enclosure = 0, + extension = 0.035) # VIA2-3.1 Minimum width of Via[2-3] -drc["minwidth_via2"] = 0.065 # VIA2-3.2 Minimum spacing of Via[2-3] -drc["via2_to_via2"] = 0.075 +drc.add_layer("via2", + width = 0.065, + spacing = 0.075) # METALINT.1 Minimum width of intermediate metal -drc["minwidth_metal3"] = 0.07 # METALINT.2 Minimum spacing of intermediate metal -#drc["metal3_to_metal3"] = 0.07 -# Minimum spacing of metal3 wider than 0.09 & longer than 0.3 = 0.09 -# Minimum spacing of metal3 wider than 0.27 & longer than 0.9 = 0.27 -# Minimum spacing of metal3 wider than 0.5 & longer than 1.8 = 0.5 -# Minimum spacing of metal3 wider than 0.9 & longer than 2.7 = 0.9 -# Minimum spacing of metal3 wider than 1.5 & longer than 4.0 = 1.5 -drc["metal3_to_metal3"] = drc_lut({(0.00, 0.0) : 0.07, - (0.09, 0.3) : 0.09, - (0.27, 0.9) : 0.27, - (0.50, 1.8) : 0.5, - (0.90, 2.7) : 0.9, - (1.50, 4.0) : 1.5}) +# Minimum spacing of m3 wider than 0.09 & longer than 0.3 = 0.09 +# Minimum spacing of m3 wider than 0.27 & longer than 0.9 = 0.27 +# Minimum spacing of m3 wider than 0.5 & longer than 1.8 = 0.5 +# Minimum spacing of m3 wider than 0.9 & longer than 2.7 = 0.9 +# Minimum spacing of m3 wider than 1.5 & longer than 4.0 = 1.5 +drc.add_layer("m3", + width = 0.07, + spacing = drc_lut({(0.00, 0.0) : 0.07, + (0.09, 0.3) : 0.09, + (0.27, 0.9) : 0.27, + (0.50, 1.8) : 0.5, + (0.90, 2.7) : 0.9, + (1.50, 4.0) : 1.5})) # METALINT.3 Minimum enclosure around via1 on two opposite sides -drc["metal3_extend_via2"] = 0.035 -# Reserved for asymmetric enclosures -drc["metal3_enclosure_via2"] = 0 +drc.add_enclosure("m3", + layer = "via2", + enclosure = 0, + extension = 0.035) + # METALINT.4 Minimum enclosure around via[2-3] on two opposite sides -drc["metal3_extend_via3"]=0.035 -# Reserved for asymmetric enclosures -drc["metal3_enclosure_via3"] = 0 -# Not a rule -drc["minarea_metal3"] = 0 +drc.add_enclosure("m3", + layer = "via3", + enclosure = 0, + extension = 0.035) # VIA2-3.1 Minimum width of Via[2-3] -drc["minwidth_via3"] = 0.07 # VIA2-3.2 Minimum spacing of Via[2-3] -drc["via3_to_via3"] = 0.085 +drc.add_layer("via3", + width = 0.07, + spacing = 0.085) # METALSMG.1 Minimum width of semi-global metal -drc["minwidth_metal4"] = 0.14 # METALSMG.2 Minimum spacing of semi-global metal -#drc["metal4_to_metal4"] = 0.14 -# Minimum spacing of metal4 wider than 0.27 & longer than 0.9 = 0.27 -# Minimum spacing of metal4 wider than 0.5 & longer than 1.8 = 0.5 -# Minimum spacing of metal4 wider than 0.9 & longer than 2.7 = 0.9 -# Minimum spacing of metal4 wider than 1.5 & longer than 4.0 = 1.5 -drc["metal4_to_metal4"] = drc_lut({(0.00, 0.0) : 0.14, - (0.27, 0.9) : 0.27, - (0.50, 1.8) : 0.5, - (0.90, 2.7) : 0.9, - (1.50, 4.0) : 1.5}) +# Minimum spacing of m4 wider than 0.27 & longer than 0.9 = 0.27 +# Minimum spacing of m4 wider than 0.5 & longer than 1.8 = 0.5 +# Minimum spacing of m4 wider than 0.9 & longer than 2.7 = 0.9 +# Minimum spacing of m4 wider than 1.5 & longer than 4.0 = 1.5 +drc.add_layer("m4", + width = 0.14, + spacing = drc_lut({(0.00, 0.0) : 0.14, + (0.27, 0.9) : 0.27, + (0.50, 1.8) : 0.5, + (0.90, 2.7) : 0.9, + (1.50, 4.0) : 1.5})) # METALSMG.3 Minimum enclosure around via[3-6] on two opposite sides -drc["metal4_extend_via3"] = 0.0025 -# Reserved for asymmetric enclosure -drc["metal4_enclosure_via3"] = 0.0025 -# Not a rule -drc["minarea_metal4"] = 0 +drc.add_enclosure("m4", + layer = "via3", + enclosure = 0.0025) # Metal 5-10 are ommitted - - ################################################### -##END DRC/LVS Rules -################################################### - -################################################### -##Spice Simulation Parameters +# Spice Simulation Parameters ################################################### #spice info @@ -337,6 +417,11 @@ parameter["sa_inv_nmos_size"] = 0.27 #micro-meters parameter["bitcell_drain_cap"] = 0.1 #In Femto-Farad, approximation of drain capacitance ################################################### -##END Spice Simulation Parameters +# Technology Tool Preferences ################################################### +drc_name = "calibre" +lvs_name = "calibre" +pex_name = "calibre" + +blackbox_bitcell = False diff --git a/technology/scn3me_subm/__init__.py b/technology/scn3me_subm/__init__.py index 87b26056..6b08d0b9 100644 --- a/technology/scn3me_subm/__init__.py +++ b/technology/scn3me_subm/__init__.py @@ -35,9 +35,5 @@ except: DRCLVS_HOME=OPENRAM_TECH+"/scn3me_subm/tech" os.environ["DRCLVS_HOME"] = DRCLVS_HOME -# try: -# SPICE_MODEL_DIR = os.path.abspath(os.environ.get("SPICE_MODEL_DIR")) -# except: -OPENRAM_TECH=os.path.abspath(os.environ.get("OPENRAM_TECH")) -os.environ["SPICE_MODEL_DIR"] = "{0}/{1}/models".format(OPENRAM_TECH, TECHNOLOGY) +os.environ["SPICE_MODEL_DIR"] = "{0}/models".format(os.path.dirname(__file__)) diff --git a/technology/scn3me_subm/mag_lib/.magicrc b/technology/scn3me_subm/tech/.magicrc similarity index 100% rename from technology/scn3me_subm/mag_lib/.magicrc rename to technology/scn3me_subm/tech/.magicrc diff --git a/technology/scn3me_subm/mag_lib/setup.tcl b/technology/scn3me_subm/tech/setup.tcl similarity index 100% rename from technology/scn3me_subm/mag_lib/setup.tcl rename to technology/scn3me_subm/tech/setup.tcl diff --git a/technology/scn3me_subm/tech/tech.py b/technology/scn3me_subm/tech/tech.py index 074009f2..9e6f370b 100755 --- a/technology/scn3me_subm/tech/tech.py +++ b/technology/scn3me_subm/tech/tech.py @@ -1,9 +1,24 @@ import os from design_rules import * +from module_type import * +from custom_cell_properties import CellProperties """ File containing the process technology parameters for SCMOS 3me, subm, 180nm. """ +# This uses the default classes to instantiate module from +# '$OPENRAM_HOME/compiler/modules'. +# Using tech_modules['cellname'] you can override each class by providing a custom +# implementation in '$OPENRAM_TECHDIR/modules/' +# For example: tech_modules['contact'] = 'contact_scn3me' +tech_modules = ModuleType() + +################################################### +# Custom cell properties +################################################### +cell_properties = CellProperties() +cell_properties.bitcell.mirror.x = True +cell_properties.bitcell.mirror.y = False #GDS file info GDS={} @@ -29,24 +44,24 @@ GDS["zoom"] = 0.5 # create the GDS layer map layer={} -layer["vtg"] = -1 -layer["vth"] = -1 -layer["contact"] = 47 -layer["pwell"] = 41 -layer["nwell"] = 42 -layer["active"] = 43 -layer["pimplant"] = 44 -layer["nimplant"] = 45 -layer["poly"] = 46 -layer["active_contact"] = 48 -layer["metal1"] = 49 -layer["via1"] = 50 -layer["metal2"] = 51 -layer["via2"] = 61 -layer["metal3"] = 62 -layer["text"] = 63 -layer["boundary"] = 63 -layer["blockage"] = 83 +layer["vtg"] = (-1, 0) +layer["vth"] = (-1, 0) +layer["contact"] = (47, 0) +layer["pwell"] = (41, 0) +layer["nwell"] = (42, 0) +layer["active"] = (43, 0) +layer["pimplant"] = (44, 0) +layer["nimplant"] = (45, 0) +layer["poly"] = (46, 0) +layer["active_contact"] = (48, 0) +layer["metal1"] = (49, 0) +layer["via1"] = (50, 0) +layer["metal2"] = (51, 0) +layer["via2"] = (61, 0) +layer["metal3"] = (62, 0) +layer["text"] = (63, 0) +layer["boundary"] = (63, 0) +layer["blockage"] = (83, 0) ################################################### ##END GDS Layer Map @@ -277,3 +292,15 @@ parameter["bitcell_drain_cap"] = 0.2 #In Femto-Farad, approximation of dr ################################################### ##END Spice Simulation Parameters ################################################### + +################################################### +##BEGIN Technology Tool Preferences +################################################### + +drc_name = "magic" +lvs_name = "netgen" +pex_name = "magic" + +################################################### +##END Technology Tool Preferences +################################################### diff --git a/technology/scn4m_subm/__init__.py b/technology/scn4m_subm/__init__.py index c7a863f0..5fbd8c08 100644 --- a/technology/scn4m_subm/__init__.py +++ b/technology/scn4m_subm/__init__.py @@ -35,9 +35,5 @@ except: DRCLVS_HOME=OPENRAM_TECH+"/scn4m_subm/tech" os.environ["DRCLVS_HOME"] = DRCLVS_HOME -# try: -# SPICE_MODEL_DIR = os.path.abspath(os.environ.get("SPICE_MODEL_DIR")) -# except: -OPENRAM_TECH=os.path.abspath(os.environ.get("OPENRAM_TECH")) -os.environ["SPICE_MODEL_DIR"] = "{0}/{1}/models".format(OPENRAM_TECH, TECHNOLOGY) +os.environ["SPICE_MODEL_DIR"] = "{0}/models".format(os.path.dirname(__file__)) diff --git a/technology/scn4m_subm/mag_lib/.magicrc b/technology/scn4m_subm/mag_lib/.magicrc deleted file mode 100644 index 0dfe42ef..00000000 --- a/technology/scn4m_subm/mag_lib/.magicrc +++ /dev/null @@ -1,5 +0,0 @@ -path sys +$::env(OPENRAM_TECH)/scn4m_subm/tech -tech load SCN4M_SUBM.20 -noprompt -scalegrid 1 4 -set GND gnd -set VDD vdd diff --git a/technology/scn4m_subm/tech/.magicrc b/technology/scn4m_subm/tech/.magicrc new file mode 100644 index 00000000..c85bb879 --- /dev/null +++ b/technology/scn4m_subm/tech/.magicrc @@ -0,0 +1,8 @@ +set openram_paths [split $::env(OPENRAM_TECH) ":"] +foreach p $openram_paths { + path sys +$p/scn4m_subm/tech +} +tech load SCN4M_SUBM.20 -noprompt +scalegrid 1 4 +set GND gnd +set VDD vdd diff --git a/technology/scn4m_subm/mag_lib/setup.tcl b/technology/scn4m_subm/tech/setup.tcl similarity index 93% rename from technology/scn4m_subm/mag_lib/setup.tcl rename to technology/scn4m_subm/tech/setup.tcl index 95e7dbea..09bbea27 100644 --- a/technology/scn4m_subm/mag_lib/setup.tcl +++ b/technology/scn4m_subm/tech/setup.tcl @@ -6,7 +6,7 @@ equate class {-circuit1 pfet} {-circuit2 p} flatten class {-circuit1 dummy_cell_6t} flatten class {-circuit1 dummy_cell_1rw_1r} flatten class {-circuit1 dummy_cell_1w_1r} -flatten class {-circuit1 bitcell_array_0} +flatten class {-circuit1 pbitcell} flatten class {-circuit1 pbitcell_0} flatten class {-circuit1 pbitcell_1} property {-circuit1 nfet} remove as ad ps pd diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py index 37e55e21..55826ec5 100644 --- a/technology/scn4m_subm/tech/tech.py +++ b/technology/scn4m_subm/tech/tech.py @@ -7,12 +7,34 @@ # import os from design_rules import * +from module_type import * +from custom_cell_properties import cell_properties """ File containing the process technology parameters for SCMOS 4m, 0.35um """ -#GDS file info +################################################### +# Custom modules +################################################### + +# This uses the default classes to instantiate module from +# '$OPENRAM_HOME/compiler/modules'. +# Using tech_modules['cellname'] you can override each class by providing a custom +# implementation in '$OPENRAM_TECHDIR/modules/' +# For example: tech_modules['contact'] = 'contact_scn4m' +tech_modules = module_type() + +################################################### +# Custom cell properties +################################################### +cell_properties = cell_properties() +cell_properties.bitcell.mirror.x = True +cell_properties.bitcell.mirror.y = False + +################################################### +# GDS file info +################################################### GDS={} # gds units # From http://www.cnf.cornell.edu/cnf_spie9.html: "The first @@ -28,40 +50,73 @@ GDS["unit"]=(0.001,1e-6) # default label zoom GDS["zoom"] = 0.5 +################################################### +# Interconnect stacks +################################################### + +poly_stack = ("poly", "poly_contact", "m1") +active_stack = ("active", "active_contact", "m1") +m1_stack = ("m1", "via1", "m2") +m2_stack = ("m2", "via2", "m3") +m3_stack = ("m3", "via3", "m4") + +layer_indices = {"poly": 0, + "active": 0, + "m1": 1, + "m2": 2, + "m3": 3, + "m4": 4} + +# The FEOL stacks get us up to m1 +feol_stacks = [poly_stack, + active_stack] + +# The BEOL stacks are m1 and up +beol_stacks = [m1_stack, + m2_stack, + m3_stack] + +layer_stacks = feol_stacks + beol_stacks + +preferred_directions = {"poly": "V", + "active": "V", + "m1": "H", + "m2": "V", + "m3": "H", + "m4": "V"} + +################################################### +# Power grid +################################################### +# Use M3/M4 +power_grid = m3_stack ################################################### ##GDS Layer Map ################################################### # create the GDS layer map -layer={} -layer["vtg"] = -1 -layer["vth"] = -1 -layer["contact"] = 47 -layer["pwell"] = 41 -layer["nwell"] = 42 -layer["active"] = 43 -layer["pimplant"] = 44 -layer["nimplant"] = 45 -layer["poly"] = 46 -layer["active_contact"] = 48 -layer["metal1"] = 49 -layer["via1"] = 50 -layer["metal2"] = 51 -layer["via2"] = 61 -layer["metal3"] = 62 -layer["via3"] = 30 -layer["metal4"] = 31 -layer["text"] = 63 -layer["boundary"] = 63 -layer["blockage"] = 83 +layer={} +layer["pwell"] = (41, 0) +layer["nwell"] = (42, 0) +layer["active"] = (43, 0) +layer["pimplant"] = (44, 0) +layer["nimplant"] = (45, 0) +layer["poly"] = (46, 0) +layer["poly_contact"] = (47, 0) +layer["active_contact"] = (48, 0) +layer["m1"] = (49, 0) +layer["via1"] = (50, 0) +layer["m2"] = (51, 0) +layer["via2"] = (61, 0) +layer["m3"] = (62, 0) +layer["via3"] = (30, 0) +layer["m4"] = (31, 0) +layer["text"] = (63, 0) +layer["boundary"] = (63, 0) ################################################### -##END GDS Layer Map -################################################### - -################################################### -##DRC/LVS Rules Setup +# DRC/LVS Rules Setup ################################################### _lambda_ = 0.2 @@ -79,10 +134,6 @@ drclvs_home=os.environ.get("DRCLVS_HOME") drc = design_rules("scn4me_sub") -drc["body_tie_down"] = 0 -drc["has_pwell"] = True -drc["has_nwell"] = True - #grid size is 1/2 a lambda drc["grid"]=0.5*_lambda_ @@ -95,150 +146,173 @@ drc["layer_map"]=os.environ.get("OPENRAM_TECH")+"/scn3me_subm/layers.map" drc["minwidth_tx"] = 4*_lambda_ drc["minlength_channel"] = 2*_lambda_ -# 1.3 Minimum spacing between wells of same type (if both are drawn) -drc["well_to_well"] = 6*_lambda_ -# 1.4 Minimum spacing between wells of different type (if both are drawn) +# 1.4 Minimum spacing between wells of different type (if both are drawn) drc["pwell_to_nwell"] = 0 -# 1.1 Minimum width -drc["minwidth_well"] = 12*_lambda_ +# 1.3 Minimum spacing between wells of same type (if both are drawn) +# 1.1 Minimum width +drc.add_layer("nwell", + width = 12*_lambda_, + spacing = 6*_lambda_) +drc.add_layer("pwell", + width = 12*_lambda_, + spacing = 6*_lambda_) # 3.1 Minimum width -drc["minwidth_poly"] = 2*_lambda_ # 3.2 Minimum spacing over active -drc["poly_to_poly"] = 3*_lambda_ +drc.add_layer("poly", + width = 2*_lambda_, + spacing = 3*_lambda_) # 3.3 Minimum gate extension of active drc["poly_extend_active"] = 2*_lambda_ # 5.5.b Minimum spacing between poly contact and other poly (alternative rules) -drc["poly_to_polycontact"] = 4*_lambda_ +drc["poly_to_contact"] = 4*_lambda_ # ?? -drc["active_enclosure_gate"] = 0.0 +drc["active_enclose_gate"] = 0.0 # 3.5 Minimum field poly to active drc["poly_to_active"] = _lambda_ # 3.2.a Minimum spacing over field poly drc["poly_to_field_poly"] = 3*_lambda_ -# Not a rule -drc["minarea_poly"] = 0.0 -# ?? -drc["active_to_body_active"] = 4*_lambda_ # Fix me # 2.1 Minimum width -drc["minwidth_active"] = 3*_lambda_ # 2.2 Minimum spacing -drc["active_to_active"] = 3*_lambda_ +drc.add_layer("active", + width = 3*_lambda_, + spacing = 4*_lambda_) + # 2.3 Source/drain active to well edge -drc["well_enclosure_active"] = 6*_lambda_ -# Reserved for asymmetric enclosures -drc["well_extend_active"] = 6*_lambda_ -# Not a rule -drc["minarea_active"] = 0.0 +drc.add_enclosure("nwell", + layer = "active", + enclosure = 6*_lambda_) +drc.add_enclosure("pwell", + layer = "active", + enclosure = 6*_lambda_) # 4.1 Minimum select spacing to channel of transistor to ensure adequate source/drain width drc["implant_to_channel"] = 3*_lambda_ # 4.2 Minimum select overlap of active -drc["implant_enclosure_active"] = 2*_lambda_ +drc.add_enclosure("implant", + layer = "active", + enclosure = 2*_lambda_) # 4.3 Minimum select overlap of contact -drc["implant_enclosure_contact"] = _lambda_ +drc.add_enclosure("implant", + layer = "contact", + enclosure = _lambda_) # Not a rule drc["implant_to_contact"] = 0 # Not a rule -drc["implant_to_implant"] = 0 -# Not a rule -drc["minwidth_implant"] = 0 +drc.add_layer("implant", + width = 0, + spacing = 0) # 6.1 Exact contact size -drc["minwidth_contact"] = 2*_lambda_ # 5.3 Minimum contact spacing -drc["contact_to_contact"] = 3*_lambda_ -# 6.2.b Minimum active overlap -drc["active_enclosure_contact"] = _lambda_ -# Reserved for asymmetric enclosure -drc["active_extend_contact"] = _lambda_ -# 5.2.b Minimum poly overlap -drc["poly_enclosure_contact"] = _lambda_ -# Reserved for asymmetric enclosures -drc["poly_extend_contact"] = _lambda_ +drc.add_layer("active_contact", + width = 2*_lambda_, + spacing = 3*_lambda_) +# 6.2.b Minimum active overlap +drc.add_enclosure("active", + layer = "active_contact", + enclosure = _lambda_) +drc.add_enclosure("active", + layer = "contact", + enclosure = _lambda_) # Reserved for other technologies -drc["contact_to_gate"] = 2*_lambda_ +drc["active_contact_to_gate"] = 2*_lambda_ # 5.4 Minimum spacing to gate of transistor -drc["contact_to_poly"] = 2*_lambda_ - +drc["poly_contact_to_gate"] = 2*_lambda_ + +# 6.1 Exact contact size +# 5.3 Minimum contact spacing +drc.add_layer("poly_contact", + width = 2*_lambda_, + spacing = 3*_lambda_) +# 5.2.b Minimum poly overlap +drc.add_enclosure("poly", + layer = "poly_contact", + enclosure = _lambda_) +# Reserved for other technologies +drc["poly_contact_to_gate"] = 2*_lambda_ +# 5.4 Minimum spacing to gate of transistor +drc["poly_contact_to_poly"] = 2*_lambda_ + # 7.1 Minimum width -drc["minwidth_metal1"] = 3*_lambda_ # 7.2 Minimum spacing -drc["metal1_to_metal1"] = 3*_lambda_ +drc.add_layer("m1", + width = 3*_lambda_, + spacing = 3*_lambda_) # 7.3 Minimum overlap of any contact -drc["metal1_enclosure_contact"] = _lambda_ -# Reserved for asymmetric enclosure -drc["metal1_extend_contact"] = _lambda_ -# 8.3 Minimum overlap by metal1 -drc["metal1_enclosure_via1"] = _lambda_ -# Reserve for asymmetric enclosures -drc["metal1_extend_via1"] = _lambda_ -# Not a rule -drc["minarea_metal1"] = 0 +drc.add_enclosure("m1", + layer = "poly_contact", + enclosure = _lambda_) +drc.add_enclosure("m1", + layer = "active_contact", + enclosure = _lambda_) +# 8.3 Minimum overlap by m1 +drc.add_enclosure("m1", + layer = "via1", + enclosure = _lambda_) # 8.1 Exact size -drc["minwidth_via1"] = 2*_lambda_ # 8.2 Minimum via1 spacing -drc["via1_to_via1"] = 3*_lambda_ +drc.add_layer("via1", + width = 2*_lambda_, + spacing = 3*_lambda_) # 9.1 Minimum width -drc["minwidth_metal2"] = 3*_lambda_ # 9.2 Minimum spacing -drc["metal2_to_metal2"] = 3*_lambda_ +drc.add_layer("m2", + width = 3*_lambda_, + spacing = 3*_lambda_) # 9.3 Minimum overlap of via1 -drc["metal2_extend_via1"] = _lambda_ -# Reserved for asymmetric enclosures -drc["metal2_enclosure_via1"] = _lambda_ -# 14.3 Minimum overlap by metal2 -drc["metal2_extend_via2"] = _lambda_ -# Reserved for asymmetric enclosures -drc["metal2_enclosure_via2"] = _lambda_ -# Not a rule -drc["minarea_metal2"] = 0 +drc.add_enclosure("m2", + layer = "via1", + enclosure = _lambda_) +# 14.3 Minimum overlap by m2 +drc.add_enclosure("m2", + layer = "via2", + enclosure = _lambda_) # 14.1 Exact size -drc["minwidth_via2"] = 2*_lambda_ # 14.2 Minimum spacing -drc["via2_to_via2"] = 3*_lambda_ +drc.add_layer("via2", + width = 2*_lambda_, + spacing = 3*_lambda_) # 15.1 Minimum width -drc["minwidth_metal3"] = 3*_lambda_ -# 15.2 Minimum spacing to metal3 -drc["metal3_to_metal3"] = 3*_lambda_ +# 15.2 Minimum spacing to m3 +drc.add_layer("m3", + width = 3*_lambda_, + spacing = 3*_lambda_) + # 15.3 Minimum overlap of via 2 -drc["metal3_extend_via2"] = _lambda_ -# Reserved for asymmetric enclosures -drc["metal3_enclosure_via2"] = _lambda_ -# 21.3 Minimum overlap by metal3 -drc["metal3_extend_via3"] = _lambda_ -# Reserved for asymmetric enclosures -drc["metal3_enclosure_via3"] = _lambda_ -# Not a rule -drc["minarea_metal3"] = 0 +drc.add_enclosure("m3", + layer = "via2", + enclosure = _lambda_) + +# 21.3 Minimum overlap by m3 +drc.add_enclosure("m3", + layer = "via3", + enclosure = _lambda_) # 21.1 Exact size -drc["minwidth_via3"] = 2*_lambda_ # 21.2 Minimum spacing -drc["via3_to_via3"] = 3*_lambda_ +drc.add_layer("via3", + width = 2*_lambda_, + spacing = 3*_lambda_) # 22.1 Minimum width -drc["minwidth_metal4"] = 6*_lambda_ -# 22.2 Minimum spacing to metal4 -drc["metal4_to_metal4"] = 6*_lambda_ +# 22.2 Minimum spacing to m4 +drc.add_layer("m4", + width = 6*_lambda_, + spacing = 6*_lambda_) + # 22.3 Minimum overlap of via 3 -drc["metal4_extend_via3"] = 2*_lambda_ -# Reserved for asymmetric enclosures -drc["metal4_enclosure_via3"] = 2*_lambda_ -# Not a rule -drc["minarea_metal4"] = 0 +drc.add_enclosure("m4", + layer = "via3", + enclosure = 2*_lambda_) ################################################### -##END DRC/LVS Rules -################################################### - -################################################### -##Spice Simulation Parameters +# Spice Simulation Parameters ################################################### # spice model info @@ -303,5 +377,11 @@ parameter["sa_inv_nmos_size"] = 9*_lambda_ parameter["bitcell_drain_cap"] = 0.2 #In Femto-Farad, approximation of drain capacitance ################################################### -##END Spice Simulation Parameters +# Technology Tool Preferences ################################################### + +drc_name = "magic" +lvs_name = "netgen" +pex_name = "magic" + +blackbox_bitcell = False