fix merge conflicts

This commit is contained in:
jcirimel 2020-07-21 11:38:34 -07:00
commit df4a231c04
332 changed files with 16248 additions and 8392 deletions

6
.gitignore vendored
View File

@ -1,8 +1,12 @@
.DS_Store
*~
*.orig
*.rej
*.pyc
*.aux
*.out
*.toc
*.synctex.gz
**/model_data
**/model_data
outputs
technology/freepdk45/ncsu_basekit

View File

@ -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:

View File

@ -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

20
LICENSE
View File

@ -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

View File

@ -18,7 +18,7 @@ An open-source static random access memory (SRAM) compiler.
# What is OpenRAM?
<img align="right" width="25%" src="images/SCMOS_16kb_sram.jpg">
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

View File

@ -1 +1 @@
theme: jekyll-theme-minimal
theme: jekyll-theme-dinky

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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):
"""

15
compiler/base/errors.py Normal file
View File

@ -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

View File

@ -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) + " )"

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -10,11 +10,12 @@ import re
import os
import math
import tech
from delay_data import *
from wire_spice_model import *
from power_data import *
from delay_data import delay_data
from wire_spice_model import wire_spice_model
from power_data import power_data
import logical_effort
class spice():
"""
This provides a set of useful generic types for hierarchy
@ -30,19 +31,21 @@ class spice():
self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
# Holds subckts/mods for this module
self.mods = []
self.mods = []
# Holds the pins for this module
self.pins = []
# The type map of each pin: INPUT, OUTPUT, INOUT, POWER, GROUND
# for each instance, this is the set of nets/nodes that map to the pins for this instance
self.pin_type = {}
self.pin_type = {}
# THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the
# Spice format)
self.conns = []
# If this is set, it will out output subckt or isntances of this (for row/col caps etc.)
self.no_instances = False
# Keep track of any comments to add the the spice
try:
self.commments
except:
except AttributeError:
self.comments = []
self.sp_read()
@ -56,7 +59,7 @@ class spice():
try:
self.commments
except:
except AttributeError:
self.comments = []
self.comments.append(comment)
@ -65,7 +68,9 @@ class spice():
""" Adds a pin to the pins list. Default type is INOUT signal. """
self.pins.append(name)
self.pin_type[name]=pin_type
debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(name,pin_type))
debug.check(pin_type in self.valid_signal_types,
"Invalid signaltype for {0}: {1}".format(name,
pin_type))
def add_pin_list(self, pin_list, pin_type="INOUT"):
""" Adds a pin_list to the pins list """
@ -73,36 +78,43 @@ class spice():
# or a list that is the same length as the pin list.
if type(pin_type)==str:
for pin in pin_list:
debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(pin,pin_type))
self.add_pin(pin,pin_type)
debug.check(pin_type in self.valid_signal_types,
"Invalid signaltype for {0}: {1}".format(pin,
pin_type))
self.add_pin(pin, pin_type)
elif len(pin_type)==len(pin_list):
for (pin,ptype) in zip(pin_list, pin_type):
debug.check(ptype in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(pin,ptype))
self.add_pin(pin,ptype)
for (pin, ptype) in zip(pin_list, pin_type):
debug.check(ptype in self.valid_signal_types,
"Invalid signaltype for {0}: {1}".format(pin,
ptype))
self.add_pin(pin, ptype)
else:
debug.error("Mismatch in type and pin list lengths.", -1)
def add_pin_types(self, type_list):
"""Add pin types for all the cell's pins.
Typically, should only be used for handmade cells."""
#This only works if self.pins == bitcell.pin_names
"""
Add pin types for all the cell's pins.
Typically, should only be used for handmade cells.
"""
# This only works if self.pins == bitcell.pin_names
if self.pin_names != self.pins:
debug.error("{} spice subcircuit port names do not match pin_names\
\n SPICE names={}\
\n Module names={}\
".format(self.name, self.pin_names, self.pins),1)
self.pin_type = {pin:type for pin,type in zip(self.pin_names, type_list)}
".format(self.name, self.pin_names, self.pins), 1)
self.pin_type = {pin: type for pin, type in zip(self.pin_names, type_list)}
def get_pin_type(self, name):
""" Returns the type of the signal pin. """
pin_type = self.pin_type[name]
debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(name,pin_type))
debug.check(pin_type in self.valid_signal_types,
"Invalid signaltype for {0}: {1}".format(name, pin_type))
return pin_type
def get_pin_dir(self, name):
""" Returns the direction of the pin. (Supply/ground are INOUT). """
if self.pin_type[name] in ["POWER","GROUND"]:
if self.pin_type[name] in ["POWER", "GROUND"]:
return "INOUT"
else:
return self.pin_type[name]
@ -125,11 +137,10 @@ class spice():
output_list.append(pin)
return output_list
def copy_pins(self, other_module, suffix=""):
""" This will copy all of the pins from the other module and add an optional suffix."""
for pin in other_module.pins:
self.add_pin(pin+suffix, other_module.get_pin_type(pin))
self.add_pin(pin + suffix, other_module.get_pin_type(pin))
def get_inouts(self):
""" These use pin types to determine pin lists. These
@ -144,7 +155,6 @@ class spice():
"""Adds a subckt/submodule to the subckt hierarchy"""
self.mods.append(mod)
def connect_inst(self, args, check=True):
"""Connects the pins of the last instance added
It is preferred to use the function with the check to find if
@ -169,21 +179,23 @@ class spice():
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
len(self.insts),
len(self.conns)))
debug.error("Instances: \n"+str(insts_string))
debug.error("Instances: \n" + str(insts_string))
debug.error("-----")
debug.error("Connections: \n"+str(conns_string),1)
debug.error("Connections: \n" + str(conns_string), 1)
def get_conns(self, inst):
"""Returns the connections of a given instance."""
for i in range(len(self.insts)):
if inst is self.insts[i]:
return self.conns[i]
#If not found, returns None
# If not found, returns None
return None
def sp_read(self):
"""Reads the sp file (and parse the pins) from the library
Otherwise, initialize it to null for dynamic generation"""
"""
Reads the sp file (and parse the pins) from the library
Otherwise, initialize it to null for dynamic generation
"""
if self.sp_file and os.path.isfile(self.sp_file):
debug.info(3, "opening {0}".format(self.sp_file))
f = open(self.sp_file)
@ -198,17 +210,37 @@ class spice():
# parses line into ports and remove subckt
self.pins = subckt_line.split(" ")[2:]
else:
debug.info(4, "no spfile {0}".format(self.sp_file))
self.spice = []
# We don't define self.lvs and will use self.spice if dynamically created
# or they are the same file
if self.lvs_file != self.sp_file and os.path.isfile(self.lvs_file):
debug.info(3, "opening {0}".format(self.lvs_file))
f = open(self.lvs_file)
self.lvs = f.readlines()
for i in range(len(self.lvs)):
self.lvs[i] = self.lvs[i].rstrip(" \n")
f.close()
# pins and subckt should be the same
# find the correct subckt line in the file
subckt = re.compile("^.subckt {}".format(self.name), re.IGNORECASE)
subckt_line = list(filter(subckt.search, self.lvs))[0]
# parses line into ports and remove subckt
lvs_pins = subckt_line.split(" ")[2:]
debug.check(lvs_pins == self.pins, "LVS and spice file pin mismatch.")
def check_net_in_spice(self, net_name):
"""Checks if a net name exists in the current. Intended to be check nets in hand-made cells."""
#Remove spaces and lower case then add spaces. Nets are separated by spaces.
net_formatted = ' '+net_name.lstrip().rstrip().lower()+' '
# Remove spaces and lower case then add spaces.
# Nets are separated by spaces.
net_formatted = ' ' + net_name.lstrip().rstrip().lower() + ' '
for line in self.spice:
#Lowercase the line and remove any part of the line that is a comment.
# Lowercase the line and remove any part of the line that is a comment.
line = line.lower().split('*')[0]
#Skip .subckt or .ENDS lines
# Skip .subckt or .ENDS lines
if line.find('.') == 0:
continue
if net_formatted in line:
@ -220,7 +252,7 @@ class spice():
nets_match = True
for net in nets:
nets_match = nets_match and self.check_net_in_spice(net)
return nets_match
return nets_match
def contains(self, mod, modlist):
for x in modlist:
@ -228,54 +260,64 @@ class spice():
return True
return False
def sp_write_file(self, sp, usedMODS):
""" Recursive spice subcircuit write;
Writes the spice subcircuit from the library or the dynamically generated one"""
if not self.spice:
def sp_write_file(self, sp, usedMODS, lvs_netlist=False):
"""
Recursive spice subcircuit write;
Writes the spice subcircuit from the library or the dynamically generated one
"""
if self.no_instances:
return
elif not self.spice:
# If spice isn't defined, we dynamically generate one.
# recursively write the modules
for i in self.mods:
if self.contains(i, usedMODS):
continue
usedMODS.append(i)
i.sp_write_file(sp, usedMODS)
i.sp_write_file(sp, usedMODS, lvs_netlist)
if len(self.insts) == 0:
return
if self.pins == []:
return
# write out the first spice line (the subcircuit)
sp.write("\n.SUBCKT {0} {1}\n".format(self.name,
" ".join(self.pins)))
for pin in self.pins:
sp.write("* {1:6}: {0} \n".format(pin,self.pin_type[pin]))
sp.write("* {1:6}: {0} \n".format(pin, self.pin_type[pin]))
for line in self.comments:
sp.write("* {}\n".format(line))
# every instance must have a set of connections, even if it is empty.
if len(self.insts)!=len(self.conns):
if len(self.insts) != len(self.conns):
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
len(self.insts),
len(self.conns)))
debug.error("Instances: \n"+str(self.insts))
debug.error("Instances: \n" + str(self.insts))
debug.error("-----")
debug.error("Connections: \n"+str(self.conns),1)
debug.error("Connections: \n" + str(self.conns), 1)
for i in range(len(self.insts)):
# we don't need to output connections of empty instances.
# these are wires and paths
if self.conns[i] == []:
continue
if hasattr(self.insts[i].mod,"spice_device"):
# Instance with no devices in it needs no subckt/instance
if self.insts[i].mod.no_instances:
continue
if lvs_netlist and hasattr(self.insts[i].mod, "lvs_device"):
sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name,
" ".join(self.conns[i])))
sp.write("\n")
elif hasattr(self.insts[i].mod, "spice_device"):
sp.write(self.insts[i].mod.spice_device.format(self.insts[i].name,
" ".join(self.conns[i])))
sp.write("\n")
else:
sp.write("X{0} {1} {2}\n".format(self.insts[i].name,
" ".join(self.conns[i]),
@ -284,11 +326,14 @@ class spice():
sp.write(".ENDS {0}\n".format(self.name))
else:
# write the subcircuit itself
# If spice is a hard module, output the spice file contents.
# Including the file path makes the unit test fail for other users.
#if os.path.isfile(self.sp_file):
# if os.path.isfile(self.sp_file):
# sp.write("\n* {0}\n".format(self.sp_file))
sp.write("\n".join(self.spice))
if lvs_netlist and hasattr(self, "lvs"):
sp.write("\n".join(self.lvs))
else:
sp.write("\n".join(self.spice))
sp.write("\n")
@ -302,21 +347,32 @@ class spice():
del usedMODS
spfile.close()
def lvs_write(self, spname):
"""Writes the lvs to files"""
debug.info(3, "Writing to {0}".format(spname))
spfile = open(spname, 'w')
spfile.write("*FIRST LINE IS A COMMENT\n")
usedMODS = list()
self.sp_write_file(spfile, usedMODS, True)
del usedMODS
spfile.close()
def analytical_delay(self, corner, slew, load=0.0):
"""Inform users undefined delay module while building new modules"""
# FIXME: Slew is not used in the model right now. Can be added heuristically as linear factor
# FIXME: Slew is not used in the model right now.
# Can be added heuristically as linear factor
relative_cap = logical_effort.convert_farad_to_relative_c(load)
stage_effort = self.get_stage_effort(relative_cap)
# If it fails, then keep running with a valid object.
if stage_effort == None:
if not stage_effort:
return delay_data(0.0, 0.0)
abs_delay = stage_effort.get_absolute_delay()
corner_delay = self.apply_corners_analytically(abs_delay, corner)
SLEW_APPROXIMATION = 0.1
corner_slew = SLEW_APPROXIMATION*corner_delay
corner_slew = SLEW_APPROXIMATION * corner_delay
return delay_data(corner_delay, corner_slew)
def get_stage_effort(self, cout, inp_is_rise=True):
@ -326,7 +382,7 @@ class spice():
debug.warning("Class {0} name {1}"
.format(self.__class__.__name__,
self.name))
return None
return None
def get_cin(self):
"""Returns input load in Femto-Farads. All values generated using
@ -342,35 +398,35 @@ class spice():
debug.warning("Design Class {0} input capacitance function needs to be defined"
.format(self.__class__.__name__))
debug.warning("Class {0} name {1}"
.format(self.__class__.__name__,
self.name))
return 0
.format(self.__class__.__name__,
self.name))
return 0
def cal_delay_with_rc(self, corner, r, c ,slew, swing = 0.5):
"""
Calculate the delay of a mosfet by
def cal_delay_with_rc(self, corner, r, c, slew, swing=0.5):
"""
Calculate the delay of a mosfet by
modeling it as a resistance driving a capacitance
"""
swing_factor = abs(math.log(1-swing)) # time constant based on swing
delay = swing_factor * r * c #c is in ff and delay is in fs
swing_factor = abs(math.log(1 - swing)) # time constant based on swing
delay = swing_factor * r * c # c is in ff and delay is in fs
delay = self.apply_corners_analytically(delay, corner)
delay = delay * 0.001 #make the unit to ps
delay = delay * 0.001 # make the unit to ps
# Output slew should be linear to input slew which is described
# Output slew should be linear to input slew which is described
# as 0.005* slew.
# The slew will be also influenced by the delay.
# If no input slew(or too small to make impact)
# The mimum slew should be the time to charge RC.
# If no input slew(or too small to make impact)
# The mimum slew should be the time to charge RC.
# Delay * 2 is from 0 to 100% swing. 0.6*2*delay is from 20%-80%.
slew = delay * 0.6 * 2 + 0.005 * slew
return delay_data(delay = delay, slew = slew)
return delay_data(delay=delay, slew=slew)
def apply_corners_analytically(self, delay, corner):
"""Multiply delay by corner factors"""
proc,vdd,temp = corner
#FIXME: type of delay is needed to know which process to use.
proc_mult = max(self.get_process_delay_factor(proc))
proc, vdd, temp = corner
# FIXME: type of delay is needed to know which process to use.
proc_mult = max(self.get_process_delay_factor(proc))
volt_mult = self.get_voltage_delay_factor(vdd)
temp_mult = self.get_temp_delay_factor(temp)
return delay * proc_mult * volt_mult * temp_mult
@ -385,48 +441,51 @@ class spice():
elif mos_proc == 'F':
proc_factors.append(0.9)
elif mos_proc == 'S':
proc_factors.append(1.1)
proc_factors.append(1.1)
return proc_factors
def get_voltage_delay_factor(self, voltage):
"""Returns delay increase due to voltage.
Implemented as linear factor based off nominal voltage.
"""
return tech.spice["nom_supply_voltage"]/voltage
return tech.spice["nom_supply_voltage"] / voltage
def get_temp_delay_factor(self, temp):
"""Returns delay increase due to temperature (in C).
Determines effect on threshold voltage and then linear factor is estimated.
"""
#Some portions of equation condensed (phi_t = k*T/q for T in Kelvin) in mV
#(k/q)/100 = .008625, The division 100 simplifies the conversion from C to K and mV to V
thermal_voltage_nom = 0.008625*tech.spice["nom_temperature"]
thermal_voltage = 0.008625*temp
vthresh = (tech.spice["nom_threshold"]+2*(thermal_voltage-thermal_voltage_nom))
#Calculate effect on Vdd-Vth. The current vdd is not used here. A separate vdd factor is calculated.
return (tech.spice["nom_supply_voltage"] - tech.spice["nom_threshold"])/(tech.spice["nom_supply_voltage"]-vthresh)
# Some portions of equation condensed (phi_t = k*T/q for T in Kelvin) in mV
# (k/q)/100 = .008625, The division 100 simplifies the conversion from C to K and mV to V
thermal_voltage_nom = 0.008625 * tech.spice["nom_temperature"]
thermal_voltage = 0.008625 * temp
vthresh = (tech.spice["nom_threshold"] + 2 * (thermal_voltage - thermal_voltage_nom))
# Calculate effect on Vdd-Vth.
# The current vdd is not used here.
# A separate vdd factor is calculated.
return (tech.spice["nom_supply_voltage"] - tech.spice["nom_threshold"]) / (tech.spice["nom_supply_voltage"] - vthresh)
def return_delay(self, delay, slew):
return delay_data(delay, slew)
def generate_rc_net(self,lump_num, wire_length, wire_width):
def generate_rc_net(self, lump_num, wire_length, wire_width):
return wire_spice_model(lump_num, wire_length, wire_width)
def calc_dynamic_power(self, corner, c, freq, swing=1.0):
"""
"""
Calculate dynamic power using effective capacitance, frequency, and corner (PVT)
"""
proc,vdd,temp = corner
net_vswing = vdd*swing
power_dyn = c*vdd*net_vswing*freq
proc, vdd, temp = corner
net_vswing = vdd * swing
power_dyn = c * vdd * net_vswing * freq
#Apply 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))
# 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)

View File

@ -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]

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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
"""

View File

@ -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.

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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"])

View File

@ -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",

View File

@ -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",

View File

@ -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()

View File

@ -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):
"""

View File

@ -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)
self.add_graph_edges(graph, port_nets)

View File

@ -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)
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self)
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self)

View File

@ -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.
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.

View File

@ -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")

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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))

147
compiler/custom/and2_dec.py Normal file
View File

@ -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()

156
compiler/custom/and3_dec.py Normal file
View File

@ -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()

159
compiler/custom/and4_dec.py Normal file
View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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<key2 for any pair.)
"""
# If any one pair is less than, return False
debug.check(len(key1)==len(key2),"Comparing invalid key lengths.")
for k1,k2 in zip(key1,key2):
debug.check(len(key1) == len(key2), "Comparing invalid key lengths.")
for k1, k2 in zip(key1, key2):
if k1 < k2:
return False
return True

View File

@ -6,8 +6,9 @@
# All rights reserved.
#
class drc_value():
"""
"""
A single DRC value.
"""
def __init__(self, value):

View File

@ -2,6 +2,7 @@ word_size = 32
num_words = 128
tech_name = "scn4m_subm"
nominal_corners_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]
@ -9,6 +10,3 @@ temperatures = [25]
output_path = "temp"
output_name = "sram_{0}_{1}_{2}".format(word_size, num_words, tech_name)
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"

View File

@ -6,6 +6,7 @@ num_r_ports = 1
num_w_ports = 0
tech_name = "scn4m_subm"
nominal_corners_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]
@ -17,7 +18,3 @@ output_path = "temp"
output_name = "sram_1rw_1r_{0}_{1}_{2}".format(word_size,
num_words,
tech_name)
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"

View File

@ -0,0 +1,21 @@
word_size = 4
num_words = 64
words_per_row = 2
num_rw_ports = 1
num_r_ports = 0
num_w_ports = 0
tech_name = "scn4m_subm"
nominal_corners_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]
# route_supplies = True
check_lvsdrc = True
output_path = "temp"
output_name = "sram_1rw_{0}_{1}_{2}".format(word_size,
num_words,
tech_name)

View File

@ -6,6 +6,7 @@ num_r_ports = 1
num_w_ports = 1
tech_name = "scn4m_subm"
nominal_corners_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]
@ -18,6 +19,3 @@ output_name = "sram_1w_1r_{0}_{1}_{2}".format(word_size,
num_words,
tech_name)
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"

View File

@ -2,6 +2,7 @@ word_size = 2
num_words = 16
tech_name = "freepdk45"
nominal_corners_only = False
process_corners = ["TT"]
supply_voltages = [1.0]
temperatures = [25]

View File

@ -2,6 +2,7 @@ word_size = 2
num_words = 16
tech_name = "scn4m_subm"
nominal_corners_only = False
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]
@ -14,6 +15,3 @@ output_name = "sram_{0}_{1}_{2}".format(word_size,
num_words,
tech_name)
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"

View File

@ -2,6 +2,7 @@ word_size = 64
num_words = 1024
tech_name = "scn4m_subm"
nominal_corners_only = False
process_corners = ["TT"]
supply_voltages = [ 5.0 ]
temperatures = [ 25 ]
@ -10,7 +11,3 @@ output_path = "temp"
output_name = "sram_{0}_{1}_{2}".format(word_size,
num_words,
tech_name)
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"

View File

@ -2,8 +2,9 @@ word_size = 16
num_words = 256
tech_name = "scn4m_subm"
nominal_corners_only = False
process_corners = ["TT"]
supply_voltages = [3.3]
supply_voltages = [5.0]
temperatures = [25]
output_path = "temp"
@ -11,6 +12,3 @@ output_name = "sram_{0}_{1}_{2}".format(word_size,
num_words,
tech_name)
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"

View File

@ -12,14 +12,14 @@ class Gds2reader:
self.fileHandle = None
self.layoutObject = layoutObject
self.debugToTerminal=debugToTerminal
#do we dump debug data to the screen
def print64AsBinary(self,number):
for index in range(0,64):
print((number>>(63-index))&0x1,eol='')
print("\n")
def stripNonASCII(self,bytestring):
string = bytestring.decode('utf-8')
return string
@ -29,20 +29,20 @@ class Gds2reader:
#(1)sign (7)exponent (56)mantissa
#exponent is excess 64, mantissa has no implied 1
#a normal IEEE double is like this:
#(1)sign (11)exponent (52)mantissa
#(1)sign (11)exponent (52)mantissa
data = struct.unpack('>q',ibmData)[0]
sign = (data >> 63)&0x01
exponent = (data >> 56) & 0x7f
mantissa = data<<8 #chop off sign and exponent
if mantissa == 0:
newFloat = 0.0
else:
else:
exponent = ((exponent-64)*4)+1023 #convert to double exponent
#re normalize
while mantissa & 0x8000000000000000 == 0:
while mantissa & 0x8000000000000000 == 0:
mantissa<<=1
exponent-=1
exponent-=1
mantissa<<=1 #remove the assumed high bit
exponent-=1
#check for underflow error -- should handle these properly!
@ -56,7 +56,7 @@ class Gds2reader:
#convert back to double
newFloat = struct.unpack('>d',asciiDouble)[0]
return newFloat
def ieeeFloatCheck(self,aFloat):
asciiDouble = struct.pack('>d',aFloat)
data = struct.unpack('>q',asciiDouble)[0]
@ -70,12 +70,12 @@ class Gds2reader:
asciiDouble = struct.pack('>q',(sign<<63)|(exponent+1023<<52)|(mantissa>>12))
newFloat = struct.unpack('>d',asciiDouble)[0]
print("Check:"+str(newFloat))
def readNextRecord(self):
global offset
recordLengthAscii = self.fileHandle.read(2) #first 2 bytes tell us the length of the record
if len(recordLengthAscii)==0:
return
return
recordLength = struct.unpack(">h",recordLengthAscii) #gives us a tuple with a short int inside
offset_int = int(recordLength[0]) # extract length
offset += offset_int # count offset
@ -96,20 +96,20 @@ class Gds2reader:
else:
print("Invalid GDSII Header")
return -1
#read records until we hit the UNITS section... this is the last part of the header
while 1:
record = self.readNextRecord()
idBits = record[0:2]
## Modified Date
if idBits==b'\x01\x02' and len(record)==26:
modYear = struct.unpack(">h",record[2:4])[0]
modYear = struct.unpack(">h",record[2:4])[0]
modMonth = struct.unpack(">h",record[4:6])[0]
modDay = struct.unpack(">h",record[6:8])[0]
modHour = struct.unpack(">h",record[8:10])[0]
modMinute = struct.unpack(">h",record[10:12])[0]
modSecond = struct.unpack(">h",record[12:14])[0]
lastAccessYear = struct.unpack(">h",record[14:16])[0]
lastAccessYear = struct.unpack(">h",record[14:16])[0]
lastAccessMonth = struct.unpack(">h",record[16:18])[0]
lastAccessDay = struct.unpack(">h",record[18:20])[0]
lastAccessHour = struct.unpack(">h",record[20:22])[0]
@ -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]

View File

@ -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()

View File

@ -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<x:#moving left
boundaryEquivalent+=[(x,y-halfWidth)]
@ -96,9 +96,9 @@ class GdsPath:
boundaryEquivalent+=[(x-halfWidth,y+halfWidth)]
if(y < lastY and x > nextX):
boundaryEquivalent+=[(x+halfWidth,y-halfWidth)]
if nextX == None: #end of path, put in the last 2 points
if lastX<x:#moving right
if nextX == None: #end of path, put in the last 2 points
if lastX<x:#moving right
boundaryEquivalent+=[(x,y+halfWidth)]
if lastX>x:#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=""

View File

@ -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]<bottomBound):
return False
return True
def isShapeInsideOfBox(self,shapeCoordinates, boxCoordinates):
"""
Go through every point in the shape to test if they are all inside the box.
@ -520,8 +526,8 @@ class VlsiLayout:
if not self.isPointInsideOfBox(point,boxCoordinates):
return False
return True
def fillAreaDensity(self, layerToFill = 0, offsetInMicrons = (0,0), coverageWidth = 100.0, coverageHeight = 100.0, minSpacing = 0.22, blockSize = 1.0):
effectiveBlock = blockSize+minSpacing
widthInBlocks = int(coverageWidth/effectiveBlock)
@ -540,7 +546,7 @@ class VlsiLayout:
shiftedBoundaryCoordinates.append((shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1]))
joint = self.doShapesIntersect(self.tempCoordinates, shiftedBoundaryCoordinates)
if joint:
self.tempPassFail = False
self.tempPassFail = False
common = self.isShapeInsideOfBox(shiftedBoundaryCoordinates,self.tempCoordinates)
if common:
self.tempPassFail = False
@ -553,11 +559,11 @@ class VlsiLayout:
shiftedBoundaryCoordinates.append((shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1]))
joint = self.doShapesIntersect(self.tempCoordinates, shiftedBoundaryCoordinates)
if joint:
self.tempPassFail = False
self.tempPassFail = False
common = self.isShapeInsideOfBox(shiftedBoundaryCoordinates,self.tempCoordinates)
if common:
self.tempPassFail = False
for yIndex in range(0,heightInBlocks):
for xIndex in range(0,widthInBlocks):
percentDone = (float((yIndex*heightInBlocks)+xIndex) / (heightInBlocks*widthInBlocks))*100
@ -576,7 +582,7 @@ class VlsiLayout:
passFailRecord.append(self.tempPassFail)
print("Percent Complete:"+str(percentDone))
passFailIndex=0
for yIndex in range(0,heightInBlocks):
for xIndex in range(0,widthInBlocks):
@ -587,41 +593,50 @@ class VlsiLayout:
passFailIndex += 1
print("Done\n\n")
def getLayoutBorder(self,borderlayer):
cellSizeMicron=None
def getLayoutBorder(self, lpp):
cellSizeMicron = None
for boundary in self.structures[self.rootStructureName].boundaries:
if boundary.drawingLayer==borderlayer:
if sameLPP((boundary.drawingLayer, boundary.purposeLayer),
lpp):
if self.debug:
debug.info(1,"Find border "+str(boundary.coordinates))
left_bottom=boundary.coordinates[0]
right_top=boundary.coordinates[2]
cellSize=[right_top[0]-left_bottom[0],right_top[1]-left_bottom[1]]
cellSizeMicron=[cellSize[0]*self.units[0],cellSize[1]*self.units[0]]
if not(cellSizeMicron):
print("Error: "+str(self.rootStructureName)+".cell_size information not found yet")
debug.info(1, "Find border "+str(boundary.coordinates))
left_bottom = boundary.coordinates[0]
right_top = boundary.coordinates[2]
cellSize = [right_top[0]-left_bottom[0],
right_top[1]-left_bottom[1]]
cellSizeMicron = [cellSize[0]*self.units[0],
cellSize[1]*self.units[0]]
debug.check(cellSizeMicron,
"Error: "+str(self.rootStructureName)+".cell_size information not found yet")
return cellSizeMicron
def measureSize(self,startStructure):
self.rootStructureName=startStructure
def measureSize(self, startStructure):
self.rootStructureName = self.padText(startStructure)
self.populateCoordinateMap()
cellBoundary = [None, None, None, None]
for TreeUnit in self.xyTree:
cellBoundary=self.measureSizeInStructure(TreeUnit,cellBoundary)
cellSize=[cellBoundary[2]-cellBoundary[0],cellBoundary[3]-cellBoundary[1]]
cellSizeMicron=[cellSize[0]*self.units[0],cellSize[1]*self.units[0]]
cellBoundary = self.measureSizeInStructure(TreeUnit, cellBoundary)
cellSize = [cellBoundary[2]-cellBoundary[0],
cellBoundary[3]-cellBoundary[1]]
cellSizeMicron = [cellSize[0]*self.units[0],
cellSize[1]*self.units[0]]
return cellSizeMicron
def measureBoundary(self,startStructure):
self.rootStructureName=startStructure
def measureBoundary(self, startStructure):
self.rootStructureName = self.padText(startStructure)
self.populateCoordinateMap()
cellBoundary = [None, None, None, None]
for TreeUnit in self.xyTree:
cellBoundary=self.measureSizeInStructure(TreeUnit,cellBoundary)
return [[self.units[0]*cellBoundary[0],self.units[0]*cellBoundary[1]],
[self.units[0]*cellBoundary[2],self.units[0]*cellBoundary[3]]]
def measureSizeInStructure(self,structure,cellBoundary):
(structureName,structureOrigin,structureuVector,structurevVector)=structure
cellBoundary = self.measureSizeInStructure(TreeUnit, cellBoundary)
return [[self.units[0]*cellBoundary[0],
self.units[0]*cellBoundary[1]],
[self.units[0]*cellBoundary[2],
self.units[0]*cellBoundary[3]]]
def measureSizeInStructure(self, structure, cellBoundary):
(structureName, structureOrigin,
structureuVector, structurevVector) = structure
for boundary in self.structures[str(structureName)].boundaries:
left_bottom=boundary.coordinates[0]
right_top=boundary.coordinates[2]
@ -631,7 +646,7 @@ class VlsiLayout:
thisBoundary[2]+structureOrigin[0],thisBoundary[3]+structureOrigin[1]]
cellBoundary=self.updateBoundary(thisBoundary,cellBoundary)
return cellBoundary
def updateBoundary(self,thisBoundary,cellBoundary):
[left_bott_X,left_bott_Y,right_top_X,right_top_Y]=thisBoundary
# If any are None
@ -648,17 +663,17 @@ class VlsiLayout:
cellBoundary[3]=right_top_Y
return cellBoundary
def getTexts(self, layer):
def getTexts(self, lpp):
"""
Get all of the labels on a given layer only at the root level.
"""
text_list = []
for Text in self.structures[self.rootStructureName].texts:
if Text.drawingLayer == layer:
if sameLPP((Text.drawingLayer, Text.purposeLayer),
lpp):
text_list.append(Text)
return text_list
def getPinShape(self, pin_name):
"""
Search for a pin label and return the largest enclosing rectangle
@ -671,15 +686,15 @@ class VlsiLayout:
max_pin = None
max_area = 0
for pin in pin_list:
(layer,boundary) = pin
(layer, boundary) = pin
new_area = boundaryArea(boundary)
if max_pin == None or new_area>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

View File

@ -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)

View File

@ -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] <config file>\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.")

File diff suppressed because it is too large Load Diff

View File

@ -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())

View File

@ -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]

View File

@ -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

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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"""

View File

@ -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"]

View File

@ -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"""

View File

@ -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):

View File

@ -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):

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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
return combination

View File

@ -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
return combination

View File

@ -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

View File

@ -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]

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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 bit<self.left_rbl:
replica_bit = bit+1
replica_bit = bit + 1
# dummy column
column_offset = self.left_rbl - bit
# Creating right_rbl
else:
replica_bit = bit+self.row_size+1
replica_bit = bit + self.row_size + 1
# dummy column + replica column + bitcell colums
column_offset = self.left_rbl - bit + self.row_size
self.replica_columns[bit] = factory.create(module_type="replica_column",
rows=self.row_size,
left_rbl=self.left_rbl,
right_rbl=self.right_rbl,
column_offset=column_offset,
replica_bit=replica_bit)
self.add_mod(self.replica_columns[bit])
# Dummy row
self.dummy_row = factory.create(module_type="dummy_array",
cols=self.column_size,
rows=1,
# dummy column + left replica column
column_offset=1 + self.left_rbl,
mirror=0)
self.add_mod(self.dummy_row)
# Dummy col (mirror starting at first if odd replica+dummy rows)
self.dummy_col = factory.create(module_type="dummy_array",
cols=1,
rows=self.row_size + self.extra_rows,
mirror=(self.left_rbl+1)%2)
self.add_mod(self.dummy_col)
# If there are bitcell end caps, replace the dummy cells on the edge of the bitcell array with end caps.
try:
end_caps_enabled = cell_properties.bitcell.end_caps
except AttributeError:
end_caps_enabled = False
# Dummy Row or Col Cap, depending on bitcell array properties
edge_row_module_type = ("col_cap_array" if end_caps_enabled else "dummy_array")
self.edge_row = factory.create(module_type=edge_row_module_type,
cols=self.column_size,
rows=1,
# dummy column + left replica column(s)
column_offset=1 + self.left_rbl,
mirror=0)
self.add_mod(self.edge_row)
# Dummy Col or Row Cap, depending on bitcell array properties
edge_col_module_type = ("row_cap_array" if end_caps_enabled else "dummy_array")
self.edge_col_left = factory.create(module_type=edge_col_module_type,
cols=1,
column_offset=0,
rows=self.row_size + self.extra_rows,
mirror=(self.left_rbl + 1) % 2)
self.add_mod(self.edge_col_left)
self.edge_col_right = factory.create(module_type=edge_col_module_type,
cols=1,
# dummy column
# + left replica column(s)
# + bitcell columns
# + right replica column(s)
column_offset = 1 + self.left_rbl + self.column_size + self.right_rbl,
rows=self.row_size + self.extra_rows,
mirror=(self.left_rbl + 1) %2)
self.add_mod(self.edge_col_right)
def add_pins(self):
self.bitcell_array_wl_names = self.bitcell_array.get_all_wordline_names()
self.bitcell_array_bl_names = self.bitcell_array.get_all_bitline_names()
self.bitcell_array_wl_names = [x for x in self.bitcell_array.pins if x.startswith("w")]
self.bitcell_array_bl_names = [x for x in self.bitcell_array.pins if x.startswith("b")]
# These are the non-indexed names
self.dummy_cell_wl_names = ["dummy_"+x for x in self.cell.get_all_wl_names()]
self.dummy_cell_bl_names = ["dummy_"+x for x in self.cell.get_all_bitline_names()]
# These are the non-indexed names
self.dummy_cell_wl_names = ["dummy_" + x for x in self.cell.get_all_wl_names()]
self.dummy_cell_bl_names = ["dummy_" + x for x in self.cell.get_all_bitline_names()]
self.dummy_row_bl_names = self.bitcell_array_bl_names
# A dictionary because some ports may have nothing
self.rbl_bl_names = {}
self.rbl_br_names = {}
self.rbl_wl_names = {}
# Create the full WL names include dummy, replica, and regular bit cells
self.replica_col_wl_names = []
self.replica_col_wl_names.extend(["{0}_bot".format(x) for x in self.dummy_cell_wl_names])
# Left port WLs (one dummy for each port when we allow >1 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)

View File

@ -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 <rows-right_rbl)
# Replic bit specifies which other bit (in the full range (0,rows) to make a replica cell.
if (row>self.left_rbl and row<self.total_size-self.right_rbl-1):
# Replic bit specifies which other bit (in the full range (0,rows) to make a replica cell.
if (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)

View File

@ -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)

View File

@ -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)
self.add_graph_edges(graph, port_nets)

View File

@ -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

View File

@ -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

View File

@ -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"))
height=drc("minwidth_m1"))

View File

@ -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

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More