Merge branch 'dev' into pdriver

This commit is contained in:
Jennifer Sowash 2018-11-12 11:30:37 -08:00
commit b366d88041
447 changed files with 40560 additions and 4313721 deletions

View File

@ -1,6 +1,6 @@
freepdk45:
script: "/home/gitlab-runner/regress_freepdk45.sh"
scn3me_subm:
script: "/home/gitlab-runner/regress_scn3me_subm.sh"
scn4m_subm:
script: "/home/gitlab-runner/regress_scn4m_subm.sh"

View File

@ -20,26 +20,33 @@ other OpenRAM features. Please see the README.md file on how to run
the unit tests. Unit tests should work in all technologies. We will run
the tests on your contributions before they will be accepted.
# Internal Development
For internal development, follow all of the following steps EXCEPT
do not fork your own copy. Instead, create a branch in our private repository
and consult with me when you want to merge it into the dev branch.
All unit tests should pass first.
# Pull Request Process
1. One time, create a GitHub account at http://github.com
2. Create a fork of the OpenRAM project on the github web page:
https://github.com/mguthaus/OpenRAM
https://github.com/vlsida/openram
It is on the upper right and says "Fork": This will make your own
OpenRAM repository on GitHub in your account.
3. Clone your repository (or use an existing cloned copy if you've
already done this once):
```
git clone https://github.com/<youruser>/OpenRAM.git
cd OpenRAM
git clone https://github.com/<youruser>/oepnram.git
cd openram
```
4. Set up a new upstream that points to MY OpenRAM repository that you
forked (only first time):
```
git remote add upstream https://github.com/mguthaus/OpenRAM.git
git remote add upstream https://github.com/vlsida/openram.git
```
You now have two remotes for this project:
* origin which points to your GitHub fork of the project. You can read

View File

@ -7,8 +7,9 @@ https://github.com/mguthaus/OpenRAM/blob/master/OpenRAM_ICCAD_2016_presentation.
The OpenRAM compiler has very few dependencies:
* ngspice-26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later)
* Python 3.5 and higher
* Python numpy
* a setup script for each technology
* Python numpy (pip3 install numpy to install)
* flask_table (pip3 install flask to install)
* a setup script for each technology you want to use
* a technology directory for each technology with the base cells
If you want to perform DRC and LVS, you will need either:
@ -20,13 +21,13 @@ the compiler source directory. OPENERAM_TECH should point to a root
technology directory that contains subdirs of all other technologies.
For example, in bash, add to your .bashrc:
```
export OPENRAM_HOME="$HOME/OpenRAM/compiler"
export OPENRAM_TECH="$HOME/OpenRAM/technology"
export OPENRAM_HOME="$HOME/openram/compiler"
export OPENRAM_TECH="$HOME/openram/technology"
```
For example, in csh/tcsh, add to your .cshrc/.tcshrc:
```
setenv OPENRAM_HOME "$HOME/OpenRAM/compiler"
setenv OPENRAM_TECH "$HOME/OpenRAM/technology"
setenv OPENRAM_HOME "$HOME/openram/compiler"
setenv OPENRAM_TECH "$HOME/openram/technology"
```
We include the tech files necessary for FreePDK and SCMOS. The SCMOS
@ -48,7 +49,7 @@ We do not distribute the PDK, but you may get it from:
If you are using SCMOS, you should install Magic and netgen from:
http://opencircuitdesign.com/magic/
http://opencircuitdesign.com/netgen/
We have included the SCN3ME design rules from QFlow:
We have included the SCN4M design rules from QFlow:
http://opencircuitdesign.com/qflow/
# DIRECTORY STRUCTURE
@ -56,17 +57,19 @@ We have included the SCN3ME design rules from QFlow:
* compiler - openram compiler itself (pointed to by OPENRAM_HOME)
* compiler/base - base data structure modules
* compiler/pgates - parameterized cells (e.g. logic gates)
* compiler/bitcells - various bitcell styles
* compiler/modules - high-level modules (e.g. decoders, etc.)
* compiler/verify - DRC and LVS verification wrappers
* compiler/verify - DRC and LVS verification wrappers
* compiler/characterizer - timing characterization code
* compiler/gdsMill - GDSII reader/writer
* compiler/router - detailed router
* compiler/router - router for signals and power supplies
* compiler/tests - unit tests
* technology - openram technology directory (pointed to by OPENRAM_TECH)
* technology/freepdk45 - example configuration library for freepdk45 technology node
* technology/scn3me_subm - example configuration library SCMOS technology node
* technology/scn4m_subm - example configuration library SCMOS technology node
* technology/scn3me_subm - unsupported configuration (not enough metal layers)
* technology/setup_scripts - setup scripts to customize your PDKs and OpenRAM technologies
* docs - LaTeX manual (likely outdated)
* docs - LaTeX manual (outdated)
* lib - IP library of pregenerated memories
@ -89,17 +92,9 @@ To increase the verbosity of the test, add one (or more) -v options:
python tests/00_code_format_check_test.py -v -t freepdk45
```
To specify a particular technology use "-t <techname>" such as
"-t scn3me_subm". The default for a unit test is freepdk45 whereas
the default for openram.py is specified in the configuration file.
"-t freepdk45" or "-t scn4m_subm". The default for a unit test is scn4m_subm.
The default for openram.py is specified in the configuration file.
A regression daemon script that can be used with cron is included in
a separate repository at https://github.com/mguthaus/openram-daemons
```
regress_daemon.py
regress_daemon.sh
```
This updates a git repository, checks out code, and sends an email
report with status information.
# CREATING CUSTOM TECHNOLOGIES

View File

@ -15,6 +15,7 @@ class contact(hierarchy_design.hierarchy_design):
necessary to import layouts into Magic which requires the select to be in the same GDS
hierarchy as the contact.
"""
def __init__(self, layer_stack, dimensions=[1,1], implant_type=None, well_type=None):
if implant_type or well_type:
name = "{0}_{1}_{2}_{3}x{4}_{5}{6}".format(layer_stack[0],
@ -24,13 +25,14 @@ class contact(hierarchy_design.hierarchy_design):
dimensions[1],
implant_type,
well_type)
else:
name = "{0}_{1}_{2}_{3}x{4}".format(layer_stack[0],
layer_stack[1],
layer_stack[2],
dimensions[0],
dimensions[1])
layer_stack[1],
layer_stack[2],
dimensions[0],
dimensions[1])
hierarchy_design.hierarchy_design.__init__(self, name)
debug.info(4, "create contact object {0}".format(name))
@ -71,21 +73,21 @@ class contact(hierarchy_design.hierarchy_design):
self.second_layer_name = second_layer
def setup_layout_constants(self):
self.contact_width = drc["minwidth_{0}". format(self.via_layer_name)]
contact_to_contact = drc["{0}_to_{0}".format(self.via_layer_name)]
self.contact_width = drc("minwidth_{0}". format(self.via_layer_name))
contact_to_contact = drc("{0}_to_{0}".format(self.via_layer_name))
self.contact_pitch = self.contact_width + contact_to_contact
self.contact_array_width = self.contact_width + (self.dimensions[0] - 1) * self.contact_pitch
self.contact_array_height = self.contact_width + (self.dimensions[1] - 1) * self.contact_pitch
# DRC rules
first_layer_minwidth = drc["minwidth_{0}".format(self.first_layer_name)]
first_layer_minarea = drc["minarea_{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)]
second_layer_minwidth = drc["minwidth_{0}".format(self.second_layer_name)]
second_layer_minarea = drc["minarea_{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)]
first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name))
first_layer_minarea = drc("minarea_{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))
second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name))
second_layer_minarea = drc("minarea_{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.first_layer_horizontal_enclosure = max((first_layer_minwidth - self.contact_array_width) / 2,
first_layer_enclosure)
@ -143,16 +145,16 @@ class contact(hierarchy_design.hierarchy_design):
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_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")
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"]
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,

View File

@ -18,6 +18,7 @@ class design(hierarchy_design):
hierarchy_design.__init__(self,name)
self.setup_drc_constants()
self.setup_multiport_constants()
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)
@ -28,24 +29,59 @@ class design(hierarchy_design):
def setup_drc_constants(self):
""" These are some DRC constants used in many places in the compiler."""
from tech import drc
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"]
self.active_width = drc["minwidth_active"]
self.contact_width = drc["minwidth_contact"]
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")
self.active_width = drc("minwidth_active")
self.active_space = drc("active_to_body_active")
self.contact_width = drc("minwidth_contact")
self.poly_to_active = drc["poly_to_active"]
self.poly_extend_active = drc["poly_extend_active"]
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"]
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")
def setup_multiport_constants(self):
"""
These are contants and lists that aid multiport design.
Ports are always in the order RW, W, R.
Port indices start from 0 and increment.
A first RW port will have clk0, csb0, web0, addr0, data0
A first W port (with no RW ports) will be: clk0, csb0, addr0, data0
"""
total_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
# These are the read/write port indices.
self.readwrite_ports = []
# These are the read/write and write-only port indices
self.write_ports = []
# These are teh read/write and read-only port indice
self.read_ports = []
# These are all the ports
self.all_ports = list(range(total_ports))
port_number = 0
for port in range(OPTS.num_rw_ports):
self.readwrite_ports.append(port_number)
self.write_ports.append(port_number)
self.read_ports.append(port_number)
port_number += 1
for port in range(OPTS.num_w_ports):
self.write_ports.append(port_number)
port_number += 1
for port in range(OPTS.num_r_ports):
self.read_ports.append(port_number)
port_number += 1
def analytical_power(self, proc, vdd, temp, load):
""" Get total power of a module """
@ -53,3 +89,14 @@ class design(hierarchy_design):
for inst in self.insts:
total_module_power += inst.mod.analytical_power(proc, vdd, temp, load)
return total_module_power
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]
s = "********** design {0} **********\n".format(self.name)
s += "\n pins ({0})={1}\n".format(len(self.pins), pins)
s += "\n objs ({0})=\n{1}".format(len(self.objs), "\n".join(objs))
s += "\n insts ({0})=\n{1}\n".format(len(self.insts), "\n".join(insts))
return s

View File

@ -6,6 +6,7 @@ from vector import vector
import tech
import math
from globals import OPTS
from utils import round_to_grid
class geometry:
"""
@ -46,14 +47,21 @@ class geometry:
def normalize(self):
""" Re-find the LL and UR points after a transform """
(first,second)=self.boundary
ll = vector(min(first[0],second[0]),min(first[1],second[1]))
ur = vector(max(first[0],second[0]),max(first[1],second[1]))
ll = vector(min(first[0],second[0]),min(first[1],second[1])).snap_to_grid()
ur = vector(max(first[0],second[0]),max(first[1],second[1])).snap_to_grid()
self.boundary=[ll,ur]
def update_boundary(self):
""" Update the boundary with a new placement. """
self.compute_boundary(self.offset,self.mirror,self.rotate)
def compute_boundary(self,offset=vector(0,0),mirror="",rotate=0):
""" Transform with offset, mirror and rotation to get the absolute pin location.
We must then re-find the ll and ur. The master is the cell instance. """
if OPTS.netlist_only:
return
(ll,ur) = [vector(0,0),vector(self.width,self.height)]
if mirror=="MX":
ll=ll.scale(1,-1)
ur=ur.scale(1,-1)
@ -124,7 +132,7 @@ class instance(geometry):
An instance of an instance/module with a specified location and
rotation
"""
def __init__(self, name, mod, offset, mirror, rotate):
def __init__(self, name, mod, offset=[0,0], mirror="R0", rotate=0):
"""Initializes an instance to represent a module"""
geometry.__init__(self)
debug.check(mirror not in ["R90","R180","R270"], "Please use rotation and not mirroring during instantiation.")
@ -135,8 +143,16 @@ class instance(geometry):
self.rotate = rotate
self.offset = vector(offset).snap_to_grid()
self.mirror = mirror
self.width = mod.width
self.height = mod.height
if OPTS.netlist_only:
self.width = 0
self.height = 0
else:
if mirror in ["R90","R270"] or rotate in [90,270]:
self.width = round_to_grid(mod.height)
self.height = round_to_grid(mod.width)
else:
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)
@ -184,10 +200,18 @@ class instance(geometry):
self.mod.gds_write_file(self.gds)
# now write an instance of my module/structure
new_layout.addInstance(self.gds,
offsetInMicrons=self.offset,
mirror=self.mirror,
rotate=self.rotate)
offsetInMicrons=self.offset,
mirror=self.mirror,
rotate=self.rotate)
def place(self, offset, mirror="R0", rotate=0):
""" This updates the placement of an instance. """
debug.info(3, "placing instance {}".format(self.name))
# Update the placement of an already added instance
self.offset = vector(offset).snap_to_grid()
self.mirror = mirror
self.rotate = rotate
self.update_boundary()
def get_pin(self,name,index=-1):
@ -223,7 +247,7 @@ class instance(geometry):
def __str__(self):
""" override print function output """
return "inst: " + self.name + " mod=" + self.mod.name
return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")"
def __repr__(self):
""" override print function output """
@ -245,13 +269,13 @@ class path(geometry):
# supported right now. It might not work in gdsMill.
assert(0)
def gds_write_file(self, newLayout):
def gds_write_file(self, new_layout):
"""Writes the path to GDS"""
debug.info(4, "writing path (" + str(self.layerNumber) + "): " + self.coordinates)
newLayout.addPath(layerNumber=self.layerNumber,
purposeNumber=0,
coordinates=self.coordinates,
width=self.path_width)
new_layout.addPath(layerNumber=self.layerNumber,
purposeNumber=0,
coordinates=self.coordinates,
width=self.path_width)
def get_blockages(self, layer):
""" Fail since we don't support paths yet. """
@ -286,15 +310,15 @@ class label(geometry):
debug.info(4,"creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset))
def gds_write_file(self, newLayout):
def gds_write_file(self, new_layout):
"""Writes the text label to GDS"""
debug.info(4, "writing label (" + str(self.layerNumber) + "): " + self.text)
newLayout.addText(text=self.text,
layerNumber=self.layerNumber,
purposeNumber=0,
offsetInMicrons=self.offset,
magnification=self.zoom,
rotate=None)
new_layout.addText(text=self.text,
layerNumber=self.layerNumber,
purposeNumber=0,
offsetInMicrons=self.offset,
magnification=self.zoom,
rotate=None)
def get_blockages(self, layer):
""" Returns an empty list since text cannot be blockages. """
@ -306,7 +330,7 @@ class label(geometry):
def __repr__(self):
""" override print function output """
return "( label: " + self.text + " @" + str(self.offset) + " layer=" + self.layerNumber + " )"
return "( label: " + self.text + " @" + str(self.offset) + " layer=" + str(self.layerNumber) + " )"
class rectangle(geometry):
"""Represents a rectangular shape"""
@ -318,8 +342,8 @@ class rectangle(geometry):
self.layerNumber = layerNumber
self.offset = vector(offset).snap_to_grid()
self.size = vector(width, height).snap_to_grid()
self.width = self.size.x
self.height = self.size.y
self.width = round_to_grid(self.size.x)
self.height = round_to_grid(self.size.y)
self.compute_boundary(offset,"",0)
debug.info(4, "creating rectangle (" + str(self.layerNumber) + "): "
@ -333,16 +357,16 @@ class rectangle(geometry):
else:
return []
def gds_write_file(self, newLayout):
def gds_write_file(self, new_layout):
"""Writes the rectangular shape to GDS"""
debug.info(4, "writing rectangle (" + str(self.layerNumber) + "):"
+ str(self.width) + "x" + str(self.height) + " @ " + str(self.offset))
newLayout.addBox(layerNumber=self.layerNumber,
purposeNumber=0,
offsetInMicrons=self.offset,
width=self.width,
height=self.height,
center=False)
new_layout.addBox(layerNumber=self.layerNumber,
purposeNumber=0,
offsetInMicrons=self.offset,
width=self.width,
height=self.height,
center=False)
def __str__(self):
""" override print function output """

View File

@ -16,8 +16,14 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
def __init__(self, name):
self.gds_file = OPTS.openram_tech + "gds_lib/" + name + ".gds"
self.sp_file = OPTS.openram_tech + "sp_lib/" + name + ".sp"
try:
self.gds_file
except AttributeError:
self.gds_file = OPTS.openram_tech + "gds_lib/" + name + ".gds"
try:
self.sp_file
except AttributeError:
self.sp_file = OPTS.openram_tech + "sp_lib/" + name + ".sp"
self.name = name
hierarchy_layout.layout.__init__(self, name)

View File

@ -5,6 +5,7 @@ import debug
from tech import drc, GDS
from tech import layer as techlayer
import os
from globals import OPTS
from vector import vector
from pin_layout import pin_layout
import lef
@ -27,7 +28,7 @@ class layout(lef.lef):
self.insts = [] # Holds module/cell layout instances
self.objs = [] # Holds all other objects (labels, geometries, etc)
self.pin_map = {} # Holds name->pin_layout map for all pins
self.visited = False # Flag for traversing the hierarchy
self.visited = [] # List of modules we have already visited
self.is_library_cell = False # Flag for library cells
self.gds_read()
@ -118,12 +119,12 @@ class layout(lef.lef):
for pin in pin_list:
pin.rect = [pin.ll() - offset, pin.ur() - offset]
def add_inst(self, name, mod, offset=[0,0], mirror="R0",rotate=0):
"""Adds an instance of a mod to this module"""
self.insts.append(geometry.instance(name, mod, offset, mirror, rotate))
debug.info(3, "adding instance {}".format(self.insts[-1]))
debug.info(4, "instance list: " + ",".join(x.name for x in self.insts))
# This is commented out for runtime reasons
#debug.info(4, "instance list: " + ",".join(x.name for x in self.insts))
return self.insts[-1]
def get_inst(self, name):
@ -133,11 +134,13 @@ class layout(lef.lef):
return inst
return None
def add_rect(self, layer, offset, width=0, height=0):
"""Adds a rectangle on a given layer,offset with width and height"""
if width==0:
def add_rect(self, layer, offset, width=None, height=None):
"""
Adds a rectangle on a given layer,offset with width and height
"""
if not width:
width=drc["minwidth_{}".format(layer)]
if height==0:
if not height:
height=drc["minwidth_{}".format(layer)]
# negative layers indicate "unused" layers in a given technology
layer_num = techlayer[layer]
@ -146,11 +149,13 @@ class layout(lef.lef):
return self.objs[-1]
return None
def add_rect_center(self, layer, offset, width=0, height=0):
"""Adds a rectangle on a given layer at the center point with width and height"""
if width==0:
def add_rect_center(self, layer, offset, width=None, height=None):
"""
Adds a rectangle on a given layer at the center point with width and height
"""
if not width:
width=drc["minwidth_{}".format(layer)]
if height==0:
if not height:
height=drc["minwidth_{}".format(layer)]
# negative layers indicate "unused" layers in a given technology
layer_num = techlayer[layer]
@ -162,7 +167,9 @@ class layout(lef.lef):
def add_segment_center(self, layer, start, end):
""" Add a min-width rectanglular segment using center line on the start to end point """
"""
Add a min-width rectanglular segment using center line on the start to end point
"""
minwidth_layer = drc["minwidth_{}".format(layer)]
if start.x!=end.x and start.y!=end.y:
debug.error("Nonrectilinear center rect!",-1)
@ -176,7 +183,9 @@ class layout(lef.lef):
def get_pin(self, text):
""" Return the pin or list of pins """
"""
Return the pin or list of pins
"""
try:
if len(self.pin_map[text])>1:
debug.error("Should use a pin iterator since more than one pin {}".format(text),-1)
@ -191,8 +200,13 @@ class layout(lef.lef):
def get_pins(self, text):
""" Return a pin list (instead of a single pin) """
return self.pin_map[text]
"""
Return a pin list (instead of a single pin)
"""
if text in self.pin_map.keys():
return self.pin_map[text]
else:
return []
def copy_layout_pin(self, instance, pin_name, new_name=""):
"""
@ -206,7 +220,9 @@ class layout(lef.lef):
self.add_layout_pin(new_name, pin.layer, pin.ll(), pin.width(), pin.height())
def add_layout_pin_segment_center(self, text, layer, start, end):
""" Creates a path like pin with center-line convention """
"""
Creates a path like pin with center-line convention
"""
debug.check(start.x==end.x or start.y==end.y,"Cannot have a non-manhatten layout pin.")
@ -231,9 +247,9 @@ class layout(lef.lef):
def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None):
""" Creates a path like pin with center-line convention """
if width==None:
if not width:
width=drc["minwidth_{0}".format(layer)]
if height==None:
if not height:
height=drc["minwidth_{0}".format(layer)]
ll_offset = offset - vector(0.5*width,0.5*height)
@ -242,16 +258,20 @@ class layout(lef.lef):
def remove_layout_pin(self, text):
"""Delete a labeled pin (or all pins of the same name)"""
"""
Delete a labeled pin (or all pins of the same name)
"""
self.pin_map[text]=[]
def add_layout_pin(self, text, layer, offset, width=None, height=None):
"""Create a labeled pin """
if width==None:
"""
Create a labeled pin
"""
if not width:
width=drc["minwidth_{0}".format(layer)]
if height==None:
if not height:
height=drc["minwidth_{0}".format(layer)]
new_pin = pin_layout(text, [offset,offset+vector(width,height)], layer)
try:
@ -269,13 +289,14 @@ class layout(lef.lef):
return new_pin
def add_label_pin(self, text, layer, offset, width=None, height=None):
"""Create a labeled pin WITHOUT the pin data structure. This is not an
"""
Create a labeled pin WITHOUT the pin data structure. This is not an
actual pin but a named net so that we can add a correspondence point
in LVS.
"""
if width==None:
if not width:
width=drc["minwidth_{0}".format(layer)]
if height==None:
if not height:
height=drc["minwidth_{0}".format(layer)]
self.add_rect(layer=layer,
offset=offset,
@ -312,7 +333,7 @@ class layout(lef.lef):
position_list=coordinates,
width=width)
def add_route(self, design, layers, coordinates):
def add_route(self, layers, coordinates, layer_widths):
"""Connects a routing path on given layer,coordinates,width. The
layers are the (horizontal, via, vertical). add_wire assumes
preferred direction routing whereas this includes layers in
@ -323,7 +344,8 @@ class layout(lef.lef):
# add an instance of our path that breaks down into rectangles and contacts
route.route(obj=self,
layer_stack=layers,
path=coordinates)
path=coordinates,
layer_widths=layer_widths)
def add_wire(self, layers, coordinates):
@ -379,24 +401,23 @@ class layout(lef.lef):
dimensions=size,
implant_type=implant_type,
well_type=well_type)
debug.check(mirror=="R0","Use rotate to rotate vias instead of mirror.")
height = via.height
width = via.width
debug.check(mirror=="R0","Use rotate to rotate vias instead of mirror.")
if rotate==0:
corrected_offset = offset + vector(-0.5*width,-0.5*height)
elif rotate==90:
corrected_offset = offset + vector(0.5*height,-0.5*width)
elif rotate==180:
corrected_offset = offset + vector(-0.5*width,0.5*height)
corrected_offset = offset + vector(0.5*width,0.5*height)
elif rotate==270:
corrected_offset = offset + vector(-0.5*height,0.5*width)
else:
debug.error("Invalid rotation argument.",-1)
#print(rotate,offset,"->",corrected_offset)
self.add_mod(via)
inst=self.add_inst(name=via.name,
mod=via,
@ -426,61 +447,72 @@ class layout(lef.lef):
def gds_read(self):
"""Reads a GDSII file in the library and checks if it exists
Otherwise, start a new layout for dynamic generation."""
if OPTS.netlist_only:
self.gds = None
return
# open the gds file if it exists or else create a blank layout
if os.path.isfile(self.gds_file):
debug.info(3, "opening %s" % self.gds_file)
debug.info(3, "opening {}".format(self.gds_file))
self.is_library_cell=True
self.gds = gdsMill.VlsiLayout(units=GDS["unit"])
reader = gdsMill.Gds2reader(self.gds)
reader.loadFromFile(self.gds_file)
else:
debug.info(4, "creating structure %s" % self.name)
debug.info(3, "Creating layout structure {}".format(self.name))
self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"])
def print_gds(self, gds_file=None):
"""Print the gds file (not the vlsi class) to the terminal """
if gds_file == None:
gds_file = self.gds_file
debug.info(4, "Printing %s" % gds_file)
debug.info(4, "Printing {}".format(gds_file))
arrayCellLayout = gdsMill.VlsiLayout(units=GDS["unit"])
reader = gdsMill.Gds2reader(arrayCellLayout, debugToTerminal=1)
reader.loadFromFile(gds_file)
def clear_visited(self):
""" Recursively clear the visited flag """
if not self.visited:
for i in self.insts:
i.mod.clear_visited()
self.visited = False
self.visited = []
def gds_write_file(self, newLayout):
def gds_write_file(self, gds_layout):
"""Recursive GDS write function"""
# Visited means that we already prepared self.gds for this subtree
if self.visited:
if self.name in self.visited:
return
for i in self.insts:
i.gds_write_file(newLayout)
i.gds_write_file(gds_layout)
for i in self.objs:
i.gds_write_file(newLayout)
i.gds_write_file(gds_layout)
for pin_name in self.pin_map.keys():
for pin in self.pin_map[pin_name]:
pin.gds_write_file(newLayout)
self.visited = True
pin.gds_write_file(gds_layout)
self.visited.append(self.name)
def gds_write(self, gds_name):
"""Write the entire gds of the object to the file."""
debug.info(3, "Writing to {0}".format(gds_name))
debug.info(3, "Writing to {}".format(gds_name))
# If we already wrote a GDS, we need to reset and traverse it again in
# case we made changes.
if not self.is_library_cell and self.visited:
debug.info(3, "Creating layout structure {}".format(self.name))
self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"])
writer = gdsMill.Gds2writer(self.gds)
# MRG: 3/2/18 We don't want to clear the visited flag since
# this would result in duplicates of all instances being placed in self.gds
# which may have been previously processed!
#self.clear_visited()
# MRG: 10/4/18 We need to clear if we make changes and write a second GDS!
self.clear_visited()
# recursively create all the remaining objects
self.gds_write_file(self.gds)
# populates the xyTree data structure for gds
# self.gds.prepareForWrite()
writer.writeToFile(gds_name)
debug.info(3, "Done writing to {}".format(gds_name))
def get_boundary(self):
""" Return the lower-left and upper-right coordinates of boundary """
@ -599,6 +631,7 @@ class layout(lef.lef):
"""
Connect a mapping of pin -> name for a bus. This could be
replaced with a channel router in the future.
NOTE: This has only really been tested with point-to-point connections (not multiple pins on a net).
"""
(horizontal_layer, via_layer, vertical_layer)=layer_stack
if horizontal:
@ -708,31 +741,61 @@ class layout(lef.lef):
self.add_wire(layer_stack, [pin.center(), mid, trunk_mid])
def create_channel_route(self, route_map, top_pins, bottom_pins, offset,
def create_channel_route(self, netlist, pins, offset,
layer_stack=("metal1", "via1", "metal2"), pitch=None,
vertical=False):
"""
This is a simple channel route for one-to-one connections that
will jog the top route whenever there is a conflict. It does NOT
try to minimize the number of tracks -- instead, it picks an order to avoid the vertical
conflicts between pins.
"""
The net list is a list of the nets. Each net is a list of pin
names to be connected. Pins is a dictionary of the pin names
to the pin structures. 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.
def remove_pin_from_graph(pin, g):
# Remove the pin from the keys
"""
def remove_net_from_graph(pin, g):
"""
Remove the pin from the graph and all conflicts
"""
g.pop(pin,None)
# Remove the pin from all conflicts
# This is O(n^2), so maybe optimize it.
# FIXME: This is O(n^2), so maybe optimize it.
for other_pin,conflicts in g.items():
if pin in conflicts:
conflicts.remove(pin)
vcg[other_pin]=conflicts
g[other_pin]=conflicts
return g
def vcg_nets_overlap(net1, net2, vertical):
"""
Check all the pin pairs on two nets and return a pin
overlap if any pin overlaps
"""
for pin1 in net1:
for pin2 in net2:
if vcg_pin_overlap(pin1, pin2, vertical):
return True
return False
def vcg_pin_overlap(pin1, pin2, vertical):
""" Check for vertical or horizontal overlap of the two pins """
# Pin 1 must be in the "BOTTOM" set
x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x-pin2.center().x)<pitch
# Pin 1 must be in the "LEFT" set
y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y-pin2.center().y)<pitch
overlaps = (not vertical and x_overlap) or (vertical and y_overlap)
return overlaps
if not pitch:
pitch = self.m2_pitch
# merge the two dictionaries to easily access all pins
all_pins = {**top_pins, **bottom_pins}
# FIXME: Must extend this to a horizontal conflict graph too if we want to minimize the
# number of tracks!
@ -740,53 +803,59 @@ class layout(lef.lef):
# Initialize the vertical conflict graph (vcg) and make a list of all pins
vcg = {}
for (top_name, bot_name) in route_map:
vcg[top_name] = []
vcg[bot_name] = []
# Create names for the nets for the graphs
nets = {}
index = 0
#print(netlist)
for pin_list in netlist:
net_name = "n{}".format(index)
index += 1
nets[net_name] = []
for pin_name in pin_list:
pin = pins[pin_name]
nets[net_name].append(pin)
# Find the vertical pin conflicts
# FIXME: O(n^2) but who cares for now
for top_name,top_pin in top_pins.items():
for bot_name,bot_pin in bottom_pins.items():
if not vertical and abs(top_pin.center().x-bot_pin.center().x) < pitch:
vcg[top_name].append(bot_name)
vcg[bot_name].append(top_name)
elif vertical and abs(top_pin.center().y-bot_pin.center().y) < pitch:
vcg[top_name].append(bot_name)
vcg[bot_name].append(top_name)
# This is the starting offset of the first trunk
if vertical:
half_minwidth = 0.5*drc["minwidth_{}".format(layer_stack[2])]
offset = offset + vector(half_minwidth,0)
else:
half_minwidth = 0.5*drc["minwidth_{}".format(layer_stack[0])]
offset = offset + vector(0,half_minwidth)
for net_name1 in nets:
if net_name1 not in vcg.keys():
vcg[net_name1]=[]
for net_name2 in nets:
if net_name2 not in vcg.keys():
vcg[net_name2]=[]
# Skip yourself
if net_name1 == net_name2:
continue
if vcg_nets_overlap(nets[net_name1], nets[net_name2], vertical):
vcg[net_name2].append(net_name1)
#FIXME: What if we have a cycle?
# list of routes to do
while vcg:
#print(vcg)
#from pprint import pformat
#print("VCG:\n",pformat(vcg))
# get a route from conflict graph with empty fanout set
route_pin=None
for route_pin,conflicts in vcg.items():
net_name=None
for net_name,conflicts in vcg.items():
if len(conflicts)==0:
remove_pin_from_graph(route_pin,vcg)
vcg=remove_net_from_graph(net_name,vcg)
break
else:
# FIXME: We don't support cyclic VCGs right now.
debug.error("Cyclic VCG in channel router.",-1)
# Get the connected pins from the routing map
for pin_connections in route_map:
if route_pin in pin_connections:
break
#print("Routing:",route_pin,pin_connections)
# Remove the other pins from the conflict graph too
for other_pin in pin_connections:
remove_pin_from_graph(other_pin, vcg)
# Create a list of the pins rather than a list of the names
pin_list = [all_pins[pin_name] for pin_name in pin_connections]
# Add the trunk route and move up to next track
# These are the pins we'll have to connect
pin_list = nets[net_name]
#print("Routing:",net_name,[x.name for x in pin_list])
# Remove the net from other constriants in the VCG
vcg=remove_net_from_graph(net_name, vcg)
# Add the trunk routes from the bottom up for horizontal or the left to right for vertical
if vertical:
self.add_vertical_trunk_route(pin_list, offset, layer_stack, pitch)
offset += vector(pitch,0)
@ -795,22 +864,22 @@ class layout(lef.lef):
offset += vector(0,pitch)
def create_vertical_channel_route(self, route_map, left_inst, right_inst, offset,
def create_vertical_channel_route(self, netlist, pins, offset,
layer_stack=("metal1", "via1", "metal2"),
pitch=None):
"""
Wrapper to create a vertical channel route
"""
self.create_channel_route(route_map, left_inst, right_inst, offset,
layer_stack, pitch, vertical=True)
self.create_channel_route(netlist, pins, offset, layer_stack,
pitch, vertical=True)
def create_horizontal_channel_route(self, route_map, top_pins, bottom_pins, offset,
layer_stack=("metal1", "via1", "metal2"),
pitch=None):
def create_horizontal_channel_route(self, netlist, pins, offset,
layer_stack=("metal1", "via1", "metal2"),
pitch=None):
"""
Wrapper to create a horizontal channel route
"""
self.create_channel_route(route_map, top_pins, bottom_pins, offset,
self.create_channel_route(netlist, pins, offset,
layer_stack, pitch, vertical=False)
def add_enclosure(self, insts, layer="nwell"):
@ -833,19 +902,37 @@ class layout(lef.lef):
width=xmax-xmin,
height=ymax-ymin)
def add_power_pin(self, name, loc, rotate=True):
def copy_power_pins(self, inst, name):
"""
This will copy a power pin if it is on M3. If it is on M1, it will add a power via too.
"""
pins=inst.get_pins(name)
for pin in pins:
if pin.layer=="metal3":
self.add_layout_pin(name, pin.layer, pin.ll(), pin.width(), pin.height())
elif pin.layer=="metal1":
self.add_power_pin(name, pin.center())
else:
debug.warning("{0} pins of {1} should be on metal3 or metal1 for supply router.".format(name,inst.name))
def add_power_pin(self, name, loc, rotate=90):
"""
Add a single power pin from M3 own to M1
Add a single power pin from M3 down to M1 at the given center location
"""
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=loc,
rotate=90 if rotate else 0)
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=loc,
rotate=90 if rotate else 0)
rotate=float(rotate))
via=self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=loc,
rotate=float(rotate))
self.add_layout_pin_rect_center(text=name,
layer="metal3",
offset=loc)
offset=loc,
width=via.width,
height=via.height)
def add_power_ring(self, bbox):
"""
@ -964,7 +1051,7 @@ class layout(lef.lef):
def pdf_write(self, pdf_name):
# NOTE: Currently does not work (Needs further research)
#self.pdf_name = self.name + ".pdf"
debug.info(0, "Writing to %s" % pdf_name)
debug.info(0, "Writing to {}".format(pdf_name))
pdf = gdsMill.pdfLayout(self.gds)
return

View File

@ -91,26 +91,33 @@ class spice(verilog.verilog):
group of modules are generated."""
if (check and (len(self.insts[-1].mod.pins) != len(args))):
debug.error("Connections: {}".format(self.insts[-1].mod.pins))
debug.error("Connections: {}".format(args))
from pprint import pformat
modpins_string=pformat(self.insts[-1].mod.pins)
argpins_string=pformat(args)
debug.error("Connections: {}".format(modpins_string))
debug.error("Connections: {}".format(argpins_string))
debug.error("Number of net connections ({0}) does not match last instance ({1})".format(len(self.insts[-1].mod.pins),
len(args)), 1)
self.conns.append(args)
if check and (len(self.insts)!=len(self.conns)):
from pprint import pformat
insts_string=pformat(self.insts)
conns_string=pformat(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(insts_string))
debug.error("-----")
debug.error("Connections: \n"+str(self.conns),1)
debug.error("Connections: \n"+str(conns_string),1)
def sp_read(self):
"""Reads the sp file (and parse the pins) from the library
Otherwise, initialize it to null for dynamic generation"""
if os.path.isfile(self.sp_file):
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)
self.spice = f.readlines()

View File

@ -1,115 +1,115 @@
import gdsMill
import tech
import globals
import math
import debug
import datetime
from collections import defaultdict
class lef:
"""
SRAM LEF Class open GDS file, read pins information, obstruction
and write them to LEF file
"""
def __init__(self,layers):
# LEF db units per micron
self.lef_units = 1000
# These are the layers of the obstructions
self.lef_layers = layers
def lef_write(self, lef_name):
"""Write the entire lef of the object to the file."""
debug.info(3, "Writing to {0}".format(lef_name))
self.indent = "" # To maintain the indent level easily
self.lef = open(lef_name,"w")
self.lef_write_header()
for pin in self.pins:
self.lef_write_pin(pin)
self.lef_write_obstructions()
self.lef_write_footer()
self.lef.close()
def lef_write_header(self):
""" Header of LEF file """
self.lef.write("VERSION 5.4 ;\n")
self.lef.write("NAMESCASESENSITIVE ON ;\n")
self.lef.write("BUSBITCHARS \"[]\" ;\n")
self.lef.write("DIVIDERCHAR \"/\" ;\n")
self.lef.write("UNITS\n")
self.lef.write(" DATABASE MICRONS {0} ;\n".format(self.lef_units))
self.lef.write("END UNITS\n")
self.lef.write("SITE MacroSite\n")
self.indent += " "
self.lef.write("{0}CLASS Core ;\n".format(self.indent))
self.lef.write("{0}SIZE {1} by {2} ;\n".format(self.indent,
self.lef_units*self.width,
self.lef_units*self.height))
self.indent = self.indent[:-3]
self.lef.write("END MacroSite\n")
self.lef.write("{0}MACRO {1}\n".format(self.indent,self.name))
self.indent += " "
self.lef.write("{0}CLASS BLOCK ;\n".format(self.indent))
self.lef.write("{0}SIZE {1} BY {2} ;\n" .format(self.indent,
self.lef_units*self.width,
self.lef_units*self.height))
self.lef.write("{0}SYMMETRY X Y R90 ;\n".format(self.indent))
self.lef.write("{0}SITE MacroSite ;\n".format(self.indent))
def lef_write_footer(self):
self.lef.write("{0}END {1}\n".format(self.indent,self.name))
self.indent = self.indent[:-3]
self.lef.write("END LIBRARY\n")
def lef_write_pin(self, name):
pin_dir = self.get_pin_dir(name)
pin_type = self.get_pin_type(name)
self.lef.write("{0}PIN {1}\n".format(self.indent,name))
self.indent += " "
self.lef.write("{0}DIRECTION {1} ;\n".format(self.indent,pin_dir))
if pin_type in ["POWER","GROUND"]:
self.lef.write("{0}USE {1} ; \n".format(self.indent,pin_type))
self.lef.write("{0}SHAPE ABUTMENT ; \n".format(self.indent))
self.lef.write("{0}PORT\n".format(self.indent))
self.indent += " "
# We could sort these together to minimize different layer sections, but meh.
pin_list = self.get_pins(name)
for pin in pin_list:
self.lef.write("{0}LAYER {1} ;\n".format(self.indent,pin.layer))
self.lef_write_rect(pin.rect)
# End the PORT
self.indent = self.indent[:-3]
self.lef.write("{0}END\n".format(self.indent))
# End the PIN
self.indent = self.indent[:-3]
self.lef.write("{0}END {1}\n".format(self.indent,name))
def lef_write_obstructions(self):
""" Write all the obstructions on each layer """
self.lef.write("{0}OBS\n".format(self.indent))
for layer in self.lef_layers:
self.lef.write("{0}LAYER {1} ;\n".format(self.indent,layer))
self.indent += " "
blockages = self.get_blockages(layer,True)
for b in blockages:
self.lef_write_rect(b)
self.indent = self.indent[:-3]
self.lef.write("{0}END\n".format(self.indent))
def lef_write_rect(self, rect):
""" Write a LEF rectangle """
self.lef.write("{0}RECT ".format(self.indent))
for item in rect:
self.lef.write(" {0} {1}".format(self.lef_units*item[0], self.lef_units*item[1]))
self.lef.write(" ;\n")
import gdsMill
import tech
import globals
import math
import debug
import datetime
from collections import defaultdict
class lef:
"""
SRAM LEF Class open GDS file, read pins information, obstruction
and write them to LEF file
"""
def __init__(self,layers):
# LEF db units per micron
self.lef_units = 1000
# These are the layers of the obstructions
self.lef_layers = layers
def lef_write(self, lef_name):
"""Write the entire lef of the object to the file."""
debug.info(3, "Writing to {0}".format(lef_name))
self.indent = "" # To maintain the indent level easily
self.lef = open(lef_name,"w")
self.lef_write_header()
for pin in self.pins:
self.lef_write_pin(pin)
self.lef_write_obstructions()
self.lef_write_footer()
self.lef.close()
def lef_write_header(self):
""" Header of LEF file """
self.lef.write("VERSION 5.4 ;\n")
self.lef.write("NAMESCASESENSITIVE ON ;\n")
self.lef.write("BUSBITCHARS \"[]\" ;\n")
self.lef.write("DIVIDERCHAR \"/\" ;\n")
self.lef.write("UNITS\n")
self.lef.write(" DATABASE MICRONS {0} ;\n".format(self.lef_units))
self.lef.write("END UNITS\n")
self.lef.write("SITE MacroSite\n")
self.indent += " "
self.lef.write("{0}CLASS Core ;\n".format(self.indent))
self.lef.write("{0}SIZE {1} by {2} ;\n".format(self.indent,
self.lef_units*self.width,
self.lef_units*self.height))
self.indent = self.indent[:-3]
self.lef.write("END MacroSite\n")
self.lef.write("{0}MACRO {1}\n".format(self.indent,self.name))
self.indent += " "
self.lef.write("{0}CLASS BLOCK ;\n".format(self.indent))
self.lef.write("{0}SIZE {1} BY {2} ;\n" .format(self.indent,
self.lef_units*self.width,
self.lef_units*self.height))
self.lef.write("{0}SYMMETRY X Y R90 ;\n".format(self.indent))
self.lef.write("{0}SITE MacroSite ;\n".format(self.indent))
def lef_write_footer(self):
self.lef.write("{0}END {1}\n".format(self.indent,self.name))
self.indent = self.indent[:-3]
self.lef.write("END LIBRARY\n")
def lef_write_pin(self, name):
pin_dir = self.get_pin_dir(name)
pin_type = self.get_pin_type(name)
self.lef.write("{0}PIN {1}\n".format(self.indent,name))
self.indent += " "
self.lef.write("{0}DIRECTION {1} ;\n".format(self.indent,pin_dir))
if pin_type in ["POWER","GROUND"]:
self.lef.write("{0}USE {1} ; \n".format(self.indent,pin_type))
self.lef.write("{0}SHAPE ABUTMENT ; \n".format(self.indent))
self.lef.write("{0}PORT\n".format(self.indent))
self.indent += " "
# We could sort these together to minimize different layer sections, but meh.
pin_list = self.get_pins(name)
for pin in pin_list:
self.lef.write("{0}LAYER {1} ;\n".format(self.indent,pin.layer))
self.lef_write_rect(pin.rect)
# End the PORT
self.indent = self.indent[:-3]
self.lef.write("{0}END\n".format(self.indent))
# End the PIN
self.indent = self.indent[:-3]
self.lef.write("{0}END {1}\n".format(self.indent,name))
def lef_write_obstructions(self):
""" Write all the obstructions on each layer """
self.lef.write("{0}OBS\n".format(self.indent))
for layer in self.lef_layers:
self.lef.write("{0}LAYER {1} ;\n".format(self.indent,layer))
self.indent += " "
blockages = self.get_blockages(layer,True)
for b in blockages:
self.lef_write_rect(b)
self.indent = self.indent[:-3]
self.lef.write("{0}END\n".format(self.indent))
def lef_write_rect(self, rect):
""" Write a LEF rectangle """
self.lef.write("{0}RECT ".format(self.indent))
for item in rect:
self.lef.write(" {0} {1}".format(self.lef_units*item[0], self.lef_units*item[1]))
self.lef.write(" ;\n")

View File

@ -1,7 +1,8 @@
import debug
from tech import GDS
from tech import GDS, drc
from vector import vector
from tech import layer
import math
class pin_layout:
"""
@ -18,6 +19,10 @@ class pin_layout:
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]
debug.check(self.width()>0,"Zero width pin.")
debug.check(self.height()>0,"Zero height pin.")
# 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)]
@ -30,32 +35,80 @@ class pin_layout:
return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1])
def __repr__(self):
""" override print function output """
return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1])
"""
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])
def __hash__(self):
""" Implement the hash function for sets etc. """
return hash(repr(self))
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.name==other.name and self.layer==other.layer and self.rect == other.rect)
return (self.layer==other.layer and self.rect == other.rect)
else:
return False
def overlaps(self, other):
def inflate(self, spacing=None):
"""
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
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 = self.rect[0]
ur = self.rect[1]
oll = other.rect[0]
our = other.rect[1]
# Start assuming no overlaps
(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)]
def xoverlaps(self, other):
""" Check if shape has x overlap """
(ll,ur) = self.rect
(oll,our) = other.rect
x_overlaps = False
y_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):
x_overlaps = True
# check if other is within self x range
if (oll.x >= ll.x and oll.x <= ur.x) or (our.x >= ll.x and our.x <= ur.x):
x_overlaps = True
return x_overlaps
def yoverlaps(self, other):
""" Check if shape has x overlap """
(ll,ur) = self.rect
(oll,our) = other.rect
y_overlaps = False
# check if self is within other y range
if (ll.y >= oll.y and ll.y <= our.y) or (ur.y >= oll.y and ur.y <= our.y):
y_overlaps = True
@ -63,7 +116,63 @@ class pin_layout:
if (oll.y >= ll.y and oll.y <= ur.y) or (our.y >= ll.y and our.y <= ur.y):
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
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
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:
return False
if not self.xcontains(other):
return False
if not self.ycontains(other):
return False
return True
def contained_by_any(self, shape_list):
""" Checks if shape is contained by any in the list """
for shape in shape_list:
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:
return False
x_overlaps = self.xoverlaps(other)
y_overlaps = self.yoverlaps(other)
return x_overlaps and y_overlaps
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)
@ -191,3 +300,163 @@ class pin_layout:
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
#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]
else:
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
def dist(x1, y1, x2, y2):
return 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
top = r1_ur.y < r2_ll.y
if top and left:
return dist(r1_ll.x, r1_ur.y, r2_ur.x, r2_ll.y)
elif left and bottom:
return dist(r1_ll.x, r1_ll.y, r2_ur.x, r2_ur.y)
elif bottom and right:
return dist(r1_ur.x, r1_ll.y, r2_ll.x, r2_ur.y)
elif right and top:
return dist(r1_ur.x, r1_ur.y, r2_ll.x, r2_ll.y)
elif left:
return r1_ll.x - r2_ur.x
elif right:
return r2_ll.x - r1.ur.x
elif bottom:
return r1_ll.y - r2_ur.y
elif top:
return r2_ll.y - r1_ur.y
else:
# rectangles intersect
return 0
def overlap_length(self, other):
"""
Calculate the intersection segment and determine its length
"""
if self.contains(other):
return math.inf
elif other.contains(self):
return math.inf
else:
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))
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
(if any)
"""
(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)
r1_lr = vector(r1_ur.x, r1_ll.y)
r2_ul = vector(r2_ll.x, r2_ur.y)
r2_lr = vector(r2_ur.x, r2_ll.y)
from itertools import tee
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
# R1 edges CW
r1_cw_points = [r1_ll, r1_ul, r1_ur, r1_lr, r1_ll]
r1_edges = []
for (p,q) in pairwise(r1_cw_points):
r1_edges.append([p,q])
# R2 edges CW
r2_cw_points = [r2_ll, r2_ul, r2_ur, r2_lr, r2_ll]
r2_edges = []
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
# Two pairs of them should intersect
intersections = []
for r1e in r1_edges:
for r2e in r2_edges:
i = self.segment_intersection(r1e, r2e)
if i:
intersections.append(i)
return intersections
def on_segment(self, p, q, r):
"""
Given three co-linear points, determine if q lies on segment pr
"""
if q[0] <= max(p[0], r[0]) and \
q[0] >= min(p[0], r[0]) and \
q[1] <= max(p[1], r[1]) and \
q[1] >= min(p[1], r[1]):
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
# 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:
x = (b2*c1 - b1*c2)/determinant
y = (a1*c2 - a2*c1)/determinant
r = [x,y]
if self.on_segment(a, r, b) and self.on_segment(c, r, d):
return [x, y]
return None

View File

@ -1,27 +1,34 @@
from tech import drc
import debug
from design import design
from contact import contact
from itertools import tee
from vector import vector
from vector3d import vector3d
class route():
class route(design):
"""
Object route (used by the router module)
Add a route of minimium metal width between a set of points.
The widths are the layer widths of the layer stack.
(Vias are in numer of vias.)
The wire must be completely rectilinear and the
z-dimension of the points refers to the layers (plus via)
z-dimension of the points refers to the layers.
The points are the center of the wire.
This can have non-preferred direction routing.
"""
def __init__(self, obj, layer_stack, path):
unique_route_id = 0
def __init__(self, obj, layer_stack, path, layer_widths=[None,1,None]):
name = "route_{0}".format(route.unique_route_id)
route.unique_route_id += 1
design.design.__init__(self, name)
design.__init__(self, name)
debug.info(3, "create route obj {0}".format(name))
self.obj = obj
self.layer_stack = layer_stack
self.layer_widths = layer_widths
self.path = path
self.setup_layers()
@ -29,16 +36,16 @@ class route():
def setup_layers(self):
(horiz_layer, via_layer, vert_layer) = self.layer_stack
self.via_layer_name = via_layer
self.vert_layer_name = vert_layer
self.vert_layer_width = drc["minwidth_{0}".format(vert_layer)]
self.horiz_layer_name = horiz_layer
self.horiz_layer_width = drc["minwidth_{0}".format(horiz_layer)]
(self.horiz_layer_name, self.via_layer, self.vert_layer_name) = self.layer_stack
(self.horiz_layer_width, self.num_vias, self.vert_layer_width) = self.layer_widths
if not self.vert_layer_width:
self.vert_layer_width = drc("minwidth_{0}".format(self.vert_layer_name))
if not self.horiz_layer_width:
self.horiz_layer_width = drc("minwidth_{0}".format(self.horiz_layer_name))
# offset this by 1/2 the via size
self.c=contact(self.layer_stack, (1, 1))
self.c=contact(self.layer_stack, (self.num_vias, self.num_vias))
def create_wires(self):
@ -52,30 +59,56 @@ class route():
next(b, None)
return zip(a, b)
plist = pairwise(self.path)
plist = list(pairwise(self.path))
for p0,p1 in plist:
if p0.z != p1.z: # via
# offset if not rotated
#via_offset = vector(p0.x+0.5*self.c.width,p0.y+0.5*self.c.height)
# offset if rotated
via_offset = vector(p0.x+0.5*self.c.height,p0.y-0.5*self.c.width)
self.obj.add_via(self.layer_stack,via_offset,rotate=90)
via_size = [self.num_vias]*2
self.obj.add_via_center(self.layer_stack,vector(p0.x,p0.y),size=via_size,rotate=90)
elif p0.x != p1.x and p0.y != p1.y: # diagonal!
debug.error("Non-changing direction!")
debug.error("Diagonal route! {}".format(self.path),-3)
else:
# this will draw an extra corner at the end but that is ok
self.draw_corner_wire(p1)
# draw the point to point wire
self.draw_wire(p0,p1)
# Draw the layers on the ends of the wires to ensure full width
# connections
self.draw_corner_wire(plist[0][0])
self.draw_corner_wire(plist[-1][1])
def get_layer_width(self, layer_zindex):
"""
Return the layer width
"""
if layer_zindex==0:
return self.horiz_layer_width
elif layer_zindex==1:
return self.vert_layer_width
else:
debug.error("Incorrect layer zindex.",-1)
def get_layer_name(self, layer_zindex):
"""
Return the layer name
"""
if layer_zindex==0:
return self.horiz_layer_name
elif layer_zindex==1:
return self.vert_layer_name
else:
debug.error("Incorrect layer zindex.",-1)
def draw_wire(self, p0, p1):
"""
This draws a straight wire with layer_minwidth
"""
layer_name = self.layer_stack[2*p0.z]
layer_width = drc["minwidth_{0}".format(layer_name)]
layer_width = self.get_layer_width(p0.z)
layer_name = self.get_layer_name(p0.z)
# always route left to right or bottom to top
if p0.z != p1.z:
@ -99,18 +132,18 @@ class route():
height = end.y - start.y
width = layer_width
deisgn.add_rect(layer=layer_name,
offset=offset,
width=width,
height=height)
self.obj.add_rect(layer=layer_name,
offset=vector(offset.x,offset.y),
width=width,
height=height)
def draw_corner_wire(self, p0):
""" This function adds the corner squares since the center
line convention only draws to the center of the corner."""
layer_name = self.layer_stack[2*p0.z]
layer_width = drc["minwidth_{0}".format(layer_name)]
layer_width = self.get_layer_width(p0.z)
layer_name = self.get_layer_name(p0.z)
offset = vector(p0.x-0.5*layer_width,p0.y-0.5*layer_width)
self.obj.add_rect(layer=layer_name,
offset=offset,

View File

@ -3,6 +3,7 @@ import gdsMill
import tech
import math
import globals
import debug
from vector import vector
from pin_layout import pin_layout
@ -37,12 +38,6 @@ def pin_center(boundary):
"""
return [0.5 * (boundary[0] + boundary[2]), 0.5 * (boundary[1] + boundary[3])]
def pin_rect(boundary):
"""
This returns a LL,UR point pair.
"""
return [vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])]
def auto_measure_libcell(pin_list, name, units, layer):
"""
Open a GDS file and find the pins in pin_list as text on a given layer.
@ -66,44 +61,60 @@ def auto_measure_libcell(pin_list, name, units, layer):
def get_gds_size(name, gds_filename, units, layer):
"""
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))
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.measureSize(name)
# returns width,height
return measure_result
def get_libcell_size(name, units, layer):
"""
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))
def get_gds_pins(pin_names, name, gds_filename, units):
"""
Open a GDS file and find the pins in pin_names as text on a given layer.
Return these as a rectangle layer pair for each pin.
"""
cell_vlsi = gdsMill.VlsiLayout(units=units)
reader = gdsMill.Gds2reader(cell_vlsi)
reader.loadFromFile(cell_gds)
reader.loadFromFile(gds_filename)
cell = {}
measure_result = cell_vlsi.getLayoutBorder(layer)
if measure_result == None:
measure_result = cell_vlsi.measureSize(name)
# returns width,height
return measure_result
for pin_name in pin_names:
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))
return cell
def get_libcell_pins(pin_list, name, units, layer):
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"
cell_vlsi = gdsMill.VlsiLayout(units=units)
reader = gdsMill.Gds2reader(cell_vlsi)
reader.loadFromFile(cell_gds)
cell = {}
for pin in pin_list:
cell[str(pin)]=[]
label_list=cell_vlsi.getPinShapeByLabel(str(pin))
for label in label_list:
(name,layer,boundary)=label
rect = pin_rect(boundary)
# this is a list because other cells/designs may have must-connect pins
cell[str(pin)].append(pin_layout(pin, rect, layer))
return cell
return(get_gds_pins(pin_list, name, cell_gds, units))

View File

@ -11,24 +11,24 @@ class vector():
concise vector operations, output, and other more complex
data structures like lists.
"""
def __init__(self, x, y=None):
def __init__(self, x, y=0):
""" init function support two init method"""
# will take single input as a coordinate
if y==None:
self.x = x[0]
self.y = x[1]
if isinstance(x, (list,tuple,vector)):
self.x = float(x[0])
self.y = float(x[1])
#will take two inputs as the values of a coordinate
else:
self.x = x
self.y = y
self.x = float(x)
self.y = float(y)
def __str__(self):
""" override print function output """
return "["+str(self.x)+","+str(self.y)+"]"
return "v["+str(self.x)+","+str(self.y)+"]"
def __repr__(self):
""" override print function output """
return "["+str(self.x)+","+str(self.y)+"]"
return "v["+str(self.x)+","+str(self.y)+"]"
def __setitem__(self, index, value):
"""
@ -36,12 +36,12 @@ class vector():
can set value by vector[index]=value
"""
if index==0:
self.x=value
self.x=float(value)
elif index==1:
self.y=value
self.y=float(value)
else:
self.x=value[0]
self.y=value[1]
self.x=float(value[0])
self.y=float(value[1])
def __getitem__(self, index):
"""
@ -84,6 +84,14 @@ class vector():
"""
return vector(other[0]- self.x, other[1] - self.y)
def __hash__(self):
"""
Override - function (hash)
Note: This assumes that you DON'T CHANGE THE VECTOR or it will
break things.
"""
return hash((self.x,self.y))
def snap_to_grid(self):
self.x = self.snap_offset_to_grid(self.x)
self.y = self.snap_offset_to_grid(self.y)

View File

@ -33,14 +33,14 @@ class wire(path):
self.via_layer_name = via_layer
self.vert_layer_name = vert_layer
self.vert_layer_width = drc["minwidth_{0}".format(vert_layer)]
self.vert_layer_width = drc("minwidth_{0}".format(vert_layer))
self.horiz_layer_name = horiz_layer
self.horiz_layer_width = drc["minwidth_{0}".format(horiz_layer)]
self.horiz_layer_width = drc("minwidth_{0}".format(horiz_layer))
via_connect = contact(self.layer_stack,
(1, 1))
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.node_to_node = [drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.width,
drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.height]
# create a 1x1 contact
def create_vias(self):

View File

@ -13,7 +13,7 @@ class bitcell(design.design):
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
(width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"])
def __init__(self):
design.design.__init__(self, "cell_6t")
@ -38,55 +38,33 @@ class bitcell(design.design):
def list_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 = ["bl[{0}]".format(col),
"br[{0}]".format(col),
"wl[{0}]".format(row),
bitcell_pins = ["bl_{0}".format(col),
"br_{0}".format(col),
"wl_{0}".format(row),
"vdd",
"gnd"]
return bitcell_pins
def list_row_pins(self):
""" Creates a list of all row pins (except for gnd and vdd) """
def list_all_wl_names(self):
""" Creates a list of all wordline pin names """
row_pins = ["wl"]
return row_pins
def list_read_row_pins(self):
""" Creates a list of row pins associated with read ports """
row_pins = ["wl"]
return row_pins
def list_write_row_pins(self):
""" Creates a list of row pins associated with write ports """
row_pins = ["wl"]
return row_pins
def list_column_pins(self):
""" Creates a list of all column pins (except for gnd and vdd) """
def list_all_bitline_names(self):
""" Creates a list of all bitline pin names (both bl and br) """
column_pins = ["bl", "br"]
return column_pins
def list_read_column_pins(self):
""" Creates a list of column pins associated with read ports """
def list_all_bl_names(self):
""" Creates a list of all bl pins names """
column_pins = ["bl"]
return column_pins
def list_read_bar_column_pins(self):
""" Creates a list of column pins associated with read_bar ports """
def list_all_br_names(self):
""" Creates a list of all br pins names """
column_pins = ["br"]
return column_pins
def list_write_column_pins(self):
""" Creates a list of column pins associated with write ports """
column_pins = ["bl"]
return column_pins
def list_write_bar_column_pins(self):
""" Creates a list of column pins asscociated with write_bar ports"""
column_pins = ["br"]
return column_pins
def analytical_power(self, proc, vdd, temp, load):
"""Bitcell power in nW. Only characterizes leakage."""
from tech import spice

View File

@ -0,0 +1,98 @@
import design
import debug
import utils
from tech import GDS,layer
class bitcell_1rw_1r(design.design):
"""
A single bit cell (6T, 8T, etc.) 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 = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
(width,height) = utils.get_libcell_size("cell_1rw_1r", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "cell_1rw_1r", GDS["unit"])
def __init__(self):
design.design.__init__(self, "cell_1rw_1r")
debug.info(2, "Create bitcell with 1RW and 1R Port")
self.width = bitcell_1rw_1r.width
self.height = bitcell_1rw_1r.height
self.pin_map = bitcell_1rw_1r.pin_map
def analytical_delay(self, slew, load=0, swing = 0.5):
# delay of bit cell is not like a driver(from WL)
# so the slew used should be 0
# it should not be slew dependent?
# because the value is there
# the delay is only over half transsmission gate
from tech import spice
r = spice["min_tx_r"]*3
c_para = spice["min_tx_drain_c"]
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = swing)
return result
def list_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),
"vdd",
"gnd"]
return bitcell_pins
def list_all_wl_names(self):
""" Creates a list of all wordline pin names """
row_pins = ["wl0", "wl1"]
return row_pins
def list_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
def list_all_bl_names(self):
""" Creates a list of all bl pins names """
column_pins = ["bl0", "bl1"]
return column_pins
def list_all_br_names(self):
""" Creates a list of all br pins names """
column_pins = ["br0", "br1"]
return column_pins
def list_read_bl_names(self):
""" Creates a list of bl pin names associated with read ports """
column_pins = ["bl0", "bl1"]
return column_pins
def list_read_br_names(self):
""" Creates a list of br pin names associated with read ports """
column_pins = ["br0", "br1"]
return column_pins
def list_write_bl_names(self):
""" Creates a list of bl pin names associated with write ports """
column_pins = ["bl0"]
return column_pins
def list_write_br_names(self):
""" Creates a list of br pin names asscociated with write ports"""
column_pins = ["br0"]
return column_pins
def analytical_power(self, proc, vdd, temp, load):
"""Bitcell power in nW. Only characterizes leakage."""
from tech import spice
leakage = spice["bitcell_leakage"]
dynamic = 0 #temporary
total_power = self.return_power(dynamic, leakage)
return total_power

View File

@ -0,0 +1,888 @@
import contact
import design
import debug
from tech import drc, parameter, spice
from vector import vector
from ptx import ptx
from globals import OPTS
class pbitcell(design.design):
"""
This module implements a parametrically sized multi-port bitcell,
with a variable number of read/write, write, and read ports
"""
def __init__(self, replica_bitcell=False):
self.num_rw_ports = OPTS.num_rw_ports
self.num_w_ports = OPTS.num_w_ports
self.num_r_ports = OPTS.num_r_ports
self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports
self.replica_bitcell = replica_bitcell
if self.replica_bitcell:
name = "replica_pbitcell_{0}RW_{1}W_{2}R".format(self.num_rw_ports, self.num_w_ports, self.num_r_ports)
else:
name = "pbitcell_{0}RW_{1}W_{2}R".format(self.num_rw_ports, self.num_w_ports, self.num_r_ports)
design.design.__init__(self, name)
debug.info(2, "create a multi-port bitcell with {0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports,
self.num_w_ports,
self.num_r_ports))
self.create_netlist()
# We must always create the bitcell layout because some transistor sizes in the other netlists depend on it
self.create_layout()
def create_netlist(self):
self.add_pins()
self.add_modules()
self.create_storage()
if(self.num_rw_ports > 0):
self.create_readwrite_ports()
if(self.num_w_ports > 0):
self.create_write_ports()
if(self.num_r_ports > 0):
self.create_read_ports()
def create_layout(self):
self.calculate_spacing()
self.calculate_postions()
self.place_storage()
self.route_storage()
self.route_rails()
if(self.num_rw_ports > 0):
self.place_readwrite_ports()
self.route_readwrite_access()
if(self.num_w_ports > 0):
self.place_write_ports()
self.route_write_access()
if(self.num_r_ports > 0):
self.place_read_ports()
self.route_read_access()
self.extend_well()
self.route_wordlines()
self.route_bitlines()
self.route_supply()
if self.replica_bitcell:
self.route_rbc_short()
# in netlist_only mode, calling offset_all_coordinates or translate_all will not be possible
# this function is not needed to calculate the dimensions of pbitcell in netlist_only mode though
if not OPTS.netlist_only:
self.offset_all_coordinates()
gnd_overlap = vector(0, 0.5*contact.well.width)
self.translate_all(gnd_overlap)
self.DRC_LVS()
def add_pins(self):
""" add pins and set names for bitlines and wordlines """
self.rw_bl_names = []
self.rw_br_names = []
self.w_bl_names = []
self.w_br_names = []
self.r_bl_names = []
self.r_br_names = []
self.rw_wl_names = []
self.w_wl_names = []
self.r_wl_names = []
port = 0
for k in range(self.num_rw_ports):
self.add_pin("bl{}".format(port))
self.add_pin("br{}".format(port))
self.rw_bl_names.append("bl{}".format(port))
self.rw_br_names.append("br{}".format(port))
port += 1
for k in range(self.num_w_ports):
self.add_pin("bl{}".format(port))
self.add_pin("br{}".format(port))
self.w_bl_names.append("bl{}".format(port))
self.w_br_names.append("br{}".format(port))
port += 1
for k in range(self.num_r_ports):
self.add_pin("bl{}".format(port))
self.add_pin("br{}".format(port))
self.r_bl_names.append("bl{}".format(port))
self.r_br_names.append("br{}".format(port))
port += 1
port = 0
for k in range(self.num_rw_ports):
self.add_pin("wl{}".format(port))
self.rw_wl_names.append("wl{}".format(port))
port += 1
for k in range(self.num_w_ports):
self.add_pin("wl{}".format(port))
self.w_wl_names.append("wl{}".format(port))
port += 1
for k in range(self.num_r_ports):
self.add_pin("wl{}".format(port))
self.r_wl_names.append("wl{}".format(port))
port += 1
self.add_pin("vdd")
self.add_pin("gnd")
# if this is a replica bitcell, replace the instances of Q_bar with vdd
if self.replica_bitcell:
self.Q_bar = "vdd"
else:
self.Q_bar = "Q_bar"
def add_modules(self):
""" Determine size of transistors and add ptx modules """
# if there are any read/write ports, then the inverter nmos is sized based the number of read/write ports
if(self.num_rw_ports > 0):
inverter_nmos_width = self.num_rw_ports*parameter["6T_inv_nmos_size"]
inverter_pmos_width = parameter["6T_inv_pmos_size"]
readwrite_nmos_width = parameter["6T_access_size"]
write_nmos_width = parameter["6T_access_size"]
read_nmos_width = 2*parameter["6T_inv_pmos_size"]
# if there are no read/write ports, then the inverter nmos is statically sized for the dual port case
else:
inverter_nmos_width = 2*parameter["6T_inv_pmos_size"]
inverter_pmos_width = parameter["6T_inv_pmos_size"]
readwrite_nmos_width = parameter["6T_access_size"]
write_nmos_width = parameter["6T_access_size"]
read_nmos_width = 2*parameter["6T_inv_pmos_size"]
# create ptx for inverter transistors
self.inverter_nmos = ptx(width=inverter_nmos_width,
tx_type="nmos")
self.add_mod(self.inverter_nmos)
self.inverter_pmos = ptx(width=inverter_pmos_width,
tx_type="pmos")
self.add_mod(self.inverter_pmos)
# create ptx for readwrite transitors
self.readwrite_nmos = ptx(width=readwrite_nmos_width,
tx_type="nmos")
self.add_mod(self.readwrite_nmos)
# create ptx for write transitors
self.write_nmos = ptx(width=write_nmos_width,
tx_type="nmos")
self.add_mod(self.write_nmos)
# create ptx for read transistors
self.read_nmos = ptx(width=read_nmos_width,
tx_type="nmos")
self.add_mod(self.read_nmos)
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)
write_nmos_contact_extension = 0.5*(self.write_nmos.active_contact.height - self.write_nmos.active_height)
read_nmos_contact_extension = 0.5*(self.read_nmos.active_contact.height - self.read_nmos.active_height)
max_contact_extension = max(readwrite_nmos_contact_extension, write_nmos_contact_extension, read_nmos_contact_extension)
# 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)
# y-position of access transistors
self.port_ypos = self.m1_space + 0.5*contact.m1m2.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 + self.m2_space + self.m2_width
m2_constraint = self.bitline_offset + self.m2_space + 0.5*contact.m1m2.height - 0.5*self.readwrite_nmos.active_width
self.write_port_spacing = max(self.active_space, self.m1_space, m2_constraint)
self.read_port_spacing = self.bitline_offset + self.m2_space
# spacing between cross coupled inverters
self.inverter_to_inverter_spacing = contact.poly.height + self.m1_space
# calculations related to inverter connections
inverter_pmos_contact_extension = 0.5*(self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height)
inverter_nmos_contact_extension = 0.5*(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.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
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
# spacing between wordlines (and gnd)
self.rowline_spacing = self.m1_space + contact.m1m2.width
self.rowline_offset = -0.5*self.m1_width
# spacing for vdd
implant_constraint = max(inverter_pmos_contact_extension, 0) + 2*self.implant_enclose_active + 0.5*(contact.well.width - 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
# read port dimensions
width_reduction = self.read_nmos.active_width - self.read_nmos.get_pin("D").cx()
self.read_port_width = 2*self.read_nmos.active_width - 2*width_reduction
def calculate_postions(self):
""" Calculate positions that describe the edges and dimensions of the cell """
self.botmost_ypos = -0.5*self.m1_width - self.total_ports*self.rowline_spacing
self.topmost_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height + self.vdd_offset
self.leftmost_xpos = -0.5*self.inverter_to_inverter_spacing - self.inverter_nmos.active_width \
- self.num_rw_ports*(self.readwrite_nmos.active_width + self.write_port_spacing) \
- self.num_w_ports*(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.width = -2*self.leftmost_xpos
self.height = self.topmost_ypos - self.botmost_ypos
self.center_ypos = 0.5*(self.topmost_ypos + self.botmost_ypos)
def create_storage(self):
"""
Creates the crossed coupled inverters that act as storage for the bitcell.
The stored value of the cell is denoted as "Q", and the inverted value as "Q_bar".
"""
# create active for nmos
self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left",
mod=self.inverter_nmos)
self.connect_inst(["Q", self.Q_bar, "gnd", "gnd"])
self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right",
mod=self.inverter_nmos)
self.connect_inst(["gnd", "Q", self.Q_bar, "gnd"])
# create active for pmos
self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left",
mod=self.inverter_pmos)
self.connect_inst(["Q", self.Q_bar, "vdd", "vdd"])
self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right",
mod=self.inverter_pmos)
self.connect_inst(["vdd", "Q", self.Q_bar, "vdd"])
def place_storage(self):
""" Places the transistors for the crossed coupled inverters in the bitcell """
# calculate transistor offsets
left_inverter_xpos = -0.5*self.inverter_to_inverter_spacing - self.inverter_nmos.active_width
right_inverter_xpos = 0.5*self.inverter_to_inverter_spacing
inverter_pmos_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap
# create active for nmos
self.inverter_nmos_left.place([left_inverter_xpos, self.inverter_nmos_ypos])
self.inverter_nmos_right.place([right_inverter_xpos, self.inverter_nmos_ypos])
# create active for pmos
self.inverter_pmos_left.place([left_inverter_xpos, inverter_pmos_ypos])
self.inverter_pmos_right.place([right_inverter_xpos, inverter_pmos_ypos])
# update furthest left and right transistor edges (this will propagate to further transistor offset calculations)
self.left_building_edge = left_inverter_xpos
self.right_building_edge = right_inverter_xpos + self.inverter_nmos.active_width
def route_storage(self):
""" Routes inputs and outputs of inverters to cross couple them """
# connect input (gate) of inverters
self.add_path("poly", [self.inverter_nmos_left.get_pin("G").uc(), self.inverter_pmos_left.get_pin("G").bc()])
self.add_path("poly", [self.inverter_nmos_right.get_pin("G").uc(), self.inverter_pmos_right.get_pin("G").bc()])
# connect output (drain/source) of inverters
self.add_path("metal1", [self.inverter_nmos_left.get_pin("D").uc(), self.inverter_pmos_left.get_pin("D").bc()], width=contact.well.second_layer_width)
self.add_path("metal1", [self.inverter_nmos_right.get_pin("S").uc(), self.inverter_pmos_right.get_pin("S").bc()], width=contact.well.second_layer_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, self.cross_couple_upper_ypos)
self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=contact_offset_left,
rotate=90)
contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x - 0.5*contact.poly.height, self.cross_couple_lower_ypos)
self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=contact_offset_right,
rotate=90)
# connect contacts to gate poly (cross couple connections)
gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").lc().x, contact_offset_left.y)
self.add_path("poly", [contact_offset_left, gate_offset_right])
gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").rc().x, contact_offset_right.y)
self.add_path("poly", [contact_offset_right, gate_offset_left])
def route_rails(self):
""" Adds gnd and vdd rails and connects them to the inverters """
# Add rails for vdd and gnd
gnd_ypos = self.rowline_offset - self.total_ports*self.rowline_spacing
self.gnd_position = vector(0, gnd_ypos)
self.gnd = self.add_layout_pin_rect_center(text="gnd",
layer="metal1",
offset=self.gnd_position,
width=self.width,
height=self.m1_width)
vdd_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height + self.vdd_offset
self.vdd_position = vector(0, vdd_ypos)
self.vdd = self.add_layout_pin_rect_center(text="vdd",
layer="metal1",
offset=self.vdd_position,
width=self.width,
height=self.m1_width)
def create_readwrite_ports(self):
"""
Creates read/write ports to the bit cell. A differential pair of transistor can both read and write, like in a 6T cell.
A read or write is enabled by setting a Read-Write-Wordline (RWWL) high, subsequently turning on the transistor.
The transistor is connected between a Read-Write-Bitline (RWBL) and the storage component of the cell (Q).
In a write operation, driving RWBL high or low sets the value of the cell.
In a read operation, RWBL is precharged, then is either remains high or is discharged depending on the value of the cell.
This is a differential design, so each write port has a mirrored port that connects RWBR to Q_bar.
"""
# define read/write transistor variables as empty arrays based on the number of read/write ports
self.readwrite_nmos_left = [None] * self.num_rw_ports
self.readwrite_nmos_right = [None] * self.num_rw_ports
# iterate over the number of read/write ports
for k in range(0,self.num_rw_ports):
# add read/write transistors
self.readwrite_nmos_left[k] = self.add_inst(name="readwrite_nmos_left{}".format(k),
mod=self.readwrite_nmos)
self.connect_inst([self.rw_bl_names[k], self.rw_wl_names[k], "Q", "gnd"])
self.readwrite_nmos_right[k] = self.add_inst(name="readwrite_nmos_right{}".format(k),
mod=self.readwrite_nmos)
self.connect_inst([self.Q_bar, self.rw_wl_names[k], self.rw_br_names[k], "gnd"])
def place_readwrite_ports(self):
""" Places read/write ports in the bit cell """
# define read/write transistor variables as empty arrays based on the number of read/write ports
self.rwwl_positions = [None] * self.num_rw_ports
self.rwbl_positions = [None] * self.num_rw_ports
self.rwbr_positions = [None] * self.num_rw_ports
# iterate over the number of read/write ports
for k in range(0,self.num_rw_ports):
# calculate read/write transistor offsets
left_readwrite_transistor_xpos = self.left_building_edge \
- (k+1)*self.write_port_spacing \
- (k+1)*self.readwrite_nmos.active_width
right_readwrite_transistor_xpos = self.right_building_edge \
+ (k+1)*self.write_port_spacing \
+ k*self.readwrite_nmos.active_width
# place read/write transistors
self.readwrite_nmos_left[k].place(offset=[left_readwrite_transistor_xpos, self.port_ypos])
self.readwrite_nmos_right[k].place(offset=[right_readwrite_transistor_xpos, self.port_ypos])
# add pin for RWWL
rwwl_ypos = self.rowline_offset - k*self.rowline_spacing
self.rwwl_positions[k] = vector(0, rwwl_ypos)
self.add_layout_pin_rect_center(text=self.rw_wl_names[k],
layer="metal1",
offset=self.rwwl_positions[k],
width=self.width,
height=self.m1_width)
# add pins for RWBL and RWBR
rwbl_xpos = left_readwrite_transistor_xpos - self.bitline_offset + 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",
offset=self.rwbl_positions[k],
width=drc["minwidth_metal2"],
height=self.height)
rwbr_xpos = right_readwrite_transistor_xpos + self.readwrite_nmos.active_width + self.bitline_offset - 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",
offset=self.rwbr_positions[k],
width=drc["minwidth_metal2"],
height=self.height)
# update furthest left and right transistor edges
self.left_building_edge = left_readwrite_transistor_xpos
self.right_building_edge = right_readwrite_transistor_xpos + self.readwrite_nmos.active_width
def create_write_ports(self):
"""
Creates write ports in the bit cell. A differential pair of transistors can write only.
A write is enabled by setting a Write-Rowline (WWL) high, subsequently turning on the transistor.
The transistor is connected between a Write-Bitline (WBL) and the storage component of the cell (Q).
In a write operation, driving WBL high or low sets the value of the cell.
This is a differential design, so each write port has a mirrored port that connects WBR to Q_bar.
"""
# define write transistor variables as empty arrays based on the number of write ports
self.write_nmos_left = [None] * self.num_w_ports
self.write_nmos_right = [None] * self.num_w_ports
# iterate over the number of write ports
for k in range(0,self.num_w_ports):
# add write transistors
self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k),
mod=self.write_nmos)
self.connect_inst([self.w_bl_names[k], self.w_wl_names[k], "Q", "gnd"])
self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k),
mod=self.write_nmos)
self.connect_inst([self.Q_bar, self.w_wl_names[k], self.w_br_names[k], "gnd"])
def place_write_ports(self):
""" Places write ports in the bit cell """
# define write transistor variables as empty arrays based on the number of write ports
self.wwl_positions = [None] * self.num_w_ports
self.wbl_positions = [None] * self.num_w_ports
self.wbr_positions = [None] * self.num_w_ports
# iterate over the number of write ports
for k in range(0,self.num_w_ports):
# Add transistors
# calculate write transistor offsets
left_write_transistor_xpos = self.left_building_edge \
- (k+1)*self.write_port_spacing \
- (k+1)*self.write_nmos.active_width
right_write_transistor_xpos = self.right_building_edge \
+ (k+1)*self.write_port_spacing \
+ k*self.write_nmos.active_width
# add write transistors
self.write_nmos_left[k].place(offset=[left_write_transistor_xpos, self.port_ypos])
self.write_nmos_right[k].place(offset=[right_write_transistor_xpos, self.port_ypos])
# add pin for WWL
wwl_ypos = rwwl_ypos = self.rowline_offset - self.num_rw_ports*self.rowline_spacing - k*self.rowline_spacing
self.wwl_positions[k] = vector(0, wwl_ypos)
self.add_layout_pin_rect_center(text=self.w_wl_names[k],
layer="metal1",
offset=self.wwl_positions[k],
width=self.width,
height=self.m1_width)
# add pins for WBL and WBR
wbl_xpos = left_write_transistor_xpos - self.bitline_offset + 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",
offset=self.wbl_positions[k],
width=drc["minwidth_metal2"],
height=self.height)
wbr_xpos = right_write_transistor_xpos + self.write_nmos.active_width + self.bitline_offset - 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",
offset=self.wbr_positions[k],
width=drc["minwidth_metal2"],
height=self.height)
# update furthest left and right transistor edges
self.left_building_edge = left_write_transistor_xpos
self.right_building_edge = right_write_transistor_xpos + self.write_nmos.active_width
def create_read_ports(self):
"""
Creates read ports in the bit cell. A differential pair of ports can read only.
Two transistors function as a read port, denoted as the "read transistor" and the "read-access transistor".
The read transistor is connected to RWL (gate), RBL (drain), and the read-access transistor (source).
The read-access transistor is connected to Q_bar (gate), gnd (source), and the read transistor (drain).
A read is enabled by setting a Read-Rowline (RWL) high, subsequently turning on the read transistor.
The Read-Bitline (RBL) is precharged to high, and when the value of Q_bar is high, the read-access transistor
is turned on, creating a connection between RBL and gnd. RBL subsequently discharges allowing for a differential read
using sense amps. This is a differential design, so each read port has a mirrored port that connects RBL_bar to Q.
"""
# define read transistor variables as empty arrays based on the number of read ports
self.read_nmos_left = [None] * self.num_r_ports
self.read_nmos_right = [None] * self.num_r_ports
self.read_access_nmos_left = [None] * self.num_r_ports
self.read_access_nmos_right = [None] * self.num_r_ports
# iterate over the number of read ports
for k in range(0,self.num_r_ports):
# add read-access transistors
self.read_access_nmos_left[k] = self.add_inst(name="read_access_nmos_left{}".format(k),
mod=self.read_nmos)
self.connect_inst(["RA_to_R_left{}".format(k), self.Q_bar, "gnd", "gnd"])
self.read_access_nmos_right[k] = self.add_inst(name="read_access_nmos_right{}".format(k),
mod=self.read_nmos)
self.connect_inst(["gnd", "Q", "RA_to_R_right{}".format(k), "gnd"])
# add read transistors
self.read_nmos_left[k] = self.add_inst(name="read_nmos_left{}".format(k),
mod=self.read_nmos)
self.connect_inst([self.r_bl_names[k], self.r_wl_names[k], "RA_to_R_left{}".format(k), "gnd"])
self.read_nmos_right[k] = self.add_inst(name="read_nmos_right{}".format(k),
mod=self.read_nmos)
self.connect_inst(["RA_to_R_right{}".format(k), self.r_wl_names[k], self.r_br_names[k], "gnd"])
def place_read_ports(self):
""" Places the read ports in the bit cell """
# define read transistor variables as empty arrays based on the number of read ports
self.rwl_positions = [None] * self.num_r_ports
self.rbl_positions = [None] * self.num_r_ports
self.rbr_positions = [None] * self.num_r_ports
# calculate offset to overlap the drain of the read-access transistor with the source of the read transistor
overlap_offset = self.read_nmos.get_pin("D").cx() - self.read_nmos.get_pin("S").cx()
# iterate over the number of read ports
for k in range(0,self.num_r_ports):
# calculate transistor offsets
left_read_transistor_xpos = self.left_building_edge \
- (k+1)*self.read_port_spacing \
- (k+1)*self.read_port_width
right_read_transistor_xpos = self.right_building_edge \
+ (k+1)*self.read_port_spacing \
+ k*self.read_port_width
# add read-access transistors
self.read_access_nmos_left[k].place(offset=[left_read_transistor_xpos+overlap_offset, self.port_ypos])
self.read_access_nmos_right[k].place(offset=[right_read_transistor_xpos, self.port_ypos])
# add read transistors
self.read_nmos_left[k].place(offset=[left_read_transistor_xpos, self.port_ypos])
self.read_nmos_right[k].place(offset=[right_read_transistor_xpos+overlap_offset, self.port_ypos])
# add pin for RWL
rwl_ypos = rwwl_ypos = self.rowline_offset - self.num_rw_ports*self.rowline_spacing - self.num_w_ports*self.rowline_spacing - k*self.rowline_spacing
self.rwl_positions[k] = vector(0, rwl_ypos)
self.add_layout_pin_rect_center(text=self.r_wl_names[k],
layer="metal1",
offset=self.rwl_positions[k],
width=self.width,
height=self.m1_width)
# add pins for RBL and RBR
rbl_xpos = left_read_transistor_xpos - self.bitline_offset + 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",
offset=self.rbl_positions[k],
width=drc["minwidth_metal2"],
height=self.height)
rbr_xpos = right_read_transistor_xpos + self.read_port_width + self.bitline_offset - 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",
offset=self.rbr_positions[k],
width=drc["minwidth_metal2"],
height=self.height)
def route_wordlines(self):
""" Routes gate of transistors to their respective wordlines """
port_transistors = []
for k in range(self.num_rw_ports):
port_transistors.append(self.readwrite_nmos_left[k])
port_transistors.append(self.readwrite_nmos_right[k])
for k in range(self.num_w_ports):
port_transistors.append(self.write_nmos_left[k])
port_transistors.append(self.write_nmos_right[k])
for k in range(self.num_r_ports):
port_transistors.append(self.read_nmos_left[k])
port_transistors.append(self.read_nmos_right[k])
wl_positions = []
for k in range(self.num_rw_ports):
wl_positions.append(self.rwwl_positions[k])
wl_positions.append(self.rwwl_positions[k])
for k in range(self.num_w_ports):
wl_positions.append(self.wwl_positions[k])
wl_positions.append(self.wwl_positions[k])
for k in range(self.num_r_ports):
wl_positions.append(self.rwl_positions[k])
wl_positions.append(self.rwl_positions[k])
for k in range(2*self.total_ports):
gate_offset = port_transistors[k].get_pin("G").bc()
port_contact_offset = gate_offset + vector(0, -self.gate_contact_yoffset + self.poly_extend_active)
wl_contact_offset = vector(gate_offset.x, wl_positions[k].y)
# 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_contact_center(layers=("poly", "contact", "metal1"),
offset=port_contact_offset)
self.add_path("poly", [gate_offset, port_contact_offset])
self.add_path("metal1", [port_contact_offset, wl_contact_offset])
else:
self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=port_contact_offset)
self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=port_contact_offset)
self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=wl_contact_offset,
rotate=90)
self.add_path("poly", [gate_offset, port_contact_offset])
self.add_path("metal2", [port_contact_offset, wl_contact_offset])
def route_bitlines(self):
""" Routes read/write transistors to their respective bitlines """
left_port_transistors = []
right_port_transistors = []
for k in range(self.num_rw_ports):
left_port_transistors.append(self.readwrite_nmos_left[k])
right_port_transistors.append(self.readwrite_nmos_right[k])
for k in range(self.num_w_ports):
left_port_transistors.append(self.write_nmos_left[k])
right_port_transistors.append(self.write_nmos_right[k])
for k in range(self.num_r_ports):
left_port_transistors.append(self.read_nmos_left[k])
right_port_transistors.append(self.read_nmos_right[k])
bl_positions = []
br_positions = []
for k in range(self.num_rw_ports):
bl_positions.append(self.rwbl_positions[k])
br_positions.append(self.rwbr_positions[k])
for k in range(self.num_w_ports):
bl_positions.append(self.wbl_positions[k])
br_positions.append(self.wbr_positions[k])
for k in range(self.num_r_ports):
bl_positions.append(self.rbl_positions[k])
br_positions.append(self.rbr_positions[k])
for k in range(self.total_ports):
port_contact_offest = left_port_transistors[k].get_pin("S").center()
bl_offset = vector(bl_positions[k].x, port_contact_offest.y)
self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=port_contact_offest)
self.add_path("metal2", [port_contact_offest, bl_offset], width=contact.m1m2.height)
for k in range(self.total_ports):
port_contact_offest = right_port_transistors[k].get_pin("D").center()
br_offset = vector(br_positions[k].x, port_contact_offest.y)
self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=port_contact_offest)
self.add_path("metal2", [port_contact_offest, br_offset], width=contact.m1m2.height)
def route_supply(self):
""" Route inverter nmos and read-access nmos to gnd. Route inverter pmos to vdd. """
# route inverter nmos and read-access nmos to gnd
nmos_contact_positions = []
nmos_contact_positions.append(self.inverter_nmos_left.get_pin("S").center())
nmos_contact_positions.append(self.inverter_nmos_right.get_pin("D").center())
for k in range(self.num_r_ports):
nmos_contact_positions.append(self.read_access_nmos_left[k].get_pin("D").center())
nmos_contact_positions.append(self.read_access_nmos_right[k].get_pin("S").center())
for position in nmos_contact_positions:
self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=position)
if position.x > 0:
contact_correct = 0.5*contact.m1m2.height
else:
contact_correct = -0.5*contact.m1m2.height
supply_offset = vector(position.x + contact_correct, self.gnd_position.y)
self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=supply_offset,
rotate=90)
self.add_path("metal2", [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.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.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right])
def route_readwrite_access(self):
""" Routes read/write transistors to the storage component of the bitcell """
for k in range(self.num_rw_ports):
mid = vector(self.readwrite_nmos_left[k].get_pin("D").uc().x, 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.readwrite_nmos_left[k].get_pin("D").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
self.add_path("metal1", [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.readwrite_nmos_right[k].get_pin("S").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
self.add_path("metal1", [mid, Q_bar_pos])
def route_write_access(self):
""" Routes read/write transistors to the storage component of the bitcell """
for k in range(self.num_w_ports):
mid = vector(self.write_nmos_left[k].get_pin("D").uc().x, 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.write_nmos_left[k].get_pin("D").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
self.add_path("metal1", [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.write_nmos_right[k].get_pin("S").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
self.add_path("metal1", [mid, Q_bar_pos])
def route_read_access(self):
""" Routes read access transistors to the storage component of the bitcell """
# add poly to metal1 contacts for gates of the inverters
left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x - drc["poly_to_polycontact"] - 0.5*contact.poly.width, self.cross_couple_upper_ypos)
self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=left_storage_contact,
rotate=90)
right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x + drc["poly_to_polycontact"] + 0.5*contact.poly.width, self.cross_couple_upper_ypos)
self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=right_storage_contact,
rotate=90)
inverter_gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_upper_ypos)
self.add_path("poly", [left_storage_contact, inverter_gate_offset_left])
inverter_gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").rc().x, self.cross_couple_upper_ypos)
self.add_path("poly", [right_storage_contact, inverter_gate_offset_right])
# add poly to metal1 contacts for gates of read-access transistors
# route from read-access contacts to inverter contacts on metal1
for k in range(self.num_r_ports):
port_contact_offset = self.read_access_nmos_left[k].get_pin("G").uc() + vector(0, self.gate_contact_yoffset - self.poly_extend_active)
self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=port_contact_offset)
self.add_path("poly", [self.read_access_nmos_left[k].get_pin("G").uc(), port_contact_offset])
mid = vector(self.read_access_nmos_left[k].get_pin("G").uc().x, self.cross_couple_upper_ypos)
self.add_path("metal1", [port_contact_offset, mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
self.add_path("metal1", [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_contact_center(layers=("poly", "contact", "metal1"),
offset=port_contact_offset)
self.add_path("poly", [self.read_access_nmos_right[k].get_pin("G").uc(), port_contact_offset])
mid = vector(self.read_access_nmos_right[k].get_pin("G").uc().x, self.cross_couple_upper_ypos)
self.add_path("metal1", [port_contact_offset, mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
self.add_path("metal1", [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)
# 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) - drc["well_enclosure_active"]
inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap - drc["well_enclosure_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*drc["well_enclosure_active"]
well_height = self.vdd_position.y - inverter_well_ypos + drc["well_enclosure_active"] + drc["minwidth_tx"]
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_contact_center(layers=("active", "contact", "metal1"),
offset=offset,
rotate=90,
implant_type="p",
well_type="p")
# connect nimplants to vdd
offset = vector(0, self.vdd_position.y)
self.add_contact_center(layers=("active", "contact", "metal1"),
offset=offset,
rotate=90,
implant_type="n",
well_type="n")
def list_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 = []
for port in range(self.total_ports):
bitcell_pins.append("bl{0}_{1}".format(port,col))
bitcell_pins.append("br{0}_{1}".format(port,col))
for port in range(self.total_ports):
bitcell_pins.append("wl{0}_{1}".format(port,row))
bitcell_pins.append("vdd")
bitcell_pins.append("gnd")
return bitcell_pins
def list_all_wl_names(self):
""" Creates a list of all wordline pin names """
wordline_names = self.rw_wl_names + self.w_wl_names + self.r_wl_names
return wordline_names
def list_all_bitline_names(self):
""" Creates a list of all bitline pin names (both bl and br) """
bitline_pins = []
for port in range(self.total_ports):
bitline_pins.append("bl{0}".format(port))
bitline_pins.append("br{0}".format(port))
return bitline_pins
def list_all_bl_names(self):
""" Creates a list of all bl pins names """
bl_pins = self.rw_bl_names + self.w_bl_names + self.r_bl_names
return bl_pins
def list_all_br_names(self):
""" Creates a list of all br pins names """
br_pins = self.rw_br_names + self.w_br_names + self.r_br_names
return br_pins
def route_rbc_short(self):
""" route the short from Q_bar to gnd necessary for the replica bitcell """
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])
def analytical_delay(self, slew, load=0, swing = 0.5):
#FIXME: Delay copied exactly over from bitcell
from tech import spice
r = spice["min_tx_r"]*3
c_para = spice["min_tx_drain_c"]
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = swing)
return result
def analytical_power(self, proc, vdd, temp, load):
"""Bitcell power in nW. Only characterizes leakage."""
from tech import spice
leakage = spice["bitcell_leakage"]
dynamic = 0 #temporary
total_power = self.return_power(dynamic, leakage)
return total_power

View File

@ -10,9 +10,9 @@ 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"]
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
(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"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"])
def __init__(self):
design.design.__init__(self, "replica_cell_6t")

View File

@ -0,0 +1,23 @@
import design
import debug
import utils
from tech import GDS,layer
class replica_bitcell_1rw_1r(design.design):
"""
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 = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
(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"])
def __init__(self):
design.design.__init__(self, "replica_cell_1rw_1r")
debug.info(2, "Create replica bitcell 1rw+1r object")
self.width = replica_bitcell_1rw_1r.width
self.height = replica_bitcell_1rw_1r.height
self.pin_map = replica_bitcell_1rw_1r.pin_map

View File

@ -0,0 +1,82 @@
import debug
import design
from tech import drc, spice
from vector import vector
from globals import OPTS
from pbitcell import pbitcell
class replica_pbitcell(design.design):
"""
Creates a replica bitcell using pbitcell
"""
def __init__(self):
self.num_rw_ports = OPTS.num_rw_ports
self.num_w_ports = OPTS.num_w_ports
self.num_r_ports = OPTS.num_r_ports
self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports
design.design.__init__(self, "replica_pbitcell")
debug.info(1, "create a replica bitcell using pbitcell with {0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports,
self.num_w_ports,
self.num_r_ports))
self.create_netlist()
self.create_layout()
def create_netlist(self):
self.add_pins()
self.add_modules()
self.create_modules()
def create_layout(self):
self.place_pbitcell()
self.route_rbc_connections()
self.DRC_LVS()
def add_pins(self):
for port in range(self.total_ports):
self.add_pin("bl{}".format(port))
self.add_pin("br{}".format(port))
for port in range(self.total_ports):
self.add_pin("wl{}".format(port))
self.add_pin("vdd")
self.add_pin("gnd")
def add_modules(self):
self.prbc = pbitcell(replica_bitcell=True)
self.add_mod(self.prbc)
self.height = self.prbc.height
self.width = self.prbc.width
def create_modules(self):
self.prbc_inst = self.add_inst(name="pbitcell",
mod=self.prbc)
temp = []
for port in range(self.total_ports):
temp.append("bl{}".format(port))
temp.append("br{}".format(port))
for port in range(self.total_ports):
temp.append("wl{}".format(port))
temp.append("vdd")
temp.append("gnd")
self.connect_inst(temp)
def place_pbitcell(self):
offset = [0,0]
self.prbc_inst.place(offset=offset)
def route_rbc_connections(self):
for port in range(self.total_ports):
self.copy_layout_pin(self.prbc_inst, "bl{}".format(port))
self.copy_layout_pin(self.prbc_inst, "br{}".format(port))
for port in range(self.total_ports):
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

@ -5,6 +5,9 @@ from globals import OPTS,find_exe,get_tool
from .lib import *
from .delay import *
from .setup_hold import *
from .functional import *
from .worst_case import *
from .simulation import *
debug.info(1,"Initializing characterizer...")
@ -15,7 +18,7 @@ if not OPTS.analytical_delay:
if OPTS.spice_name != "":
OPTS.spice_exe=find_exe(OPTS.spice_name)
if OPTS.spice_exe=="":
if OPTS.spice_exe=="" or OPTS.spice_exe==None:
debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name),1)
else:
(OPTS.spice_name,OPTS.spice_exe) = get_tool("spice",["xa", "hspice", "ngspice", "ngspice.exe"])

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,290 @@
import sys,re,shutil
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
class functional(simulation):
"""
Functions to write random data values to a random address then read them back and check
for successful SRAM operation.
"""
def __init__(self, sram, spfile, corner):
simulation.__init__(self, sram, spfile, corner)
# Seed the characterizer with a constant seed for unit tests
if OPTS.is_unit_test:
random.seed(12345)
self.set_corner(corner)
self.set_spice_constants()
self.set_stimulus_variables()
self.create_signal_names()
# Number of checks can be changed
self.num_cycles = 2
self.stored_words = {}
self.write_check = []
self.read_check = []
def set_spice_constants(self):
"""Spice constants for functional test"""
simulation.set_spice_constants(self)
#Heuristic increase for functional period. Base feasible period typically does not pass the functional test
#for column mux of this size. Increase the feasible period by 20% for this case.
if self.sram.words_per_row >= 4:
self.period = self.period*1.2
def run(self):
# Generate a random sequence of reads and writes
self.write_random_memory_sequence()
# Run SPICE simulation
self.write_functional_stimulus()
self.stim.run_sim()
# read DOUT values from SPICE simulation. If the values do not fall within the noise margins, return the error.
(success, error) = self.read_stim_results()
if not success:
return (0, error)
# Check read values with written values. If the values do not match, return an error.
return self.check_stim_results()
def write_random_memory_sequence(self):
rw_ops = ["noop", "write", "read"]
w_ops = ["noop", "write"]
r_ops = ["noop", "read"]
rw_read_din_data = "0"*self.word_size
check = 0
# First cycle idle
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, 0, self.t_current)
self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size)
# Write at least once
addr = self.gen_addr()
word = self.gen_data()
comment = self.gen_cycle_comment("write", word, addr, 0, self.t_current)
self.add_write(comment, addr, word, 0)
self.stored_words[addr] = word
# Read at least once. For multiport, it is important that one read cycle uses all RW and R port to read from the same address simultaniously.
# This will test the viablilty of the transistor sizing in the bitcell.
for port in self.all_ports:
if port in self.write_ports:
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
else:
comment = self.gen_cycle_comment("read", word, addr, port, self.t_current)
self.add_read_one_port(comment, addr, rw_read_din_data, port)
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
check += 1
self.cycle_times.append(self.t_current)
self.t_current += self.period
# Perform a random sequence of writes and reads on random ports, using random addresses and random words
for i in range(self.num_cycles):
w_addrs = []
for port in self.all_ports:
if port in self.readwrite_ports:
op = random.choice(rw_ops)
elif port in self.write_ports:
op = random.choice(w_ops)
else:
op = random.choice(r_ops)
if op == "noop":
addr = "0"*self.addr_size
word = "0"*self.word_size
self.add_noop_one_port(addr, word, port)
elif op == "write":
addr = self.gen_addr()
word = self.gen_data()
# two ports cannot write to the same address
if addr in w_addrs:
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
else:
comment = self.gen_cycle_comment("write", word, addr, port, self.t_current)
self.add_write_one_port(comment, addr, word, port)
self.stored_words[addr] = word
w_addrs.append(addr)
else:
(addr,word) = random.choice(list(self.stored_words.items()))
# cannot read from an address that is currently being written to
if addr in w_addrs:
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
else:
comment = self.gen_cycle_comment("read", word, addr, port, self.t_current)
self.add_read_one_port(comment, addr, rw_read_din_data, port)
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
check += 1
self.cycle_times.append(self.t_current)
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.t_current)
self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size)
def read_stim_results(self):
# Extrat DOUT values from spice timing.lis
for (word, dout_port, eo_period, check) in self.write_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))
if value > 0.88 * self.vdd_voltage:
sp_read_value = "1" + sp_read_value
elif value < 0.12 * self.vdd_voltage:
sp_read_value = "0" + sp_read_value
else:
error ="FAILED: {0}_{1} value {2} at time {3}n does not fall within noise margins <{4} or >{5}.".format(dout_port,
bit,
value,
eo_period,
0.12*self.vdd_voltage,
0.88*self.vdd_voltage)
return (0, error)
self.read_check.append([sp_read_value, dout_port, eo_period, check])
return (1, "SUCCESS")
def check_stim_results(self):
for i in range(len(self.write_check)):
if self.write_check[i][0] != self.read_check[i][0]:
error = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n".format(self.read_check[i][1],
self.read_check[i][0],
self.write_check[i][0],
int((self.read_check[i][2]-self.period)/self.period),
self.read_check[i][2])
return(0, error)
return(1, "SUCCESS")
def gen_data(self):
""" Generates a random word to write. """
rand = random.randint(0,(2**self.word_size)-1)
data_bits = self.convert_to_bin(rand,False)
return data_bits
def gen_addr(self):
""" Generates a random address value to write to. """
rand = random.randint(0,(2**self.addr_size)-1)
addr_bits = self.convert_to_bin(rand,True)
return addr_bits
def get_data(self):
""" Gets an available address and corresponding word. """
# Currently unused but may need later depending on how the functional test develops
addr = random.choice(self.stored_words.keys())
word = self.stored_words[addr]
return (addr,word)
def convert_to_bin(self,value,is_addr):
""" Converts addr & word to usable binary values. """
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)):
new_value = "0" + new_value
#print("Binary Conversion: {} to {}".format(value, new_value))
return new_value
def create_signal_names(self):
self.addr_name = "A"
self.din_name = "DIN"
self.dout_name = "DOUT"
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.write("* Functional test stimulus file for {}ns period\n\n".format(self.period))
self.stim = stimuli(self.sf,self.corner)
#Write include statements
self.sram_sp_file = "{}sram.sp".format(OPTS.openram_temp)
shutil.copy(self.sp_file, self.sram_sp_file)
self.stim.write_include(self.sram_sp_file)
#Write Vdd/Gnd statements
self.sf.write("\n* Global Power Supplies\n")
self.stim.write_supply()
#Instantiate the SRAM
self.sf.write("\n* Instantiation of the SRAM\n")
self.stim.inst_sram(sram=self.sram,
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,
sram_name=self.name)
# 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):
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))
# Write debug comments to stim file
self.sf.write("\n\n * Sequence of operations\n")
for comment in self.fn_cycle_comments:
self.sf.write("*{}\n".format(comment))
# 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):
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)
# Generate address bits
for port in self.all_ports:
for bit in range(self.addr_size):
sig_name="{0}{1}_{2} ".format(self.addr_name, port, bit)
self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][bit], self.period, self.slew, 0.05)
# 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)
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)
# Generate CLK signals
for port in self.all_ports:
self.stim.gen_pulse(sig_name="{0}{1}".format(tech.spice["clk"], port),
v1=self.gnd_voltage,
v2=self.vdd_voltage,
offset=self.period,
period=self.period,
t_rise=self.slew,
t_fall=self.slew)
# Generate DOUT value measurements
self.sf.write("\n * Generation of dout measurements\n")
for (word, dout_port, eo_period, check) in self.write_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=t_intital,
t_final=t_final)
self.stim.write_control(self.cycle_times[-1] + self.period)
self.sf.close()

View File

@ -12,18 +12,27 @@ class lib:
""" lib file generation."""
def __init__(self, out_dir, sram, sp_file, use_model=OPTS.analytical_delay):
self.out_dir = out_dir
self.sram = sram
self.sp_file = sp_file
self.use_model = use_model
self.set_port_indices()
self.prepare_tables()
self.create_corners()
self.characterize_corners()
def set_port_indices(self):
"""Copies port information set in the SRAM instance"""
self.total_port_num = len(self.sram.all_ports)
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
def prepare_tables(self):
""" Determine the load/slews if they aren't specified in the config file. """
# These are the parameters to determine the table sizes
@ -75,7 +84,7 @@ class lib:
debug.info(1,"Writing to {0}".format(lib_name))
self.characterize()
self.lib.close()
self.parse_info()
def characterize(self):
""" Characterize the current corner. """
@ -85,20 +94,21 @@ class lib:
self.write_header()
self.write_data_bus()
self.write_addr_bus()
self.write_control_pins()
self.write_clk()
#Loop over all ports.
for port in self.all_ports:
#set the read and write port as inputs.
self.write_data_bus(port)
self.write_addr_bus(port)
self.write_control_pins(port) #need to split this into sram and port control signals
self.write_clk_timing_power(port)
self.write_footer()
def write_footer(self):
""" Write the footer """
self.lib.write("}\n")
self.lib.write(" }\n") #Closing brace for the cell
self.lib.write("}\n") #Closing brace for the library
def write_header(self):
""" Write the header information """
@ -127,10 +137,15 @@ 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.
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 : \"CSb\";\n")
self.lib.write(" value : {};\n".format(self.char_results["leakage_power"]))
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))
@ -267,12 +282,12 @@ class lib:
self.lib.write(" }\n\n")
def write_FF_setuphold(self):
def write_FF_setuphold(self, port):
""" Adds Setup and Hold timing results"""
self.lib.write(" timing(){ \n")
self.lib.write(" timing_type : setup_rising; \n")
self.lib.write(" related_pin : \"clk\"; \n")
self.lib.write(" related_pin : \"clk{0}\"; \n".format(port))
self.lib.write(" rise_constraint(CONSTRAINT_TABLE) {\n")
rounded_values = list(map(round_time,self.times["setup_times_LH"]))
self.write_values(rounded_values,len(self.slews)," ")
@ -284,7 +299,7 @@ class lib:
self.lib.write(" }\n")
self.lib.write(" timing(){ \n")
self.lib.write(" timing_type : hold_rising; \n")
self.lib.write(" related_pin : \"clk\"; \n")
self.lib.write(" related_pin : \"clk{0}\"; \n".format(port))
self.lib.write(" rise_constraint(CONSTRAINT_TABLE) {\n")
rounded_values = list(map(round_time,self.times["hold_times_LH"]))
self.write_values(rounded_values,len(self.slews)," ")
@ -295,135 +310,112 @@ class lib:
self.lib.write(" }\n")
self.lib.write(" }\n")
def write_data_bus(self):
def write_data_bus_output(self, read_port):
""" Adds data bus timing results."""
self.lib.write(" bus(DIN){\n")
self.lib.write(" bus(DOUT{0}){{\n".format(read_port))
self.lib.write(" bus_type : DATA; \n")
self.lib.write(" direction : in; \n")
# This is conservative, but limit to range that we characterized.
self.lib.write(" max_capacitance : {0}; \n".format(max(self.loads)))
self.lib.write(" min_capacitance : {0}; \n".format(min(self.loads)))
self.lib.write(" memory_write(){ \n")
self.lib.write(" address : ADDR; \n")
self.lib.write(" clocked_on : clk; \n")
self.lib.write(" }\n")
self.lib.write(" bus(DOUT){\n")
self.lib.write(" bus_type : DATA; \n")
self.lib.write(" direction : out; \n")
self.lib.write(" direction : output; \n")
# This is conservative, but limit to range that we characterized.
self.lib.write(" max_capacitance : {0}; \n".format(max(self.loads)))
self.lib.write(" min_capacitance : {0}; \n".format(min(self.loads)))
self.lib.write(" memory_read(){ \n")
self.lib.write(" address : ADDR; \n")
self.lib.write(" address : ADDR{0}; \n".format(read_port))
self.lib.write(" }\n")
self.lib.write(" pin(DOUT[{0}:0]){{\n".format(self.sram.word_size - 1))
self.write_FF_setuphold()
self.lib.write(" pin(DOUT{1}[{0}:0]){{\n".format(self.sram.word_size - 1, read_port))
self.lib.write(" timing(){ \n")
self.lib.write(" timing_sense : non_unate; \n")
self.lib.write(" related_pin : \"clk\"; \n")
self.lib.write(" related_pin : \"clk{0}\"; \n".format(read_port))
self.lib.write(" timing_type : rising_edge; \n")
self.lib.write(" cell_rise(CELL_TABLE) {\n")
self.write_values(self.char_results["delay_lh"],len(self.loads)," ")
self.write_values(self.char_port_results[read_port]["delay_lh"],len(self.loads)," ")
self.lib.write(" }\n") # rise delay
self.lib.write(" cell_fall(CELL_TABLE) {\n")
self.write_values(self.char_results["delay_hl"],len(self.loads)," ")
self.write_values(self.char_port_results[read_port]["delay_hl"],len(self.loads)," ")
self.lib.write(" }\n") # fall delay
self.lib.write(" rise_transition(CELL_TABLE) {\n")
self.write_values(self.char_results["slew_lh"],len(self.loads)," ")
self.write_values(self.char_port_results[read_port]["slew_lh"],len(self.loads)," ")
self.lib.write(" }\n") # rise trans
self.lib.write(" fall_transition(CELL_TABLE) {\n")
self.write_values(self.char_results["slew_hl"],len(self.loads)," ")
self.write_values(self.char_port_results[read_port]["slew_hl"],len(self.loads)," ")
self.lib.write(" }\n") # fall trans
self.lib.write(" }\n") # timing
self.lib.write(" }\n") # pin
self.lib.write(" }\n\n") # bus
def write_data_bus_input(self, write_port):
""" Adds DIN data bus timing results."""
def write_addr_bus(self):
self.lib.write(" bus(DIN{0}){{\n".format(write_port))
self.lib.write(" bus_type : DATA; \n")
self.lib.write(" direction : input; \n")
# This is conservative, but limit to range that we characterized.
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
self.lib.write(" memory_write(){ \n")
self.lib.write(" address : ADDR{0}; \n".format(write_port))
self.lib.write(" clocked_on : clk{0}; \n".format(write_port))
self.lib.write(" }\n")
self.lib.write(" pin(DIN{1}[{0}:0]){{\n".format(self.sram.word_size - 1, write_port))
self.write_FF_setuphold(write_port)
self.lib.write(" }\n") # pin
self.lib.write(" }\n") #bus
def write_data_bus(self, port):
""" Adds data bus timing results."""
if port in self.write_ports:
self.write_data_bus_input(port)
if port in self.read_ports:
self.write_data_bus_output(port)
def write_addr_bus(self, port):
""" Adds addr bus timing results."""
self.lib.write(" bus(ADDR){\n")
self.lib.write(" bus(ADDR{0}){{\n".format(port))
self.lib.write(" bus_type : ADDR; \n")
self.lib.write(" direction : input; \n")
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
self.lib.write(" max_transition : {0};\n".format(self.slews[-1]))
self.lib.write(" pin(ADDR[{0}:0])".format(self.sram.addr_size - 1))
self.lib.write(" pin(ADDR{1}[{0}:0])".format(self.sram.addr_size - 1, port))
self.lib.write("{\n")
self.write_FF_setuphold()
self.write_FF_setuphold(port)
self.lib.write(" }\n")
self.lib.write(" }\n\n")
def write_control_pins(self):
def write_control_pins(self, port):
""" Adds control pins timing results."""
ctrl_pin_names = ["CSb", "OEb", "WEb"]
#The control pins are still to be determined. This is a placeholder for what could be.
ctrl_pin_names = ["CSb{0}".format(port)]
if port in self.readwrite_ports:
ctrl_pin_names.append("WEb{0}".format(port))
for i in ctrl_pin_names:
self.lib.write(" pin({0})".format(i))
self.lib.write("{\n")
self.lib.write(" direction : input; \n")
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
self.write_FF_setuphold()
self.write_FF_setuphold(port)
self.lib.write(" }\n\n")
def write_clk(self):
def write_clk_timing_power(self, port):
""" Adds clk pin timing results."""
self.lib.write(" pin(clk){\n")
self.lib.write(" pin(clk{0}){{\n".format(port))
self.lib.write(" clock : true;\n")
self.lib.write(" direction : input; \n")
# FIXME: This depends on the clock buffer size in the control logic
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
# Find the average power of 1 and 0 bits for writes and reads over all loads/slews
# Could make it a table, but this is fine for now.
avg_write_power = np.mean(self.char_results["write1_power"] + self.char_results["write0_power"])
avg_read_power = np.mean(self.char_results["read1_power"] + self.char_results["read0_power"])
self.add_clk_control_power(port)
# Equally divide read/write power between first and second half of clock period
self.lib.write(" internal_power(){\n")
self.lib.write(" when : \"!CSb & clk & !WEb\"; \n")
self.lib.write(" rise_power(scalar){\n")
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
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(" }\n")
self.lib.write(" }\n")
self.lib.write(" internal_power(){\n")
self.lib.write(" when : \"!CSb & !clk & WEb\"; \n")
self.lib.write(" rise_power(scalar){\n")
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
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(" }\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\"; \n")
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")
min_pulse_width = round_time(self.char_results["min_period"])/2.0
min_period = round_time(self.char_results["min_period"])
min_pulse_width = round_time(self.char_sram_results["min_period"])/2.0
min_period = round_time(self.char_sram_results["min_period"])
self.lib.write(" timing(){ \n")
self.lib.write(" timing_type :\"min_pulse_width\"; \n")
self.lib.write(" related_pin : clk; \n")
self.lib.write(" related_pin : clk{0}; \n".format(port))
self.lib.write(" rise_constraint(scalar) {\n")
self.lib.write(" values(\"{0}\"); \n".format(min_pulse_width))
self.lib.write(" }\n")
@ -433,7 +425,7 @@ class lib:
self.lib.write(" }\n")
self.lib.write(" timing(){ \n")
self.lib.write(" timing_type :\"minimum_period\"; \n")
self.lib.write(" related_pin : clk; \n")
self.lib.write(" related_pin : clk{0}; \n".format(port))
self.lib.write(" rise_constraint(scalar) {\n")
self.lib.write(" values(\"{0}\"); \n".format(min_period))
self.lib.write(" }\n")
@ -441,21 +433,65 @@ class lib:
self.lib.write(" values(\"{0}\"); \n".format(min_period))
self.lib.write(" }\n")
self.lib.write(" }\n")
self.lib.write(" }\n")
self.lib.write(" }\n")
self.lib.write(" }\n\n")
def add_clk_control_power(self, port):
"""Writes powers under the clock pin group for a specified port"""
#Web added to read/write ports. Likely to change when control logic finished.
web_name = ""
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"])
self.lib.write(" internal_power(){\n")
self.lib.write(" when : \"!CSb{0} & clk{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(" }\n")
self.lib.write(" fall_power(scalar){\n")
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
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"])
self.lib.write(" internal_power(){\n")
self.lib.write(" when : \"!CSb{0} & !clk{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(" }\n")
self.lib.write(" fall_power(scalar){\n")
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
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")
def compute_delay(self):
""" Do the analysis if we haven't characterized the SRAM yet """
if not hasattr(self,"d"):
self.d = delay(self.sram, self.sp_file, self.corner)
if self.use_model:
self.char_results = self.d.analytical_delay(self.sram,self.slews,self.loads)
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
probe_data = self.sram.word_size - 1
self.char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads)
char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads)
self.char_sram_results, self.char_port_results = char_results
def compute_setup_hold(self):
""" Do the analysis if we haven't characterized a FF yet """
# Do the analysis if we haven't characterized a FF yet
@ -466,3 +502,37 @@ class lib:
else:
self.times = self.sh.analyze(self.slews,self.slews)
def parse_info(self):
if OPTS.is_unit_test:
return
datasheet = open(OPTS.openram_temp +'/datasheet.info', 'a+')
for (corner, lib_name) in zip(self.corners, self.lib_files):
# ports = ""
# if OPTS.num_rw_ports>0:
# ports += "{}_".format(OPTS.num_rw_ports)
# if OPTS.num_w_ports>0:
# ports += "{}_".format(OPTS.num_w_ports)
# if OPTS.num_r_ports>0:
# ports += "{}_".format(OPTS.num_r_ports)
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13}".format("sram_{0}_{1}_{2}".format(OPTS.word_size, OPTS.num_words, OPTS.tech_name),
OPTS.num_words,
OPTS.num_banks,
OPTS.num_rw_ports,
OPTS.num_w_ports,
OPTS.num_r_ports,
OPTS.tech_name,
self.corner[1],
self.corner[2],
self.corner[0],
round_time(self.char_sram_results["min_period"]),
self.out_dir,
lib_name,
OPTS.word_size))
datasheet.close()

View File

@ -3,7 +3,7 @@ import tech
from .stimuli import *
import debug
from .charutils import *
import ms_flop
import dff
from globals import OPTS
@ -15,9 +15,9 @@ class setup_hold():
def __init__(self, corner):
# This must match the spice model order
self.pins = ["data", "dout", "dout_bar", "clk", "vdd", "gnd"]
self.model_name = "ms_flop"
self.model_location = OPTS.openram_tech + "sp_lib/ms_flop.sp"
self.pins = ["data", "dout", "clk", "vdd", "gnd"]
self.model_name = "dff"
self.model_location = OPTS.openram_tech + "sp_lib/dff.sp"
self.period = tech.spice["feasible_period"]
debug.info(2,"Feasible period from technology file: {0} ".format(self.period))
@ -276,17 +276,36 @@ class setup_hold():
HL_setup = []
LH_hold = []
HL_hold = []
#For debugging, skips characterization and returns dummy values.
# i = 1.0
# for self.related_input_slew in related_slews:
# for self.constrained_input_slew in constrained_slews:
# LH_setup.append(i)
# HL_setup.append(i+1.0)
# LH_hold.append(i+2.0)
# HL_hold.append(i+3.0)
# i+=4.0
# times = {"setup_times_LH": LH_setup,
# "setup_times_HL": HL_setup,
# "hold_times_LH": LH_hold,
# "hold_times_HL": HL_hold
# }
# return times
for self.related_input_slew in related_slews:
for self.constrained_input_slew in constrained_slews:
debug.info(1, "Clock slew: {0} Data slew: {1}".format(self.related_input_slew,self.constrained_input_slew))
LH_setup_time = self.setup_LH_time()
debug.info(1, " Setup Time for low_to_high transistion: {0}".format(LH_setup_time))
debug.info(1, " Setup Time for low_to_high transition: {0}".format(LH_setup_time))
HL_setup_time = self.setup_HL_time()
debug.info(1, " Setup Time for high_to_low transistion: {0}".format(HL_setup_time))
debug.info(1, " Setup Time for high_to_low transition: {0}".format(HL_setup_time))
LH_hold_time = self.hold_LH_time()
debug.info(1, " Hold Time for low_to_high transistion: {0}".format(LH_hold_time))
debug.info(1, " Hold Time for low_to_high transition: {0}".format(LH_hold_time))
HL_hold_time = self.hold_HL_time()
debug.info(1, " Hold Time for high_to_low transistion: {0}".format(HL_hold_time))
debug.info(1, " Hold Time for high_to_low transition: {0}".format(HL_hold_time))
LH_setup.append(LH_setup_time)
HL_setup.append(HL_setup_time)
LH_hold.append(LH_hold_time)

View File

@ -0,0 +1,222 @@
import sys,re,shutil
from design import design
import debug
import math
import tech
from .stimuli import *
from .trim_spice import *
from .charutils import *
import utils
from globals import OPTS
class simulation():
def __init__(self, sram, spfile, corner):
self.sram = sram
self.name = self.sram.name
self.word_size = self.sram.word_size
self.addr_size = self.sram.addr_size
self.num_cols = self.sram.num_cols
self.num_rows = self.sram.num_rows
self.num_banks = self.sram.num_banks
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
def set_corner(self,corner):
""" Set the corner values """
self.corner = corner
(self.process, self.vdd_voltage, self.temperature) = corner
def set_spice_constants(self):
""" sets feasible timing parameters """
self.period = tech.spice["feasible_period"]
self.slew = tech.spice["rise_time"]*2
self.load = tech.spice["msflop_in_cap"]*4
self.gnd_voltage = 0
def set_stimulus_variables(self):
# Clock signals
self.cycle_times = []
self.t_current = 0
# control signals: only one cs_b for entire multiported sram, one we_b for each write port
self.csb_values = [[] for port in self.all_ports]
self.web_values = [[] for port in self.readwrite_ports]
# Three dimensional list to handle each addr and data bits for wach port over the number of checks
self.addr_values = [[[] for bit in range(self.addr_size)] for port in self.all_ports]
self.data_values = [[[] for bit in range(self.word_size)] for port in self.write_ports]
# For generating comments in SPICE stimulus
self.cycle_comments = []
self.fn_cycle_comments = []
def add_control_one_port(self, port, op):
"""Appends control signals for operation to a given port"""
#Determine values to write to port
web_val = 1
csb_val = 1
if op == "read":
csb_val = 0
elif op == "write":
csb_val = 0
web_val = 0
elif op != "noop":
debug.error("Could not add control signals for port {0}. Command {1} not recognized".format(port,op),1)
# Append the values depending on the type of port
self.csb_values[port].append(csb_val)
# If port is in both lists, add rw control signal. Condition indicates its a RW port.
if port in self.readwrite_ports:
self.web_values[port].append(web_val)
def add_data(self, data, port):
""" Add the array of data values """
debug.check(len(data)==self.word_size, "Invalid data word size.")
bit = self.word_size - 1
for c in data:
if c=="0":
self.data_values[port][bit].append(0)
elif c=="1":
self.data_values[port][bit].append(1)
else:
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.")
bit = self.addr_size - 1
for c in address:
if c=="0":
self.addr_values[port][bit].append(0)
elif c=="1":
self.addr_values[port][bit].append(1)
else:
debug.error("Non-binary address string",1)
bit -= 1
def add_write(self, comment, address, data, port):
""" Add the control values for a write cycle. """
debug.check(port in self.write_ports, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_ports))
debug.info(2, comment)
self.fn_cycle_comments.append(comment)
self.append_cycle_comment(port, comment)
self.cycle_times.append(self.t_current)
self.t_current += self.period
self.add_control_one_port(port, "write")
self.add_data(data,port)
self.add_address(address,port)
#This value is hard coded here. Possibly change to member variable or set in add_noop_one_port
noop_data = "0"*self.word_size
#Add noops to all other ports.
for unselected_port in self.all_ports:
if unselected_port != port:
self.add_noop_one_port(address, noop_data, unselected_port)
def add_read(self, comment, address, din_data, port):
""" Add the control values for a read cycle. """
debug.check(port in self.read_ports, "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, self.read_ports))
debug.info(2, comment)
self.fn_cycle_comments.append(comment)
self.append_cycle_comment(port, comment)
self.cycle_times.append(self.t_current)
self.t_current += self.period
self.add_control_one_port(port, "read")
#If the port is also a readwrite then add data.
if port in self.write_ports:
self.add_data(din_data,port)
self.add_address(address, port)
#This value is hard coded here. Possibly change to member variable or set in add_noop_one_port
noop_data = "0"*self.word_size
#Add noops to all other ports.
for unselected_port in self.all_ports:
if unselected_port != port:
self.add_noop_one_port(address, noop_data, unselected_port)
def add_noop_all_ports(self, comment, address, data):
""" Add the control values for a noop to all ports. """
debug.info(2, comment)
self.fn_cycle_comments.append(comment)
self.append_cycle_comment("All", comment)
self.cycle_times.append(self.t_current)
self.t_current += self.period
for port in self.all_ports:
self.add_noop_one_port(address, data, port)
def add_write_one_port(self, comment, address, data, port):
""" Add the control values for a write cycle. Does not increment the period. """
debug.check(port in self.write_ports, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_ports))
debug.info(2, comment)
self.fn_cycle_comments.append(comment)
self.add_control_one_port(port, "write")
self.add_data(data,port)
self.add_address(address,port)
def add_read_one_port(self, comment, address, din_data, port):
""" Add the control values for a read cycle. Does not increment the period. """
debug.check(port in self.read_ports, "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, self.read_ports))
debug.info(2, comment)
self.fn_cycle_comments.append(comment)
self.add_control_one_port(port, "read")
#If the port is also a readwrite then add data.
if port in self.write_ports:
self.add_data(din_data,port)
self.add_address(address, port)
def add_noop_one_port(self, address, data, port):
""" Add the control values for a noop to a single port. Does not increment the period. """
self.add_control_one_port(port, "noop")
if port in self.write_ports:
self.add_data(data,port)
self.add_address(address, port)
def append_cycle_comment(self, port, comment):
"""Add comment to list to be printed in stimulus file"""
#Clean up time before appending. Make spacing dynamic as well.
time = "{0:.2f} ns:".format(self.t_current)
time_spacing = len(time)+6
self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times),
port,
time,
time_spacing,
comment))
def gen_cycle_comment(self, op, word, addr, port, t_current):
if op == "noop":
comment = "\tIdle during cycle {0} ({1}ns - {2}ns)".format(int(t_current/self.period),
t_current,
t_current+self.period)
elif op == "write":
comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word,
addr,
port,
int(t_current/self.period),
t_current,
t_current+self.period)
else:
comment = "\tReading {0} from address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word,
addr,
port,
int(t_current/self.period),
t_current,
t_current+self.period)
return comment

View File

@ -28,24 +28,53 @@ class stimuli():
(self.process, self.voltage, self.temperature) = corner
self.device_models = tech.spice["fet_models"][self.process]
def inst_sram(self, abits, dbits, sram_name):
def inst_sram(self, sram, port_signal_names, port_info, abits, dbits, sram_name):
""" Function to instatiate an SRAM subckt. """
pin_names = self.gen_pin_names(port_signal_names, port_info, abits, dbits)
#Only checking length. This should check functionality as well (TODO) and/or import that information from the SRAM
debug.check(len(sram.pins) == len(pin_names), "Number of pins generated for characterization do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(sram.pins,pin_names))
self.sf.write("Xsram ")
for i in range(dbits):
self.sf.write("DIN[{0}] ".format(i))
for i in range(abits):
self.sf.write("A[{0}] ".format(i))
for i in tech.spice["control_signals"]:
self.sf.write("{0} ".format(i))
self.sf.write("{0} ".format(tech.spice["clk"]))
for i in range(dbits):
self.sf.write("DOUT[{0}] ".format(i))
self.sf.write("{0} {1} ".format(self.vdd_name, self.gnd_name))
for pin in pin_names:
self.sf.write("{0} ".format(pin))
self.sf.write("{0}\n".format(sram_name))
def gen_pin_names(self, port_signal_names, port_info, abits, dbits):
"""Creates the pins names of the SRAM based on the no. of ports."""
#This may seem redundant as the pin names are already defined in the sram. However, it is difficult to extract the
#functionality from the names, so they are recreated. As the order is static, changing the order of the pin names
#will cause issues here.
pin_names = []
(addr_name, din_name, dout_name) = port_signal_names
(total_ports, write_index, read_index) = port_info
for write_input in write_index:
for i in range(dbits):
pin_names.append("{0}{1}_{2}".format(din_name,write_input, i))
for port in range(total_ports):
for i in range(abits):
pin_names.append("{0}{1}_{2}".format(addr_name,port,i))
#Control signals not finalized.
for port in range(total_ports):
pin_names.append("CSB{0}".format(port))
for port in range(total_ports):
if (port in read_index) and (port in write_index):
pin_names.append("WEB{0}".format(port))
for port in range(total_ports):
pin_names.append("{0}{1}".format(tech.spice["clk"], port))
for read_output in read_index:
for i in range(dbits):
pin_names.append("{0}{1}_{2}".format(dout_name,read_output, i))
pin_names.append("{0}".format(self.vdd_name))
pin_names.append("{0}".format(self.gnd_name))
return pin_names
def inst_model(self, pins, model_name):
""" Function to instantiate a generic model with a set of pins """
self.sf.write("X{0} ".format(model_name))
@ -138,7 +167,7 @@ class stimuli():
to the initial value.
"""
# the initial value is not a clock time
debug.check(len(clk_times)==len(data_values),"Clock and data value lengths don't match.")
debug.check(len(clk_times)==len(data_values),"Clock and data value lengths don't match. {0} clock values, {1} data values for {2}".format(len(clk_times), len(data_values), sig_name))
# shift signal times earlier for setup time
times = np.array(clk_times) - setup*period
@ -198,9 +227,24 @@ class stimuli():
power_exp,
t_initial,
t_final))
def gen_meas_value(self, meas_name, dout, t_intital, t_final):
measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_intital, t_final)
self.sf.write(measure_string)
def write_control(self, end_time):
def write_control(self, end_time, runlvl=4):
""" Write the control cards to run and end the simulation """
# These are guesses...
if runlvl==1:
reltol = 0.02 # 2%
elif runlvl==2:
reltol = 0.01 # 1%
elif runlvl==3:
reltol = 0.005 # 0.5%
else:
reltol = 0.001 # 0.1%
# UIC is needed for ngspice to converge
self.sf.write(".TRAN 5p {0}n UIC\n".format(end_time))
if OPTS.spice_name == "ngspice":
@ -208,9 +252,9 @@ class stimuli():
# which is more accurate, but slower than the default trapezoid method
# Do not remove this or it may not converge due to some "pa_00" nodes
# unless you figure out what these are.
self.sf.write(".OPTIONS POST=1 RUNLVL=4 PROBE method=gear TEMP={}\n".format(self.temperature))
self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear TEMP={1}\n".format(reltol,self.temperature))
else:
self.sf.write(".OPTIONS POST=1 RUNLVL=4 PROBE TEMP={}\n".format(self.temperature))
self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE TEMP={1}\n".format(runlvl,self.temperature))
# create plots for all signals
self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n")
@ -240,12 +284,15 @@ class stimuli():
def write_supply(self):
""" Writes supply voltage statements """
self.sf.write("V{0} {0} 0.0 {1}\n".format(self.vdd_name, self.voltage))
self.sf.write("V{0} {0} 0.0 {1}\n".format(self.gnd_name, 0))
gnd_node_name = "0"
self.sf.write("V{0} {0} {1} {2}\n".format(self.vdd_name, gnd_node_name, self.voltage))
# This is for the test power supply
self.sf.write("V{0} {0} 0.0 {1}\n".format("test"+self.vdd_name, self.voltage))
self.sf.write("V{0} {0} 0.0 {1}\n".format("test"+self.gnd_name, 0))
self.sf.write("V{0} {0} {1} {2}\n".format("test"+self.vdd_name, gnd_node_name, self.voltage))
self.sf.write("V{0} {0} {1} {2}\n".format("test"+self.gnd_name, gnd_node_name, 0.0))
#Adding a commented out supply for simulators where gnd and 0 are not global grounds.
self.sf.write("\n*Nodes gnd and 0 are the same global ground node in ngspice/hspice/xa. Otherwise, this source may be needed.\n")
self.sf.write("*V{0} {0} {1} {2}\n".format(self.gnd_name, gnd_node_name, 0.0))
def run_sim(self):
""" Run hspice in batch mode and output rawfile to parse. """

View File

@ -1,5 +1,6 @@
import debug
from math import log
import re
class trim_spice():
"""
@ -54,8 +55,8 @@ class trim_spice():
else:
col_address = 0
# 1. Keep cells in the bitcell array based on WL and BL
wl_name = "wl[{}]".format(wl_address)
bl_name = "bl[{}]".format(int(self.words_per_row*data_bit + col_address))
wl_name = "wl_{}".format(wl_address)
bl_name = "bl_{}".format(int(self.words_per_row*data_bit + col_address))
# Prepend info about the trimming
addr_msg = "Keeping {} address".format(address)
@ -73,25 +74,28 @@ class trim_spice():
self.sp_buffer.insert(0, "* It should NOT be used for LVS!!")
self.sp_buffer.insert(0, "* WARNING: This is a TRIMMED NETLIST.")
self.remove_insts("bitcell_array",[wl_name,bl_name])
wl_regex = r"wl\d*_{}".format(wl_address)
bl_regex = r"bl\d*_{}".format(int(self.words_per_row*data_bit + col_address))
self.remove_insts("bitcell_array",[wl_regex,bl_regex])
# 2. Keep sense amps basd on BL
# FIXME: The bit lines are not indexed the same in sense_amp_array
#self.remove_insts("sense_amp_array",[bl_name])
#self.remove_insts("sense_amp_array",[bl_regex])
# 3. Keep column muxes basd on BL
self.remove_insts("column_mux_array",[bl_name])
self.remove_insts("column_mux_array",[bl_regex])
# 4. Keep write driver based on DATA
data_name = "data[{}]".format(data_bit)
self.remove_insts("write_driver_array",[data_name])
data_regex = r"data_{}".format(data_bit)
self.remove_insts("write_driver_array",[data_regex])
# 5. Keep wordline driver based on WL
# Need to keep the gater too
#self.remove_insts("wordline_driver",wl_name)
#self.remove_insts("wordline_driver",wl_regex)
# 6. Keep precharges based on BL
self.remove_insts("precharge_array",[bl_name])
self.remove_insts("precharge_array",[bl_regex])
# Everything else isn't worth removing. :)
@ -107,6 +111,10 @@ class trim_spice():
match of the line with a term so you can search for a single
net connection, the instance name, anything..
"""
removed_insts = 0
#Expects keep_inst_list are regex patterns. Compile them here.
compiled_patterns = [re.compile(pattern) for pattern in keep_inst_list]
start_name = ".SUBCKT {}".format(subckt_name)
end_name = ".ENDS {}".format(subckt_name)
@ -120,11 +128,14 @@ class trim_spice():
new_buffer.append(line)
in_subckt=False
elif in_subckt:
for k in keep_inst_list:
if k in line:
removed_insts += 1
for pattern in compiled_patterns:
if pattern.search(line) != None:
new_buffer.append(line)
removed_insts -= 1
break
else:
new_buffer.append(line)
self.sp_buffer = new_buffer
debug.info(2, "Removed {} instances from {} subcircuit.".format(removed_insts, subckt_name))

View File

@ -0,0 +1,77 @@
import sys,re,shutil
import debug
import tech
import math
from .stimuli import *
from .trim_spice import *
from .charutils import *
import utils
from globals import OPTS
from .delay import delay
class worst_case(delay):
"""Functions to test for the worst case delay in a target SRAM
The current worst case determines a feasible period for the SRAM then tests
several bits and record the delay and differences between the bits.
"""
def __init__(self, sram, spfile, corner):
delay.__init__(self,sram,spfile,corner)
def analyze(self,probe_address, probe_data, slews, loads):
"""
Main function to test the delays of different bits.
"""
debug.check(OPTS.num_rw_ports < 2 and OPTS.num_w_ports < 1 and OPTS.num_r_ports < 1 ,
"Bit testing does not currently support multiport.")
#Dict to hold all characterization values
char_sram_data = {}
self.set_probe(probe_address, probe_data)
#self.prepare_netlist()
self.load=max(loads)
self.slew=max(slews)
# 1) Find a feasible period and it's corresponding delays using the trimmed array.
feasible_delays = self.find_feasible_period()
# 2) Find the delays of several bits
test_bits = self.get_test_bits()
bit_delays = self.simulate_for_bit_delays(test_bits)
for i in range(len(test_bits)):
debug.info(1, "Bit tested: addr {0[0]} data_pos {0[1]}\n Values {1}".format(test_bits[i], bit_delays[i]))
def simulate_for_bit_delays(self, test_bits):
"""Simulates the delay of the sram of over several bits."""
bit_delays = [{} for i in range(len(test_bits))]
#Assumes a bitcell with only 1 rw port. (6t, port 0)
port = 0
self.targ_read_ports = [self.read_ports[port]]
self.targ_write_ports = [self.write_ports[port]]
for i in range(len(test_bits)):
(bit_addr, bit_data) = test_bits[i]
self.set_probe(bit_addr, bit_data)
debug.info(1,"Delay bit test: period {}, addr {}, data_pos {}".format(self.period, bit_addr, bit_data))
(success, results)=self.run_delay_simulation()
debug.check(success, "Bit Test Failed: period {}, addr {}, data_pos {}".format(self.period, bit_addr, bit_data))
bit_delays[i] = results[port]
return bit_delays
def get_test_bits(self):
"""Statically determines address and bit values to test"""
#First and last address, first middle, and last bit. Last bit is repeated twice with different data position.
bit_addrs = ["0"*self.addr_size, "0"+"1"*(self.addr_size-1), "1"*self.addr_size, "1"*self.addr_size]
data_positions = [0, (self.word_size-1)//2, 0, self.word_size-1]
#Return them in a tuple form
return [(bit_addrs[i], data_positions[i]) for i in range(len(bit_addrs))]

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,17 @@
from flask_table import *
class characterization_corners(Table):
corner_name = Col('Corner Name')
process = Col('Process')
power_supply = Col('Power Supply')
temperature = Col('Temperature')
library_name_suffix = Col('Library Name Suffix')
class characterization_corners_item(object):
def __init__(self, corner_name, process, power_supply, temperature, library_name_suffix):
self.corner_name = corner_name
self.process = process
self.power_supply = power_supply
self.temperature = temperature
self.library_name_suffix = library_name_suffix

View File

@ -0,0 +1,69 @@
from flask_table import *
from operating_conditions import *
from characterization_corners import *
from deliverables import *
from timing_and_current_data import *
from in_out import *
import os
from globals import OPTS
class datasheet():
def __init__(self,identifier):
self.io = []
self.corners = []
self.timing = []
self.operating = []
self.dlv = []
self.name = identifier
self.html = ""
def generate_html(self):
self.html = """<style>
#data {
font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
max-width: 800px
}
#data td, #data th {
border: 1px solid #ddd;
padding: 8px;
}
#data tr:nth-child(even){background-color: #f2f2f2;}
#data tr:hover {background-color: #ddd;}
#data th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
background-color: #4CAF50;
color: white;
}
</style>"""
self.html +='<p style=font-size: 20px;font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;>'+ self.name + '.html' + '</p>'
# self.html +='<p style=font-size: 20px;font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;>{0}</p>'
# self.html +='<p style=font-size: 20px;font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;>{0}</p>'
self.html +='<p style=font-size: 20px;font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;>Ports and Configuration (DEBUG)</p>'
self.html += in_out(self.io,table_id='data').__html__().replace('&lt;','<').replace('&#34;','"').replace('&gt;',">")
self.html +='<p style=font-size: 20px;font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;>Operating Conditions</p>'
self.html += operating_conditions(self.operating,table_id='data').__html__()
self.html += '<p style=font-size: 20px;font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;>Timing and Current Data</p>'
self.html += timing_and_current_data(self.timing,table_id='data').__html__()
self.html += '<p style=font-size: 20px;font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;>Characterization Corners</p>'
self.html += characterization_corners(self.corners,table_id='data').__html__()
self.html +='<p style=font-size: 20px;font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;>Deliverables</p>'
self.html += deliverables(self.dlv,table_id='data').__html__().replace('&lt;','<').replace('&#34;','"').replace('&gt;',">")
self.html +='<p style=font-size: 20px;font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;>*Feature only supported with characterizer</p>'
self.html +='<img src=' + os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/datasheet/assets/vlsi_logo.png alt="VLSIDA" />'

View File

@ -0,0 +1,157 @@
#!/usr/bin/env python3
"""
Datasheet Generator
TODO:
locate all port elements in .lib
Locate all timing elements in .lib
Diagram generation
Improve css
"""
import debug
from globals import OPTS
if OPTS.datasheet_gen:
import flask_table
import os, math
import optparse
import csv
from deliverables import *
from operating_conditions import *
from timing_and_current_data import *
from characterization_corners import *
from datasheet import *
from in_out import *
else:
debug.warning("Python library flask_table not found. Skipping html datasheet generation. This can be installed with pip install flask-table.")
def process_name(corner):
if corner == "TT":
return "Typical - Typical"
if corner == "SS":
return "Slow - Slow"
if corner == "FF":
return "Fast - Fast"
else:
return "custom"
def parse_file(f,pages):
with open(f) as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
line_count = 0
for row in csv_reader:
found = 0
NAME = row[0]
NUM_WORDS = row[1]
NUM_BANKS = row[2]
NUM_RW_PORTS = row[3]
NUM_W_PORTS = row[4]
NUM_R_PORTS = row[5]
TECH_NAME = row[6]
TEMP = row[8]
VOLT = row[7]
PROC = row[9]
MIN_PERIOD = row[10]
OUT_DIR = row[11]
LIB_NAME = row[12]
WORD_SIZE = row[13]
for sheet in pages:
if sheet.name == row[0]:
found = 1
#if the .lib information is for an existing datasheet compare timing data
for item in sheet.operating:
if item.parameter == 'Operating Temperature':
if float(TEMP) > float(item.max):
item.typ = item.max
item.max = TEMP
if float(TEMP) < float(item.min):
item.typ = item.min
item.min = TEMP
if item.parameter == 'Power supply (VDD) range':
if float(VOLT) > float(item.max):
item.typ = item.max
item.max = VOLT
if float(VOLT) < float(item.min):
item.typ = item.min
item.min = VOLT
if item.parameter == 'Operating Frequncy (F)':
try:
if float(math.floor(1000/float(MIN_PERIOD)) < float(item.max)):
item.max = str(math.floor(1000/float(MIN_PERIOD)))
except Exception:
pass
new_sheet.corners.append(characterization_corners_item(PROC,process_name(PROC),VOLT,TEMP,LIB_NAME.replace(OUT_DIR,'').replace(NAME,'')))
new_sheet.dlv.append(deliverables_item('.lib','Synthesis models','<a href="file://{0}">{1}</a>'.format(LIB_NAME,LIB_NAME.replace(OUT_DIR,''))))
if found == 0:
new_sheet = datasheet(NAME)
pages.append(new_sheet)
new_sheet.corners.append(characterization_corners_item(PROC,process_name(PROC),VOLT,TEMP,LIB_NAME.replace(OUT_DIR,'').replace(NAME,'')))
new_sheet.operating.append(operating_conditions_item('Power supply (VDD) range',VOLT,VOLT,VOLT,'Volts'))
new_sheet.operating.append(operating_conditions_item('Operating Temperature',TEMP,TEMP,TEMP,'Celsius'))
try:
new_sheet.operating.append(operating_conditions_item('Operating Frequency (F)*','','',str(math.floor(1000/float(MIN_PERIOD))),'MHz'))
except Exception:
new_sheet.operating.append(operating_conditions_item('Operating Frequency (F)*','','',"unknown",'MHz')) #analytical model fails to provide MIN_PERIOD
new_sheet.timing.append(timing_and_current_data_item('Cycle time','2','3','4'))
new_sheet.timing.append(timing_and_current_data_item('Access time','2','3','4'))
new_sheet.timing.append(timing_and_current_data_item('Positive clk setup','2','3','4'))
new_sheet.timing.append(timing_and_current_data_item('Positive clk hold','2','3','4'))
new_sheet.timing.append(timing_and_current_data_item('RW setup','2','3','4'))
new_sheet.timing.append(timing_and_current_data_item('RW hold','2','3','4'))
new_sheet.timing.append(timing_and_current_data_item('AC current','2','3','4'))
new_sheet.timing.append(timing_and_current_data_item('Standby current','2','3','4'))
new_sheet.timing.append(timing_and_current_data_item('Area','2','3','4'))
new_sheet.dlv.append(deliverables_item('.sp','SPICE netlists','<a href="file://{0}{1}.{2}">{1}.{2}</a>'.format(OUT_DIR,NAME,'sp')))
new_sheet.dlv.append(deliverables_item('.v','Verilog simulation models','<a href="file://{0}{1}.{2}">{1}.{2}</a>'.format(OUT_DIR,NAME,'v')))
new_sheet.dlv.append(deliverables_item('.gds','GDSII layout views','<a href="file://{0}{1}.{2}">{1}.{2}</a>'.format(OUT_DIR,NAME,'gds')))
new_sheet.dlv.append(deliverables_item('.lef','LEF files','<a href="file://{0}{1}.{2}">{1}.{2}</a>'.format(OUT_DIR,NAME,'lef')))
new_sheet.dlv.append(deliverables_item('.lib','Synthesis models','<a href="file://{0}">{1}</a>'.format(LIB_NAME,LIB_NAME.replace(OUT_DIR,''))))
new_sheet.io.append(in_out_item('WORD_SIZE',WORD_SIZE))
new_sheet.io.append(in_out_item('NUM_WORDS',NUM_WORDS))
new_sheet.io.append(in_out_item('NUM_BANKS',NUM_BANKS))
new_sheet.io.append(in_out_item('NUM_RW_PORTS',NUM_RW_PORTS))
new_sheet.io.append(in_out_item('NUM_R_PORTS',NUM_R_PORTS))
new_sheet.io.append(in_out_item('NUM_W_PORTS',NUM_W_PORTS))
class datasheet_gen():
def datasheet_write(name):
if OPTS.datasheet_gen:
in_dir = OPTS.openram_temp
if not (os.path.isdir(in_dir)):
os.mkdir(in_dir)
#if not (os.path.isdir(out_dir)):
# os.mkdir(out_dir)
datasheets = []
parse_file(in_dir + "/datasheet.info", datasheets)
for sheets in datasheets:
with open(name, 'w+') as f:
sheets.generate_html()
f.write(sheets.html)

View File

@ -0,0 +1,13 @@
from flask_table import *
class deliverables(Table):
typ = Col('Type')
description = Col('Description')
link = Col('Link')
class deliverables_item(object):
def __init__(self, typ, description,link):
self.typ = typ
self.description = description
self.link = link

View File

@ -0,0 +1,11 @@
from flask_table import *
class in_out(Table):
typ = Col('Type')
description = Col('Description')
class in_out_item(object):
def __init__(self, typ, description):
self.typ = typ
self.description = description

View File

@ -0,0 +1,17 @@
from flask_table import *
class operating_conditions(Table):
parameter = Col('Parameter')
min = Col('Min')
typ = Col('Typ')
max = Col('Max')
units = Col('Units')
class operating_conditions_item(object):
def __init__(self, parameter, min, typ, max, units):
self.parameter = parameter
self.min = min
self.typ = typ
self.max = max
self.units = units

View File

@ -0,0 +1,16 @@
from flask_table import *
class timing_and_current_data(Table):
parameter = Col('Parameter')
min = Col('Min')
max = Col('Max')
units = Col('Units')
class timing_and_current_data_item(object):
def __init__(self, parameter, min, max, units):
self.parameter = parameter
self.min = min
self.max = max
self.units = units

View File

@ -0,0 +1,40 @@
import debug
from drc_value import *
from drc_lut import *
class design_rules():
"""
This is a class that implements the design rules structures.
"""
def __init__(self, name):
self.tech_name = name
self.rules = {}
def add(self, name, value):
self.rules[name] = value
def __call__(self, name, *args):
rule = self.rules[name]
if callable(rule):
return rule(*args)
else:
return rule
def __setitem__(self, b, c):
"""
For backward compatibility with existing rules.
"""
self.rules[b] = c
def __getitem__(self, b):
"""
For backward compatibility with existing rules.
"""
rule = self.rules[b]
if not callable(rule):
return rule
else:
debug.error("Must call complex DRC rule {} with arguments.".format(b),-1)

43
compiler/drc/drc_lut.py Normal file
View File

@ -0,0 +1,43 @@
import debug
class drc_lut():
"""
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.
For exampe, the key values can be width and length,
and it would return the rule for a wire of at least a given width and length.
A dimension can be ignored by passing inf.
"""
def __init__(self, table):
self.table = table
def __call__(self, *key):
"""
Lookup a given tuple in the table.
"""
if len(key)==0:
first_key = list(sorted(self.table.keys()))[0]
return self.table[first_key]
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):
"""
Determine if key1>=key2 for all tuple pairs.
(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):
if k1 < k2:
return False
return True

17
compiler/drc/drc_value.py Normal file
View File

@ -0,0 +1,17 @@
class drc_value():
"""
A single DRC value.
"""
def __init__(self, value):
self.value = value
def __call__(self, *args):
"""
Return the value.
"""
return self.value

View File

@ -1,6 +1,5 @@
word_size = 2
num_words = 16
num_banks = 1
tech_name = "freepdk45"
process_corners = ["TT"]
@ -8,5 +7,5 @@ supply_voltages = [1.0]
temperatures = [25]
output_path = "temp"
output_name = "sram_2_16_1_freepdk45"
output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name)

View File

@ -1,13 +0,0 @@
word_size = 2
num_words = 16
num_banks = 1
tech_name = "scn3me_subm"
process_corners = ["TT"]
supply_voltages = [ 5.0 ]
temperatures = [ 25 ]
output_path = "temp"
output_name = "sram_2_16_1_scn3me_subm"

View File

@ -0,0 +1,14 @@
word_size = 2
num_words = 16
tech_name = "scn4m_subm"
process_corners = ["TT"]
supply_voltages = [ 3.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

@ -294,7 +294,7 @@ class Gds2reader:
mirrorFlag = bool(transFlags&0x8000) ##these flags are a bit sketchy
rotateFlag = bool(transFlags&0x0002)
magnifyFlag = bool(transFlags&0x0004)
thisSref.transFlags=(mirrorFlag,rotateFlag,magnifyFlag)
thisSref.transFlags=[mirrorFlag,magnifyFlag,rotateFlag]
if(self.debugToTerminal==1):
print("\t\t\tMirror X:"+str(mirrorFlag))
print( "\t\t\tRotate:"+str(rotateFlag))
@ -345,7 +345,7 @@ class Gds2reader:
mirrorFlag = bool(transFlags&0x8000) ##these flags are a bit sketchy
rotateFlag = bool(transFlags&0x0002)
magnifyFlag = bool(transFlags&0x0004)
thisAref.transFlags=(mirrorFlag,rotateFlag,magnifyFlag)
thisAref.transFlags=[mirrorFlag,magnifyFlag,rotateFlag]
if(self.debugToTerminal==1):
print("\t\t\tMirror X:"+str(mirrorFlag))
print("\t\t\tRotate:"+str(rotateFlag))
@ -408,7 +408,7 @@ class Gds2reader:
mirrorFlag = bool(transFlags&0x8000) ##these flags are a bit sketchy
rotateFlag = bool(transFlags&0x0002)
magnifyFlag = bool(transFlags&0x0004)
thisText.transFlags=(mirrorFlag,rotateFlag,magnifyFlag)
thisText.transFlags=[mirrorFlag,magnifyFlag,rotateFlag]
if(self.debugToTerminal==1):
print("\t\t\tMirror X:"+str(mirrorFlag))
print("\t\t\tRotate:"+str(rotateFlag))

View File

@ -280,8 +280,13 @@ class Gds2writer:
if(thisSref.transFlags!=""):
idBits=b'\x1A\x01'
mirrorFlag = int(thisSref.transFlags[0])<<15
rotateFlag = int(thisSref.transFlags[1])<<1
magnifyFlag = int(thisSref.transFlags[2])<<3
# The rotate and magnify flags specify "absolute" rotate and magnify.
# It is unclear what that is (ignore all further rotates/mags in the
# hierarchy? But anyway, calibre doesn't support it.
rotateFlag=0
magnifyFlag = 0
#rotateFlag = int(thisSref.transFlags[2])<<1
#magnifyFlag = int(thisSref.transFlags[1])<<2
transFlags = struct.pack(">H",mirrorFlag|rotateFlag|magnifyFlag)
self.writeRecord(idBits+transFlags)
if(thisSref.magFactor!=""):
@ -327,15 +332,20 @@ class Gds2writer:
if(thisAref.transFlags):
idBits=b'\x1A\x01'
mirrorFlag = int(thisAref.transFlags[0])<<15
rotateFlag = int(thisAref.transFlags[1])<<1
magnifyFlag = int(thisAref.transFlags[0])<<3
# The rotate and magnify flags specify "absolute" rotate and magnify.
# It is unclear what that is (ignore all further rotates/mags in the
# hierarchy? But anyway, calibre doesn't support it.
rotateFlag=0
magnifyFlag = 0
#rotateFlag = int(thisAref.transFlags[2])<<1
#magnifyFlag = int(thisAref.transFlags[1])<<2
transFlags = struct.pack(">H",mirrorFlag|rotateFlag|magnifyFlag)
self.writeRecord(idBits+transFlags)
if(thisAref.magFactor):
if(thisAref.magFactor!=""):
idBits=b'\x1B\x05'
magFactor=self.ibmDataFromIeeeDouble(thisAref.magFactor)
self.writeRecord(idBits+magFactor)
if(thisAref.rotateAngle):
if(thisAref.rotateAngle!=""):
idBits=b'\x1C\x05'
rotateAngle=self.ibmDataFromIeeeDouble(thisAref.rotateAngle)
self.writeRecord(idBits+rotateAngle)
@ -374,15 +384,20 @@ class Gds2writer:
if(thisText.transFlags != ""):
idBits=b'\x1A\x01'
mirrorFlag = int(thisText.transFlags[0])<<15
rotateFlag = int(thisText.transFlags[1])<<1
magnifyFlag = int(thisText.transFlags[0])<<3
# The rotate and magnify flags specify "absolute" rotate and magnify.
# It is unclear what that is (ignore all further rotates/mags in the
# hierarchy? But anyway, calibre doesn't support it.
rotateFlag=0
magnifyFlag = 0
#rotateFlag = int(thisText.transFlags[2])<<1
#magnifyFlag = int(thisText.transFlags[1])<<2
transFlags = struct.pack(">H",mirrorFlag|rotateFlag|magnifyFlag)
self.writeRecord(idBits+transFlags)
if(thisText.magFactor != ""):
if(thisText.magFactor!=""):
idBits=b'\x1B\x05'
magFactor=self.ibmDataFromIeeeDouble(thisText.magFactor)
self.writeRecord(idBits+magFactor)
if(thisText.rotateAngle != ""):
if(thisText.rotateAngle!=""):
idBits=b'\x1C\x05'
rotateAngle=self.ibmDataFromIeeeDouble(thisText.rotateAngle)
self.writeRecord(idBits+rotateAngle)

View File

@ -118,7 +118,7 @@ class GdsSref:
self.elementFlags=""
self.plex=""
self.sName=""
self.transFlags=(False,False,False)
self.transFlags=[0,0,0]
self.magFactor=""
self.rotateAngle=""
self.coordinates=""
@ -129,7 +129,7 @@ class GdsAref:
self.elementFlags=""
self.plex=""
self.aName=""
self.transFlags=(False,False,False)
self.transFlags=[0,0,0]
self.magFactor=""
self.rotateAngle=""
self.coordinates=""
@ -141,7 +141,7 @@ class GdsText:
self.plex=""
self.drawingLayer=""
self.purposeLayer = None
self.transFlags=(False,False,False)
self.transFlags=[0,0,0]
self.magFactor=""
self.rotateAngle=""
self.pathType=""
@ -167,4 +167,4 @@ class GdsBox:
self.drawingLayer=""
self.purposeLayer = None
self.boxValue=""
self.coordinates=""
self.coordinates=""

View File

@ -60,17 +60,23 @@ class VlsiLayout:
self.tempCoordinates=None
self.tempPassFail = True
# This is a dict indexed by the pin labels.
# It contains a list of list of shapes, one for each occurance of the label.
# Multiple labels may be disconnected.
self.pins = {}
def rotatedCoordinates(self,coordinatesToRotate,rotateAngle):
#helper method to rotate a list of coordinates
angle=math.radians(float(0))
if(rotateAngle):
angle = math.radians(float(repr(rotateAngle)))
angle = math.radians(float(rotateAngle))
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 += [(newX,newY)]
coordinatesRotate.extend((newX,newY))
return coordinatesRotate
def rename(self,newName):
@ -141,7 +147,7 @@ class VlsiLayout:
contained by any other structure. this is the root."""
structureNames=[]
for name in self.structures:
structureNames+=[name]
structureNames.append(name)
for name in self.structures:
if(len(self.structures[name].srefs)>0): #does this structure reference any others?
@ -152,19 +158,20 @@ class VlsiLayout:
self.rootStructureName = structureNames[0]
def traverseTheHierarchy(self, startingStructureName=None, delegateFunction = None,
transformPath = [], rotateAngle = 0, transFlags = (0,0,0), coordinates = (0,0)):
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
if(rotateAngle == None or rotateAngle == ""):
rotateAngle = 0
angle = 0
else:
rotateAngle = math.radians(float(rotateAngle))
mRotate = matrix([[math.cos(rotateAngle),-math.sin(rotateAngle),0.0],
[math.sin(rotateAngle),math.cos(rotateAngle),0.0],
# MRG: Added negative to make CCW rotate 8/29/18
angle = math.radians(float(rotateAngle))
mRotate = matrix([[math.cos(angle),-math.sin(angle),0.0],
[math.sin(angle),math.cos(angle),0.0],
[0.0,0.0,1.0]])
#set up the translation matrix
translateX = float(coordinates[0])
@ -180,7 +187,7 @@ class VlsiLayout:
#we need to keep track of all transforms in the hierarchy
#when we add an element to the xy tree, we apply all transforms from the bottom up
transformPath += [(mRotate,mScale,mTranslate)]
transformPath.append((mRotate,mScale,mTranslate))
if delegateFunction != None:
delegateFunction(startingStructureName, transformPath)
#starting with a particular structure, we will recursively traverse the tree
@ -190,15 +197,12 @@ class VlsiLayout:
#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
# if (sref.sName.count("via") == 0):
self.traverseTheHierarchy(startingStructureName = sref.sName,
delegateFunction = delegateFunction,
transformPath = transformPath,
rotateAngle = sref.rotateAngle,
transFlags = sref.transFlags,
coordinates = sref.coordinates)
# else:
# print("WARNING: via encountered, ignoring:", sref.sName)
#MUST HANDLE AREFs HERE AS WELL
#when we return, drop the last transform from the transformPath
del transformPath[-1]
@ -207,7 +211,11 @@ class VlsiLayout:
def initialize(self):
self.deduceHierarchy()
#self.traverseTheHierarchy()
self.populateCoordinateMap()
self.populateCoordinateMap()
for layerNumber in self.layerNumbersInUse:
self.processLabelPins(layerNumber)
def populateCoordinateMap(self):
def addToXyTree(startingStructureName = None,transformPath = None):
@ -225,12 +233,14 @@ class VlsiLayout:
uVector = transform[0] * uVector #rotate
vVector = transform[0] * vVector #rotate
origin = transform[1] * origin #scale
uVector = transform[1] * uVector #rotate
vVector = transform[1] * vVector #rotate
uVector = transform[1] * uVector #scale
vVector = transform[1] * vVector #scale
origin = transform[2] * origin #translate
#we don't need to do a translation on the basis vectors
self.xyTree+=[(startingStructureName,origin,uVector,vVector)] #populate the xyTree with each
#structureName and coordinate space
#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):
@ -274,8 +284,9 @@ class VlsiLayout:
Method to insert one layout into another at a particular offset.
"""
offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1]))
if self.debug==1:
debug.info(0,"DEBUG: GdsMill vlsiLayout: addInstance: type %s, nameOfLayout "%type(layoutToAdd),nameOfLayout)
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))
@ -295,7 +306,7 @@ class VlsiLayout:
# If layoutToAdd is a unique object (not this), then copy heirarchy,
# 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:
@ -306,19 +317,7 @@ class VlsiLayout:
#also combine the "layers in use" list
for layerNumber in layoutToAdd.layerNumbersInUse:
if layerNumber not in self.layerNumbersInUse:
self.layerNumbersInUse += [layerNumber]
#Also, check if the user units / microns is the same as this Layout
#if (layoutToAdd.units != self.units):
#print("WARNING: VlsiLayout: Units from design to be added do not match target Layout")
# if debug: print("DEBUG: vlsilayout: Using %d layers")
# If we can't find the structure, error
#if StructureFound == False:
#print("ERROR: vlsiLayout.addInstance: [%s] Name not found in local structures, "%(nameOfLayout))
#return #FIXME: remove!
#exit(1)
self.layerNumbersInUse.append(layerNumber)
#add a reference to the new layout structure in this layout's root
layoutToAddSref = GdsSref()
@ -326,9 +325,11 @@ class VlsiLayout:
layoutToAddSref.coordinates = offsetInLayoutUnits
if mirror or rotate:
########flags = (mirror around x-axis, absolute rotation, absolute magnification)
layoutToAddSref.transFlags = (False,False,False)
#Below angles are angular angles(relative), not absolute
layoutToAddSref.transFlags = [0,0,0]
# transFlags = (mirror around x-axis, magnification, rotation)
# If magnification or rotation is true, it is the flags are then
# followed by an amount in the record
if mirror=="R90":
rotate = 90.0
if mirror=="R180":
@ -336,18 +337,20 @@ class VlsiLayout:
if mirror=="R270":
rotate = 270.0
if rotate:
#layoutToAddSref.transFlags[2] = 1
layoutToAddSref.rotateAngle = rotate
if mirror == "x" or mirror == "MX":
layoutToAddSref.transFlags = (True,False,False)
layoutToAddSref.transFlags[0] = 1
if mirror == "y" or mirror == "MY": #NOTE: "MY" option will override specified rotate angle
layoutToAddSref.transFlags = (True,False,False)
layoutToAddSref.transFlags[0] = 1
#layoutToAddSref.transFlags[2] = 1
layoutToAddSref.rotateAngle = 180.0
if mirror == "xy" or mirror == "XY": #NOTE: "XY" option will override specified rotate angle
layoutToAddSref.transFlags = (False,False,False)
#layoutToAddSref.transFlags[2] = 1
layoutToAddSref.rotateAngle = 180.0
#add the sref to the root structure
self.structures[self.rootStructureName].srefs+=[layoutToAddSref]
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):
"""
@ -365,11 +368,7 @@ class VlsiLayout:
(offsetInLayoutUnits[0],offsetInLayoutUnits[1]+heightInLayoutUnits),
offsetInLayoutUnits]
else:
#is there where gdsmill is halving the coordinates???
#if you printGDS of temp.gds, the header says 1 user unit = .0005 database units. By default user units = .001.
#something to do with the ieeedouble in gdswriter.py????
startPoint = (offsetInLayoutUnits[0]-widthInLayoutUnits/2, offsetInLayoutUnits[1]-heightInLayoutUnits/2) #width/2 height/2
startPoint = (offsetInLayoutUnits[0]-widthInLayoutUnits/2.0, offsetInLayoutUnits[1]-heightInLayoutUnits/2.0)
coordinates=[startPoint,
(startPoint[0]+widthInLayoutUnits,startPoint[1]),
(startPoint[0]+widthInLayoutUnits,startPoint[1]+heightInLayoutUnits),
@ -382,7 +381,7 @@ class VlsiLayout:
boundaryToAdd.coordinates = coordinates
boundaryToAdd.purposeLayer = purposeNumber
#add the sref to the root structure
self.structures[self.rootStructureName].boundaries+=[boundaryToAdd]
self.structures[self.rootStructureName].boundaries.append(boundaryToAdd)
def addPath(self, layerNumber=0, purposeNumber = None, coordinates=[(0,0)], width=1.0):
"""
@ -394,14 +393,14 @@ class VlsiLayout:
for coordinate in coordinates:
cX = self.userUnits(coordinate[0])
cY = self.userUnits(coordinate[1])
layoutUnitCoordinates += [(cX,cY)]
layoutUnitCoordinates.append((cX,cY))
pathToAdd = GdsPath()
pathToAdd.drawingLayer=layerNumber
pathToAdd.purposeLayer = purposeNumber
pathToAdd.pathWidth=widthInLayoutUnits
pathToAdd.coordinates=layoutUnitCoordinates
#add the sref to the root structure
self.structures[self.rootStructureName].paths+=[pathToAdd]
self.structures[self.rootStructureName].paths.append(pathToAdd)
def addText(self, text, layerNumber=0, purposeNumber = None, offsetInMicrons=(0,0), magnification=0.1, rotate = None):
offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1]))
@ -410,17 +409,17 @@ class VlsiLayout:
textToAdd.purposeLayer = purposeNumber
textToAdd.dataType = 0
textToAdd.coordinates = [offsetInLayoutUnits]
textToAdd.transFlags = [0,0,0]
if(len(text)%2 == 1):
#pad with a zero
text = text + '\x00'
textToAdd.textString = text
textToAdd.transFlags = (False,False,True)
#textToAdd.transFlags[1] = 1
textToAdd.magFactor = magnification
if rotate:
textToAdd.transFlags = (False,True,True)
#textToAdd.transFlags[2] = 1
textToAdd.rotateAngle = rotate
#add the sref to the root structure
self.structures[self.rootStructureName].texts+=[textToAdd]
self.structures[self.rootStructureName].texts.append(textToAdd)
def isBounded(self,testPoint,startPoint,endPoint):
#these arguments are touples of (x,y) coordinates
@ -488,6 +487,10 @@ class VlsiLayout:
return False #these shapes are ok
def isPointInsideOfBox(self,pointCoordinates,boxCoordinates):
"""
Check if a point is contained in the shape
"""
debug.check(len(boxCoordinates)==4,"Invalid number of coordinates for box.")
leftBound = boxCoordinates[0][0]
rightBound = boxCoordinates[0][0]
topBound = boxCoordinates[0][1]
@ -509,7 +512,9 @@ class VlsiLayout:
return True
def isShapeInsideOfBox(self,shapeCoordinates, boxCoordinates):
#go through every point in the shape to test if they are all inside the box
"""
Go through every point in the shape to test if they are all inside the box.
"""
for point in shapeCoordinates:
if not self.isPointInsideOfBox(point,boxCoordinates):
return False
@ -532,7 +537,7 @@ class VlsiLayout:
#remap coordinates
shiftedBoundaryCoordinates = []
for shapeCoordinate in boundary.rotatedCoordinates(rotateAngle):
shiftedBoundaryCoordinates+=[(shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1])]
shiftedBoundaryCoordinates.append((shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1]))
joint = self.doShapesIntersect(self.tempCoordinates, shiftedBoundaryCoordinates)
if joint:
self.tempPassFail = False
@ -545,7 +550,7 @@ class VlsiLayout:
#remap coordinates
shiftedBoundaryCoordinates = []
for shapeCoordinate in path.equivalentBoundaryCoordinates(rotateAngle):
shiftedBoundaryCoordinates+=[(shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1])]
shiftedBoundaryCoordinates.append((shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1]))
joint = self.doShapesIntersect(self.tempCoordinates, shiftedBoundaryCoordinates)
if joint:
self.tempPassFail = False
@ -568,7 +573,7 @@ class VlsiLayout:
self.traverseTheHierarchy(delegateFunction = isThisBlockOk)
#if its bad, this global tempPassFail will be false
#if true, we can add the block
passFailRecord+=[self.tempPassFail]
passFailRecord.append(self.tempPassFail)
print("Percent Complete:"+str(percentDone))
@ -579,10 +584,11 @@ class VlsiLayout:
blockY = (yIndex*effectiveBlock)+offsetInMicrons[1]
if passFailRecord[passFailIndex]:
self.addBox(layerToFill, (blockX,blockY), width=blockSize, height=blockSize)
passFailIndex+=1
passFailIndex += 1
print("Done\n\n")
def getLayoutBorder(self,borderlayer):
cellSizeMicron=None
for boundary in self.structures[self.rootStructureName].boundaries:
if boundary.drawingLayer==borderlayer:
if self.debug:
@ -614,29 +620,22 @@ class VlsiLayout:
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=Structure[0]
StructureOrigin=[Structure[1][0],Structure[1][1]]
StructureuVector=[Structure[2][0],Structure[2][1],Structure[2][2]]
StructurevVector=[Structure[3][0],Structure[3][1],Structure[3][2]]
#debug.info(debug_level,"Checking Structure: "+str(StructureName))
#debug.info(debug_level,"-Structure Structure Origin:"+str(StructureOrigin))
#debug.info(debug_level,"-Structure direction: uVector["+str(StructureuVector)+"]")
#debug.info(debug_level,"-Structure direction: vVector["+str(StructurevVector)+"]")
for boundary in self.structures[str(StructureName)].boundaries:
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]
thisBoundary=[left_bottom[0],left_bottom[1],right_top[0],right_top[1]]
thisBoundary=self.transformRectangle(thisBoundary,StructureuVector,StructurevVector)
thisBoundary=[thisBoundary[0]+StructureOrigin[0],thisBoundary[1]+StructureOrigin[1],
thisBoundary[2]+StructureOrigin[0],thisBoundary[3]+StructureOrigin[1]]
thisBoundary=self.transformRectangle(thisBoundary,structureuVector,structurevVector)
thisBoundary=[thisBoundary[0]+structureOrigin[0],thisBoundary[1]+structureOrigin[1],
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 cellBoundary==[None,None,None,None]:
# If any are None
if not (cellBoundary[0] and cellBoundary[1] and cellBoundary[2] and cellBoundary[3]):
cellBoundary=thisBoundary
else:
if cellBoundary[0]>left_bott_X:
@ -650,147 +649,123 @@ class VlsiLayout:
return cellBoundary
def getLabelDBInfo(self,label_name):
def getTexts(self, layer):
"""
Return the coordinates in DB units and layer of all matching labels
Get all of the labels on a given layer only at the root level.
"""
label_list = []
label_layer = None
label_coordinate = [None, None]
# Why must this be the last one found? It breaks if we return the first.
text_list = []
for Text in self.structures[self.rootStructureName].texts:
if Text.textString == label_name or Text.textString == label_name+"\x00":
label_layer = Text.drawingLayer
label_coordinate = Text.coordinates[0]
if label_layer!=None:
label_list.append((label_coordinate,label_layer))
debug.check(len(label_list)>0,"Did not find labels {0}.".format(label_name))
return label_list
def getLabelInfo(self,label_name):
"""
Return the coordinates in USER units and layer of a label
"""
label_list=self.getLabelDBInfo(label_name)
new_list=[]
for label in label_list:
(label_coordinate,label_layer)=label
user_coordinates = [x*self.units[0] for x in label_coordinate]
new_list.append(user_coordinates,label_layer)
return new_list
if Text.drawingLayer == layer:
text_list.append(Text)
return text_list
def getPinShapeByLocLayer(self, coordinate, layer):
"""
Return the largest enclosing rectangle on a layer and at a location.
Coordinates should be in USER units.
"""
db_coordinate = [x/self.units[0] for x in coordinate]
return self.getPinShapeByDBLocLayer(db_coordinate, layer)
def getPinShapeByDBLocLayer(self, coordinate, layer):
"""
Return the largest enclosing rectangle on a layer and at a location.
Coordinates should be in DB units.
"""
pin_boundaries=self.getAllPinShapesInStructureList(coordinate, layer)
if len(pin_boundaries) == 0:
debug.warning("Did not find pin on layer {0} at coordinate {1}".format(layer, coordinate))
# sort the boundaries, return the max area pin boundary
pin_boundaries.sort(key=boundaryArea,reverse=True)
pin_boundary=pin_boundaries[0]
# Convert to USER units
pin_boundary=[pin_boundary[0]*self.units[0],pin_boundary[1]*self.units[0],
pin_boundary[2]*self.units[0],pin_boundary[3]*self.units[0]]
# Make a name if we don't have the pin name
return ["p"+str(coordinate)+"_"+str(layer), layer, pin_boundary]
def getAllPinShapesByLocLayer(self, coordinate, layer):
"""
Return ALL the enclosing rectangles on the same layer
at the given coordinate. Coordinates should be in USER units.
"""
db_coordinate = [int(x/self.units[0]) for x in coordinate]
return self.getAllPinShapesByDBLocLayer(db_coordinate, layer)
def getAllPinShapesByDBLocLayer(self, coordinate, layer):
"""
Return ALL the enclosing rectangles on the same layer
at the given coordinate. Coordinates should be in DB units.
"""
pin_boundaries=self.getAllPinShapesInStructureList(coordinate, layer)
# Convert to user units
new_boundaries = []
for pin_boundary in pin_boundaries:
new_boundaries.append([pin_boundary[0]*self.units[0],pin_boundary[1]*self.units[0],
pin_boundary[2]*self.units[0],pin_boundary[3]*self.units[0]])
# Make a name if we don't have the pin name
return ["p"+str(coordinate)+"_"+str(layer), layer, new_boundaries]
def getPinShapeByLabel(self,label_name):
def getPinShape(self, pin_name):
"""
Search for a pin label and return the largest enclosing rectangle
on the same layer as the pin label.
If there are multiple pin lists, return the max of each.
"""
label_list=self.getLabelDBInfo(label_name)
shape_list=[]
for label in label_list:
(label_coordinate,label_layer)=label
shape = self.getPinShapeByDBLocLayer(label_coordinate, label_layer)
shape_list.append(shape)
return shape_list
pin_map = self.pins[pin_name]
max_pins = []
for pin_list in pin_map:
max_pin = None
max_area = 0
for pin in pin_list:
(layer,boundary) = pin
new_area = boundaryArea(boundary)
if max_pin == None or new_area>max_area:
max_pin = pin
max_area = new_area
max_pins.append(max_pin)
def getAllPinShapesByLabel(self,label_name):
return max_pins
def getAllPinShapes(self, pin_name):
"""
Search for a pin label and return ALL the enclosing rectangles on the same layer
as the pin label.
"""
label_list=self.getLabelDBInfo(label_name)
shape_list=[]
for label in label_list:
(label_coordinate,label_layer)=label
shape_list.append(self.getAllPinShapesByDBLocLayer(label_coordinate, label_layer))
shape_list = []
pin_map = self.pins[pin_name]
for pin_list in pin_map:
for pin in pin_list:
(pin_layer, boundary) = pin
shape_list.append(pin)
return shape_list
def getAllPinShapesInStructureList(self,coordinates,layer):
def processLabelPins(self, layer):
"""
Given a coordinate, search for enclosing structures on the given layer.
Return all pin shapes.
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)
# Get all of the shapes on the layer at all levels
# and transform them to the current level
shapes = self.getAllShapes(layer)
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))
label_text = label.textString
# Remove the padding if it exists
if label_text[-1] == "\x00":
label_text = label_text[0:-1]
try:
self.pins[label_text]
except KeyError:
self.pins[label_text] = []
self.pins[label_text].append(pin_shapes)
def getAllShapes(self,layer):
"""
Return all gshapes on a given layer in [llx, lly, urx, ury] format and
user units.
"""
boundaries = []
for TreeUnit in self.xyTree:
boundaries += self.getPinInStructure(coordinates,layer,TreeUnit)
#print(TreeUnit[0])
boundaries.extend(self.getShapesInStructure(layer,TreeUnit))
# Remove duplicates without defining a hash
# (could be sped up by creating hash and using list(set())
new_boundaries = []
for boundary in boundaries:
if boundary not in new_boundaries:
new_boundaries.append(boundary)
# Convert to user units
boundaries = []
for boundary in new_boundaries:
boundaries.append([boundary[0]*self.units[0],boundary[1]*self.units[0],
boundary[2]*self.units[0],boundary[3]*self.units[0]])
return boundaries
def getPinInStructure(self,coordinates,layer,structure):
def getShapesInStructure(self,layer,structure):
"""
Go through all the shapes in a structure and return the list of shapes
that the label coordinates are inside.
Go through all the shapes in a structure and return the list of shapes in
the form [llx, lly, urx, ury]
"""
# check if this is a rectangle
structureName=structure[0]
structureOrigin=[structure[1][0],structure[1][1]]
structureuVector=[structure[2][0],structure[2][1],structure[2][2]]
structurevVector=[structure[3][0],structure[3][1],structure[3][2]]
(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:
# Pin enclosures only work on rectangular pins so ignore any non rectangle
# This may report not finding pins, but the user should fix this by adding a rectangle.
# FIXME: Right now, this only supports rectangular shapes!
#debug.check(len(boundary.coordinates)==5,"Non-rectangular shape.")
if len(boundary.coordinates)!=5:
continue
if layer==boundary.drawingLayer:
@ -798,15 +773,15 @@ class VlsiLayout:
right_top=boundary.coordinates[2]
# Rectangle is [leftx, bottomy, rightx, topy].
boundaryRect=[left_bottom[0],left_bottom[1],right_top[0],right_top[1]]
# perform the rotation
boundaryRect=self.transformRectangle(boundaryRect,structureuVector,structurevVector)
# add the offset
boundaryRect=[boundaryRect[0]+structureOrigin[0].item(),boundaryRect[1]+structureOrigin[1].item(),
boundaryRect[2]+structureOrigin[0].item(),boundaryRect[3]+structureOrigin[1].item()]
if self.labelInRectangle(coordinates,boundaryRect):
boundaries.append(boundaryRect)
boundaries.append(boundaryRect)
return boundaries
def transformRectangle(self,originalRectangle,uVector,vVector):
"""
Transforms the four coordinates of a rectangle in space
@ -830,8 +805,12 @@ class VlsiLayout:
"""
Rotate a coordinate in space.
"""
x=coordinate[0]*uVector[0].item()+coordinate[1]*uVector[1].item()
y=coordinate[1]*vVector[1].item()+coordinate[0]*vVector[0].item()
# MRG: 9/3/18 Incorrect matrixi multiplication!
# This is fixed to be:
# |u[0] v[0]| |x| |x'|
# |u[1] v[1]|x|y|=|y'|
x=coordinate[0]*uVector[0].item()+coordinate[1]*vVector[0].item()
y=coordinate[0]*uVector[1].item()+coordinate[1]*vVector[1].item()
transformCoordinate=[x,y]
return transformCoordinate
@ -841,13 +820,14 @@ class VlsiLayout:
"""
Checks if a coordinate is within a given rectangle. Rectangle is [leftx, bottomy, rightx, topy].
"""
coordinate_In_Rectangle_x_range=(coordinate[0]>=int(rectangle[0]))&(coordinate[0]<=int(rectangle[2]))
coordinate_In_Rectangle_y_range=(coordinate[1]>=int(rectangle[1]))&(coordinate[1]<=int(rectangle[3]))
coordinate_In_Rectangle_x_range=(coordinate[0]>=rectangle[0])&(coordinate[0]<=rectangle[2])
coordinate_In_Rectangle_y_range=(coordinate[1]>=rectangle[1])&(coordinate[1]<=rectangle[3])
if coordinate_In_Rectangle_x_range & coordinate_In_Rectangle_y_range:
return True
else:
return False
def boundaryArea(A):
"""
Returns boundary area for sorting.

View File

@ -24,7 +24,7 @@ def parse_args():
global OPTS
option_list = {
optparse.make_option("-b", "--backannotated", action="store_true", dest="run_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"),
@ -59,7 +59,7 @@ def parse_args():
OPTS.tech_name = "scmos"
# Alias SCMOS to AMI 0.5um
if OPTS.tech_name == "scmos":
OPTS.tech_name = "scn3me_subm"
OPTS.tech_name = "scn4m_subm"
return (options, args)
@ -74,10 +74,12 @@ def print_banner():
print("|=========" + name.center(60) + "=========|")
print("|=========" + " ".center(60) + "=========|")
print("|=========" + "VLSI Design and Automation Lab".center(60) + "=========|")
print("|=========" + "University of California Santa Cruz CE Department".center(60) + "=========|")
print("|=========" + "Computer Science and Engineering Department".center(60) + "=========|")
print("|=========" + "University of California Santa Cruz".center(60) + "=========|")
print("|=========" + " ".center(60) + "=========|")
print("|=========" + "VLSI Computer Architecture Research Group".center(60) + "=========|")
print("|=========" + "Oklahoma State University ECE Department".center(60) + "=========|")
print("|=========" + "Electrical and Computer Engineering Department".center(60) + "=========|")
print("|=========" + "Oklahoma State University".center(60) + "=========|")
print("|=========" + " ".center(60) + "=========|")
user_info = "Usage help: openram-user-group@ucsc.edu"
print("|=========" + user_info.center(60) + "=========|")
@ -98,9 +100,17 @@ def check_versions():
minor_required = 5
if not (major_python_version == major_required and minor_python_version >= minor_required):
debug.error("Python {0}.{1} or greater is required.".format(major_required,minor_required),-1)
# FIXME: Check versions of other tools here??
# or, this could be done in each module (e.g. verify, characterizer, etc.)
global OPTS
try:
import flask_table
OPTS.datasheet_gen = 1
except:
OPTS.datasheet_gen = 0
def init_openram(config_file, is_unit_test=True):
"""Initialize the technology, paths, simulators, etc."""
@ -116,6 +126,8 @@ def init_openram(config_file, is_unit_test=True):
import_tech()
init_paths()
# Reset the static duplicate name checker for unit tests.
import hierarchy_design
hierarchy_design.hierarchy_design.name_map=[]
@ -142,22 +154,31 @@ def init_openram(config_file, is_unit_test=True):
def get_tool(tool_type, preferences):
def get_tool(tool_type, preferences, default_name=None):
"""
Find which tool we have from a list of preferences and return the
one selected and its full path.
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))
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)
if 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)
else:
debug.info(1, "Could not find {0}, trying next {1} tool.".format(name,tool_type))
debug.info(1, "Using {0}: {1}".format(tool_type,exe_name))
return(default_name,exe_name)
else:
return(None,"")
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)
else:
debug.info(1, "Could not find {0}, trying next {1} tool.".format(name,tool_type))
else:
return(None,"")
def read_config(config_file, is_unit_test=True):
@ -195,32 +216,35 @@ def read_config(config_file, is_unit_test=True):
# 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
# Massage the output path to be an absolute one
if not OPTS.output_path.endswith('/'):
OPTS.output_path += "/"
if not OPTS.output_path.startswith('/'):
OPTS.output_path = os.getcwd() + "/" + OPTS.output_path
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
# If we are only generating a netlist, we can't do DRC/LVS
if OPTS.netlist_only:
OPTS.check_lvsdrc=False
# If config didn't set output name, make a reasonable default.
if (OPTS.output_name == ""):
OPTS.output_name = "sram_{0}rw_{1}b_{2}w_{3}bank_{4}".format(OPTS.rw_ports,
OPTS.word_size,
OPTS.num_words,
OPTS.num_banks,
OPTS.tech_name)
ports = ""
if OPTS.num_rw_ports>0:
ports += "{}rw_".format(OPTS.num_rw_ports)
if OPTS.num_w_ports>0:
ports += "{}w_".format(OPTS.num_w_ports)
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,
ports,
OPTS.tech_name)
# Don't delete the output dir, it may have other files!
# make the directory if it doesn't exist
try:
os.makedirs(OPTS.output_path, 0o750)
except OSError as e:
if e.errno == 17: # errno.EEXIST
os.chmod(OPTS.output_path, 0o750)
except:
debug.error("Unable to make output directory.",-1)
def end_openram():
@ -244,7 +268,8 @@ def cleanup_paths():
if not OPTS.purge_temp:
debug.info(0,"Preserving temp directory: {}".format(OPTS.openram_temp))
return
if os.path.exists(OPTS.openram_temp):
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)
contents = [os.path.join(OPTS.openram_temp, i) for i in os.listdir(OPTS.openram_temp)]
@ -270,7 +295,8 @@ def setup_paths():
# Add all of the subdirs to the python path
# These subdirs are modules and don't need to be added: characterizer, verify
for subdir in ["gdsMill", "tests", "router", "modules", "base", "pgates"]:
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)
debug.check(os.path.isdir(full_path),
"$OPENRAM_HOME/{0} does not exist: {1}".format(subdir,full_path))
@ -280,14 +306,6 @@ def setup_paths():
OPTS.openram_temp += "/"
debug.info(1, "Temporary files saved in " + OPTS.openram_temp)
cleanup_paths()
# make the directory if it doesn't exist
try:
os.makedirs(OPTS.openram_temp, 0o750)
except OSError as e:
if e.errno == 17: # errno.EEXIST
os.chmod(OPTS.openram_temp, 0o750)
def is_exe(fpath):
@ -303,7 +321,29 @@ def find_exe(check_exe):
if is_exe(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))
os.makedirs(OPTS.openram_temp, 0o750)
except OSError as e:
if e.errno == 17: # errno.EEXIST
os.chmod(OPTS.openram_temp, 0o750)
# Don't delete the output dir, it may have other files!
# make the directory if it doesn't exist
try:
os.makedirs(OPTS.output_path, 0o750)
except OSError as e:
if e.errno == 17: # errno.EEXIST
os.chmod(OPTS.output_path, 0o750)
except:
debug.error("Unable to make output directory.",-1)
# imports correct technology directories for testing
def import_tech():
global OPTS
@ -362,8 +402,6 @@ def report_status():
debug.error("{0} is not an integer in config file.".format(OPTS.word_size))
if type(OPTS.num_words)!=int:
debug.error("{0} is not an integer in config file.".format(OPTS.sram_size))
if type(OPTS.num_banks)!=int:
debug.error("{0} is not an integer in config file.".format(OPTS.num_banks))
if not OPTS.tech_name:
debug.error("Tech name must be specified in config file.")
@ -372,6 +410,9 @@ def report_status():
print("Word size: {0}\nWords: {1}\nBanks: {2}".format(OPTS.word_size,
OPTS.num_words,
OPTS.num_banks))
if OPTS.netlist_only:
print("Netlist only mode (no physical design is being done).")
if not OPTS.check_lvsdrc:
print("DRC/LVS/PEX checking is disabled.")

File diff suppressed because it is too large Load Diff

View File

@ -15,13 +15,42 @@ class bank_select(design.design):
banks are created in upper level SRAM module
"""
def __init__(self, name="bank_select"):
def __init__(self, name="bank_select", port="rw"):
design.design.__init__(self, name)
self.port = port
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_pins()
self.add_modules()
self.create_modules()
def create_layout(self):
self.calculate_module_offsets()
self.place_modules()
self.route_modules()
self.DRC_LVS()
def add_pins(self):
# Number of control lines in the bus
self.num_control_lines = 6
if self.port == "rw":
self.num_control_lines = 4
else:
self.num_control_lines = 3
# The order of the control signals on the control bus:
self.input_control_signals = ["clk_buf", "tri_en_bar", "tri_en", "clk_buf_bar", "w_en", "s_en"]
# FIXME: Update for multiport (these names are not right)
self.input_control_signals = ["clk_buf", "clk_buf_bar"]
if (self.port == "rw") or (self.port == "w"):
self.input_control_signals.append("w_en")
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]
@ -31,33 +60,36 @@ class bank_select(design.design):
self.add_pin("vdd","POWER")
self.add_pin("gnd","GROUND")
self.create_modules()
self.calculate_module_offsets()
self.add_modules()
self.route_modules()
self.DRC_LVS()
def create_modules(self):
def add_modules(self):
""" Create modules for later instantiation """
from importlib import reload
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell = self.mod_bitcell()
height = self.bitcell.height + drc("poly_to_active")
# 1x Inverter
self.inv = pinv()
self.add_mod(self.inv)
self.inv_sel = pinv(height=height)
self.add_mod(self.inv_sel)
# 4x Inverter
self.inv4x = pinv(4)
self.inv = self.inv4x = pinv(4)
self.add_mod(self.inv4x)
self.nor2 = pnor2()
self.nor2 = pnor2(height=height)
self.add_mod(self.nor2)
self.inv4x_nor = pinv(size=4, height=height)
self.add_mod(self.inv4x_nor)
self.nand2 = pnand2()
self.add_mod(self.nand2)
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_nand = self.inv4x.width + 2*self.m2_pitch + drc("pwell_to_nwell")
self.xoffset_nor = self.inv4x.width + 2*self.m2_pitch + drc("pwell_to_nwell")
self.xoffset_inv = max(self.xoffset_nand + self.nand2.width, self.xoffset_nor + self.nor2.width)
self.xoffset_bank_sel_inv = 0
self.xoffset_inputs = 0
@ -67,15 +99,10 @@ class bank_select(design.design):
self.height = self.yoffset_maxpoint + 2*self.m1_pitch
self.width = self.xoffset_inv + self.inv4x.width
def add_modules(self):
def create_modules(self):
# bank select inverter
self.bank_select_inv_position = vector(self.xoffset_bank_sel_inv, 0)
# bank select inverter (must be made unique if more than one OR)
self.bank_sel_inv=self.add_inst(name="bank_sel_inv",
mod=self.inv,
offset=[self.xoffset_bank_sel_inv, 0])
mod=self.inv_sel)
self.connect_inst(["bank_sel", "bank_sel_bar", "vdd", "gnd"])
self.logic_inst = []
@ -87,7 +114,64 @@ class bank_select(design.design):
name_nor = "nor_{}".format(input_name)
name_inv = "inv_{}".format(input_name)
y_offset = self.inv.height * i
# These require OR (nor2+inv) gates since they are active low.
# (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.connect_inst([input_name,
"bank_sel_bar",
gated_name+"_temp_bar",
"vdd",
"gnd"])
# They all get inverters on the output
self.inv_inst.append(self.add_inst(name=name_inv,
mod=self.inv4x_nor))
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,
mod=self.nand2))
self.connect_inst([input_name,
"bank_sel",
gated_name+"_temp_bar",
"vdd",
"gnd"])
# They all get inverters on the output
self.inv_inst.append(self.add_inst(name=name_inv,
mod=self.inv4x))
self.connect_inst([gated_name+"_temp_bar",
gated_name,
"vdd",
"gnd"])
def place_modules(self):
# bank select inverter
self.bank_select_inv_position = vector(self.xoffset_bank_sel_inv, 0)
# bank select inverter (must be made unique if more than one OR)
self.bank_sel_inv.place(vector(self.xoffset_bank_sel_inv, 0))
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]
if i == 0:
y_offset = 0
else:
y_offset = self.inv4x_nor.height + self.inv.height * (i-1)
if i%2:
y_offset += self.inv.height
mirror = "MX"
@ -96,42 +180,20 @@ class bank_select(design.design):
# These require OR (nor2+inv) gates since they are active low.
# (writes occur on clk low)
if input_name in ("clk_buf", "tri_en_bar"):
if input_name in ("clk_buf"):
self.logic_inst.append(self.add_inst(name=name_nor,
mod=self.nor2,
offset=[self.xoffset_nor, y_offset],
mirror=mirror))
self.connect_inst([input_name,
"bank_sel_bar",
gated_name+"_temp_bar",
"vdd",
"gnd"])
logic_inst.place(offset=[self.xoffset_nor, y_offset],
mirror=mirror)
# the rest are AND (nand2+inv) gates
else:
self.logic_inst.append(self.add_inst(name=name_nand,
mod=self.nand2,
offset=[self.xoffset_nand, y_offset],
mirror=mirror))
bank_sel_signal = "bank_sel"
self.connect_inst([input_name,
"bank_sel",
gated_name+"_temp_bar",
"vdd",
"gnd"])
logic_inst.place(offset=[self.xoffset_nand, y_offset],
mirror=mirror)
# They all get inverters on the output
self.inv_inst.append(self.add_inst(name=name_inv,
mod=self.inv4x,
offset=[self.xoffset_inv, y_offset],
mirror=mirror))
self.connect_inst([gated_name+"_temp_bar",
gated_name,
"vdd",
"gnd"])
inv_inst.place(offset=[self.xoffset_inv, y_offset],
mirror=mirror)
def route_modules(self):
@ -161,7 +223,7 @@ class bank_select(design.design):
self.add_label_pin(text="bank_sel_bar",
layer="metal2",
offset=vector(xoffset_bank_sel_bar, 0),
height=2*self.inv.height)
height=self.inv.height)
self.add_via_center(layers=("metal1","via1","metal2"),
offset=bank_sel_bar_pin.rc())
@ -173,7 +235,7 @@ class bank_select(design.design):
input_name = self.input_control_signals[i]
gated_name = self.control_signals[i]
if input_name in ("clk_buf", "tri_en_bar"):
if input_name in ("clk_buf"):
xoffset_bank_signal = xoffset_bank_sel_bar
else:
xoffset_bank_signal = xoffset_bank_sel

View File

@ -1,201 +1,227 @@
import debug
import design
from tech import drc, spice
from vector import vector
from globals import OPTS
class bitcell_array(design.design):
"""
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="bitcell_array"):
design.design.__init__(self, name)
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
self.column_size = cols
self.row_size = rows
from importlib import reload
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.cell = self.mod_bitcell()
self.add_mod(self.cell)
# We increase it by a well enclosure so the precharges don't overlap our wells
self.height = self.row_size*self.cell.height + drc["well_enclosure_active"] + self.m1_width
self.width = self.column_size*self.cell.width + self.m1_width
self.add_pins()
self.create_layout()
self.add_layout_pins()
# We don't offset this because we need to align
# the replica bitcell in the control logic
#self.offset_all_coordinates()
self.DRC_LVS()
def add_pins(self):
row_list = self.cell.list_row_pins()
column_list = self.cell.list_column_pins()
for col in range(self.column_size):
for cell_column in column_list:
self.add_pin(cell_column+"[{0}]".format(col))
for row in range(self.row_size):
for cell_row in row_list:
self.add_pin(cell_row+"[{0}]".format(row))
self.add_pin("vdd")
self.add_pin("gnd")
def create_layout(self):
xoffset = 0.0
self.cell_inst = {}
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]=self.add_inst(name=name,
mod=self.cell,
offset=[xoffset, tempy],
mirror=dir_key)
self.connect_inst(self.cell.list_bitcell_pins(col, row))
yoffset += self.cell.height
xoffset += self.cell.width
def add_layout_pins(self):
""" Add the layout pins """
column_list = self.cell.list_column_pins()
offset = vector(0.0, 0.0)
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)
# increments to the next column width
offset.x += self.cell.width
row_list = self.cell.list_row_pins()
offset.x = 0.0
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())
# increments to the next row height
offset.y += self.cell.height
# For every second row and column, add a via for vdd
for row in range(self.row_size):
for col in range(self.column_size):
inst = self.cell_inst[row,col]
for vdd_pin in inst.get_pins("vdd"):
# Drop to M1 if needed
if vdd_pin.layer == "metal1":
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=vdd_pin.center(),
rotate=90)
# Always drop to M2
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=vdd_pin.center())
self.add_layout_pin_rect_center(text="vdd",
layer="metal3",
offset=vdd_pin.center())
# For every second row and column (+1), add a via for gnd
for row in range(self.row_size):
for col in range(self.column_size):
inst = self.cell_inst[row,col]
for gnd_pin in inst.get_pins("gnd"):
# Drop to M1 if needed
if gnd_pin.layer == "metal1":
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=gnd_pin.center(),
rotate=90)
# Always drop to M2
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=gnd_pin.center())
self.add_layout_pin_rect_center(text="gnd",
layer="metal3",
offset=gnd_pin.center())
def analytical_delay(self, slew, load=0):
from tech import drc
wl_wire = self.gen_wl_wire()
wl_wire.return_delay_over_wire(slew)
wl_to_cell_delay = wl_wire.return_delay_over_wire(slew)
# hypothetical delay from cell to bl end without sense amp
bl_wire = self.gen_bl_wire()
cell_load = 2 * bl_wire.return_input_cap() # we ingore the wire r
# hence just use the whole c
bl_swing = 0.1
cell_delay = self.cell.analytical_delay(wl_to_cell_delay.slew, cell_load, swing = bl_swing)
#we do not consider the delay over the wire for now
return self.return_delay(cell_delay.delay+wl_to_cell_delay.delay,
wl_to_cell_delay.slew)
def analytical_power(self, proc, vdd, temp, load):
"""Power of Bitcell array and bitline in nW."""
from tech import drc
# Dynamic Power from Bitline
bl_wire = self.gen_bl_wire()
cell_load = 2 * bl_wire.return_input_cap()
bl_swing = 0.1 #This should probably be defined in the tech file or input
freq = spice["default_event_rate"]
bitline_dynamic = bl_swing*cell_load*vdd*vdd*freq #not sure if calculation is correct
#Calculate the bitcell power which currently only includes leakage
cell_power = self.cell.analytical_power(proc, vdd, temp, load)
#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
def gen_wl_wire(self):
wl_wire = self.generate_rc_net(int(self.column_size), self.width, drc["minwidth_metal1"])
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):
bl_pos = 0
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), self.height, drc["minwidth_metal1"])
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
return bl_wire
def output_load(self, bl_pos=0):
bl_wire = self.gen_bl_wire()
return bl_wire.wire_c # sense amp only need to charge small portion of the bl
# set as one segment for now
def input_load(self):
wl_wire = self.gen_wl_wire()
return wl_wire.return_input_cap()
import debug
import design
from tech import drc, spice
from vector import vector
from globals import OPTS
class bitcell_array(design.design):
"""
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="bitcell_array"):
design.design.__init__(self, name)
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
self.column_size = cols
self.row_size = rows
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()
def create_netlist(self):
""" Create and connect the netlist """
self.add_modules()
self.add_pins()
self.create_modules()
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 + drc("well_enclosure_active") + self.m1_width
self.width = self.column_size*self.cell.width + self.m1_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.add_layout_pins()
self.DRC_LVS()
def add_pins(self):
row_list = self.cell.list_all_wl_names()
column_list = self.cell.list_all_bitline_names()
for col in range(self.column_size):
for cell_column in column_list:
self.add_pin(cell_column+"_{0}".format(col))
for row in range(self.row_size):
for cell_row in row_list:
self.add_pin(cell_row+"_{0}".format(row))
self.add_pin("vdd")
self.add_pin("gnd")
def add_modules(self):
""" Add the modules used in this design """
from importlib import reload
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.cell = self.mod_bitcell()
self.add_mod(self.cell)
def create_modules(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.connect_inst(self.cell.list_bitcell_pins(col, row))
def add_layout_pins(self):
""" Add the layout pins """
row_list = self.cell.list_all_wl_names()
column_list = self.cell.list_all_bitline_names()
offset = vector(0.0, 0.0)
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)
# increments to the next column width
offset.x += self.cell.width
offset.x = 0.0
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())
# increments to the next row height
offset.y += self.cell.height
# For every second row and column, add a via for vdd
for row in range(self.row_size):
for col in range(self.column_size):
inst = self.cell_inst[row,col]
for vdd_pin in inst.get_pins("vdd"):
# Drop to M1 if needed
if vdd_pin.layer == "metal1":
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=vdd_pin.center(),
rotate=90)
# Always drop to M2
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=vdd_pin.center())
self.add_layout_pin_rect_center(text="vdd",
layer="metal3",
offset=vdd_pin.center())
# For every second row and column (+1), add a via for gnd
for row in range(self.row_size):
for col in range(self.column_size):
inst = self.cell_inst[row,col]
for gnd_pin in inst.get_pins("gnd"):
# Drop to M1 if needed
if gnd_pin.layer == "metal1":
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=gnd_pin.center(),
rotate=90)
# Always drop to M2
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=gnd_pin.center())
self.add_layout_pin_rect_center(text="gnd",
layer="metal3",
offset=gnd_pin.center())
def analytical_delay(self, slew, load=0):
from tech import drc
wl_wire = self.gen_wl_wire()
wl_wire.return_delay_over_wire(slew)
wl_to_cell_delay = wl_wire.return_delay_over_wire(slew)
# hypothetical delay from cell to bl end without sense amp
bl_wire = self.gen_bl_wire()
cell_load = 2 * bl_wire.return_input_cap() # we ingore the wire r
# hence just use the whole c
bl_swing = 0.1
cell_delay = self.cell.analytical_delay(wl_to_cell_delay.slew, cell_load, swing = bl_swing)
#we do not consider the delay over the wire for now
return self.return_delay(cell_delay.delay+wl_to_cell_delay.delay,
wl_to_cell_delay.slew)
def analytical_power(self, proc, vdd, temp, load):
"""Power of Bitcell array and bitline in nW."""
from tech import drc
# Dynamic Power from Bitline
bl_wire = self.gen_bl_wire()
cell_load = 2 * bl_wire.return_input_cap()
bl_swing = 0.1 #This should probably be defined in the tech file or input
freq = spice["default_event_rate"]
bitline_dynamic = bl_swing*cell_load*vdd*vdd*freq #not sure if calculation is correct
#Calculate the bitcell power which currently only includes leakage
cell_power = self.cell.analytical_power(proc, vdd, temp, load)
#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
def gen_wl_wire(self):
if OPTS.netlist_only:
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
return wl_wire
def gen_bl_wire(self):
if OPTS.netlist_only:
height = 0
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.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
return bl_wire
def output_load(self, bl_pos=0):
bl_wire = self.gen_bl_wire()
return bl_wire.wire_c # sense amp only need to charge small portion of the bl
# set as one segment for now
def input_load(self):
wl_wire = self.gen_wl_wire()
return wl_wire.return_input_cap()

View File

@ -18,23 +18,40 @@ class control_logic(design.design):
Dynamically generated Control logic for the total SRAM circuit.
"""
def __init__(self, num_rows):
def __init__(self, num_rows, words_per_row, port_type="rw"):
""" Constructor """
design.design.__init__(self, "control_logic")
debug.info(1, "Creating {}".format(self.name))
name = "control_logic_" + port_type
design.design.__init__(self, name)
debug.info(1, "Creating {}".format(name))
self.num_rows = num_rows
self.create_layout()
self.DRC_LVS()
self.words_per_row = words_per_row
self.port_type = port_type
if self.port_type == "rw":
self.num_control_signals = 2
else:
self.num_control_signals = 1
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.setup_signal_busses()
self.add_pins()
self.add_modules()
self.create_modules()
def create_layout(self):
""" Create layout and route between modules """
self.setup_layout_offsets()
self.add_pins()
self.create_modules()
self.add_rails()
self.add_modules()
self.add_routing()
self.route_rails()
self.place_modules()
self.route_all()
#self.add_lvs_correspondence_points()
self.DRC_LVS()
def add_pins(self):
@ -46,13 +63,13 @@ class control_logic(design.design):
self.add_pin("vdd","POWER")
self.add_pin("gnd","GROUND")
def create_modules(self):
""" add all the required modules """
def add_modules(self):
""" Add all the required modules """
dff = dff_inv()
dff_height = dff.height
self.ctrl_dff_array = dff_inv_array(rows=3,columns=1)
self.ctrl_dff_array = dff_inv_array(rows=self.num_control_signals,columns=1)
self.add_mod(self.ctrl_dff_array)
self.nand2 = pnand2(height=dff_height)
@ -72,41 +89,65 @@ class control_logic(design.design):
self.inv8 = pinv(size=16, height=dff_height)
self.add_mod(self.inv8)
from importlib import reload
c = reload(__import__(OPTS.replica_bitline))
replica_bitline = getattr(c, OPTS.replica_bitline)
# FIXME: These should be tuned according to the size!
delay_stages = 4 # Must be non-inverting
if (self.port_type == "rw") or (self.port_type == "r"):
from importlib import reload
c = reload(__import__(OPTS.replica_bitline))
replica_bitline = getattr(c, OPTS.replica_bitline)
delay_stages, delay_fanout = self.get_delay_chain_size()
bitcell_loads = int(math.ceil(self.num_rows / 2.0))
self.replica_bitline = replica_bitline(delay_stages, delay_fanout, bitcell_loads, name="replica_bitline_"+self.port_type)
self.add_mod(self.replica_bitline)
def get_delay_chain_size(self):
"""Determine the size of the delay chain used for the Sense Amp Enable """
# FIXME: These should be tuned according to the additional size parameters
delay_fanout = 3 # This can be anything >=2
bitcell_loads = int(math.ceil(self.num_rows / 5.0))
self.replica_bitline = replica_bitline(delay_stages, delay_fanout, bitcell_loads)
self.add_mod(self.replica_bitline)
def setup_layout_offsets(self):
""" Setup layout offsets, determine the size of the busses etc """
# These aren't for instantiating, but we use them to get the dimensions
#self.poly_contact_offset = vector(0.5*contact.poly.width,0.5*contact.poly.height)
# Have the cell gap leave enough room to route an M2 wire.
# Some cells may have pwell/nwell spacing problems too when the wells are different heights.
#self.cell_gap = max(self.m2_pitch,drc["pwell_to_nwell"])
# Delay stages Must be non-inverting
if self.words_per_row >= 8:
delay_stages = 8
elif self.words_per_row == 4:
delay_stages = 6
else:
delay_stages = 4
return (delay_stages, delay_fanout)
def setup_signal_busses(self):
""" Setup bus names, determine the size of the busses etc """
# List of input control signals
self.input_list =["csb","web","oeb"]
self.dff_output_list =["cs_bar", "cs", "we_bar", "we", "oe_bar", "oe"]
if self.port_type == "rw":
self.input_list = ["csb", "web"]
else:
self.input_list = ["csb"]
if self.port_type == "rw":
self.dff_output_list = ["cs_bar", "cs", "we_bar", "we"]
else:
self.dff_output_list = ["cs_bar", "cs"]
# list of output control signals (for making a vertical bus)
self.internal_bus_list = ["clk_buf", "clk_buf_bar", "we", "cs", "oe"]
if self.port_type == "rw":
self.internal_bus_list = ["clk_buf", "clk_buf_bar", "we", "cs"]
else:
self.internal_bus_list = ["clk_buf", "clk_buf_bar", "cs"]
# leave space for the bus plus one extra space
self.internal_bus_width = (len(self.internal_bus_list)+1)*self.m2_pitch
# Ooutputs to the bank
self.output_list = ["s_en", "w_en", "clk_buf_bar", "clk_buf"]
# # with tri/tri_en
# self.output_list = ["s_en", "w_en", "tri_en", "tri_en_bar", "clk_buf_bar", "clk_buf"]
# Outputs to the bank
if self.port_type == "r":
self.output_list = ["s_en"]
elif self.port_type == "w":
self.output_list = ["w_en"]
else:
self.output_list = ["s_en", "w_en"]
self.output_list.append("clk_buf_bar")
self.output_list.append("clk_buf")
self.supply_list = ["vdd", "gnd"]
def add_rails(self):
def route_rails(self):
""" Add the input signal inverted tracks """
height = 4*self.inv1.height - self.m2_pitch
offset = vector(self.ctrl_dff_array.width,0)
@ -114,223 +155,189 @@ class control_logic(design.design):
self.rail_offsets = self.create_vertical_bus("metal2", self.m2_pitch, offset, self.internal_bus_list, height)
def add_modules(self):
def create_modules(self):
""" Create all the modules """
self.create_dffs()
self.create_clk_row()
if (self.port_type == "rw") or (self.port_type == "w"):
self.create_we_row()
if (self.port_type == "rw") or (self.port_type == "r"):
self.create_rbl_in_row()
self.create_sen_row()
self.create_rbl()
def place_modules(self):
""" Place all the modules """
# Keep track of all right-most instances to determine row boundary
# and add the vdd/gnd pins
self.row_end_inst = []
# Add the control flops on the left of the bus
self.add_dffs()
self.place_dffs()
row = 0
# Add the logic on the right of the bus
self.add_clk_row(row=0) # clk is a double-high cell
self.add_we_row(row=2)
# self.add_trien_row(row=3)
# self.add_trien_bar_row(row=4)
self.add_rbl_in_row(row=3)
self.add_sen_row(row=4)
self.add_rbl(row=5)
self.place_clk_row(row=row) # clk is a double-high cell
row += 2
if (self.port_type == "rw") or (self.port_type == "w"):
self.place_we_row(row=row)
pre_height = self.w_en_inst.uy()
control_center_y = self.w_en_inst.by()
row += 1
if (self.port_type == "rw") or (self.port_type == "r"):
self.place_rbl_in_row(row=row)
self.place_sen_row(row=row+1)
self.place_rbl(row=row+2)
pre_height = self.rbl_inst.uy()
control_center_y = self.rbl_inst.by()
self.add_lvs_correspondence_points()
# This offset is used for placement of the control logic in the SRAM level.
self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y)
# This offset is used for placement of the control logic in
# the SRAM level.
self.control_logic_center = vector(self.ctrl_dff_inst.rx(), self.rbl_inst.by())
self.height = self.rbl_inst.uy()
# Extra pitch on top and right
self.height = pre_height + self.m3_pitch
# Max of modules or logic rows
self.width = max(self.rbl_inst.rx(), max([inst.rx() for inst in self.row_end_inst]))
if (self.port_type == "rw") or (self.port_type == "r"):
self.width = max(self.rbl_inst.rx(), max([inst.rx() for inst in self.row_end_inst])) + self.m2_pitch
else:
self.width = max([inst.rx() for inst in self.row_end_inst]) + self.m2_pitch
def add_routing(self):
def route_all(self):
""" Routing between modules """
self.route_dffs()
#self.route_trien()
#self.route_trien_bar()
self.route_rbl_in()
self.route_wen()
self.route_sen()
if (self.port_type == "rw") or (self.port_type == "w"):
self.route_wen()
if (self.port_type == "rw") or (self.port_type == "r"):
self.route_rbl_in()
self.route_sen()
self.route_clk()
self.route_supply()
def add_rbl(self,row):
""" Add the replica bitline """
def create_rbl(self):
""" Create the replica bitline """
self.rbl_inst=self.add_inst(name="replica_bitline",
mod=self.replica_bitline)
self.connect_inst(["rbl_in", "pre_s_en", "vdd", "gnd"])
def place_rbl(self,row):
""" Place the replica bitline """
y_off = row * self.inv1.height + 2*self.m1_pitch
# Add the RBL above the rows
# Add to the right of the control rows and routing channel
self.replica_bitline_offset = vector(0, y_off)
self.rbl_inst=self.add_inst(name="replica_bitline",
mod=self.replica_bitline,
offset=self.replica_bitline_offset)
self.connect_inst(["rbl_in", "pre_s_en", "vdd", "gnd"])
self.rbl_inst.place(self.replica_bitline_offset)
def add_clk_row(self,row):
""" Add the multistage clock buffer below the control flops """
x_off = self.ctrl_dff_array.width + self.internal_bus_width
(y_off,mirror)=self.get_offset(row)
clkbuf_offset = vector(x_off,y_off)
def create_clk_row(self):
""" Create the multistage clock buffer """
self.clkbuf_inst = self.add_inst(name="clkbuf",
mod=self.clkbuf,
offset=clkbuf_offset)
mod=self.clkbuf)
self.connect_inst(["clk","clk_buf_bar","clk_buf","vdd","gnd"])
def place_clk_row(self,row):
""" Place the multistage clock buffer below the control flops """
x_off = self.ctrl_dff_array.width + self.internal_bus_width
(y_off,mirror)=self.get_offset(row)
clkbuf_offset = vector(x_off,y_off)
self.clkbuf_inst.place(clkbuf_offset)
self.row_end_inst.append(self.clkbuf_inst)
def add_rbl_in_row(self,row):
x_off = self.ctrl_dff_array.width + self.internal_bus_width
(y_off,mirror)=self.get_offset(row)
# input: OE, clk_buf_bar,CS output: rbl_in_bar
self.rbl_in_bar_offset = vector(x_off, y_off)
self.rbl_in_bar_inst=self.add_inst(name="nand3_rbl_in_bar",
mod=self.nand3,
offset=self.rbl_in_bar_offset,
mirror=mirror)
self.connect_inst(["clk_buf_bar", "oe", "cs", "rbl_in_bar", "vdd", "gnd"])
x_off += self.nand3.width
def create_rbl_in_row(self):
self.rbl_in_bar_inst=self.add_inst(name="nand2_rbl_in_bar",
mod=self.nand2)
self.connect_inst(["clk_buf_bar", "cs", "rbl_in_bar", "vdd", "gnd"])
# input: rbl_in_bar, output: rbl_in
self.rbl_in_offset = vector(x_off, y_off)
self.rbl_in_inst=self.add_inst(name="inv_rbl_in",
mod=self.inv1,
offset=self.rbl_in_offset,
mirror=mirror)
mod=self.inv1)
self.connect_inst(["rbl_in_bar", "rbl_in", "vdd", "gnd"])
self.row_end_inst.append(self.rbl_in_inst)
def add_sen_row(self,row):
""" The sense enable buffer gets placed to the far right of the
row. """
def place_rbl_in_row(self,row):
x_off = self.ctrl_dff_array.width + self.internal_bus_width
(y_off,mirror)=self.get_offset(row)
self.rbl_in_bar_offset = vector(x_off, y_off)
self.rbl_in_bar_inst.place(offset=self.rbl_in_bar_offset,
mirror=mirror)
x_off += self.nand2.width
self.rbl_in_offset = vector(x_off, y_off)
self.rbl_in_inst.place(offset=self.rbl_in_offset,
mirror=mirror)
self.row_end_inst.append(self.rbl_in_inst)
def create_sen_row(self):
""" Create the sense enable buffer. """
# input: pre_s_en, output: pre_s_en_bar
self.pre_s_en_bar_offset = vector(x_off, y_off)
self.pre_s_en_bar_inst=self.add_inst(name="inv_pre_s_en_bar",
mod=self.inv2,
offset=self.pre_s_en_bar_offset,
mirror=mirror)
mod=self.inv2)
self.connect_inst(["pre_s_en", "pre_s_en_bar", "vdd", "gnd"])
x_off += self.inv2.width
# BUFFER INVERTERS FOR S_EN
# input: input: pre_s_en_bar, output: s_en
self.s_en_offset = vector(x_off, y_off)
self.s_en_inst=self.add_inst(name="inv_s_en",
mod=self.inv8,
offset=self.s_en_offset,
mirror=mirror)
mod=self.inv8)
self.connect_inst(["pre_s_en_bar", "s_en", "vdd", "gnd"])
def place_sen_row(self,row):
"""
The sense enable buffer gets placed to the far right of the
row.
"""
x_off = self.ctrl_dff_array.width + self.internal_bus_width
(y_off,mirror)=self.get_offset(row)
self.pre_s_en_bar_offset = vector(x_off, y_off)
self.pre_s_en_bar_inst.place(offset=self.pre_s_en_bar_offset,
mirror=mirror)
x_off += self.inv2.width
self.s_en_offset = vector(x_off, y_off)
self.s_en_inst.place(offset=self.s_en_offset,
mirror=mirror)
self.row_end_inst.append(self.s_en_inst)
def add_trien_row(self, row):
x_off = self.ctrl_dff_array.width + self.internal_bus_width
(y_off,mirror)=self.get_offset(row)
x_off += self.nand2.width
# BUFFER INVERTERS FOR TRI_EN
tri_en_offset = vector(x_off, y_off)
self.tri_en_inst=self.add_inst(name="inv_tri_en1",
mod=self.inv2,
offset=tri_en_offset,
mirror=mirror)
self.connect_inst(["pre_tri_en_bar", "pre_tri_en1", "vdd", "gnd"])
x_off += self.inv2.width
tri_en_buf1_offset = vector(x_off, y_off)
self.tri_en_buf1_inst=self.add_inst(name="tri_en_buf1",
mod=self.inv2,
offset=tri_en_buf1_offset,
mirror=mirror)
self.connect_inst(["pre_tri_en1", "pre_tri_en_bar1", "vdd", "gnd"])
x_off += self.inv2.width
tri_en_buf2_offset = vector(x_off, y_off)
self.tri_en_buf2_inst=self.add_inst(name="tri_en_buf2",
mod=self.inv8,
offset=tri_en_buf2_offset,
mirror=mirror)
self.connect_inst(["pre_tri_en_bar1", "tri_en", "vdd", "gnd"])
self.row_end_inst.append(self.tri_en_inst)
def add_trien_bar_row(self, row):
x_off = self.ctrl_dff_array.width + self.internal_bus_width
(y_off,mirror)=self.get_offset(row)
# input: OE, clk_buf_bar output: tri_en_bar
tri_en_bar_offset = vector(x_off,y_off)
self.tri_en_bar_inst=self.add_inst(name="nand2_tri_en",
mod=self.nand2,
offset=tri_en_bar_offset,
mirror=mirror)
self.connect_inst(["clk_buf_bar", "oe", "pre_tri_en_bar", "vdd", "gnd"])
x_off += self.nand2.width
# BUFFER INVERTERS FOR TRI_EN
tri_en_bar_buf1_offset = vector(x_off, y_off)
self.tri_en_bar_buf1_inst=self.add_inst(name="tri_en_bar_buf1",
mod=self.inv2,
offset=tri_en_bar_buf1_offset,
mirror=mirror)
self.connect_inst(["pre_tri_en_bar", "pre_tri_en2", "vdd", "gnd"])
x_off += self.inv2.width
tri_en_bar_buf2_offset = vector(x_off, y_off)
self.tri_en_bar_buf2_inst=self.add_inst(name="tri_en_bar_buf2",
mod=self.inv8,
offset=tri_en_bar_buf2_offset,
mirror=mirror)
self.connect_inst(["pre_tri_en2", "tri_en_bar", "vdd", "gnd"])
x_off += self.inv8.width
self.row_end_inst.append(self.tri_en_bar_buf2_inst)
def route_dffs(self):
""" Route the input inverters """
dff_out_map = zip(["dout_bar[{}]".format(i) for i in range(3)], ["cs", "we", "oe"])
if self.port_type == "r":
control_inputs = ["cs"]
else:
control_inputs = ["cs", "we"]
dff_out_map = zip(["dout_bar_{}".format(i) for i in range(2*self.num_control_signals - 1)], control_inputs)
self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets)
# Connect the clock rail to the other clock rail
in_pos = self.ctrl_dff_inst.get_pin("clk").uc()
mid_pos = in_pos + vector(0,self.m2_pitch)
mid_pos = in_pos + vector(0,2*self.m2_pitch)
rail_pos = vector(self.rail_offsets["clk_buf"].x, mid_pos.y)
self.add_wire(("metal1","via1","metal2"),[in_pos, mid_pos, rail_pos])
self.add_via_center(layers=("metal1","via1","metal2"),
offset=rail_pos,
rotate=90)
self.copy_layout_pin(self.ctrl_dff_inst, "din[0]", "csb")
self.copy_layout_pin(self.ctrl_dff_inst, "din[1]", "web")
self.copy_layout_pin(self.ctrl_dff_inst, "din[2]", "oeb")
self.copy_layout_pin(self.ctrl_dff_inst, "din_0", "csb")
if (self.port_type == "rw"):
self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web")
def add_dffs(self):
def create_dffs(self):
""" Add the three input DFFs (with inverters) """
self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs",
mod=self.ctrl_dff_array,
offset=vector(0,0))
mod=self.ctrl_dff_array)
self.connect_inst(self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list)
def place_dffs(self):
""" Place the input DFFs (with inverters) """
self.ctrl_dff_inst.place(vector(0,0))
def get_offset(self,row):
""" Compute the y-offset and mirroring """
@ -343,52 +350,67 @@ class control_logic(design.design):
return (y_off,mirror)
def add_we_row(self,row):
def create_we_row(self):
# input: WE, CS output: w_en_bar
if self.port_type == "rw":
nand_mod = self.nand3
temp = ["clk_buf_bar", "cs", "we", "w_en_bar", "vdd", "gnd"]
else:
nand_mod = self.nand2
temp = ["clk_buf_bar", "cs", "w_en_bar", "vdd", "gnd"]
self.w_en_bar_inst = self.add_inst(name="nand3_w_en_bar",
mod=nand_mod)
self.connect_inst(temp)
# input: w_en_bar, output: pre_w_en
self.pre_w_en_inst = self.add_inst(name="inv_pre_w_en",
mod=self.inv1)
self.connect_inst(["w_en_bar", "pre_w_en", "vdd", "gnd"])
# BUFFER INVERTERS FOR W_EN
self.pre_w_en_bar_inst = self.add_inst(name="inv_pre_w_en_bar",
mod=self.inv2)
self.connect_inst(["pre_w_en", "pre_w_en_bar", "vdd", "gnd"])
self.w_en_inst = self.add_inst(name="inv_w_en2",
mod=self.inv8)
self.connect_inst(["pre_w_en_bar", "w_en", "vdd", "gnd"])
def place_we_row(self,row):
x_off = self.ctrl_dff_inst.width + self.internal_bus_width
(y_off,mirror)=self.get_offset(row)
# input: WE, CS output: w_en_bar
w_en_bar_offset = vector(x_off, y_off)
self.w_en_bar_inst=self.add_inst(name="nand3_w_en_bar",
mod=self.nand3,
offset=w_en_bar_offset,
mirror=mirror)
self.connect_inst(["clk_buf_bar", "cs", "we", "w_en_bar", "vdd", "gnd"])
x_off += self.nand3.width
self.w_en_bar_inst.place(offset=w_en_bar_offset,
mirror=mirror)
if self.port_type == "rw":
x_off += self.nand3.width
else:
x_off += self.nand2.width
# input: w_en_bar, output: pre_w_en
pre_w_en_offset = vector(x_off, y_off)
self.pre_w_en_inst=self.add_inst(name="inv_pre_w_en",
mod=self.inv1,
offset=pre_w_en_offset,
mirror=mirror)
self.connect_inst(["w_en_bar", "pre_w_en", "vdd", "gnd"])
self.pre_w_en_inst.place(offset=pre_w_en_offset,
mirror=mirror)
x_off += self.inv1.width
# BUFFER INVERTERS FOR W_EN
pre_w_en_bar_offset = vector(x_off, y_off)
self.pre_w_en_bar_inst=self.add_inst(name="inv_pre_w_en_bar",
mod=self.inv2,
offset=pre_w_en_bar_offset,
mirror=mirror)
self.connect_inst(["pre_w_en", "pre_w_en_bar", "vdd", "gnd"])
self.pre_w_en_bar_inst.place(offset=pre_w_en_bar_offset,
mirror=mirror)
x_off += self.inv2.width
w_en_offset = vector(x_off, y_off)
self.w_en_inst=self.add_inst(name="inv_w_en2",
mod=self.inv8,
offset=w_en_offset,
mirror=mirror)
self.connect_inst(["pre_w_en_bar", "w_en", "vdd", "gnd"])
self.w_en_inst.place(offset=w_en_offset,
mirror=mirror)
x_off += self.inv8.width
self.row_end_inst.append(self.w_en_inst)
def route_rbl_in(self):
""" Connect the logic for the rbl_in generation """
rbl_in_map = zip(["A", "B", "C"], ["clk_buf_bar", "oe", "cs"])
rbl_in_map = zip(["A", "B"], ["clk_buf_bar", "cs"])
self.connect_vertical_bus(rbl_in_map, self.rbl_in_bar_inst, self.rail_offsets)
# Connect the NAND3 output to the inverter
@ -460,7 +482,10 @@ class control_logic(design.design):
def route_wen(self):
wen_map = zip(["A", "B", "C"], ["clk_buf_bar", "cs", "we"])
if self.port_type == "rw":
wen_map = zip(["A", "B", "C"], ["clk_buf_bar", "cs", "we"])
else:
wen_map = zip(["A", "B"], ["clk_buf_bar", "cs"])
self.connect_vertical_bus(wen_map, self.w_en_bar_inst, self.rail_offsets)
# Connect the NAND3 output to the inverter
@ -475,44 +500,6 @@ class control_logic(design.design):
self.connect_output(self.w_en_inst, "Z", "w_en")
def route_trien(self):
# Connect the NAND2 output to the buffer
tri_en_bar_pos = self.tri_en_bar_inst.get_pin("Z").center()
inv_in_pos = self.tri_en_inst.get_pin("A").center()
mid1 = vector(tri_en_bar_pos.x,inv_in_pos.y)
self.add_wire(("metal1","via1","metal2"),[tri_en_bar_pos,mid1,inv_in_pos])
# Connect the INV output to the buffer
tri_en_pos = self.tri_en_inst.get_pin("Z").center()
inv_in_pos = self.tri_en_buf1_inst.get_pin("A").center()
mid_xoffset = 0.5*(tri_en_pos.x + inv_in_pos.x)
mid1 = vector(mid_xoffset,tri_en_pos.y)
mid2 = vector(mid_xoffset,inv_in_pos.y)
self.add_path("metal1",[tri_en_pos,mid1,mid2,inv_in_pos])
self.add_path("metal1",[self.tri_en_buf1_ist.get_pin("Z").center(), self.tri_en_buf2_inst.get_pin("A").center()])
self.connect_output(self.tri_en_buf2_inst, "Z", "tri_en")
def route_trien_bar(self):
trien_map = zip(["A", "B"], ["clk_buf_bar", "oe"])
self.connect_vertical_bus(trien_map, self.tri_en_bar_inst, self.rail_offsets)
# Connect the NAND2 output to the buffer
tri_en_bar_pos = self.tri_en_bar_inst.get_pin("Z").center()
inv_in_pos = self.tri_en_bar_buf1_inst.get_pin("A").center()
mid_xoffset = 0.5*(tri_en_bar_pos.x + inv_in_pos.x)
mid1 = vector(mid_xoffset,tri_en_bar_pos.y)
mid2 = vector(mid_xoffset,inv_in_pos.y)
self.add_path("metal1",[tri_en_bar_pos,mid1,mid2,inv_in_pos])
self.add_path("metal1",[self.tri_en_bar_buf1_inst.get_pin("Z").center(), self.tri_en_bar_buf2_inst.get_pin("A").center()])
self.connect_output(self.tri_en_bar_buf2_inst, "Z", "tri_en_bar")
def route_sen(self):
rbl_out_pos = self.rbl_inst.get_pin("out").bc()
in_pos = self.pre_s_en_bar_inst.get_pin("A").lc()
@ -574,9 +561,9 @@ class control_logic(design.design):
self.add_power_pin("gnd", pin_loc)
self.add_path("metal1", [row_loc, pin_loc])
self.copy_layout_pin(self.rbl_inst,"gnd")
self.copy_layout_pin(self.rbl_inst,"vdd")
if (self.port_type == "rw") or (self.port_type == "r"):
self.copy_layout_pin(self.rbl_inst,"gnd")
self.copy_layout_pin(self.rbl_inst,"vdd")
self.copy_layout_pin(self.ctrl_dff_inst,"gnd")
self.copy_layout_pin(self.ctrl_dff_inst,"vdd")

View File

@ -13,8 +13,12 @@ class delay_chain(design.design):
Usually, this will be constant, but it could have varied fanout.
"""
unique_id = 1
def __init__(self, fanout_list, name="delay_chain"):
"""init function"""
name = name+"_{}".format(delay_chain.unique_id)
delay_chain.unique_id += 1
design.design.__init__(self, name)
# Two fanouts are needed so that we can route the vdd/gnd connections
@ -24,14 +28,23 @@ class delay_chain(design.design):
# number of inverters including any fanout loads.
self.fanout_list = fanout_list
from importlib import reload
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell = self.mod_bitcell()
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_module()
self.add_inverters()
self.create_inverters()
def create_layout(self):
# Each stage is a a row
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.place_inverters()
self.route_inverters()
self.add_layout_pins()
self.DRC_LVS()
@ -43,40 +56,22 @@ class delay_chain(design.design):
self.add_pin("vdd")
self.add_pin("gnd")
def create_module(self):
""" Add the inverter logical module """
def add_modules(self):
self.inv = pinv(route_output=False)
self.add_mod(self.inv)
# Each stage is a a row
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
def add_inverters(self):
""" Add the inverters and connect them based on the stage list """
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):
if stage_num % 2:
inv_mirror = "MX"
inv_offset = vector(0, (stage_num+1)* self.inv.height)
else:
inv_mirror = "R0"
inv_offset = vector(0, stage_num * self.inv.height)
# Add the inverter
cur_driver=self.add_inst(name="dinv{}".format(stage_num),
mod=self.inv,
offset=inv_offset,
mirror=inv_mirror)
mod=self.inv)
# keep track of the inverter instances so we can use them to get the pins
self.driver_inst_list.append(cur_driver)
# Hook up the driver
if stage_num+1==len(self.fanout_list):
stageout_name = "out"
@ -91,11 +86,8 @@ class delay_chain(design.design):
# Now add the dummy loads to the right
self.load_inst_map[cur_driver]=[]
for i in range(fanout_size):
inv_offset += vector(self.inv.width,0)
cur_load=self.add_inst(name="dload_{0}_{1}".format(stage_num,i),
mod=self.inv,
offset=inv_offset,
mirror=inv_mirror)
mod=self.inv)
# Fanout stage is always driven by driver and output is disconnected
disconnect_name = "n_{0}_{1}".format(stage_num,i)
self.connect_inst([stageout_name, disconnect_name, "vdd", "gnd"])
@ -105,6 +97,29 @@ class delay_chain(design.design):
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):
if stage_num % 2:
inv_mirror = "MX"
inv_offset = vector(0, (stage_num+1)* self.inv.height)
else:
inv_mirror = "R0"
inv_offset = vector(0, stage_num * self.inv.height)
# Add the inverter
cur_driver=self.driver_inst_list[stage_num]
cur_driver.place(offset=inv_offset,
mirror=inv_mirror)
# 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)
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. """

View File

@ -12,7 +12,7 @@ class dff(design.design):
pin_names = ["D", "Q", "clk", "vdd", "gnd"]
(width,height) = utils.get_libcell_size("dff", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"])
def __init__(self, name="dff"):
design.design.__init__(self, name)
@ -21,6 +21,25 @@ class dff(design.design):
self.height = dff.height
self.pin_map = dff.pin_map
def analytical_power(self, proc, vdd, temp, load):
"""Returns dynamic and leakage power. Results in nW"""
from tech import spice
c_eff = self.calculate_effective_capacitance(load)
f = spice["default_event_rate"]
power_dyn = c_eff*vdd*vdd*f
power_leak = spice["msflop_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"""
from tech import spice, parameter
c_load = load
c_para = spice["flop_para_cap"]#ff
transition_prob = spice["flop_transition_prob"]
return transition_prob*(c_load + c_para)
def analytical_delay(self, slew, load = 0.0):
# dont know how to calculate this now, use constant in tech file
from tech import spice

View File

@ -20,23 +20,30 @@ class dff_array(design.design):
design.design.__init__(self, name)
debug.info(1, "Creating {0} rows={1} cols={2}".format(self.name, self.rows, self.columns))
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_dff_array()
def create_layout(self):
self.width = self.columns * self.dff.width
self.height = self.rows * self.dff.height
self.place_dff_array()
self.add_layout_pins()
self.DRC_LVS()
def add_modules(self):
from importlib import reload
c = reload(__import__(OPTS.dff))
self.mod_dff = getattr(c, OPTS.dff)
self.dff = self.mod_dff("dff")
self.add_mod(self.dff)
self.width = self.columns * self.dff.width
self.height = self.rows * self.dff.height
self.create_layout()
def create_layout(self):
self.add_pins()
self.create_dff_array()
self.add_layout_pins()
self.DRC_LVS()
def add_pins(self):
for row in range(self.rows):
for col in range(self.columns):
@ -53,39 +60,44 @@ class dff_array(design.design):
for row in range(self.rows):
for col in range(self.columns):
name = "Xdff_r{0}_c{1}".format(row,col)
if (row % 2 == 0):
base = vector(col*self.dff.width,row*self.dff.height)
mirror = "R0"
else:
base = vector(col*self.dff.width,(row+1)*self.dff.height)
mirror = "MX"
self.dff_insts[row,col]=self.add_inst(name=name,
mod=self.dff,
offset=base,
mirror=mirror)
mod=self.dff)
self.connect_inst([self.get_din_name(row,col),
self.get_dout_name(row,col),
"clk",
"vdd",
"gnd"])
def place_dff_array(self):
for row in range(self.rows):
for col in range(self.columns):
name = "Xdff_r{0}_c{1}".format(row,col)
if (row % 2 == 0):
base = vector(col*self.dff.width,row*self.dff.height)
mirror = "R0"
else:
base = vector(col*self.dff.width,(row+1)*self.dff.height)
mirror = "MX"
self.dff_insts[row,col].place(offset=base,
mirror=mirror)
def get_din_name(self, row, col):
if self.columns == 1:
din_name = "din[{0}]".format(row)
din_name = "din_{0}".format(row)
elif self.rows == 1:
din_name = "din[{0}]".format(col)
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
def get_dout_name(self, row, col):
if self.columns == 1:
dout_name = "dout[{0}]".format(row)
dout_name = "dout_{0}".format(row)
elif self.rows == 1:
dout_name = "dout[{0}]".format(col)
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
@ -124,29 +136,23 @@ class dff_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")
if self.columns==1:
self.add_layout_pin(text="clk",
layer="metal2",
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,self.m3_pitch+self.m3_width),
end=vector(self.width,self.m3_pitch+self.m3_width))
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",
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(),self.m3_pitch+self.m3_width))
self.add_layout_pin_segment_center(text="clk",
layer="metal3",
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",
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))
def analytical_delay(self, slew, load=0.0):

View File

@ -26,29 +26,41 @@ class dff_buf(design.design):
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.")
self.inv1_size=inv1_size
self.inv2_size=inv2_size
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_modules()
def create_layout(self):
self.width = self.dff.width + self.inv1.width + self.inv2.width
self.height = self.dff.height
self.place_modules()
self.route_wires()
self.add_layout_pins()
self.DRC_LVS()
def add_modules(self):
from importlib import reload
c = reload(__import__(OPTS.dff))
self.mod_dff = getattr(c, OPTS.dff)
self.dff = self.mod_dff("dff")
self.add_mod(self.dff)
self.inv1 = pinv(size=inv1_size,height=self.dff.height)
self.inv1 = pinv(size=self.inv1_size,height=self.dff.height)
self.add_mod(self.inv1)
self.inv2 = pinv(size=inv2_size,height=self.dff.height)
self.inv2 = pinv(size=self.inv2_size,height=self.dff.height)
self.add_mod(self.inv2)
self.width = self.dff.width + self.inv1.width + self.inv2.width
self.height = self.dff.height
self.create_layout()
def create_layout(self):
self.add_pins()
self.add_insts()
self.add_wires()
self.add_layout_pins()
self.DRC_LVS()
def add_pins(self):
self.add_pin("D")
@ -58,26 +70,30 @@ class dff_buf(design.design):
self.add_pin("vdd")
self.add_pin("gnd")
def add_insts(self):
# Add the DFF
def create_modules(self):
self.dff_inst=self.add_inst(name="dff_buf_dff",
mod=self.dff,
offset=vector(0,0))
mod=self.dff)
self.connect_inst(["D", "qint", "clk", "vdd", "gnd"])
# Add INV1 to the right
self.inv1_inst=self.add_inst(name="dff_buf_inv1",
mod=self.inv1,
offset=vector(self.dff_inst.rx(),0))
mod=self.inv1)
self.connect_inst(["qint", "Qb", "vdd", "gnd"])
# Add INV2 to the right
self.inv2_inst=self.add_inst(name="dff_buf_inv2",
mod=self.inv2,
offset=vector(self.inv1_inst.rx(),0))
mod=self.inv2)
self.connect_inst(["Qb", "Q", "vdd", "gnd"])
def place_modules(self):
# Add the DFF
self.dff_inst.place(vector(0,0))
# Add INV1 to the right
self.inv1_inst.place(vector(self.dff_inst.rx(),0))
def add_wires(self):
# Add INV2 to the right
self.inv2_inst.place(vector(self.inv1_inst.rx(),0))
def route_wires(self):
# Route dff q to inv1 a
q_pin = self.dff_inst.get_pin("Q")
a1_pin = self.inv1_inst.get_pin("A")

View File

@ -20,18 +20,22 @@ class dff_buf_array(design.design):
name = "dff_buf_array_{0}x{1}".format(rows, columns)
design.design.__init__(self, name)
debug.info(1, "Creating {}".format(self.name))
self.inv1_size = inv1_size
self.inv2_size = inv2_size
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
self.dff = dff_buf.dff_buf(inv1_size, inv2_size)
self.add_mod(self.dff)
self.width = self.columns * self.dff.width
self.height = self.rows * self.dff.height
self.create_layout()
def create_netlist(self):
self.add_pins()
self.add_modules()
self.create_dff_array()
def create_layout(self):
self.add_pins()
self.create_dff_array()
self.width = self.columns * self.dff.width
self.height = self.rows * self.dff.height
self.place_dff_array()
self.add_layout_pins()
self.DRC_LVS()
@ -47,8 +51,25 @@ class dff_buf_array(design.design):
self.add_pin("vdd")
self.add_pin("gnd")
def add_modules(self):
self.dff = dff_buf.dff_buf(self.inv1_size, self.inv2_size)
self.add_mod(self.dff)
def create_dff_array(self):
self.dff_insts={}
for row in range(self.rows):
for col in range(self.columns):
name = "Xdff_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),
"clk",
"vdd",
"gnd"])
def place_dff_array(self):
for row in range(self.rows):
for col in range(self.columns):
name = "Xdff_r{0}_c{1}".format(row,col)
@ -58,44 +79,36 @@ class dff_buf_array(design.design):
else:
base = vector(col*self.dff.width,(row+1)*self.dff.height)
mirror = "MX"
self.dff_insts[row,col]=self.add_inst(name=name,
mod=self.dff,
offset=base,
mirror=mirror)
self.connect_inst([self.get_din_name(row,col),
self.get_dout_name(row,col),
self.get_dout_bar_name(row,col),
"clk",
"vdd",
"gnd"])
self.dff_insts[row,col].place(offset=base,
mirror=mirror)
def get_din_name(self, row, col):
if self.columns == 1:
din_name = "din[{0}]".format(row)
din_name = "din_{0}".format(row)
elif self.rows == 1:
din_name = "din[{0}]".format(col)
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
def get_dout_name(self, row, col):
if self.columns == 1:
dout_name = "dout[{0}]".format(row)
dout_name = "dout_{0}".format(row)
elif self.rows == 1:
dout_name = "dout[{0}]".format(col)
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 get_dout_bar_name(self, row, col):
if self.columns == 1:
dout_bar_name = "dout_bar[{0}]".format(row)
dout_bar_name = "dout_bar_{0}".format(row)
elif self.rows == 1:
dout_bar_name = "dout_bar[{0}]".format(col)
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
@ -140,6 +153,7 @@ class dff_buf_array(design.design):
# Create vertical spines to a single horizontal rail
clk_pin = self.dff_insts[0,0].get_pin("clk")
clk_ypos = 2*self.m3_pitch+self.m3_width
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
if self.columns==1:
self.add_layout_pin(text="clk",
@ -150,8 +164,8 @@ class dff_buf_array(design.design):
else:
self.add_layout_pin_segment_center(text="clk",
layer="metal3",
start=vector(0,self.m3_pitch+self.m3_width),
end=vector(self.width,self.m3_pitch+self.m3_width))
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")
@ -162,7 +176,7 @@ class dff_buf_array(design.design):
height=self.height)
# Drop a via to the M3 pin
self.add_via_center(layers=("metal2","via2","metal3"),
offset=vector(clk_pin.cx(),self.m3_pitch+self.m3_width))
offset=vector(clk_pin.cx(),clk_ypos))

View File

@ -18,31 +18,30 @@ class dff_inv(design.design):
name = "dff_inv_{0}".format(inv_size)
design.design.__init__(self, name)
debug.info(1, "Creating {}".format(self.name))
self.inv_size = inv_size
# This is specifically for SCMOS where the DFF vdd/gnd rails are more than min width.
# 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(inv_size>=2, "Inverter must be greater than two for rail spacing DRC rules.")
from importlib import reload
c = reload(__import__(OPTS.dff))
self.mod_dff = getattr(c, OPTS.dff)
self.dff = self.mod_dff("dff")
self.add_mod(self.dff)
self.inv1 = pinv(size=inv_size,height=self.dff.height)
self.add_mod(self.inv1)
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_pins()
self.add_modules()
self.create_modules()
def create_layout(self):
self.width = self.dff.width + self.inv1.width
self.height = self.dff.height
self.create_layout()
def create_layout(self):
self.add_pins()
self.add_insts()
self.place_modules()
self.add_wires()
self.add_layout_pins()
self.DRC_LVS()
def add_pins(self):
@ -53,18 +52,31 @@ class dff_inv(design.design):
self.add_pin("vdd")
self.add_pin("gnd")
def add_insts(self):
# Add the DFF
def add_modules(self):
from importlib import reload
c = reload(__import__(OPTS.dff))
self.mod_dff = getattr(c, OPTS.dff)
self.dff = self.mod_dff("dff")
self.add_mod(self.dff)
self.inv1 = pinv(size=self.inv_size,height=self.dff.height)
self.add_mod(self.inv1)
def create_modules(self):
self.dff_inst=self.add_inst(name="dff_inv_dff",
mod=self.dff,
offset=vector(0,0))
mod=self.dff)
self.connect_inst(["D", "Q", "clk", "vdd", "gnd"])
# Add INV1 to the right
self.inv1_inst=self.add_inst(name="dff_inv_inv1",
mod=self.inv1,
offset=vector(self.dff_inst.rx(),0))
mod=self.inv1)
self.connect_inst(["Q", "Qb", "vdd", "gnd"])
def place_modules(self):
# Place the DFF
self.dff_inst.place(vector(0,0))
# Place the INV1 to the right
self.inv1_inst.place(vector(self.dff_inst.rx(),0))
def add_wires(self):

View File

@ -20,21 +20,29 @@ class dff_inv_array(design.design):
name = "dff_inv_array_{0}x{1}".format(rows, columns)
design.design.__init__(self, name)
debug.info(1, "Creating {}".format(self.name))
self.inv_size = inv_size
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
self.dff = dff_inv.dff_inv(inv_size)
self.add_mod(self.dff)
def create_netlist(self):
self.add_pins()
self.add_modules()
self.create_dff_array()
def create_layout(self):
self.width = self.columns * self.dff.width
self.height = self.rows * self.dff.height
self.create_layout()
def create_layout(self):
self.add_pins()
self.create_dff_array()
self.place_dff_array()
self.add_layout_pins()
self.DRC_LVS()
def add_modules(self):
self.dff = dff_inv.dff_inv(self.inv_size)
self.add_mod(self.dff)
def add_pins(self):
for row in range(self.rows):
for col in range(self.columns):
@ -52,16 +60,8 @@ class dff_inv_array(design.design):
for row in range(self.rows):
for col in range(self.columns):
name = "Xdff_r{0}_c{1}".format(row,col)
if (row % 2 == 0):
base = vector(col*self.dff.width,row*self.dff.height)
mirror = "R0"
else:
base = vector(col*self.dff.width,(row+1)*self.dff.height)
mirror = "MX"
self.dff_insts[row,col]=self.add_inst(name=name,
mod=self.dff,
offset=base,
mirror=mirror)
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),
@ -69,44 +69,57 @@ class dff_inv_array(design.design):
"vdd",
"gnd"])
def place_dff_array(self):
for row in range(self.rows):
for col in range(self.columns):
name = "Xdff_r{0}_c{1}".format(row,col)
if (row % 2 == 0):
base = vector(col*self.dff.width,row*self.dff.height)
mirror = "R0"
else:
base = vector(col*self.dff.width,(row+1)*self.dff.height)
mirror = "MX"
self.dff_insts[row,col].place(offset=base,
mirror=mirror)
def get_din_name(self, row, col):
if self.columns == 1:
din_name = "din[{0}]".format(row)
din_name = "din_{0}".format(row)
elif self.rows == 1:
din_name = "din[{0}]".format(col)
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
def get_dout_name(self, row, col):
if self.columns == 1:
dout_name = "dout[{0}]".format(row)
dout_name = "dout_{0}".format(row)
elif self.rows == 1:
dout_name = "dout[{0}]".format(col)
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 get_dout_bar_name(self, row, col):
if self.columns == 1:
dout_bar_name = "dout_bar[{0}]".format(row)
dout_bar_name = "dout_bar_{0}".format(row)
elif self.rows == 1:
dout_bar_name = "dout_bar[{0}]".format(col)
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):
for row in range(self.rows):
for col in range(self.columns):
# Continous vdd rail along with label.
# Adds power pin on left of row
vdd_pin=self.dff_insts[row,col].get_pin("vdd")
self.add_power_pin("vdd", vdd_pin.lc())
# Continous gnd rail along with label.
# Adds gnd pin on left of row
gnd_pin=self.dff_insts[row,col].get_pin("gnd")
self.add_power_pin("gnd", gnd_pin.lc())
@ -140,6 +153,7 @@ 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")
if self.columns==1:
self.add_layout_pin(text="clk",
@ -150,8 +164,8 @@ class dff_inv_array(design.design):
else:
self.add_layout_pin_segment_center(text="clk",
layer="metal3",
start=vector(0,self.m3_pitch+self.m3_width),
end=vector(self.width,self.m3_pitch+self.m3_width))
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
@ -161,7 +175,7 @@ class dff_inv_array(design.design):
height=self.height)
# Drop a via to the M3 pin
self.add_via_center(layers=("metal2","via2","metal3"),
offset=vector(clk_pin.cx(),self.m3_pitch+self.m3_width))
offset=vector(clk_pin.cx(),clk_ypos))

View File

@ -24,8 +24,12 @@ class hierarchical_decoder(design.design):
from importlib import reload
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell_height = self.mod_bitcell.height
b = self.mod_bitcell()
self.bitcell_height = b.height
self.NAND_FORMAT = "DEC_NAND_{0}"
self.INV_FORMAT = "DEC_INV_{0}"
self.pre2x4_inst = []
self.pre3x8_inst = []
@ -33,22 +37,28 @@ class hierarchical_decoder(design.design):
self.num_inputs = int(math.log(self.rows, 2))
(self.no_of_pre2x4,self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs)
self.create_layout()
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
self.offset_all_coordinates()
self.DRC_LVS()
def create_layout(self):
def create_netlist(self):
self.add_modules()
self.setup_layout_constants()
self.setup_netlist_constants()
self.add_pins()
self.create_pre_decoder()
self.create_row_decoder()
self.create_input_rail()
self.create_predecode_rail()
self.route_vdd_gnd()
def create_layout(self):
self.setup_layout_constants()
self.place_pre_decoder()
self.place_row_decoder()
self.route_input_rails()
self.route_predecode_rails()
self.route_vdd_gnd()
self.offset_all_coordinates()
self.DRC_LVS()
def add_modules(self):
self.inv = pinv()
self.add_mod(self.inv)
@ -89,7 +99,7 @@ class hierarchical_decoder(design.design):
else:
debug.error("Invalid number of inputs for hierarchical decoder",-1)
def setup_layout_constants(self):
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.
@ -112,99 +122,16 @@ class hierarchical_decoder(design.design):
index = index + 1
self.predec_groups.append(lines)
self.calculate_dimensions()
def create_input_rail(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
# Find the left-most predecoder
min_x = 0
if self.no_of_pre2x4 > 0:
min_x = min(min_x, -self.pre2_4.width)
if self.no_of_pre3x8 > 0:
min_x = min(min_x, -self.pre3_8.width)
input_offset=vector(min_x - self.input_routing_width,0)
input_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.connect_input_to_predecodes()
def connect_input_to_predecodes(self):
""" Connect 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)]
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)
self.connect_input_rail(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)]
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)
self.connect_input_rail(decoder_offset, input_offset)
def connect_input_rail(self, input_offset, output_offset):
""" Connect a vertical M2 coordinate to another vertical M2 coordinate to the predecode inputs """
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=input_offset,
rotate=90)
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=output_offset,
rotate=90)
self.add_path(("metal3"), [input_offset, output_offset])
def add_pins(self):
""" Add the module pins """
for i in range(self.num_inputs):
self.add_pin("addr[{0}]".format(i))
for j in range(self.rows):
self.add_pin("decode[{0}]".format(j))
self.add_pin("vdd")
self.add_pin("gnd")
def calculate_dimensions(self):
""" Calculate the overal dimensions of the hierarchical decoder """
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
else:
self.total_number_of_predecoder_outputs = 0
debug.error("Not enough rows for a hierarchical decoder. Non-hierarchical not supported yet.",-1)
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:
@ -227,49 +154,122 @@ class hierarchical_decoder(design.design):
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
# Find the left-most predecoder
min_x = 0
if self.no_of_pre2x4 > 0:
min_x = min(min_x, -self.pre2_4.width)
if self.no_of_pre3x8 > 0:
min_x = min(min_x, -self.pre3_8.width)
input_offset=vector(min_x - self.input_routing_width,0)
input_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.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)]
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)
self.route_input_rail(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)]
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)
self.route_input_rail(decoder_offset, input_offset)
def route_input_rail(self, input_offset, output_offset):
""" Route a vertical M2 coordinate to another vertical M2 coordinate to the predecode inputs """
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=input_offset,
rotate=90)
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=output_offset,
rotate=90)
self.add_path(("metal3"), [input_offset, output_offset])
def add_pins(self):
""" Add the module pins """
for i in range(self.num_inputs):
self.add_pin("addr_{0}".format(i))
for j in range(self.rows):
self.add_pin("decode_{0}".format(j))
self.add_pin("vdd")
self.add_pin("gnd")
def create_pre_decoder(self):
""" Creates pre-decoder and places labels input address [A] """
for i in range(self.no_of_pre2x4):
self.add_pre2x4(i)
self.create_pre2x4(i)
for i in range(self.no_of_pre3x8):
self.add_pre3x8(i)
self.create_pre3x8(i)
def add_pre2x4(self,num):
def create_pre2x4(self,num):
""" Add a 2x4 predecoder to the left of the origin """
if (self.num_inputs == 2):
base = vector(-self.pre2_4.width,0)
index_off1 = index_off2 = 0
else:
base= vector(-self.pre2_4.width, num * self.pre2_4.height)
index_off1 = num * 2
index_off2 = num * 4
pins = []
for input_index in range(2):
pins.append("addr[{0}]".format(input_index + index_off1))
pins.append("addr_{0}".format(input_index + index_off1))
for output_index in range(4):
pins.append("out[{0}]".format(output_index + index_off2))
pins.append("out_{0}".format(output_index + index_off2))
pins.extend(["vdd", "gnd"])
self.pre2x4_inst.append(self.add_inst(name="pre[{0}]".format(num),
mod=self.pre2_4,
offset=base))
self.pre2x4_inst.append(self.add_inst(name="pre_{0}".format(num),
mod=self.pre2_4))
self.connect_inst(pins)
def add_pre3x8(self,num):
def create_pre3x8(self,num):
""" Add 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"
else:
height = self.no_of_pre2x4*self.pre2_4.height + num*self.pre3_8.height
offset = vector(-self.pre3_8.width, height)
# If we had 2x4 predecodes, those are used as the lower
# decode output bits
in_index_offset = num * 3 + self.no_of_pre2x4 * 2
@ -277,85 +277,116 @@ class hierarchical_decoder(design.design):
pins = []
for input_index in range(3):
pins.append("addr[{0}]".format(input_index + in_index_offset))
pins.append("addr_{0}".format(input_index + in_index_offset))
for output_index in range(8):
pins.append("out[{0}]".format(output_index + out_index_offset))
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),
mod=self.pre3_8,
offset=offset))
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] """
for i in range(self.no_of_pre2x4):
self.place_pre2x4(i)
for i in range(self.no_of_pre3x8):
self.place_pre3x8(i)
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)
else:
base= vector(-self.pre2_4.width, num * self.pre2_4.height)
self.pre2x4_inst[num].place(base)
def place_pre3x8(self,num):
""" Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """
if (self.num_inputs == 3):
offset = vector(-self.pre_3_8.width,0)
mirror ="R0"
else:
height = self.no_of_pre2x4*self.pre2_4.height + num*self.pre3_8.height
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
and add the primary decoder output pins. """
if (self.num_inputs >= 4):
self.add_decoder_nand_array()
self.add_decoder_inv_array()
self.route_decoder()
self.create_decoder_nand_array()
self.create_decoder_inv_array()
def add_decoder_nand_array(self):
def create_decoder_nand_array(self):
""" Add a column of NAND gates for final decode """
self.nand_inst = []
# Row Decoder NAND GATE array for address inputs <5.
if (self.num_inputs == 4 or self.num_inputs == 5):
self.add_nand_array(nand_mod=self.nand2)
# FIXME: Can we convert this to the connect_inst with checks?
for i in range(len(self.predec_groups[0])):
for j in range(len(self.predec_groups[1])):
pins =["out[{0}]".format(i),
"out[{0}]".format(j + len(self.predec_groups[0])),
"Z[{0}]".format(len(self.predec_groups[1])*i + j),
row = len(self.predec_groups[1])*i + j
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(args=pins, check=False)
self.connect_inst(pins)
# Row Decoder NAND GATE array for address inputs >5.
elif (self.num_inputs > 5):
self.add_nand_array(nand_mod=self.nand3,
correct=drc["minwidth_metal1"])
# This will not check that the inst connections match.
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])):
Z_index = len(self.predec_groups[1])*len(self.predec_groups[2]) * i \
+ len(self.predec_groups[2])*j + k
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(Z_index),
row = len(self.predec_groups[1])*len(self.predec_groups[2]) * i \
+ len(self.predec_groups[2])*j + k
name = self.NAND_FORMAT.format(row)
self.nand_inst.append(self.add_inst(name=name,
mod=self.nand3))
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(args=pins, check=False)
self.connect_inst(pins)
def add_nand_array(self, nand_mod, correct=0):
""" Add a column of NAND gates for the decoder above the predecoders."""
def create_decoder_inv_array(self):
"""
Add a column of INV gates for the decoder.
"""
self.nand_inst = []
self.inv_inst = []
for row in range(self.rows):
name = "DEC_NAND[{0}]".format(row)
if ((row % 2) == 0):
y_off = nand_mod.height*row
y_dir = 1
mirror = "R0"
else:
y_off = nand_mod.height*(row + 1)
y_dir = -1
mirror = "MX"
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"])
self.nand_inst.append(self.add_inst(name=name,
mod=nand_mod,
offset=[self.internal_routing_width, y_off],
mirror=mirror))
def add_decoder_inv_array(self):
"""Add a column of INV gates for the decoder above the predecoders
and to the right of the NAND decoders."""
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")
@ -364,9 +395,7 @@ class hierarchical_decoder(design.design):
else:
x_off = self.internal_routing_width + self.nand3.width
self.inv_inst = []
for row in range(self.rows):
name = "DEC_INV_[{0}]".format(row)
if (row % 2 == 0):
inv_row_height = self.inv.height * row
mirror = "R0"
@ -377,17 +406,50 @@ class hierarchical_decoder(design.design):
y_dir = -1
y_off = inv_row_height
offset = vector(x_off,y_off)
self.inv_inst.append(self.add_inst(name=name,
mod=self.inv,
offset=offset,
mirror=mirror))
self.inv_inst[row].place(offset=offset,
mirror=mirror)
# This will not check that the inst connections match.
self.connect_inst(args=["Z[{0}]".format(row),
"decode[{0}]".format(row),
"vdd", "gnd"],
check=False)
def place_row_decoder(self):
"""
Place the row-decoder by placing NAND2/NAND3 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()
def place_decoder_nand_array(self):
""" Add a column of NAND gates for final decode """
# Row Decoder NAND GATE array for address inputs <5.
if (self.num_inputs == 4 or self.num_inputs == 5):
self.place_nand_array(nand_mod=self.nand2)
# Row Decoder NAND GATE array for address inputs >5.
# FIXME: why this correct offset?)
elif (self.num_inputs > 5):
self.place_nand_array(nand_mod=self.nand3)
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)
if ((row % 2) == 0):
y_off = nand_mod.height*row
y_dir = 1
mirror = "R0"
else:
y_off = nand_mod.height*(row + 1)
y_dir = -1
mirror = "MX"
self.nand_inst[row].place(offset=[self.internal_routing_width, y_off],
mirror=mirror)
def route_decoder(self):
@ -404,7 +466,7 @@ class hierarchical_decoder(design.design):
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),
self.add_layout_pin(text="decode_{0}".format(row),
layer="metal1",
offset=z_pin.ll(),
width=z_pin.width(),
@ -412,13 +474,13 @@ class hierarchical_decoder(design.design):
def create_predecode_rail(self):
def route_predecode_rails(self):
""" Creates vertical metal 2 rails to connect predecoder and decoder stages."""
# This is not needed for inputs <4 since they have no pre/decode stages.
if (self.num_inputs >= 4):
input_offset = vector(0.5*self.m2_width,0)
input_bus_names = ["predecode[{0}]".format(i) for i in range(self.total_number_of_predecoder_outputs)]
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,
@ -426,32 +488,32 @@ class hierarchical_decoder(design.design):
length=self.height)
self.connect_rails_to_predecodes()
self.connect_rails_to_decoder()
self.route_rails_to_predecodes()
self.route_rails_to_decoder()
def connect_rails_to_predecodes(self):
def route_rails_to_predecodes(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)
predecode_name = "predecode_{}".format(pre_num * 4 + i)
out_name = "out_{}".format(i)
pin = self.pre2x4_inst[pre_num].get_pin(out_name)
self.connect_predecode_rail_m3(predecode_name, pin)
self.route_predecode_rail_m3(predecode_name, pin)
# FIXME: convert to connect_bus
for pre_num in range(self.no_of_pre3x8):
for i in range(8):
predecode_name = "predecode[{}]".format(pre_num * 8 + i + self.no_of_pre2x4 * 4)
out_name = "out[{}]".format(i)
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.connect_predecode_rail_m3(predecode_name, pin)
self.route_predecode_rail_m3(predecode_name, pin)
def connect_rails_to_decoder(self):
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],
@ -464,10 +526,10 @@ class hierarchical_decoder(design.design):
for index_A in self.predec_groups[0]:
for index_B in self.predec_groups[1]:
# FIXME: convert to connect_bus?
predecode_name = "predecode[{}]".format(index_A)
self.connect_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("A"))
predecode_name = "predecode[{}]".format(index_B)
self.connect_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B"))
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
elif (self.num_inputs > 5):
@ -475,48 +537,45 @@ class hierarchical_decoder(design.design):
for index_B in self.predec_groups[1]:
for index_C in self.predec_groups[2]:
# FIXME: convert to connect_bus?
predecode_name = "predecode[{}]".format(index_A)
self.connect_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("A"))
predecode_name = "predecode[{}]".format(index_B)
self.connect_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B"))
predecode_name = "predecode[{}]".format(index_C)
self.connect_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("C"))
predecode_name = "predecode_{}".format(index_A)
self.route_predecode_rail(predecode_name, self.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
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.inv_inst[0].lx()
b_xoffset = self.inv_inst[0].rx()
# 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):
# this will result in duplicate polygons for rails, but who cares
# Route both supplies
for n in ["vdd", "gnd"]:
supply_pin = self.inv_inst[num].get_pin(n)
# 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"),
offset=pin_pos,
rotate=90)
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=pin_pos,
rotate=90)
self.add_layout_pin_rect_center(text=n,
layer="metal3",
offset=pin_pos)
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)
# Copy the pins from the predecoders
for pre in self.pre2x4_inst + self.pre3x8_inst:
self.copy_layout_pin(pre, "vdd")
self.copy_layout_pin(pre, "gnd")
def connect_predecode_rail(self, rail_name, pin):
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()])
@ -525,7 +584,7 @@ class hierarchical_decoder(design.design):
rotate=90)
def connect_predecode_rail_m3(self, rail_name, pin):
def route_predecode_rail_m3(self, rail_name, pin):
""" Connect the routing rail to the given metal1 pin """
# This routes the pin up to the rail, basically, to avoid conflicts.
# It would be fixed with a channel router.

View File

@ -25,9 +25,9 @@ class hierarchical_predecode(design.design):
def add_pins(self):
for k in range(self.number_of_inputs):
self.add_pin("in[{0}]".format(k))
self.add_pin("in_{0}".format(k))
for i in range(self.number_of_outputs):
self.add_pin("out[{0}]".format(i))
self.add_pin("out_{0}".format(i))
self.add_pin("vdd")
self.add_pin("gnd")
@ -37,27 +37,27 @@ class hierarchical_predecode(design.design):
self.inv = pinv()
self.add_mod(self.inv)
self.create_nand(self.number_of_inputs)
self.add_nand(self.number_of_inputs)
self.add_mod(self.nand)
def create_nand(self,inputs):
def add_nand(self,inputs):
""" Create the NAND for the predecode input stage """
if inputs==2:
self.nand = pnand2()
elif inputs==3:
self.nand = pnand3()
else:
debug.error("Invalid number of predecode inputs.",-1)
debug.error("Invalid number of predecode inputs: {}".format(inputs),-1)
def setup_constraints(self):
def setup_layout_constraints(self):
self.height = self.number_of_outputs * self.nand.height
# x offset for input inverters
self.x_off_inv_1 = self.number_of_inputs*self.m2_pitch
# x offset to NAND decoder includes the left rails, mid rails and inverters, plus an extra m2 pitch
self.x_off_nand = self.x_off_inv_1 + self.inv.width + (2*self.number_of_inputs + 1) * self.m2_pitch
# x offset to NAND decoder includes the left rails, mid rails and inverters, plus two extra m2 pitches
self.x_off_nand = self.x_off_inv_1 + self.inv.width + (2*self.number_of_inputs + 2) * self.m2_pitch
# x offset to output inverters
self.x_off_inv_2 = self.x_off_nand + self.nand.width
@ -65,9 +65,9 @@ class hierarchical_predecode(design.design):
# Height width are computed
self.width = self.x_off_inv_2 + self.inv.width
def create_rails(self):
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)]
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,
@ -75,10 +75,10 @@ class hierarchical_predecode(design.design):
names=input_names,
length=self.height - 2*self.m1_width)
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)]
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 + self.m2_pitch, 2*self.m1_width)
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=offset,
@ -86,12 +86,20 @@ class hierarchical_predecode(design.design):
length=self.height - 2*self.m1_width)
def add_input_inverters(self):
def create_input_inverters(self):
""" Create the input inverters to invert input signals for the decode stage. """
self.in_inst = []
for inv_num in range(self.number_of_inputs):
name = "Xpre_inv[{0}]".format(inv_num)
name = "Xpre_inv_{0}".format(inv_num)
self.in_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"])
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"
@ -99,20 +107,24 @@ 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.append(self.add_inst(name=name,
mod=self.inv,
offset=offset,
mirror=mirror))
self.connect_inst(["in[{0}]".format(inv_num),
"inbar[{0}]".format(inv_num),
"vdd", "gnd"])
self.in_inst[inv_num].place(offset=offset,
mirror=mirror)
def add_output_inverters(self):
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 = "Xpre_nand_inv[{}]".format(inv_num)
name = "Xpre_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"
@ -120,22 +132,24 @@ class hierarchical_predecode(design.design):
y_off =(inv_num + 1)*self.inv.height
mirror = "MX"
offset = vector(self.x_off_inv_2, y_off)
self.inv_inst.append(self.add_inst(name=name,
mod=self.inv,
offset=offset,
mirror=mirror))
self.connect_inst(["Z[{}]".format(inv_num),
"out[{}]".format(inv_num),
"vdd", "gnd"])
self.inv_inst[inv_num].place(offset=offset,
mirror=mirror)
def add_nand(self,connections):
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)
name = "Xpre{0}_nand_{1}".format(inout,nand_input)
self.nand_inst.append(self.add_inst(name=name,
mod=self.nand))
self.connect_inst(connections[nand_input])
def place_nand_array(self):
""" Place the NAND stage for the decodes """
for nand_input in range(self.number_of_outputs):
inout = str(self.number_of_inputs)+"x"+str(self.number_of_outputs)
if (nand_input % 2 == 0):
y_off = nand_input * self.inv.height
mirror = "R0"
@ -143,11 +157,8 @@ class hierarchical_predecode(design.design):
y_off = (nand_input + 1) * self.inv.height
mirror = "MX"
offset = vector(self.x_off_nand, y_off)
self.nand_inst.append(self.add_inst(name=name,
mod=self.nand,
offset=offset,
mirror=mirror))
self.connect_inst(connections[nand_input])
self.nand_inst[nand_input].place(offset=offset,
mirror=mirror)
def route(self):
@ -164,8 +175,8 @@ class hierarchical_predecode(design.design):
# 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_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])
@ -191,7 +202,7 @@ class hierarchical_predecode(design.design):
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
z_pin = self.inv_inst[num].get_pin("Z")
self.add_layout_pin(text="out[{}]".format(num),
self.add_layout_pin(text="out_{}".format(num),
layer="metal1",
offset=z_pin.ll(),
height=z_pin.height(),
@ -203,8 +214,8 @@ class hierarchical_predecode(design.design):
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)
out_pin = "Abar_{}".format(inv_num)
in_pin = "in_{}".format(inv_num)
#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
@ -271,15 +282,7 @@ class hierarchical_predecode(design.design):
# Add pins in two locations
for xoffset in [in_xoffset, out_xoffset]:
pin_pos = vector(xoffset, nand_pin.cy())
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=pin_pos,
rotate=90)
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=pin_pos,
rotate=90)
self.add_layout_pin_rect_center(text=n,
layer="metal3",
offset=pin_pos)
self.add_power_pin(n, pin_pos)

View File

@ -3,6 +3,7 @@ import debug
import design
from vector import vector
from hierarchical_predecode import hierarchical_predecode
from globals import OPTS
class hierarchical_predecode2x4(hierarchical_predecode):
"""
@ -11,11 +12,20 @@ class hierarchical_predecode2x4(hierarchical_predecode):
def __init__(self):
hierarchical_predecode.__init__(self, 2)
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_pins()
self.create_modules()
self.setup_constraints()
self.create_layout()
self.DRC_LVS()
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)
def create_layout(self):
""" The general organization is from left to right:
@ -24,22 +34,20 @@ class hierarchical_predecode2x4(hierarchical_predecode):
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
4) a set of NAND gates for inversion
"""
self.create_rails()
self.add_input_inverters()
self.add_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.add_nand(connections)
self.setup_layout_constraints()
self.route_rails()
self.place_input_inverters()
self.place_output_inverters()
self.place_nand_array()
self.route()
self.DRC_LVS()
def get_nand_input_line_combination(self):
""" These are the decoder connections of the NAND gates to the A,B pins """
combination = [["Abar[0]", "Abar[1]"],
["A[0]", "Abar[1]"],
["Abar[0]", "A[1]"],
["A[0]", "A[1]"]]
combination = [["Abar_0", "Abar_1"],
["A_0", "Abar_1"],
["Abar_0", "A_1"],
["A_0", "A_1"]]
return combination

View File

@ -3,6 +3,7 @@ import debug
import design
from vector import vector
from hierarchical_predecode import hierarchical_predecode
from globals import OPTS
class hierarchical_predecode3x8(hierarchical_predecode):
"""
@ -11,43 +12,51 @@ class hierarchical_predecode3x8(hierarchical_predecode):
def __init__(self):
hierarchical_predecode.__init__(self, 3)
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_pins()
self.create_modules()
self.setup_constraints()
self.create_layout()
self.DRC_LVS()
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)
def create_layout(self):
""" The general organization is from left to right:
"""
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.create_rails()
self.add_input_inverters()
self.add_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.add_nand(connections)
self.setup_layout_constraints()
self.route_rails()
self.place_input_inverters()
self.place_output_inverters()
self.place_nand_array()
self.route()
self.DRC_LVS()
def get_nand_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]"],
["A[0]", "Abar[1]", "A[2]"],
["Abar[0]", "A[1]", "A[2]"],
["A[0]", "A[1]", "A[2]"]]
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"],
["A_0", "Abar_1", "A_2"],
["Abar_0", "A_1", "A_2"],
["A_0", "A_1", "A_2"]]
return combination

View File

@ -1,50 +0,0 @@
import globals
import design
from math import log
import design
from tech import GDS,layer
import utils
class ms_flop(design.design):
"""
Memory address flip-flop
"""
pin_names = ["din", "dout", "dout_bar", "clk", "vdd", "gnd"]
(width,height) = utils.get_libcell_size("ms_flop", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "ms_flop", GDS["unit"], layer["boundary"])
def __init__(self, name="ms_flop"):
design.design.__init__(self, name)
self.width = ms_flop.width
self.height = ms_flop.height
self.pin_map = ms_flop.pin_map
def analytical_delay(self, slew, load = 0.0):
# dont know how to calculate this now, use constant in tech file
from tech import spice
result = self.return_delay(spice["msflop_delay"], spice["msflop_slew"])
return result
def analytical_power(self, proc, vdd, temp, load):
"""Returns dynamic and leakage power. Results in nW"""
from tech import spice
c_eff = self.calculate_effective_capacitance(load)
f = spice["default_event_rate"]
power_dyn = c_eff*vdd*vdd*f
power_leak = spice["msflop_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"""
from tech import spice, parameter
c_load = load
c_para = spice["flop_para_cap"]#ff
transistion_prob = spice["flop_transisition_prob"]
return transistion_prob*(c_load + c_para)

View File

@ -1,126 +0,0 @@
import debug
import design
from tech import drc
from math import log
from vector import vector
from globals import OPTS
class ms_flop_array(design.design):
"""
An Array of D-Flipflops used for to store Data_in & Data_out of
Write_driver & Sense_amp, address inputs of column_mux &
hierdecoder
"""
def __init__(self, columns, word_size, name=""):
self.columns = columns
self.word_size = word_size
if name=="":
name = "flop_array_c{0}_w{1}".format(columns,word_size)
design.design.__init__(self, name)
debug.info(1, "Creating {}".format(self.name))
from importlib import reload
c = reload(__import__(OPTS.ms_flop))
self.mod_ms_flop = getattr(c, OPTS.ms_flop)
self.ms = self.mod_ms_flop("ms_flop")
self.add_mod(self.ms)
self.width = self.columns * self.ms.width
self.height = self.ms.height
self.words_per_row = int(self.columns / self.word_size)
self.create_layout()
def create_layout(self):
self.add_pins()
self.create_ms_flop_array()
self.add_layout_pins()
self.DRC_LVS()
def add_pins(self):
for i in range(self.word_size):
self.add_pin("din[{0}]".format(i))
for i in range(self.word_size):
self.add_pin("dout[{0}]".format(i))
self.add_pin("dout_bar[{0}]".format(i))
self.add_pin("clk")
self.add_pin("vdd")
self.add_pin("gnd")
def create_ms_flop_array(self):
self.ms_inst={}
for i in range(0,self.columns,self.words_per_row):
name = "Xdff{0}".format(i)
if (i % 2 == 0 or self.words_per_row>1):
base = vector(i*self.ms.width,0)
mirror = "R0"
else:
base = vector((i+1)*self.ms.width,0)
mirror = "MY"
index = int(i/self.words_per_row)
self.ms_inst[index]=self.add_inst(name=name,
mod=self.ms,
offset=base,
mirror=mirror)
self.connect_inst(["din[{0}]".format(index),
"dout[{0}]".format(index),
"dout_bar[{0}]".format(index),
"clk",
"vdd", "gnd"])
def add_layout_pins(self):
for i in range(self.word_size):
# Route both supplies
for n in ["vdd", "gnd"]:
for supply_pin in self.ms_inst[i].get_pins(n):
pin_pos = supply_pin.center()
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=pin_pos)
self.add_layout_pin_rect_center(text=n,
layer="metal3",
offset=pin_pos)
din_pins = self.ms_inst[i].get_pins("din")
for din_pin in din_pins:
self.add_layout_pin(text="din[{}]".format(i),
layer=din_pin.layer,
offset=din_pin.ll(),
width=din_pin.width(),
height=din_pin.height())
dout_pin = self.ms_inst[i].get_pin("dout")
self.add_layout_pin(text="dout[{}]".format(i),
layer="metal2",
offset=dout_pin.ll(),
width=dout_pin.width(),
height=dout_pin.height())
doutbar_pin = self.ms_inst[i].get_pin("dout_bar")
self.add_layout_pin(text="dout_bar[{}]".format(i),
layer="metal2",
offset=doutbar_pin.ll(),
width=doutbar_pin.width(),
height=doutbar_pin.height())
# Continous clk rail along with label.
self.add_layout_pin(text="clk",
layer="metal1",
offset=self.ms_inst[0].get_pin("clk").ll().scale(0,1),
width=self.width,
height=drc["minwidth_metal1"])
def analytical_delay(self, slew, load=0.0):
return self.ms.analytical_delay(slew=slew, load=load)

View File

@ -0,0 +1,852 @@
import sys
from tech import drc, parameter
import debug
import design
import math
from math import log,sqrt,ceil
import contact
from pinv import pinv
from pnand2 import pnand2
from pnor2 import pnor2
from vector import vector
from pinvbuf import pinvbuf
from globals import OPTS
class multibank(design.design):
"""
Dynamically generated a single bank including bitcell array,
hierarchical_decoder, precharge, (optional column_mux and column decoder),
write driver and sense amplifiers.
This module includes the tristate and bank select logic.
"""
def __init__(self, word_size, num_words, words_per_row, num_banks=1, name=""):
mod_list = ["tri_gate", "bitcell", "decoder", "wordline_driver",
"bitcell_array", "sense_amp_array", "precharge_array",
"column_mux_array", "write_driver_array", "tri_gate_array",
"dff", "bank_select"]
from importlib import reload
for mod_name in mod_list:
config_mod_name = getattr(OPTS, mod_name)
class_file = reload(__import__(config_mod_name))
mod_class = getattr(class_file , config_mod_name)
setattr (self, "mod_"+mod_name, mod_class)
if name == "":
name = "bank_{0}_{1}".format(word_size, num_words)
design.design.__init__(self, name)
debug.info(2, "create sram of size {0} with {1} words".format(word_size,num_words))
self.word_size = word_size
self.num_words = num_words
self.words_per_row = words_per_row
self.num_banks = num_banks
# The local control signals are gated when we have bank select logic,
# so this prefix will be added to all of the input signals to create
# the internal gated signals.
if self.num_banks>1:
self.prefix="gated_"
else:
self.prefix=""
self.compute_sizes()
self.add_pins()
self.create_modules()
self.add_modules()
self.setup_layout_constraints()
# FIXME: Move this to the add modules function
self.add_bank_select()
self.route_layout()
# Can remove the following, but it helps for debug!
self.add_lvs_correspondence_points()
# Remember the bank center for further placement
self.bank_center=self.offset_all_coordinates().scale(-1,-1)
self.DRC_LVS()
def add_pins(self):
""" Adding pins for Bank module"""
for i in range(self.word_size):
self.add_pin("DOUT_{0}".format(i),"OUT")
for i in range(self.word_size):
self.add_pin("BANK_DIN_{0}".format(i),"IN")
for i in range(self.addr_size):
self.add_pin("A_{0}".format(i),"INPUT")
# For more than one bank, we have a bank select and name
# the signals gated_*.
if self.num_banks > 1:
self.add_pin("bank_sel","INPUT")
for pin in ["s_en","w_en","tri_en_bar","tri_en",
"clk_buf_bar","clk_buf"]:
self.add_pin(pin,"INPUT")
self.add_pin("vdd","POWER")
self.add_pin("gnd","GROUND")
def route_layout(self):
""" Create routing amoung the modules """
self.route_central_bus()
self.route_precharge_to_bitcell_array()
self.route_col_mux_to_bitcell_array()
self.route_sense_amp_to_col_mux_or_bitcell_array()
#self.route_sense_amp_to_trigate()
#self.route_tri_gate_out()
self.route_sense_amp_out()
self.route_wordline_driver()
self.route_write_driver()
self.route_row_decoder()
self.route_column_address_lines()
self.route_control_lines()
self.add_control_pins()
if self.num_banks > 1:
self.route_bank_select()
self.route_supplies()
def add_modules(self):
""" Add modules. The order should not matter! """
# Above the bitcell array
self.add_bitcell_array()
self.add_precharge_array()
# Below the bitcell array
self.add_column_mux_array()
self.add_sense_amp_array()
self.add_write_driver_array()
# Not needed for single bank
#self.add_tri_gate_array()
# To the left of the bitcell array
self.add_row_decoder()
self.add_wordline_driver()
self.add_column_decoder()
def compute_sizes(self):
""" Computes the required sizes to create the bank """
self.num_cols = int(self.words_per_row*self.word_size)
self.num_rows = int(self.num_words / self.words_per_row)
self.row_addr_size = int(log(self.num_rows, 2))
self.col_addr_size = int(log(self.words_per_row, 2))
self.addr_size = self.col_addr_size + self.row_addr_size
debug.check(self.num_rows*self.num_cols==self.word_size*self.num_words,"Invalid bank sizes.")
debug.check(self.addr_size==self.col_addr_size + self.row_addr_size,"Invalid address break down.")
# Width for the vdd/gnd rails
self.supply_rail_width = 4*self.m2_width
# FIXME: This spacing should be width dependent...
self.supply_rail_pitch = self.supply_rail_width + 4*self.m2_space
# Number of control lines in the bus
self.num_control_lines = 6
# The order of the control signals on the control bus:
self.input_control_signals = ["clk_buf", "tri_en_bar", "tri_en", "clk_buf_bar", "w_en", "s_en"]
# These will be outputs of the gaters if this is multibank, if not, normal signals.
if self.num_banks > 1:
self.control_signals = ["gated_"+str for str in self.input_control_signals]
else:
self.control_signals = self.input_control_signals
# The central bus is the column address (one hot) and row address (binary)
if self.col_addr_size>0:
self.num_col_addr_lines = 2**self.col_addr_size
else:
self.num_col_addr_lines = 0
# The width of this bus is needed to place other modules (e.g. decoder)
# A width on each side too
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"),
2*self.m2_pitch)
def create_modules(self):
""" Create all the modules using the class loader """
self.tri = self.mod_tri_gate()
self.bitcell = self.mod_bitcell()
self.bitcell_array = self.mod_bitcell_array(cols=self.num_cols,
rows=self.num_rows)
self.add_mod(self.bitcell_array)
self.precharge_array = self.mod_precharge_array(columns=self.num_cols)
self.add_mod(self.precharge_array)
if self.col_addr_size > 0:
self.column_mux_array = self.mod_column_mux_array(columns=self.num_cols,
word_size=self.word_size)
self.add_mod(self.column_mux_array)
self.sense_amp_array = self.mod_sense_amp_array(word_size=self.word_size,
words_per_row=self.words_per_row)
self.add_mod(self.sense_amp_array)
self.write_driver_array = self.mod_write_driver_array(columns=self.num_cols,
word_size=self.word_size)
self.add_mod(self.write_driver_array)
self.row_decoder = self.mod_decoder(rows=self.num_rows)
self.add_mod(self.row_decoder)
self.tri_gate_array = self.mod_tri_gate_array(columns=self.num_cols,
word_size=self.word_size)
self.add_mod(self.tri_gate_array)
self.wordline_driver = self.mod_wordline_driver(rows=self.num_rows)
self.add_mod(self.wordline_driver)
self.inv = pinv()
self.add_mod(self.inv)
if(self.num_banks > 1):
self.bank_select = self.mod_bank_select()
self.add_mod(self.bank_select)
def add_bitcell_array(self):
""" Adding Bitcell Array """
self.bitcell_array_inst=self.add_inst(name="bitcell_array",
mod=self.bitcell_array,
offset=vector(0,0))
temp = []
for i in range(self.num_cols):
temp.append("bl_{0}".format(i))
temp.append("br_{0}".format(i))
for j in range(self.num_rows):
temp.append("wl_{0}".format(j))
temp.extend(["vdd", "gnd"])
self.connect_inst(temp)
def add_precharge_array(self):
""" Adding Precharge """
# The wells must be far enough apart
# The enclosure is for the well and the spacing is to the bitcell wells
y_offset = self.bitcell_array.height + self.m2_gap
self.precharge_array_inst=self.add_inst(name="precharge_array",
mod=self.precharge_array,
offset=vector(0,y_offset))
temp = []
for i in range(self.num_cols):
temp.append("bl_{0}".format(i))
temp.append("br_{0}".format(i))
temp.extend([self.prefix+"clk_buf_bar", "vdd"])
self.connect_inst(temp)
def add_column_mux_array(self):
""" Adding Column Mux when words_per_row > 1 . """
if self.col_addr_size > 0:
self.column_mux_height = self.column_mux_array.height + self.m2_gap
else:
self.column_mux_height = 0
return
y_offset = self.column_mux_height
self.col_mux_array_inst=self.add_inst(name="column_mux_array",
mod=self.column_mux_array,
offset=vector(0,y_offset).scale(-1,-1))
temp = []
for i in range(self.num_cols):
temp.append("bl_{0}".format(i))
temp.append("br_{0}".format(i))
for k in range(self.words_per_row):
temp.append("sel_{0}".format(k))
for j in range(self.word_size):
temp.append("bl_out_{0}".format(j))
temp.append("br_out_{0}".format(j))
temp.append("gnd")
self.connect_inst(temp)
def add_sense_amp_array(self):
""" Adding Sense amp """
y_offset = self.column_mux_height + self.sense_amp_array.height + self.m2_gap
self.sense_amp_array_inst=self.add_inst(name="sense_amp_array",
mod=self.sense_amp_array,
offset=vector(0,y_offset).scale(-1,-1))
temp = []
for i in range(self.word_size):
temp.append("sa_out_{0}".format(i))
if self.words_per_row == 1:
temp.append("bl_{0}".format(i))
temp.append("br_{0}".format(i))
else:
temp.append("bl_out_{0}".format(i))
temp.append("br_out_{0}".format(i))
temp.extend([self.prefix+"s_en", "vdd", "gnd"])
self.connect_inst(temp)
def add_write_driver_array(self):
""" Adding Write Driver """
y_offset = self.sense_amp_array.height + self.column_mux_height \
+ self.m2_gap + self.write_driver_array.height
self.write_driver_array_inst=self.add_inst(name="write_driver_array",
mod=self.write_driver_array,
offset=vector(0,y_offset).scale(-1,-1))
temp = []
for i in range(self.word_size):
temp.append("BANK_DIN_{0}".format(i))
for i in range(self.word_size):
if (self.words_per_row == 1):
temp.append("bl_{0}".format(i))
temp.append("br_{0}".format(i))
else:
temp.append("bl_out_{0}".format(i))
temp.append("br_out_{0}".format(i))
temp.extend([self.prefix+"w_en", "vdd", "gnd"])
self.connect_inst(temp)
def add_tri_gate_array(self):
""" data tri gate to drive the data bus """
y_offset = self.sense_amp_array.height+self.column_mux_height \
+ self.m2_gap + self.tri_gate_array.height
self.tri_gate_array_inst=self.add_inst(name="tri_gate_array",
mod=self.tri_gate_array,
offset=vector(0,y_offset).scale(-1,-1))
temp = []
for i in range(self.word_size):
temp.append("sa_out_{0}".format(i))
for i in range(self.word_size):
temp.append("DOUT_{0}".format(i))
temp.extend([self.prefix+"tri_en", self.prefix+"tri_en_bar", "vdd", "gnd"])
self.connect_inst(temp)
def add_row_decoder(self):
""" Add the hierarchical row decoder """
# The address and control bus will be in between decoder and the main memory array
# This bus will route address bits to the decoder input and column mux inputs.
# The wires are actually routed after we placed the stuff on both sides.
# The predecoder is below the x-axis and the main decoder is above the x-axis
# The address flop and decoder are aligned in the x coord.
x_offset = -(self.row_decoder.width + self.central_bus_width + self.wordline_driver.width)
self.row_decoder_inst=self.add_inst(name="row_decoder",
mod=self.row_decoder,
offset=vector(x_offset,0))
temp = []
for i in range(self.row_addr_size):
temp.append("A_{0}".format(i+self.col_addr_size))
for j in range(self.num_rows):
temp.append("dec_out_{0}".format(j))
temp.extend(["vdd", "gnd"])
self.connect_inst(temp)
def add_wordline_driver(self):
""" Wordline Driver """
# The wordline driver is placed to the right of the main decoder width.
x_offset = -(self.central_bus_width + self.wordline_driver.width) + self.m2_pitch
self.wordline_driver_inst=self.add_inst(name="wordline_driver",
mod=self.wordline_driver,
offset=vector(x_offset,0))
temp = []
for i in range(self.num_rows):
temp.append("dec_out_{0}".format(i))
for i in range(self.num_rows):
temp.append("wl_{0}".format(i))
temp.append(self.prefix+"clk_buf")
temp.append("vdd")
temp.append("gnd")
self.connect_inst(temp)
def add_column_decoder_module(self):
"""
Create a 2:4 or 3:8 column address decoder.
"""
# Place the col decoder right aligned with row decoder
x_off = -(self.central_bus_width + self.wordline_driver.width + self.col_decoder.width)
y_off = -(self.col_decoder.height + 2*drc("well_to_well"))
self.col_decoder_inst=self.add_inst(name="col_address_decoder",
mod=self.col_decoder,
offset=vector(x_off,y_off))
temp = []
for i in range(self.col_addr_size):
temp.append("A_{0}".format(i))
for j in range(self.num_col_addr_lines):
temp.append("sel_{0}".format(j))
temp.extend(["vdd", "gnd"])
self.connect_inst(temp)
def add_column_decoder(self):
"""
Create a decoder to decode column select lines. This could be an inverter/buffer for 1:2,
2:4 decoder, or 3:8 decoder.
"""
if self.col_addr_size == 0:
return
elif self.col_addr_size == 1:
self.col_decoder = pinvbuf(height=self.mod_dff.height)
self.add_mod(self.col_decoder)
elif self.col_addr_size == 2:
self.col_decoder = self.row_decoder.pre2_4
elif self.col_addr_size == 3:
self.col_decoder = self.row_decoder.pre3_8
else:
# No error checking before?
debug.error("Invalid column decoder?",-1)
self.add_column_decoder_module()
def add_bank_select(self):
""" Instantiate the bank select logic. """
if not self.num_banks > 1:
return
x_off = -(self.row_decoder.width + self.central_bus_width + self.wordline_driver.width)
if self.col_addr_size > 0:
y_off = min(self.col_decoder_inst.by(), self.col_mux_array_inst.by())
else:
y_off = self.row_decoder_inst.by()
y_off -= (self.bank_select.height + drc("well_to_well"))
self.bank_select_pos = vector(x_off,y_off)
self.bank_select_inst = self.add_inst(name="bank_select",
mod=self.bank_select,
offset=self.bank_select_pos)
temp = []
temp.extend(self.input_control_signals)
temp.append("bank_sel")
temp.extend(self.control_signals)
temp.extend(["vdd", "gnd"])
self.connect_inst(temp)
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")
def route_bank_select(self):
""" Route the bank select logic. """
for input_name in self.input_control_signals+["bank_sel"]:
self.copy_layout_pin(self.bank_select_inst, input_name)
for gated_name in self.control_signals:
# 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"),
offset=bus_pos,
rotate=90)
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=out_pos,
rotate=90)
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=out_pos,
rotate=90)
def setup_layout_constraints(self):
""" After the modules are instantiated, find the dimensions for the
control bus, power ring, etc. """
#The minimum point is either the bottom of the address flops,
#the column decoder (if there is one) or the tristate output
#driver.
# Leave room for the output below the tri gate.
#tri_gate_min_y_offset = self.tri_gate_array_inst.by() - 3*self.m2_pitch
write_driver_min_y_offset = self.write_driver_array_inst.by() - 3*self.m2_pitch
row_decoder_min_y_offset = self.row_decoder_inst.by()
if self.col_addr_size > 0:
col_decoder_min_y_offset = self.col_decoder_inst.by()
else:
col_decoder_min_y_offset = row_decoder_min_y_offset
if self.num_banks>1:
# The control gating logic is below the decoder
# Min of the control gating logic and tri gate.
self.min_y_offset = min(col_decoder_min_y_offset - self.bank_select.height, write_driver_min_y_offset)
else:
# Just the min of the decoder logic logic and tri gate.
self.min_y_offset = min(col_decoder_min_y_offset, write_driver_min_y_offset)
# The max point is always the top of the precharge bitlines
# Add a vdd and gnd power rail above the array
self.max_y_offset = self.precharge_array_inst.uy() + 3*self.m1_width
self.max_x_offset = self.bitcell_array_inst.ur().x + 3*self.m1_width
self.min_x_offset = self.row_decoder_inst.lx()
# # Create the core bbox for the power rings
ur = vector(self.max_x_offset, self.max_y_offset)
ll = vector(self.min_x_offset, self.min_y_offset)
self.core_bbox = [ll, ur]
self.height = ur.y - ll.y
self.width = ur.x - ll.x
def route_central_bus(self):
""" Create the address, supply, and control signal central bus lines. """
# Overall central bus width. It includes all the column mux lines,
# and control lines.
# The bank is at (0,0), so this is to the left of the y-axis.
# 2 pitches on the right for vias/jogs to access the inputs
control_bus_offset = vector(-self.m2_pitch * self.num_control_lines - self.m2_width, 0)
control_bus_length = self.bitcell_array_inst.uy()
self.bus_xoffset = self.create_vertical_bus(layer="metal2",
pitch=self.m2_pitch,
offset=control_bus_offset,
names=self.control_signals,
length=control_bus_length)
def route_precharge_to_bitcell_array(self):
""" Routing of BL and BR between pre-charge and bitcell array """
for i in range(self.num_cols):
precharge_bl = self.precharge_array_inst.get_pin("bl_{}".format(i)).bc()
precharge_br = self.precharge_array_inst.get_pin("br_{}".format(i)).bc()
bitcell_bl = self.bitcell_array_inst.get_pin("bl_{}".format(i)).uc()
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),
vector(bitcell_bl.x,yoffset), bitcell_bl])
self.add_path("metal2",[precharge_br, vector(precharge_br.x,yoffset),
vector(bitcell_br.x,yoffset), bitcell_br])
def route_col_mux_to_bitcell_array(self):
""" Routing of BL and BR between col mux and bitcell array """
# Only do this if we have a column mux!
if self.col_addr_size==0:
return
for i in range(self.num_cols):
col_mux_bl = self.col_mux_array_inst.get_pin("bl_{}".format(i)).uc()
col_mux_br = self.col_mux_array_inst.get_pin("br_{}".format(i)).uc()
bitcell_bl = self.bitcell_array_inst.get_pin("bl_{}".format(i)).bc()
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),
vector(bitcell_bl.x,yoffset), bitcell_bl])
self.add_path("metal2",[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):
""" Routing of BL and BR between sense_amp and column mux or bitcell array """
for i in range(self.word_size):
sense_amp_bl = self.sense_amp_array_inst.get_pin("bl_{}".format(i)).uc()
sense_amp_br = self.sense_amp_array_inst.get_pin("br_{}".format(i)).uc()
if self.col_addr_size>0:
# Sense amp is connected to the col mux
connect_bl = self.col_mux_array_inst.get_pin("bl_out_{}".format(i)).bc()
connect_br = self.col_mux_array_inst.get_pin("br_out_{}".format(i)).bc()
else:
# Sense amp is directly connected to the bitcell array
connect_bl = self.bitcell_array_inst.get_pin("bl_{}".format(i)).bc()
connect_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).bc()
yoffset = 0.5*(sense_amp_bl.y+connect_bl.y)
self.add_path("metal2",[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),
vector(connect_br.x,yoffset), connect_br])
def route_sense_amp_to_trigate(self):
""" Routing of sense amp output to tri_gate input """
for i in range(self.word_size):
# Connection of data_out of sense amp to data_in
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"),
offset=tri_gate_in)
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=sa_data_out)
self.add_path("metal3",[sa_data_out,tri_gate_in])
def route_sense_amp_out(self):
""" Add pins for the sense amp output """
for i in range(self.word_size):
data_pin = self.sense_amp_array_inst.get_pin("data_{}".format(i))
self.add_layout_pin_rect_center(text="DOUT_{}".format(i),
layer=data_pin.layer,
offset=data_pin.center(),
height=data_pin.height(),
width=data_pin.width()),
def route_tri_gate_out(self):
""" Metal 3 routing of tri_gate output data """
for i in range(self.word_size):
data_pin = self.tri_gate_array_inst.get_pin("out_{}".format(i))
self.add_layout_pin_rect_center(text="DOUT_{}".format(i),
layer=data_pin.layer,
offset=data_pin.center(),
height=data_pin.height(),
width=data_pin.width()),
def route_row_decoder(self):
""" Routes the row decoder inputs and supplies """
# Create inputs for the row address lines
for i in range(self.row_addr_size):
addr_idx = i + self.col_addr_size
decoder_name = "A_{}".format(i)
addr_name = "A_{}".format(addr_idx)
self.copy_layout_pin(self.row_decoder_inst, decoder_name, addr_name)
def route_write_driver(self):
""" Connecting write driver """
for i in range(self.word_size):
data_name = "data_{}".format(i)
din_name = "BANK_DIN_{}".format(i)
self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name)
def route_wordline_driver(self):
""" Connecting Wordline driver output to Bitcell WL connection """
# we don't care about bends after connecting to the input pin, so let the path code decide.
for i 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(i)).rc()
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])
# 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])
def route_column_address_lines(self):
""" Connecting the select lines of column mux to the address bus """
if not self.col_addr_size>0:
return
if self.col_addr_size == 1:
# Connect to sel[0] and sel[1]
decode_names = ["Zb", "Z"]
# The Address LSB
self.copy_layout_pin(self.col_decoder_inst, "A", "A[0]")
elif self.col_addr_size > 1:
decode_names = []
for i in range(self.num_col_addr_lines):
decode_names.append("out_{}".format(i))
for i in range(self.col_addr_size):
decoder_name = "in_{}".format(i)
addr_name = "A_{}".format(i)
self.copy_layout_pin(self.col_decoder_inst, decoder_name, addr_name)
# This will do a quick "river route" on two layers.
# When above the top select line it will offset "inward" again to prevent conflicts.
# This could be done on a single layer, but we follow preferred direction rules for later routing.
top_y_offset = self.col_mux_array_inst.get_pin("sel_{}".format(self.num_col_addr_lines-1)).cy()
for (decode_name,i) in zip(decode_names,range(self.num_col_addr_lines)):
mux_name = "sel_{}".format(i)
mux_addr_pos = self.col_mux_array_inst.get_pin(mux_name).lc()
decode_out_pos = self.col_decoder_inst.get_pin(decode_name).center()
# To get to the edge of the decoder and one track out
delta_offset = self.col_decoder_inst.rx() - decode_out_pos.x + self.m2_pitch
if decode_out_pos.y > top_y_offset:
mid1_pos = vector(decode_out_pos.x + delta_offset + i*self.m2_pitch,decode_out_pos.y)
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])
def add_lvs_correspondence_points(self):
""" This adds some points for easier debugging if LVS goes wrong.
These should probably be turned off by default though, since extraction
will show these as ports in the extracted netlist.
"""
# Add the wordline names
for i in range(self.num_rows):
wl_name = "wl_{}".format(i)
wl_pin = self.bitcell_array_inst.get_pin(wl_name)
self.add_label(text=wl_name,
layer="metal1",
offset=wl_pin.center())
# Add the bitline names
for i in range(self.num_cols):
bl_name = "bl_{}".format(i)
br_name = "br_{}".format(i)
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",
offset=bl_pin.center())
self.add_label(text=br_name,
layer="metal2",
offset=br_pin.center())
# # Add the data output names to the sense amp output
# for i in range(self.word_size):
# data_name = "data_{}".format(i)
# data_pin = self.sense_amp_array_inst.get_pin(data_name)
# self.add_label(text="sa_out_{}".format(i),
# layer="metal2",
# offset=data_pin.center())
# Add labels on the decoder
for i in range(self.word_size):
data_name = "dec_out_{}".format(i)
pin_name = "in_{}".format(i)
data_pin = self.wordline_driver_inst.get_pin(pin_name)
self.add_label(text=data_name,
layer="metal1",
offset=data_pin.center())
def route_control_lines(self):
""" Route the control lines of the entire bank """
# Make a list of tuples that we will connect.
# From control signal to the module pin
# Connection from the central bus to the main control block crosses
# pre-decoder and this connection is in metal3
connection = []
#connection.append((self.prefix+"tri_en_bar", self.tri_gate_array_inst.get_pin("en_bar").lc()))
#connection.append((self.prefix+"tri_en", self.tri_gate_array_inst.get_pin("en").lc()))
connection.append((self.prefix+"clk_buf_bar", self.precharge_array_inst.get_pin("en").lc()))
connection.append((self.prefix+"w_en", self.write_driver_array_inst.get_pin("en").lc()))
connection.append((self.prefix+"s_en", self.sense_amp_array_inst.get_pin("en").lc()))
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"),
offset=control_pos,
rotate=90)
# clk to wordline_driver
control_signal = self.prefix+"clk_buf"
pin_pos = self.wordline_driver_inst.get_pin("en").uc()
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])
control_via_pos = vector(control_x_offset, mid_pos.y)
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=control_via_pos,
rotate=90)
def add_control_pins(self):
""" Add the control signal input pins """
for ctrl in self.control_signals:
# xoffsets are the center of the rail
x_offset = self.bus_xoffset[ctrl].x - 0.5*self.m2_width
if self.num_banks > 1:
# it's not an input pin if we have multiple banks
self.add_label_pin(text=ctrl,
layer="metal2",
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",
offset=vector(x_offset, self.min_y_offset),
width=self.m2_width,
height=self.max_y_offset-self.min_y_offset)
def connect_rail_from_right(self,inst, pin, rail):
""" 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)])
# Bring it up to M2 for M2/M3 routing
self.add_via(layers=("metal1","via1","metal2"),
offset=in_pin + contact.m1m2.offset,
rotate=90)
self.add_via(layers=("metal2","via2","metal3"),
offset=in_pin + self.m2m3_via_offset,
rotate=90)
def connect_rail_from_left(self,inst, pin, rail):
""" 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,
rotate=90)
self.add_via(layers=("metal2","via2","metal3"),
offset=in_pin + self.m2m3_via_offset,
rotate=90)
def analytical_delay(self, slew, load):
""" return analytical delay of the bank"""
decoder_delay = self.row_decoder.analytical_delay(slew, self.wordline_driver.input_load())
word_driver_delay = self.wordline_driver.analytical_delay(decoder_delay.slew, self.bitcell_array.input_load())
bitcell_array_delay = self.bitcell_array.analytical_delay(word_driver_delay.slew)
bl_t_data_out_delay = self.sense_amp_array.analytical_delay(bitcell_array_delay.slew,
self.bitcell_array.output_load())
# output load of bitcell_array is set to be only small part of bl for sense amp.
data_t_DATA_delay = self.tri_gate_array.analytical_delay(bl_t_data_out_delay.slew, load)
result = decoder_delay + word_driver_delay + bitcell_array_delay + bl_t_data_out_delay + data_t_DATA_delay
return result

View File

@ -3,7 +3,7 @@ import debug
from tech import drc
from vector import vector
from precharge import precharge
from globals import OPTS
class precharge_array(design.design):
"""
@ -11,49 +11,80 @@ class precharge_array(design.design):
of bit line columns, height is the height of the bit-cell array.
"""
def __init__(self, columns, size=1):
design.design.__init__(self, "precharge_array")
unique_id = 1
def __init__(self, columns, size=1, bitcell_bl="bl", bitcell_br="br"):
name = "precharge_array_{}".format(precharge_array.unique_id)
precharge_array.unique_id += 1
design.design.__init__(self, name)
debug.info(1, "Creating {0}".format(self.name))
self.columns = columns
self.size = size
self.bitcell_bl = bitcell_bl
self.bitcell_br = bitcell_br
self.pc_cell = precharge(name="precharge", size=size)
self.add_mod(self.pc_cell)
self.width = self.columns * self.pc_cell.width
self.height = self.pc_cell.height
self.add_pins()
self.create_layout()
self.DRC_LVS()
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def add_pins(self):
"""Adds pins for spice file"""
for i in range(self.columns):
self.add_pin("bl[{0}]".format(i))
self.add_pin("br[{0}]".format(i))
self.add_pin("bl_{0}".format(i))
self.add_pin("br_{0}".format(i))
self.add_pin("en")
self.add_pin("vdd")
def create_layout(self):
self.add_insts()
self.add_layout_pins()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_insts()
def create_layout(self):
self.width = self.columns * self.pc_cell.width
self.height = self.pc_cell.height
self.place_insts()
self.add_layout_pins()
self.DRC_LVS()
def add_modules(self):
self.pc_cell = precharge(name="precharge",
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",
layer="metal1",
offset=self.pc_cell.get_pin("en").ll(),
width=self.width,
height=drc["minwidth_metal1"])
height=drc("minwidth_metal1"))
for inst in self.local_insts:
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())
def add_insts(self):
def create_insts(self):
"""Creates a precharge array by horizontally tiling the precharge cell"""
self.local_insts = []
for i in range(self.columns):
@ -63,18 +94,11 @@ class precharge_array(design.design):
mod=self.pc_cell,
offset=offset)
self.local_insts.append(inst)
self.connect_inst(["bl[{0}]".format(i), "br[{0}]".format(i), "en", "vdd"])
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.connect_inst(["bl_{0}".format(i), "br_{0}".format(i), "en", "vdd"])
def place_insts(self):
""" Places precharge array by horizontally tiling the precharge cell"""
for i in range(self.columns):
offset = vector(self.pc_cell.width * i, 0)
self.local_insts[i].place(offset)

View File

@ -18,36 +18,39 @@ class replica_bitline(design.design):
def __init__(self, delay_stages, delay_fanout, bitcell_loads, name="replica_bitline"):
design.design.__init__(self, name)
from importlib import reload
g = reload(__import__(OPTS.delay_chain))
self.mod_delay_chain = getattr(g, OPTS.delay_chain)
g = reload(__import__(OPTS.replica_bitcell))
self.mod_replica_bitcell = getattr(g, OPTS.replica_bitcell)
for pin in ["en", "out", "vdd", "gnd"]:
self.add_pin(pin)
self.bitcell_loads = bitcell_loads
self.delay_stages = delay_stages
self.delay_fanout = delay_fanout
self.create_modules()
self.calculate_module_offsets()
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_modules()
def create_layout(self):
self.calculate_module_offsets()
self.place_modules()
self.route()
self.add_layout_pins()
self.offset_all_coordinates()
self.add_layout_pins()
#self.add_lvs_correspondence_points()
# Plus a pitch for the WL contacts on the RBL
self.width = self.rbl_inst.rx() - self.dc_inst.lx() + self.m1_pitch
self.height = max(self.rbl_inst.uy(), self.dc_inst.uy())
# Extra pitch on top and right
self.width = self.rbl_inst.rx() - self.dc_inst.lx() + self.m2_pitch
self.height = max(self.rbl_inst.uy(), self.dc_inst.uy()) + self.m3_pitch
self.DRC_LVS()
def add_pins(self):
for pin in ["en", "out", "vdd", "gnd"]:
self.add_pin(pin)
def calculate_module_offsets(self):
""" Calculate all the module offsets """
@ -73,9 +76,16 @@ class replica_bitline(design.design):
self.access_tx_offset = vector(-gap_width-self.access_tx.width-self.inv.width, 0.5*self.inv.height)
def add_modules(self):
""" Add the modules for later usage """
def create_modules(self):
""" Create modules for later instantiation """
from importlib import reload
#g = reload(__import__(OPTS.delay_chain))
#self.mod_delay_chain = getattr(g, OPTS.delay_chain)
g = reload(__import__(OPTS.replica_bitcell))
self.mod_replica_bitcell = getattr(g, OPTS.replica_bitcell)
self.bitcell = self.replica_bitcell = self.mod_replica_bitcell()
self.add_mod(self.bitcell)
@ -84,7 +94,8 @@ class replica_bitline(design.design):
self.add_mod(self.rbl)
# FIXME: The FO and depth of this should be tuned
self.delay_chain = self.mod_delay_chain([self.delay_fanout]*self.delay_stages)
from delay_chain import delay_chain
self.delay_chain = delay_chain([self.delay_fanout]*self.delay_stages)
self.add_mod(self.delay_chain)
self.inv = pinv()
@ -93,40 +104,69 @@ class replica_bitline(design.design):
self.access_tx = ptx(tx_type="pmos")
self.add_mod(self.access_tx)
def add_modules(self):
""" Add all of the module instances in the logical netlist """
def create_modules(self):
""" Create all of the module instances in the logical netlist """
# This is the threshold detect inverter on the output of the RBL
self.rbl_inv_inst=self.add_inst(name="rbl_inv",
mod=self.inv,
offset=self.rbl_inv_offset,
rotate=180)
self.connect_inst(["bl[0]", "out", "vdd", "gnd"])
mod=self.inv)
self.connect_inst(["bl0_0", "out", "vdd", "gnd"])
self.tx_inst=self.add_inst(name="rbl_access_tx",
mod=self.access_tx,
offset=self.access_tx_offset)
mod=self.access_tx)
# D, G, S, B
self.connect_inst(["vdd", "delayed_en", "bl[0]", "vdd"])
self.connect_inst(["vdd", "delayed_en", "bl0_0", "vdd"])
# add the well and poly contact
self.dc_inst=self.add_inst(name="delay_chain",
mod=self.delay_chain,
offset=self.delay_chain_offset)
mod=self.delay_chain)
self.connect_inst(["en", "delayed_en", "vdd", "gnd"])
self.rbc_inst=self.add_inst(name="bitcell",
mod=self.replica_bitcell,
offset=self.bitcell_offset,
mirror="MX")
self.connect_inst(["bl[0]", "br[0]", "delayed_en", "vdd", "gnd"])
mod=self.replica_bitcell)
temp = []
for port in self.all_ports:
temp.append("bl{}_0".format(port))
temp.append("br{}_0".format(port))
for port in self.all_ports:
temp.append("delayed_en")
temp.append("vdd")
temp.append("gnd")
self.connect_inst(temp)
#self.connect_inst(["bl_0", "br_0", "delayed_en", "vdd", "gnd"])
self.rbl_inst=self.add_inst(name="load",
mod=self.rbl,
offset=self.rbl_offset)
self.connect_inst(["bl[0]", "br[0]"] + ["gnd"]*self.bitcell_loads + ["vdd", "gnd"])
mod=self.rbl)
temp = []
for port in self.all_ports:
temp.append("bl{}_0".format(port))
temp.append("br{}_0".format(port))
for wl in range(self.bitcell_loads):
for port in self.all_ports:
temp.append("gnd")
temp.append("vdd")
temp.append("gnd")
self.connect_inst(temp)
self.wl_list = self.rbl.cell.list_all_wl_names()
self.bl_list = self.rbl.cell.list_all_bl_names()
def place_modules(self):
""" Add all of the module instances in the logical netlist """
# This is the threshold detect inverter on the output of the RBL
self.rbl_inv_inst.place(offset=self.rbl_inv_offset,
rotate=180)
self.tx_inst.place(self.access_tx_offset)
self.dc_inst.place(self.delay_chain_offset)
self.rbc_inst.place(offset=self.bitcell_offset,
mirror="MX")
self.rbl_inst.place(self.rbl_offset)
def route(self):
@ -139,19 +179,76 @@ class replica_bitline(design.design):
""" Connect the RBL word lines to gnd """
# Connect the WL and gnd pins directly to the center and right gnd rails
for row in range(self.bitcell_loads):
wl = "wl[{}]".format(row)
wl = self.wl_list[0]+"_{}".format(row)
pin = self.rbl_inst.get_pin(wl)
# Route the connection to the right so that it doesn't interfere
# with the cells
# Route the connection to the right so that it doesn't interfere with the cells
# Wordlines may be close to each other when tiled, so gnd connections are routed in opposite directions
pin_right = pin.rc()
pin_extension = pin_right + vector(self.m1_pitch,0)
pin_extension = pin_right + vector(self.m3_pitch,0)
if pin.layer != "metal1":
continue
self.add_path("metal1", [pin_right, pin_extension])
pin_width_ydir = pin.uy()-pin.by()
#Width is set to pin y width to avoid DRC issues with m1 gaps
self.add_path("metal1", [pin_right, pin_extension], pin_width_ydir)
self.add_power_pin("gnd", pin_extension)
# for multiport, need to short wordlines to each other so they all connect to gnd.
wl_last = self.wl_list[-1]+"_{}".format(row)
pin_last = self.rbl_inst.get_pin(wl_last)
self.short_wordlines(pin, pin_last, "right", False, row, vector(self.m3_pitch,0))
def short_wordlines(self, wl_pin_a, wl_pin_b, pin_side, is_replica_cell, cell_row=0, offset_x_vec=None):
"""Connects the word lines together for a single bitcell. Also requires which side of the bitcell to short the pins."""
#Assumes input pins are wordlines. Also assumes the word lines are horizontal in metal1. Also assumes pins have same x coord.
#This is my (Hunter) first time editing layout in openram so this function is likely not optimal.
if len(self.all_ports) > 1:
#1. Create vertical metal for all the bitlines to connect to
#m1 needs to be extended in the y directions, direction needs to be determined as every other cell is flipped
correct_y = vector(0, 0.5*drc("minwidth_metal1"))
#x spacing depends on the side being drawn. Unknown to me (Hunter) why the size of the space differs by the side.
#I assume this is related to how a wire is draw, but I have not investigated the issue.
if pin_side == "right":
correct_x = vector(0.5*drc("minwidth_metal1"), 0)
if offset_x_vec != None:
correct_x = offset_x_vec
else:
correct_x = vector(1.5*drc("minwidth_metal1"), 0)
if wl_pin_a.uy() > wl_pin_b.uy():
self.add_path("metal1", [wl_pin_a.rc()+correct_x+correct_y, wl_pin_b.rc()+correct_x-correct_y])
else:
self.add_path("metal1", [wl_pin_a.rc()+correct_x-correct_y, wl_pin_b.rc()+correct_x+correct_y])
elif pin_side == "left":
if offset_x_vec != None:
correct_x = offset_x_vec
else:
correct_x = vector(1.5*drc("minwidth_metal1"), 0)
if wl_pin_a.uy() > wl_pin_b.uy():
self.add_path("metal1", [wl_pin_a.lc()-correct_x+correct_y, wl_pin_b.lc()-correct_x-correct_y])
else:
self.add_path("metal1", [wl_pin_a.lc()-correct_x-correct_y, wl_pin_b.lc()-correct_x+correct_y])
else:
debug.error("Could not connect wordlines on specified input side={}".format(pin_side),1)
#2. Connect word lines horizontally. Only replica cell needs. Bitline loads currently already do this.
for port in self.all_ports:
if is_replica_cell:
wl = self.wl_list[port]
pin = self.rbc_inst.get_pin(wl)
else:
wl = self.wl_list[port]+"_{}".format(cell_row)
pin = self.rbl_inst.get_pin(wl)
if pin_side == "left":
self.add_path("metal1", [pin.lc()-correct_x, pin.lc()])
elif pin_side == "right":
self.add_path("metal1", [pin.rc()+correct_x, pin.rc()])
def route_supplies(self):
""" Propagate all vdd/gnd pins up to this level for all modules """
@ -170,8 +267,13 @@ class replica_bitline(design.design):
# Replica bitcell needs to be routed up to M3
pin=self.rbc_inst.get_pin("vdd")
# Don't rotate this via to vit in FreePDK45
self.add_power_pin("vdd", pin.center(), False)
# Don't rotate this via to vit in FreePDK45. In the custom cell, the pin cannot be placed
# directly on vdd or there will be a drc error with a wordline. Place the pin slightly farther
# away then route to it. A better solution would be to rotate the m1 in the via or move the pin
# a m1_pitch below the entire cell.
pin_extension = pin.center() - vector(0,self.m1_pitch)
self.add_power_pin("vdd", pin_extension, rotate=0)
self.add_path("metal1", [pin.center(), pin_extension])
for pin in self.rbc_inst.get_pins("gnd"):
self.add_power_pin("gnd", pin.center())
@ -207,16 +309,28 @@ class replica_bitline(design.design):
# 3. Route the contact of previous route to the bitcell WL
# route bend of previous net to bitcell WL
wl_offset = self.rbc_inst.get_pin("WL").lc()
xmid_point= 0.5*(wl_offset.x+contact_offset.x)
wl_mid1 = vector(xmid_point,contact_offset.y)
wl_mid2 = vector(xmid_point,wl_offset.y)
self.add_path("metal1", [contact_offset, wl_mid1, wl_mid2, wl_offset])
wl_offset = self.rbc_inst.get_pin(self.wl_list[0]).lc()
wl_mid1 = wl_offset - vector(1.5*drc("minwidth_metal1"), 0)
wl_mid2 = vector(wl_mid1.x, contact_offset.y)
#xmid_point= 0.5*(wl_offset.x+contact_offset.x)
#wl_mid1 = vector(xmid_point,contact_offset.y)
#wl_mid2 = vector(xmid_point,wl_offset.y)
self.add_path("metal1", [wl_offset, wl_mid1, wl_mid2, contact_offset])
# 4. Short wodlines if multiport
wl = self.wl_list[0]
wl_last = self.wl_list[-1]
pin = self.rbc_inst.get_pin(wl)
pin_last = self.rbc_inst.get_pin(wl_last)
x_offset = self.short_wordlines(pin, pin_last, "left", True)
#correct = vector(0.5*drc("minwidth_metal1"), 0)
#self.add_path("metal1", [pin.lc()+correct, pin_last.lc()+correct])
# DRAIN ROUTE
# Route the drain to the vdd rail
drain_offset = self.tx_inst.get_pin("D").center()
self.add_power_pin("vdd", drain_offset)
self.add_power_pin("vdd", drain_offset, rotate=0)
# SOURCE ROUTE
# Route the drain to the RBL inverter input
@ -226,7 +340,7 @@ class replica_bitline(design.design):
# Route the connection of the source route to the RBL bitline (left)
# Via will go halfway down from the bitcell
bl_offset = self.rbc_inst.get_pin("BL").bc()
bl_offset = self.rbc_inst.get_pin(self.bl_list[0]).bc()
# Route down a pitch so we can use M2 routing
bl_down_offset = bl_offset - vector(0, self.m2_pitch)
self.add_path("metal2",[source_offset, bl_down_offset, bl_offset])
@ -248,8 +362,6 @@ class replica_bitline(design.design):
self.copy_layout_pin(self.dc_inst,"vdd")
self.copy_layout_pin(self.rbc_inst,"vdd")
# Connect the WL and vdd pins directly to the center and right vdd rails
# Connect RBL vdd pins to center and right rails
rbl_vdd_pins = self.rbl_inst.get_pins("vdd")
@ -269,9 +381,6 @@ class replica_bitline(design.design):
offset=end,
rotate=90)
# Add via for the inverter
pin = self.rbl_inv_inst.get_pin("vdd")
start = vector(self.left_vdd_pin.cx(),pin.cy())
@ -355,7 +464,7 @@ class replica_bitline(design.design):
# Connect the WL and gnd pins directly to the center and right gnd rails
for row in range(self.bitcell_loads):
wl = "wl[{}]".format(row)
wl = self.wl_list[0]+"_{}".format(row)
pin = self.rbl_inst.get_pin(wl)
if pin.layer != "metal1":
continue

View File

@ -13,7 +13,7 @@ class sense_amp(design.design):
pin_names = ["bl", "br", "dout", "en", "vdd", "gnd"]
(width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"])
def __init__(self, name):
design.design.__init__(self, name)
@ -23,6 +23,14 @@ class sense_amp(design.design):
self.height = sense_amp.height
self.pin_map = sense_amp.pin_map
def input_load(self):
#Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
from tech import spice, parameter
# 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/parameter["min_tx_size"])#ff
def analytical_delay(self, slew, load=0.0):
from tech import spice
r = spice["min_tx_r"]/(10)

View File

@ -14,64 +14,81 @@ class sense_amp_array(design.design):
design.design.__init__(self, "sense_amp_array")
debug.info(1, "Creating {0}".format(self.name))
self.word_size = word_size
self.words_per_row = words_per_row
self.row_size = self.word_size * self.words_per_row
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_sense_amp_array()
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
else:
self.width = self.amp.width * self.word_size * self.words_per_row
self.place_sense_amp_array()
self.add_layout_pins()
self.route_rails()
self.DRC_LVS()
def add_pins(self):
for i in range(0,self.word_size):
self.add_pin("data_{0}".format(i))
self.add_pin("bl_{0}".format(i))
self.add_pin("br_{0}".format(i))
self.add_pin("en")
self.add_pin("vdd")
self.add_pin("gnd")
def add_modules(self):
from importlib import reload
c = reload(__import__(OPTS.sense_amp))
self.mod_sense_amp = getattr(c, OPTS.sense_amp)
self.amp = self.mod_sense_amp("sense_amp")
self.add_mod(self.amp)
self.word_size = word_size
self.words_per_row = words_per_row
self.row_size = self.word_size * self.words_per_row
self.height = self.amp.height
self.width = self.amp.width * self.word_size * self.words_per_row
self.add_pins()
self.create_layout()
self.DRC_LVS()
def add_pins(self):
for i in range(0,self.word_size):
self.add_pin("data[{0}]".format(i))
self.add_pin("bl[{0}]".format(i))
self.add_pin("br[{0}]".format(i))
self.add_pin("en")
self.add_pin("vdd")
self.add_pin("gnd")
def create_layout(self):
self.add_sense_amp()
self.connect_rails()
# This is just used for measurements,
# so don't add the module
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell = self.mod_bitcell()
def add_sense_amp(self):
bl_pin = self.amp.get_pin("bl")
br_pin = self.amp.get_pin("br")
dout_pin = self.amp.get_pin("dout")
amp_spacing = self.amp.width * self.words_per_row
def create_sense_amp_array(self):
self.local_insts = []
for i in range(0,self.word_size):
name = "sa_d{0}".format(i)
amp_position = vector(amp_spacing * i, 0)
bl_offset = amp_position + bl_pin.ll().scale(1,0)
br_offset = amp_position + br_pin.ll().scale(1,0)
dout_offset = amp_position + dout_pin.ll()
inst = self.add_inst(name=name,
mod=self.amp,
offset=amp_position)
self.connect_inst(["bl[{0}]".format(i),
"br[{0}]".format(i),
"data[{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"])
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)
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"),
@ -79,41 +96,47 @@ class sense_amp_array(design.design):
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),
self.add_layout_pin(text="bl_{0}".format(i),
layer="metal2",
offset=bl_offset,
offset=bl_pin.ll(),
width=bl_pin.width(),
height=bl_pin.height())
self.add_layout_pin(text="br[{0}]".format(i),
self.add_layout_pin(text="br_{0}".format(i),
layer="metal2",
offset=br_offset,
offset=br_pin.ll(),
width=br_pin.width(),
height=br_pin.height())
self.add_layout_pin(text="data[{0}]".format(i),
self.add_layout_pin(text="data_{0}".format(i),
layer="metal2",
offset=dout_offset,
offset=dout_pin.ll(),
width=dout_pin.width(),
height=dout_pin.height())
def connect_rails(self):
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"])
height=drc("minwidth_metal1"))
def input_load(self):
return self.amp.input_load()
def analytical_delay(self, slew, load=0.0):
return self.amp.analytical_delay(slew=slew, load=load)

View File

@ -6,7 +6,7 @@ from tech import drc
import debug
import math
from vector import vector
from globals import OPTS
class single_level_column_mux_array(design.design):
"""
@ -14,31 +14,31 @@ class single_level_column_mux_array(design.design):
Array of column mux to read the bitlines through the 6T.
"""
def __init__(self, columns, word_size):
design.design.__init__(self, "columnmux_array")
unique_id = 1
def __init__(self, columns, word_size, bitcell_bl="bl", bitcell_br="br"):
name="single_level_column_mux_array_{}".format(single_level_column_mux_array.unique_id)
single_level_column_mux_array.unique_id += 1
design.design.__init__(self, name)
debug.info(1, "Creating {0}".format(self.name))
self.columns = columns
self.word_size = word_size
self.words_per_row = int(self.columns / self.word_size)
self.add_pins()
self.create_layout()
self.DRC_LVS()
self.bitcell_bl = bitcell_bl
self.bitcell_br = bitcell_br
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def add_pins(self):
for i in range(self.columns):
self.add_pin("bl[{}]".format(i))
self.add_pin("br[{}]".format(i))
for i in range(self.words_per_row):
self.add_pin("sel[{}]".format(i))
for i in range(self.word_size):
self.add_pin("bl_out[{}]".format(i))
self.add_pin("br_out[{}]".format(i))
self.add_pin("gnd")
def create_layout(self):
def create_netlist(self):
self.add_modules()
self.setup_layout_constants()
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()
@ -46,11 +46,22 @@ class single_level_column_mux_array(design.design):
self.add_layout_pins()
self.add_enclosure(self.mux_inst, "pwell")
self.DRC_LVS()
def add_pins(self):
for i in range(self.columns):
self.add_pin("bl_{}".format(i))
self.add_pin("br_{}".format(i))
for i in range(self.words_per_row):
self.add_pin("sel_{}".format(i))
for i in range(self.word_size):
self.add_pin("bl_out_{}".format(i))
self.add_pin("br_out_{}".format(i))
self.add_pin("gnd")
def add_modules(self):
# FIXME: Why is this 8x?
self.mux = single_level_column_mux(tx_size=8)
self.mux = single_level_column_mux(bitcell_bl=self.bitcell_bl, bitcell_br=self.bitcell_br)
self.add_mod(self.mux)
@ -65,22 +76,26 @@ class single_level_column_mux_array(design.design):
def create_array(self):
self.mux_inst = []
# For every column, add a pass gate
for col_num in range(self.columns):
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)),
"sel_{}".format(col_num % self.words_per_row),
"gnd"])
def place_array(self):
# 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.append(self.add_inst(name=name,
mod=self.mux,
offset=x_off))
self.mux_inst[col_num].place(x_off)
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)),
"sel[{}]".format(col_num % self.words_per_row),
"gnd"])
def add_layout_pins(self):
""" Add the pins after we determine the height. """
@ -88,13 +103,13 @@ class single_level_column_mux_array(design.design):
for col_num in range(self.columns):
mux_inst = self.mux_inst[col_num]
offset = mux_inst.get_pin("bl").ll()
self.add_layout_pin(text="bl[{}]".format(col_num),
self.add_layout_pin(text="bl_{}".format(col_num),
layer="metal2",
offset=offset,
height=self.height-offset.y)
offset = mux_inst.get_pin("br").ll()
self.add_layout_pin(text="br[{}]".format(col_num),
self.add_layout_pin(text="br_{}".format(col_num),
layer="metal2",
offset=offset,
height=self.height-offset.y)
@ -112,7 +127,7 @@ class single_level_column_mux_array(design.design):
""" Create address input rails on M1 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)
self.add_layout_pin(text="sel[{}]".format(j),
self.add_layout_pin(text="sel_{}".format(j),
layer="metal1",
offset=offset,
width=self.mux.width * self.columns,
@ -128,9 +143,9 @@ 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())
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,
@ -154,23 +169,23 @@ class single_level_column_mux_array(design.design):
self.add_rect(layer="metal1",
offset=bl_out_offset,
width=width,
height=drc["minwidth_metal2"])
height=drc("minwidth_metal2"))
self.add_rect(layer="metal1",
offset=br_out_offset,
width=width,
height=drc["minwidth_metal2"])
height=drc("minwidth_metal2"))
# Extend the bitline output rails and gnd downward on the first bit of each n-way mux
self.add_layout_pin(text="bl_out[{}]".format(int(j/self.words_per_row)),
self.add_layout_pin(text="bl_out_{}".format(int(j/self.words_per_row)),
layer="metal2",
offset=bl_out_offset.scale(1,0),
width=drc['minwidth_metal2'],
width=drc('minwidth_metal2'),
height=self.route_height)
self.add_layout_pin(text="br_out[{}]".format(int(j/self.words_per_row)),
self.add_layout_pin(text="br_out_{}".format(int(j/self.words_per_row)),
layer="metal2",
offset=br_out_offset.scale(1,0),
width=drc['minwidth_metal2'],
width=drc('minwidth_metal2'),
height=self.route_height)
# This via is on the right of the wire
@ -186,7 +201,7 @@ class single_level_column_mux_array(design.design):
self.add_rect(layer="metal2",
offset=bl_out_offset,
width=drc['minwidth_metal2'],
width=drc('minwidth_metal2'),
height=self.route_height-bl_out_offset.y)
# This via is on the right of the wire
self.add_via(layers=("metal1", "via1", "metal2"),
@ -194,12 +209,20 @@ class single_level_column_mux_array(design.design):
rotate=90)
self.add_rect(layer="metal2",
offset=br_out_offset,
width=drc['minwidth_metal2'],
width=drc('minwidth_metal2'),
height=self.route_height-br_out_offset.y)
# This via is on the left of the wire
self.add_via(layers=("metal1", "via1", "metal2"),
offset= br_out_offset,
rotate=90)
def analytical_delay(self, vdd, slew, load=0.0):
from tech import spice, parameter
r = spice["min_tx_r"]/(self.mux.ptx_width/parameter["min_tx_size"])
#Drains of mux transistors make up capacitance.
c_para = spice["min_tx_drain_c"]*(self.mux.ptx_width/parameter["min_tx_size"])*self.words_per_row#ff
volt_swing = spice["v_threshold_typical"]/vdd
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = volt_swing)
return self.return_delay(result.delay, result.slew)

View File

@ -12,7 +12,7 @@ class tri_gate(design.design):
pin_names = ["in", "en", "en_bar", "out", "gnd", "vdd"]
(width,height) = utils.get_libcell_size("tri_gate", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"])
unique_id = 1

View File

@ -14,34 +14,40 @@ class tri_gate_array(design.design):
design.design.__init__(self, "tri_gate_array")
debug.info(1, "Creating {0}".format(self.name))
self.columns = columns
self.word_size = word_size
self.words_per_row = int(self.columns / self.word_size)
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_array()
def create_layout(self):
self.width = (self.columns / self.words_per_row) * self.tri.width
self.height = self.tri.height
self.place_array()
self.add_layout_pins()
self.DRC_LVS()
def add_modules(self):
from importlib import reload
c = reload(__import__(OPTS.tri_gate))
self.mod_tri_gate = getattr(c, OPTS.tri_gate)
self.tri = self.mod_tri_gate("tri_gate")
self.add_mod(self.tri)
self.columns = columns
self.word_size = word_size
self.words_per_row = int(self.columns / self.word_size)
self.width = (self.columns / self.words_per_row) * self.tri.width
self.height = self.tri.height
self.create_layout()
self.DRC_LVS()
def create_layout(self):
"""generate layout """
self.add_pins()
self.create_array()
self.add_layout_pins()
def add_pins(self):
"""create the name of pins depend on the word size"""
for i in range(self.word_size):
self.add_pin("in[{0}]".format(i))
self.add_pin("in_{0}".format(i))
for i in range(self.word_size):
self.add_pin("out[{0}]".format(i))
self.add_pin("out_{0}".format(i))
for pin in ["en", "en_bar", "vdd", "gnd"]:
self.add_pin(pin)
@ -50,15 +56,19 @@ class tri_gate_array(design.design):
self.tri_inst = {}
for i in range(0,self.columns,self.words_per_row):
name = "Xtri_gate{0}".format(i)
base = vector(i*self.tri.width, 0)
self.tri_inst[i]=self.add_inst(name=name,
mod=self.tri,
offset=base)
mod=self.tri)
index = int(i/self.words_per_row)
self.connect_inst(["in[{0}]".format(index),
"out[{0}]".format(index),
self.connect_inst(["in_{0}".format(index),
"out_{0}".format(index),
"en", "en_bar", "vdd", "gnd"])
def place_array(self):
""" Place the tri gate to the array """
for i in range(0,self.columns,self.words_per_row):
base = vector(i*self.tri.width, 0)
self.tri_inst[i].place(base)
def add_layout_pins(self):
@ -66,14 +76,14 @@ class tri_gate_array(design.design):
index = int(i/self.words_per_row)
in_pin = self.tri_inst[i].get_pin("in")
self.add_layout_pin(text="in[{0}]".format(index),
self.add_layout_pin(text="in_{0}".format(index),
layer="metal2",
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),
self.add_layout_pin(text="out_{0}".format(index),
layer="metal2",
offset=out_pin.ll(),
width=out_pin.width(),
@ -97,14 +107,14 @@ class tri_gate_array(design.design):
layer="metal1",
offset=en_pin.ll().scale(0, 1),
width=width,
height=drc["minwidth_metal1"])
height=drc("minwidth_metal1"))
enbar_pin = self.tri_inst[0].get_pin("en_bar")
self.add_layout_pin(text="en_bar",
layer="metal1",
offset=enbar_pin.ll().scale(0, 1),
width=width,
height=drc["minwidth_metal1"])
height=drc("minwidth_metal1"))

View File

@ -20,30 +20,39 @@ class wordline_driver(design.design):
design.design.__init__(self, "wordline_driver")
self.rows = rows
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.design_layout()
self.create_drivers()
def create_layout(self):
self.place_drivers()
self.route_layout()
self.route_vdd_gnd()
self.offset_all_coordinates()
self.DRC_LVS()
def add_pins(self):
# inputs to wordline_driver.
for i in range(self.rows):
self.add_pin("in[{0}]".format(i))
self.add_pin("in_{0}".format(i))
# Outputs from wordline_driver.
for i in range(self.rows):
self.add_pin("wl[{0}]".format(i))
self.add_pin("wl_{0}".format(i))
self.add_pin("en")
self.add_pin("vdd")
self.add_pin("gnd")
def design_layout(self):
self.create_modules()
self.add_modules()
self.route_layout()
self.route_vdd_gnd()
def create_modules(self):
def add_modules(self):
# This is just used for measurements,
# so don't add the module
self.inv = pinv()
self.add_mod(self.inv)
@ -52,6 +61,7 @@ class wordline_driver(design.design):
self.nand2 = 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. """
@ -84,15 +94,7 @@ class wordline_driver(design.design):
def add_modules(self):
inv1_xoffset = 2*self.m1_width + 5*self.m1_space
nand2_xoffset = inv1_xoffset + self.inv.width
inv2_xoffset = nand2_xoffset + self.nand2.width
self.width = inv2_xoffset + self.inv.width
self.height = self.inv.height * self.rows
def create_drivers(self):
self.inv1_inst = []
self.nand_inst = []
self.inv2_inst = []
@ -101,11 +103,42 @@ class wordline_driver(design.design):
name_nand = "wl_driver_nand{}".format(row)
name_inv2 = "wl_driver_inv{}".format(row)
# add inv1 based on the info above
self.inv1_inst.append(self.add_inst(name=name_inv1,
mod=self.inv_no_output))
self.connect_inst(["en",
"en_bar_{0}".format(row),
"vdd", "gnd"])
# add nand 2
self.nand_inst.append(self.add_inst(name=name_nand,
mod=self.nand2))
self.connect_inst(["en_bar_{0}".format(row),
"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):
inv1_xoffset = 2*self.m1_width + 5*self.m1_space
nand2_xoffset = inv1_xoffset + self.inv.width
inv2_xoffset = nand2_xoffset + self.nand2.width
self.width = inv2_xoffset + self.inv.height
driver_height = self.inv.height
self.height = self.inv.height * self.rows
for row in range(self.rows):
if (row % 2):
y_offset = self.inv.height*(row + 1)
y_offset = driver_height*(row + 1)
inst_mirror = "MX"
else:
y_offset = self.inv.height*row
y_offset = driver_height*row
inst_mirror = "R0"
inv1_offset = [inv1_xoffset, y_offset]
@ -113,30 +146,14 @@ class wordline_driver(design.design):
inv2_offset=[inv2_xoffset, y_offset]
# add inv1 based on the info above
self.inv1_inst.append(self.add_inst(name=name_inv1,
mod=self.inv_no_output,
offset=inv1_offset,
mirror=inst_mirror))
self.connect_inst(["en",
"en_bar[{0}]".format(row),
"vdd", "gnd"])
self.inv1_inst[row].place(offset=inv1_offset,
mirror=inst_mirror)
# add nand 2
self.nand_inst.append(self.add_inst(name=name_nand,
mod=self.nand2,
offset=nand2_offset,
mirror=inst_mirror))
self.connect_inst(["en_bar[{0}]".format(row),
"in[{0}]".format(row),
"wl_bar[{0}]".format(row),
"vdd", "gnd"])
self.nand_inst[row].place(offset=nand2_offset,
mirror=inst_mirror)
# add inv2
self.inv2_inst.append(self.add_inst(name=name_inv2,
mod=self.inv,
offset=inv2_offset,
mirror=inst_mirror))
self.connect_inst(["wl_bar[{0}]".format(row),
"wl[{0}]".format(row),
"vdd", "gnd"])
self.inv2_inst[row].place(offset=inv2_offset,
mirror=inst_mirror)
def route_layout(self):
@ -188,7 +205,7 @@ class wordline_driver(design.design):
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),
self.add_layout_pin_segment_center(text="in_{0}".format(row),
layer="metal1",
start=input_offset,
end=mid_via_offset)
@ -204,7 +221,7 @@ class wordline_driver(design.design):
# 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),
self.add_layout_pin_segment_center(text="wl_{0}".format(row),
layer="metal1",
start=wl_offset,
end=wl_offset-vector(self.m1_width,0))

View File

@ -13,7 +13,7 @@ class write_driver(design.design):
pin_names = ["din", "bl", "br", "en", "gnd", "vdd"]
(width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"])
def __init__(self, name):
design.design.__init__(self, name)

View File

@ -15,71 +15,99 @@ class write_driver_array(design.design):
design.design.__init__(self, "write_driver_array")
debug.info(1, "Creating {0}".format(self.name))
self.columns = columns
self.word_size = word_size
self.words_per_row = int(columns / word_size)
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_write_array()
def create_layout(self):
if self.bitcell.width > self.driver.width:
self.width = self.columns * self.bitcell.width
else:
self.width = self.columns * self.driver.width
self.height = self.driver.height
self.place_write_array()
self.add_layout_pins()
self.DRC_LVS()
def add_pins(self):
for i in range(self.word_size):
self.add_pin("data_{0}".format(i))
for i in range(self.word_size):
self.add_pin("bl_{0}".format(i))
self.add_pin("br_{0}".format(i))
self.add_pin("en")
self.add_pin("vdd")
self.add_pin("gnd")
def add_modules(self):
from importlib import reload
c = reload(__import__(OPTS.write_driver))
self.mod_write_driver = getattr(c, OPTS.write_driver)
self.driver = self.mod_write_driver("write_driver")
self.add_mod(self.driver)
self.columns = columns
self.word_size = word_size
self.words_per_row = int(columns / word_size)
self.width = self.columns * self.driver.width
self.height = self.height = self.driver.height
self.add_pins()
self.create_layout()
self.DRC_LVS()
def add_pins(self):
for i in range(self.word_size):
self.add_pin("data[{0}]".format(i))
for i in range(self.word_size):
self.add_pin("bl[{0}]".format(i))
self.add_pin("br[{0}]".format(i))
self.add_pin("en")
self.add_pin("vdd")
self.add_pin("gnd")
def create_layout(self):
self.create_write_array()
self.add_layout_pins()
# This is just used for measurements,
# so don't add the module
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell = self.mod_bitcell()
def create_write_array(self):
self.driver_insts = {}
for i in range(0,self.columns,self.words_per_row):
name = "Xwrite_driver{}".format(i)
base = vector(i * self.driver.width,0)
index = int(i/self.words_per_row)
self.driver_insts[index]=self.add_inst(name=name,
mod=self.driver,
offset=base)
mod=self.driver)
self.connect_inst(["data[{0}]".format(index),
"bl[{0}]".format(index),
"br[{0}]".format(index),
self.connect_inst(["data_{0}".format(index),
"bl_{0}".format(index),
"br_{0}".format(index),
"en", "vdd", "gnd"])
def place_write_array(self):
if self.bitcell.width > self.driver.width:
driver_spacing = self.bitcell.width
else:
driver_spacing = self.driver.width
for i in range(0,self.columns,self.words_per_row):
index = int(i/self.words_per_row)
base = vector(i * driver_spacing,0)
self.driver_insts[index].place(base)
def add_layout_pins(self):
for i in range(self.word_size):
din_pin = self.driver_insts[i].get_pin("din")
self.add_layout_pin(text="data[{0}]".format(i),
self.add_layout_pin(text="data_{0}".format(i),
layer="metal2",
offset=din_pin.ll(),
width=din_pin.width(),
height=din_pin.height())
bl_pin = self.driver_insts[i].get_pin("bl")
self.add_layout_pin(text="bl[{0}]".format(i),
self.add_layout_pin(text="bl_{0}".format(i),
layer="metal2",
offset=bl_pin.ll(),
width=bl_pin.width(),
height=bl_pin.height())
br_pin = self.driver_insts[i].get_pin("br")
self.add_layout_pin(text="br[{0}]".format(i),
self.add_layout_pin(text="br_{0}".format(i),
layer="metal2",
offset=br_pin.ll(),
width=br_pin.width(),
@ -102,7 +130,7 @@ class write_driver_array(design.design):
layer="metal1",
offset=self.driver_insts[0].get_pin("en").ll().scale(0,1),
width=self.width,
height=drc['minwidth_metal1'])
height=drc('minwidth_metal1'))

View File

@ -7,7 +7,6 @@ a spice (.sp) file for circuit simulation
a GDS2 (.gds) file containing the layout
a LEF (.lef) file for preliminary P&R (real one should be from layout)
a Liberty (.lib) file for timing analysis/optimization
"""
import sys,os
@ -27,7 +26,6 @@ if len(args) != 1:
# These depend on arguments, so don't load them until now.
import debug
init_openram(config_file=args[0], is_unit_test=False)
# Only print banner here so it's not in unit tests
@ -38,9 +36,15 @@ report_status()
# Start importing design modules after we have the config file
import verify
import sram
output_files = ["{0}.{1}".format(OPTS.output_name,x) for x in ["sp","gds","v","lib","lef"]]
from sram import sram
from sram_config import sram_config
#from parser import *
output_extensions = ["sp","v","lib"]
if OPTS.datasheet_gen:
output_extensions.append("html")
if not OPTS.netlist_only:
output_extensions.extend(["gds","lef"])
output_files = ["{0}.{1}".format(OPTS.output_name,x) for x in output_extensions]
print("Output files are: ")
print(*output_files,sep="\n")
@ -48,11 +52,13 @@ print(*output_files,sep="\n")
start_time = datetime.datetime.now()
print_time("Start",start_time)
# Configure the SRAM organization
c = sram_config(word_size=OPTS.word_size,
num_words=OPTS.num_words)
# import SRAM test generation
s = sram.sram(word_size=OPTS.word_size,
num_words=OPTS.num_words,
num_banks=OPTS.num_banks,
name=OPTS.output_name)
s = sram(sram_config=c,
name=OPTS.output_name)
# Output the files for the resulting SRAM
s.save()

View File

@ -18,18 +18,24 @@ class options(optparse.Values):
# This is the verbosity level to control debug information. 0 is none, 1
# is minimal, etc.
debug_level = 0
# When enabled, layout is not generated (and no DRC or LVS are performed)
netlist_only = False
# This determines whether LVS and DRC is checked for each submodule.
check_lvsdrc = True
# Variable to select the variant of spice
spice_name = ""
# Should we print out the banner at startup
print_banner = True
# The spice executable being used which is derived from the user PATH.
spice_exe = ""
# Variable to select the variant of drc, lvs, pex
drc_name = ""
lvs_name = ""
pex_name = ""
# The DRC/LVS/PEX executable being used which is derived from the user PATH.
drc_exe = None
lvs_exe = None
pex_exe = None
# The spice executable being used which is derived from the user PATH.
spice_exe = ""
# Should we print out the banner at startup
print_banner = True
# Run with extracted parasitics
use_pex = False
# Remove noncritical memory cells for characterization speed-up
@ -46,19 +52,26 @@ class options(optparse.Values):
purge_temp = True
# These are the configuration parameters
rw_ports = 1
r_ports = 0
w_ports = 0
num_rw_ports = 1
num_r_ports = 0
num_w_ports = 0
# These will get initialized by the the file
supply_voltages = ""
temperatures = ""
process_corners = ""
# These are the main configuration parameters that should be over-ridden
# in a configuration file.
#num_words = 0
#word_size = 0
# You can manually specify banks, but it is better to auto-detect it.
num_banks = 1
# These are the default modules that can be over-riden
decoder = "hierarchical_decoder"
ms_flop = "ms_flop"
ms_flop_array = "ms_flop_array"
dff_array = "dff_array"
dff = "dff"
control_logic = "control_logic"
bitcell_array = "bitcell_array"

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
import contact
import design
import debug
from tech import drc, parameter, spice, info
from tech import drc, parameter, spice
from ptx import ptx
from vector import vector
from globals import OPTS
@ -11,10 +11,19 @@ class pgate(design.design):
This is a module that implements some shared functions for parameterized gates.
"""
def __init__(self, name):
def __init__(self, name, height=None):
""" Creates a generic cell """
design.design.__init__(self, name)
if height:
self.height = height
elif not height:
from importlib import reload
c = reload(__import__(OPTS.bitcell))
bitcell = getattr(c, OPTS.bitcell)
b = bitcell()
self.height = b.height
def connect_pin_to_rail(self,inst,pin,supply):
""" Conencts a ptx pin to a supply rail. """
@ -101,7 +110,7 @@ class pgate(design.design):
max_y_offset = self.height + 0.5*self.m1_width
self.nwell_position = middle_position
nwell_height = max_y_offset - middle_position.y
if info["has_nwell"]:
if drc("has_nwell"):
self.add_rect(layer="nwell",
offset=middle_position,
width=self.well_width,
@ -113,7 +122,7 @@ class pgate(design.design):
pwell_position = vector(0,-0.5*self.m1_width)
pwell_height = middle_position.y-pwell_position.y
if info["has_pwell"]:
if drc("has_pwell"):
self.add_rect(layer="pwell",
offset=pwell_position,
width=self.well_width,
@ -129,7 +138,7 @@ class pgate(design.design):
layer_stack = ("active", "contact", "metal1")
# To the right a spacing away from the pmos right active edge
contact_xoffset = pmos_pos.x + pmos.active_width + drc["active_to_body_active"]
contact_xoffset = pmos_pos.x + pmos.active_width + drc("active_to_body_active")
# Must be at least an well enclosure of active down from the top of the well
# OR align the active with the top of PMOS active.
max_y_offset = self.height + 0.5*self.m1_width
@ -176,7 +185,7 @@ class pgate(design.design):
pwell_position = vector(0,-0.5*self.m1_width)
# To the right a spacing away from the nmos right active edge
contact_xoffset = nmos_pos.x + nmos.active_width + drc["active_to_body_active"]
contact_xoffset = nmos_pos.x + nmos.active_width + drc("active_to_body_active")
# Must be at least an well enclosure of active up from the bottom of the well
contact_yoffset = max(nmos_pos.y,
self.well_enclose_active - nmos.active_contact.first_layer_height/2)

View File

@ -1,7 +1,7 @@
import contact
import pgate
import debug
from tech import drc, parameter, spice, info
from tech import drc, parameter, spice
from ptx import ptx
from vector import vector
from math import ceil
@ -17,74 +17,84 @@ class pinv(pgate.pgate):
from center of rail to rail.. The route_output will route the
output to the right side of the cell for easier access.
"""
from importlib import reload
c = reload(__import__(OPTS.bitcell))
bitcell = getattr(c, OPTS.bitcell)
unique_id = 1
def __init__(self, size=1, beta=parameter["beta"], height=bitcell.height, route_output=True):
def __init__(self, size=1, beta=parameter["beta"], height=None, route_output=True):
# We need to keep unique names because outputting to GDSII
# will use the last record with a given name. I.e., you will
# over-write a design in GDS if one has and the other doesn't
# have poly connected, for example.
name = "pinv_{}".format(pinv.unique_id)
pinv.unique_id += 1
pgate.pgate.__init__(self, name)
pgate.pgate.__init__(self, name, height)
debug.info(2, "create pinv structure {0} with size of {1}".format(name, size))
self.nmos_size = size
self.pmos_size = beta*size
self.beta = beta
self.height = height # Maybe minimize height if not defined in future?
self.route_output = False
self.add_pins()
self.create_layout()
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
# for run-time, we won't check every transitor DRC/LVS independently
# but this may be uncommented for debug purposes
#self.DRC_LVS()
def add_pins(self):
""" Adds pins for spice netlist """
self.add_pin_list(["A", "Z", "vdd", "gnd"])
def create_netlist(self):
""" Calls all functions related to the generation of the netlist """
self.add_pins()
self.determine_tx_mults()
self.add_ptx()
self.create_ptx()
def create_layout(self):
""" Calls all functions related to the generation of the layout """
self.determine_tx_mults()
self.create_ptx()
self.setup_layout_constants()
self.add_supply_rails()
self.add_ptx()
self.route_supply_rails()
self.place_ptx()
self.add_well_contacts()
self.extend_wells(self.well_pos)
self.connect_rails()
self.route_input_gate(self.pmos_inst, self.nmos_inst, self.output_pos.y, "A", rotate=0)
self.route_outputs()
def add_pins(self):
""" Adds pins for spice netlist """
self.add_pin_list(["A", "Z", "vdd", "gnd"])
def determine_tx_mults(self):
"""
Determines the number of fingers needed to achieve the size within
the height constraint. This may fail if the user has a tight height.
"""
# This may make the result differ when the layout is created...
if OPTS.netlist_only:
self.tx_mults = 1
self.nmos_width = self.nmos_size*drc("minwidth_tx")
self.pmos_width = self.pmos_size*drc("minwidth_tx")
return
# Do a quick sanity check and bail if unlikely feasible height
# Sanity check. can we make an inverter in the height with minimum tx sizes?
# Assume we need 3 metal 1 pitches (2 power rails, one between the tx for the drain)
# plus the tx height
nmos = ptx(tx_type="nmos")
pmos = ptx(width=drc["minwidth_tx"], tx_type="pmos")
pmos = ptx(width=drc("minwidth_tx"), tx_type="pmos")
tx_height = nmos.poly_height + pmos.poly_height
# rotated m1 pitch or poly to active spacing
min_channel = max(contact.poly.width + self.m1_space,
contact.poly.width + 2*drc["poly_to_active"])
contact.poly.width + 2*drc("poly_to_active"))
# This is the extra space needed to ensure DRC rules to the active contacts
extra_contact_space = max(-nmos.get_pin("D").by(),0)
# This is a poly-to-poly of a flipped cell
self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space,
drc["poly_extend_active"], self.poly_space)
drc("poly_extend_active"), self.poly_space)
total_height = tx_height + min_channel + 2*self.top_bottom_space
debug.check(self.height> total_height,"Cell height {0} too small for simple min height {1}.".format(self.height,total_height))
@ -93,16 +103,16 @@ class pinv(pgate.pgate):
# Divide the height in half. Could divide proportional to beta, but this makes
# connecting wells of multiple cells easier.
# Subtract the poly space under the rail of the tx
nmos_height_available = 0.5 * tx_height_available - 0.5*drc["poly_to_poly"]
pmos_height_available = 0.5 * tx_height_available - 0.5*drc["poly_to_poly"]
nmos_height_available = 0.5 * tx_height_available - 0.5*drc("poly_to_poly")
pmos_height_available = 0.5 * tx_height_available - 0.5*drc("poly_to_poly")
debug.info(2,"Height avail {0:.4f} PMOS {1:.4f} NMOS {2:.4f}".format(tx_height_available,
nmos_height_available,
pmos_height_available))
# Determine the number of mults for each to fit width into available space
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
self.nmos_width = self.nmos_size*drc("minwidth_tx")
self.pmos_width = self.pmos_size*drc("minwidth_tx")
nmos_required_mults = max(int(ceil(self.nmos_width/nmos_height_available)),1)
pmos_required_mults = max(int(ceil(self.pmos_width/pmos_height_available)),1)
# The mults must be the same for easy connection of poly
@ -114,9 +124,9 @@ class pinv(pgate.pgate):
# We also need to round the width to the grid or we will end up with LVS property
# mismatch errors when fingers are not a grid length and get rounded in the offset geometry.
self.nmos_width = round_to_grid(self.nmos_width / self.tx_mults)
debug.check(self.nmos_width>=drc["minwidth_tx"],"Cannot finger NMOS transistors to fit cell height.")
debug.check(self.nmos_width>=drc("minwidth_tx"),"Cannot finger NMOS transistors to fit cell height.")
self.pmos_width = round_to_grid(self.pmos_width / self.tx_mults)
debug.check(self.pmos_width>=drc["minwidth_tx"],"Cannot finger PMOS transistors to fit cell height.")
debug.check(self.pmos_width>=drc("minwidth_tx"),"Cannot finger PMOS transistors to fit cell height.")
def setup_layout_constants(self):
@ -127,13 +137,13 @@ class pinv(pgate.pgate):
# the well width is determined the multi-finger PMOS device width plus
# the well contact width and half well enclosure on both sides
self.well_width = self.pmos.active_width + self.pmos.active_contact.width \
+ drc["active_to_body_active"] + 2*drc["well_enclosure_active"]
+ drc("active_to_body_active") + 2*drc("well_enclosure_active")
self.width = self.well_width
# Height is an input parameter, so it is not recomputed.
def create_ptx(self):
def add_ptx(self):
""" Create the PMOS and NMOS transistors. """
self.nmos = ptx(width=self.nmos_width,
mults=self.tx_mults,
@ -149,7 +159,7 @@ class pinv(pgate.pgate):
connect_active=True)
self.add_mod(self.pmos)
def add_supply_rails(self):
def route_supply_rails(self):
""" Add vdd/gnd rails to the top and bottom. """
self.add_layout_pin_rect_center(text="gnd",
layer="metal1",
@ -161,26 +171,36 @@ class pinv(pgate.pgate):
offset=vector(0.5*self.width,self.height),
width=self.width)
def add_ptx(self):
def create_ptx(self):
"""
Add PMOS and NMOS to the layout at the upper-most and lowest position
Create the PMOS and NMOS netlist.
"""
self.pmos_inst=self.add_inst(name="pinv_pmos",
mod=self.pmos)
self.connect_inst(["Z", "A", "vdd", "vdd"])
self.nmos_inst=self.add_inst(name="pinv_nmos",
mod=self.nmos)
self.connect_inst(["Z", "A", "gnd", "gnd"])
def place_ptx(self):
"""
Place PMOS and NMOS to the layout at the upper-most and lowest position
to provide maximum routing in channel
"""
# place PMOS so it is half a poly spacing down from the top
self.pmos_pos = self.pmos.active_offset.scale(1,0) \
+ vector(0, self.height-self.pmos.active_height-self.top_bottom_space)
self.pmos_inst=self.add_inst(name="pinv_pmos",
mod=self.pmos,
offset=self.pmos_pos)
self.connect_inst(["Z", "A", "vdd", "vdd"])
self.pmos_inst.place(self.pmos_pos)
# place NMOS so that it is half a poly spacing up from the bottom
self.nmos_pos = self.nmos.active_offset.scale(1,0) + vector(0,self.top_bottom_space)
self.nmos_inst=self.add_inst(name="pinv_nmos",
mod=self.nmos,
offset=self.nmos_pos)
self.connect_inst(["Z", "A", "gnd", "gnd"])
self.nmos_inst.place(self.nmos_pos)
# Output position will be in between the PMOS and NMOS drains
@ -259,5 +279,5 @@ class pinv(pgate.pgate):
"""Computes effective capacitance. Results in fF"""
c_load = load
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
transistion_prob = spice["inv_transisition_prob"]
return transistion_prob*(c_load + c_para)
transition_prob = spice["inv_transition_prob"]
return transition_prob*(c_load + c_para)

View File

@ -11,55 +11,48 @@ class pinvbuf(design.design):
This is a simple inverter/buffer used for driving loads. It is
used in the column decoder for 1:2 decoding and as the clock buffer.
"""
from importlib import reload
c = reload(__import__(OPTS.bitcell))
bitcell = getattr(c, OPTS.bitcell)
unique_id = 1
def __init__(self, driver_size=4, height=bitcell.height, name=""):
def __init__(self, driver_size=4, height=None, name=""):
stage_effort = 4
self.stage_effort = 4
self.row_height = height
# FIXME: Change the number of stages to support high drives.
# stage effort of 4 or less
# The pinvbuf has a FO of 2 for the first stage, so the second stage
# should be sized "half" to prevent loading of the first stage
predriver_size = max(int(driver_size/(stage_effort/2)),1)
self.driver_size = driver_size
self.predriver_size = max(int(self.driver_size/(self.stage_effort/2)),1)
if name=="":
name = "pinvbuf_{0}_{1}_{2}".format(predriver_size, driver_size, pinvbuf.unique_id)
name = "pinvbuf_{0}_{1}_{2}".format(self.predriver_size, self.driver_size, pinvbuf.unique_id)
pinvbuf.unique_id += 1
design.design.__init__(self, name)
design.design.__init__(self, name)
debug.info(1, "Creating {}".format(self.name))
# Shield the cap, but have at least a stage effort of 4
input_size = max(1,int(predriver_size/stage_effort))
self.inv = pinv(size=input_size, height=height)
self.add_mod(self.inv)
self.inv1 = pinv(size=predriver_size, height=height)
self.add_mod(self.inv1)
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
self.inv2 = pinv(size=driver_size, height=height)
self.add_mod(self.inv2)
def create_netlist(self):
self.add_pins()
self.add_modules()
self.create_insts()
def create_layout(self):
self.width = 2*self.inv1.width + self.inv2.width
self.height = 2*self.inv1.height
self.create_layout()
self.place_modules()
self.route_wires()
self.add_layout_pins()
self.offset_all_coordinates()
self.DRC_LVS()
def create_layout(self):
self.add_pins()
self.add_insts()
self.add_wires()
self.add_layout_pins()
def add_pins(self):
self.add_pin("A")
@ -68,35 +61,54 @@ class pinvbuf(design.design):
self.add_pin("vdd")
self.add_pin("gnd")
def add_insts(self):
# Add INV1 to the right (capacitance shield)
def add_modules(self):
# Shield the cap, but have at least a stage effort of 4
input_size = max(1,int(self.predriver_size/self.stage_effort))
self.inv = pinv(size=input_size, height=self.row_height)
self.add_mod(self.inv)
self.inv1 = pinv(size=self.predriver_size, height=self.row_height)
self.add_mod(self.inv1)
self.inv2 = pinv(size=self.driver_size, height=self.row_height)
self.add_mod(self.inv2)
def create_insts(self):
# Create INV1 (capacitance shield)
self.inv1_inst=self.add_inst(name="buf_inv1",
mod=self.inv,
offset=vector(0,0))
mod=self.inv)
self.connect_inst(["A", "zb_int", "vdd", "gnd"])
# Add INV2 to the right
self.inv2_inst=self.add_inst(name="buf_inv2",
mod=self.inv1,
offset=vector(self.inv1_inst.rx(),0))
mod=self.inv1)
self.connect_inst(["zb_int", "z_int", "vdd", "gnd"])
# Add INV3 to the right
self.inv3_inst=self.add_inst(name="buf_inv3",
mod=self.inv2,
offset=vector(self.inv2_inst.rx(),0))
mod=self.inv2)
self.connect_inst(["z_int", "Zb", "vdd", "gnd"])
# Add INV4 to the bottom
self.inv4_inst=self.add_inst(name="buf_inv4",
mod=self.inv2,
offset=vector(self.inv2_inst.rx(),2*self.inv2.height),
mirror = "MX")
mod=self.inv2)
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
def place_modules(self):
# Add INV1 to the left (capacitance shield)
self.inv1_inst.place(vector(0,0))
# Add INV2 to the right of INV1
self.inv2_inst.place(vector(self.inv1_inst.rx(),0))
# Add INV3 to the right of INV2
self.inv3_inst.place(vector(self.inv2_inst.rx(),0))
# Add INV4 flipped to the bottom aligned with INV2
self.inv4_inst.place(offset=vector(self.inv2_inst.rx(),2*self.inv2.height),
mirror = "MX")
def add_wires(self):
def route_wires(self):
# inv1 Z to inv2 A
z1_pin = self.inv1_inst.get_pin("Z")
a2_pin = self.inv2_inst.get_pin("A")

View File

@ -12,52 +12,53 @@ class pnand2(pgate.pgate):
This model use ptx to generate a 2-input nand within a cetrain height.
"""
from importlib import reload
c = reload(__import__(OPTS.bitcell))
bitcell = getattr(c, OPTS.bitcell)
unique_id = 1
def __init__(self, size=1, height=bitcell.height):
def __init__(self, size=1, height=None):
""" Creates a cell for a simple 2 input nand """
name = "pnand2_{0}".format(pnand2.unique_id)
pnand2.unique_id += 1
pgate.pgate.__init__(self, name)
pgate.pgate.__init__(self, name, height)
debug.info(2, "create pnand2 structure {0} with size of {1}".format(name, 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"]
self.height = height
self.nmos_width = self.nmos_size*drc("minwidth_tx")
self.pmos_width = self.pmos_size*drc("minwidth_tx")
# FIXME: Allow these to be sized
debug.check(size==1,"Size 1 pnand2 is only supported now.")
self.tx_mults = 1
self.add_pins()
self.create_layout()
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
#self.DRC_LVS()
def add_pins(self):
""" Adds pins for spice netlist """
self.add_pin_list(["A", "B", "Z", "vdd", "gnd"])
def create_netlist(self):
self.add_pins()
self.add_ptx()
self.create_ptx()
def create_layout(self):
""" Calls all functions related to the generation of the layout """
self.create_ptx()
self.setup_layout_constants()
self.add_supply_rails()
self.add_ptx()
self.route_supply_rails()
self.place_ptx()
self.connect_rails()
self.add_well_contacts()
self.extend_wells(self.well_pos)
self.route_inputs()
self.route_output()
def create_ptx(self):
def add_pins(self):
""" Adds pins for spice netlist """
self.add_pin_list(["A", "B", "Z", "vdd", "gnd"])
def add_ptx(self):
""" Create the PMOS and NMOS transistors. """
self.nmos = ptx(width=self.nmos_width,
mults=self.tx_mults,
@ -90,7 +91,7 @@ class pnand2(pgate.pgate):
# Two PMOS devices and a well contact. Separation between each.
# Enclosure space on the sides.
self.well_width = 2*self.pmos.active_width + contact.active.width \
+ 2*drc["active_to_body_active"] + 2*drc["well_enclosure_active"]
+ 2*drc("active_to_body_active") + 2*drc("well_enclosure_active")
self.width = self.well_width
# Height is an input parameter, so it is not recomputed.
@ -99,9 +100,9 @@ class pnand2(pgate.pgate):
extra_contact_space = max(-self.nmos.get_pin("D").by(),0)
# This is a poly-to-poly of a flipped cell
self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space,
drc["poly_extend_active"], self.poly_space)
drc("poly_extend_active"), self.poly_space)
def add_supply_rails(self):
def route_supply_rails(self):
""" Add vdd/gnd rails to the top and bottom. """
self.add_layout_pin_rect_center(text="gnd",
layer="metal1",
@ -113,37 +114,47 @@ class pnand2(pgate.pgate):
offset=vector(0.5*self.width,self.height),
width=self.width)
def add_ptx(self):
def create_ptx(self):
"""
Add PMOS and NMOS to the layout at the upper-most and lowest position
Add PMOS and NMOS to the netlist.
"""
self.pmos1_inst=self.add_inst(name="pnand2_pmos1",
mod=self.pmos)
self.connect_inst(["vdd", "A", "Z", "vdd"])
self.pmos2_inst = self.add_inst(name="pnand2_pmos2",
mod=self.pmos)
self.connect_inst(["Z", "B", "vdd", "vdd"])
self.nmos1_inst=self.add_inst(name="pnand2_nmos1",
mod=self.nmos)
self.connect_inst(["Z", "B", "net1", "gnd"])
self.nmos2_inst=self.add_inst(name="pnand2_nmos2",
mod=self.nmos)
self.connect_inst(["net1", "A", "gnd", "gnd"])
def place_ptx(self):
"""
Place PMOS and NMOS to the layout at the upper-most and lowest position
to provide maximum routing in channel
"""
pmos1_pos = vector(self.pmos.active_offset.x,
self.height - self.pmos.active_height - self.top_bottom_space)
self.pmos1_inst=self.add_inst(name="pnand2_pmos1",
mod=self.pmos,
offset=pmos1_pos)
self.connect_inst(["vdd", "A", "Z", "vdd"])
self.pmos1_inst.place(pmos1_pos)
self.pmos2_pos = pmos1_pos + self.overlap_offset
self.pmos2_inst = self.add_inst(name="pnand2_pmos2",
mod=self.pmos,
offset=self.pmos2_pos)
self.connect_inst(["Z", "B", "vdd", "vdd"])
self.pmos2_inst.place(self.pmos2_pos)
nmos1_pos = vector(self.pmos.active_offset.x, self.top_bottom_space)
self.nmos1_inst=self.add_inst(name="pnand2_nmos1",
mod=self.nmos,
offset=nmos1_pos)
self.connect_inst(["Z", "B", "net1", "gnd"])
self.nmos1_inst.place(nmos1_pos)
self.nmos2_pos = nmos1_pos + self.overlap_offset
self.nmos2_inst=self.add_inst(name="pnand2_nmos2",
mod=self.nmos,
offset=self.nmos2_pos)
self.connect_inst(["net1", "A", "gnd", "gnd"])
self.nmos2_inst.place(self.nmos2_pos)
# Output position will be in between the PMOS and NMOS
self.output_pos = vector(0,0.5*(pmos1_pos.y+nmos1_pos.y+self.nmos.active_height))
@ -229,5 +240,5 @@ class pnand2(pgate.pgate):
"""Computes effective capacitance. Results in fF"""
c_load = load
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
transistion_prob = spice["nand2_transisition_prob"]
return transistion_prob*(c_load + c_para)
transition_prob = spice["nand2_transition_prob"]
return transition_prob*(c_load + c_para)

View File

@ -12,54 +12,53 @@ class pnand3(pgate.pgate):
This model use ptx to generate a 2-input nand within a cetrain height.
"""
from importlib import reload
c = reload(__import__(OPTS.bitcell))
bitcell = getattr(c, OPTS.bitcell)
unique_id = 1
def __init__(self, size=1, height=bitcell.height):
def __init__(self, size=1, height=None):
""" Creates a cell for a simple 3 input nand """
name = "pnand3_{0}".format(pnand3.unique_id)
pnand3.unique_id += 1
pgate.pgate.__init__(self, name)
pgate.pgate.__init__(self, name, height)
debug.info(2, "create pnand3 structure {0} with size of {1}".format(name, size))
# We have trouble pitch matching a 3x sizes to the bitcell...
# If we relax this, we could size this better.
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"]
self.height = height
self.nmos_width = self.nmos_size*drc("minwidth_tx")
self.pmos_width = self.pmos_size*drc("minwidth_tx")
# FIXME: Allow these to be sized
debug.check(size==1,"Size 1 pnand3 is only supported now.")
self.tx_mults = 1
self.add_pins()
self.create_layout()
#self.DRC_LVS()
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def add_pins(self):
""" Adds pins for spice netlist """
self.add_pin_list(["A", "B", "C", "Z", "vdd", "gnd"])
def create_netlist(self):
self.add_pins()
self.add_ptx()
self.create_ptx()
def create_layout(self):
""" Calls all functions related to the generation of the layout """
self.create_ptx()
self.setup_layout_constants()
self.add_supply_rails()
self.add_ptx()
self.route_supply_rails()
self.place_ptx()
self.connect_rails()
self.add_well_contacts()
self.extend_wells(self.well_pos)
self.route_inputs()
self.route_output()
def create_ptx(self):
def add_ptx(self):
""" Create the PMOS and NMOS transistors. """
self.nmos = ptx(width=self.nmos_width,
mults=self.tx_mults,
@ -84,7 +83,7 @@ class pnand3(pgate.pgate):
# Two PMOS devices and a well contact. Separation between each.
# Enclosure space on the sides.
self.well_width = 3*self.pmos.active_width + self.pmos.active_contact.width \
+ 2*drc["active_to_body_active"] + 2*drc["well_enclosure_active"] \
+ 2*drc("active_to_body_active") + 2*drc("well_enclosure_active") \
- self.overlap_offset.x
self.width = self.well_width
# Height is an input parameter, so it is not recomputed.
@ -97,9 +96,9 @@ class pnand3(pgate.pgate):
extra_contact_space = max(-nmos.get_pin("D").by(),0)
# This is a poly-to-poly of a flipped cell
self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space,
drc["poly_extend_active"], self.poly_space)
drc("poly_extend_active"), self.poly_space)
def add_supply_rails(self):
def route_supply_rails(self):
""" Add vdd/gnd rails to the top and bottom. """
self.add_layout_pin_rect_center(text="gnd",
layer="metal1",
@ -111,50 +110,61 @@ class pnand3(pgate.pgate):
offset=vector(0.5*self.width,self.height),
width=self.width)
def add_ptx(self):
def create_ptx(self):
"""
Add PMOS and NMOS to the layout at the upper-most and lowest position
Create the PMOS and NMOS in the netlist.
"""
self.pmos1_inst=self.add_inst(name="pnand3_pmos1",
mod=self.pmos)
self.connect_inst(["vdd", "A", "Z", "vdd"])
self.pmos2_inst = self.add_inst(name="pnand3_pmos2",
mod=self.pmos)
self.connect_inst(["Z", "B", "vdd", "vdd"])
self.pmos3_inst = self.add_inst(name="pnand3_pmos3",
mod=self.pmos)
self.connect_inst(["Z", "C", "vdd", "vdd"])
self.nmos1_inst=self.add_inst(name="pnand3_nmos1",
mod=self.nmos)
self.connect_inst(["Z", "C", "net1", "gnd"])
self.nmos2_inst=self.add_inst(name="pnand3_nmos2",
mod=self.nmos)
self.connect_inst(["net1", "B", "net2", "gnd"])
self.nmos3_inst=self.add_inst(name="pnand3_nmos3",
mod=self.nmos)
self.connect_inst(["net2", "A", "gnd", "gnd"])
def place_ptx(self):
"""
Place the PMOS and NMOS in the layout at the upper-most and lowest position
to provide maximum routing in channel
"""
pmos1_pos = vector(self.pmos.active_offset.x,
self.height - self.pmos.active_height - self.top_bottom_space)
self.pmos1_inst=self.add_inst(name="pnand3_pmos1",
mod=self.pmos,
offset=pmos1_pos)
self.connect_inst(["vdd", "A", "Z", "vdd"])
self.pmos1_inst.place(pmos1_pos)
pmos2_pos = pmos1_pos + self.overlap_offset
self.pmos2_inst = self.add_inst(name="pnand3_pmos2",
mod=self.pmos,
offset=pmos2_pos)
self.connect_inst(["Z", "B", "vdd", "vdd"])
self.pmos2_inst.place(pmos2_pos)
self.pmos3_pos = pmos2_pos + self.overlap_offset
self.pmos3_inst = self.add_inst(name="pnand3_pmos3",
mod=self.pmos,
offset=self.pmos3_pos)
self.connect_inst(["Z", "C", "vdd", "vdd"])
self.pmos3_inst.place(self.pmos3_pos)
nmos1_pos = vector(self.pmos.active_offset.x, self.top_bottom_space)
self.nmos1_inst=self.add_inst(name="pnand3_nmos1",
mod=self.nmos,
offset=nmos1_pos)
self.connect_inst(["Z", "C", "net1", "gnd"])
self.nmos1_inst.place(nmos1_pos)
nmos2_pos = nmos1_pos + self.overlap_offset
self.nmos2_inst=self.add_inst(name="pnand3_nmos2",
mod=self.nmos,
offset=nmos2_pos)
self.connect_inst(["net1", "B", "net2", "gnd"])
self.nmos2_inst.place(nmos2_pos)
self.nmos3_pos = nmos2_pos + self.overlap_offset
self.nmos3_inst=self.add_inst(name="pnand3_nmos3",
mod=self.nmos,
offset=self.nmos3_pos)
self.connect_inst(["net2", "A", "gnd", "gnd"])
self.nmos3_inst.place(self.nmos3_pos)
# This should be placed at the top of the NMOS well
self.well_pos = vector(0,self.nmos1_inst.uy())
@ -181,7 +191,7 @@ class pnand3(pgate.pgate):
metal_spacing = max(self.m1_space + self.m1_width, self.m2_space + self.m2_width,
self.m1_space + 0.5*contact.poly.width + 0.5*self.m1_width)
active_spacing = max(self.m1_space, 0.5*contact.poly.first_layer_width + drc["poly_to_active"])
active_spacing = max(self.m1_space, 0.5*contact.poly.first_layer_width + drc("poly_to_active"))
inputC_yoffset = self.nmos3_pos.y + self.nmos.active_height + active_spacing
self.route_input_gate(self.pmos3_inst, self.nmos3_inst, inputC_yoffset, "C", position="center")
@ -249,5 +259,5 @@ class pnand3(pgate.pgate):
"""Computes effective capacitance. Results in fF"""
c_load = load
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
transistion_prob = spice["nand3_transisition_prob"]
return transistion_prob*(c_load + c_para)
transition_prob = spice["nand3_transition_prob"]
return transition_prob*(c_load + c_para)

View File

@ -12,31 +12,26 @@ class pnor2(pgate.pgate):
This model use ptx to generate a 2-input nor within a cetrain height.
"""
from importlib import reload
c = reload(__import__(OPTS.bitcell))
bitcell = getattr(c, OPTS.bitcell)
unique_id = 1
def __init__(self, size=1, height=bitcell.height):
def __init__(self, size=1, height=None):
""" Creates a cell for a simple 2 input nor """
name = "pnor2_{0}".format(pnor2.unique_id)
pnor2.unique_id += 1
pgate.pgate.__init__(self, name)
pgate.pgate.__init__(self, name, height)
debug.info(2, "create pnor2 structure {0} with size of {1}".format(name, size))
self.nmos_size = size
# We will just make this 1.5 times for now. NORs are not ideal anyhow.
self.pmos_size = 1.5*parameter["beta"]*size
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
self.height = height
self.nmos_width = self.nmos_size*drc("minwidth_tx")
self.pmos_width = self.pmos_size*drc("minwidth_tx")
# FIXME: Allow these to be sized
debug.check(size==1,"Size 1 pnor2 is only supported now.")
self.tx_mults = 1
self.add_pins()
self.create_netlist()
self.create_layout()
#self.DRC_LVS()
@ -45,6 +40,9 @@ class pnor2(pgate.pgate):
""" Adds pins for spice netlist """
self.add_pin_list(["A", "B", "Z", "vdd", "gnd"])
def create_netlist(self):
self.add_pins()
def create_layout(self):
""" Calls all functions related to the generation of the layout """
@ -94,7 +92,7 @@ class pnor2(pgate.pgate):
# Two PMOS devices and a well contact. Separation between each.
# Enclosure space on the sides.
self.well_width = 2*self.pmos.active_width + self.pmos.active_contact.width \
+ 2*drc["active_to_body_active"] + 2*drc["well_enclosure_active"]
+ 2*drc("active_to_body_active") + 2*drc("well_enclosure_active")
self.width = self.well_width
# Height is an input parameter, so it is not recomputed.
@ -103,7 +101,7 @@ class pnor2(pgate.pgate):
extra_contact_space = max(-self.nmos.get_pin("D").by(),0)
# This is a poly-to-poly of a flipped cell
self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space,
drc["poly_extend_active"], self.poly_space)
drc("poly_extend_active"), self.poly_space)
def add_supply_rails(self):
""" Add vdd/gnd rails to the top and bottom. """
@ -239,6 +237,6 @@ class pnor2(pgate.pgate):
"""Computes effective capacitance. Results in fF"""
c_load = load
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
transistion_prob = spice["nor2_transisition_prob"]
return transistion_prob*(c_load + c_para)
transition_prob = spice["nor2_transition_prob"]
return transition_prob*(c_load + c_para)

View File

@ -12,7 +12,11 @@ class precharge(pgate.pgate):
This module implements the precharge bitline cell used in the design.
"""
def __init__(self, name, size=1):
unique_id = 1
def __init__(self, name, size=1, bitcell_bl="bl", bitcell_br="br"):
name = name+"_{}".format(precharge.unique_id)
precharge.unique_id += 1
pgate.pgate.__init__(self, name)
debug.info(2, "create single precharge cell: {0}".format(name))
@ -24,87 +28,110 @@ class precharge(pgate.pgate):
self.beta = parameter["beta"]
self.ptx_width = self.beta*parameter["min_tx_size"]
self.width = self.bitcell.width
self.bitcell_bl = bitcell_bl
self.bitcell_br = bitcell_br
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_pins()
self.create_layout()
self.add_ptx()
self.create_ptx()
def create_layout(self):
self.place_ptx()
self.connect_poly()
self.route_en()
self.place_nwell_and_contact()
self.route_vdd_rail()
self.route_bitlines()
self.connect_to_bitlines()
self.DRC_LVS()
def add_pins(self):
self.add_pin_list(["bl", "br", "en", "vdd"])
def create_layout(self):
self.create_ptx()
self.add_ptx()
self.connect_poly()
self.add_en()
self.add_nwell_and_contact()
self.add_vdd_rail()
self.add_bitlines()
self.connect_to_bitlines()
def create_ptx(self):
"""Initializes the upper and lower pmos"""
def add_ptx(self):
"""
Initializes the upper and lower pmos
"""
self.pmos = ptx(width=self.ptx_width,
tx_type="pmos")
self.add_mod(self.pmos)
# Compute the other pmos2 location, but determining offset to overlap the
# source and drain pins
self.overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll()
def add_vdd_rail(self):
"""Adds a vdd rail at the top of the cell"""
# adds the rail across the width of the cell
vdd_position = vector(0, self.height - self.m1_width)
self.add_rect(layer="metal1",
offset=vdd_position,
width=self.width,
height=self.m1_width)
def route_vdd_rail(self):
"""
Adds a vdd rail at the top of the cell
"""
# Adds the rail across the width of the cell
vdd_position = vector(0.5*self.width, self.height)
self.add_rect_center(layer="metal1",
offset=vdd_position,
width=self.width,
height=self.m1_width)
pmos_pin = self.upper_pmos2_inst.get_pin("S")
# center of vdd rail
vdd_pos = vector(pmos_pin.cx(), vdd_position.y + 0.5*self.m1_width)
self.add_path("metal1", [pmos_pin.uc(), vdd_pos])
pmos_vdd_pos = vector(pmos_pin.cx(), vdd_position.y)
self.add_path("metal1", [pmos_pin.uc(), pmos_vdd_pos])
# Add the M1->M2->M3 stack at the left edge
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=vdd_pos.scale(0,1))
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=vdd_pos.scale(0,1))
self.add_layout_pin_rect_center(text="vdd",
layer="metal3",
offset=vdd_pos.scale(0,1))
# Add vdd pin above the transistor
self.add_power_pin("vdd", pmos_pin.center(), rotate=0)
def add_ptx(self):
"""Adds both the upper_pmos and lower_pmos to the module"""
# adds the lower pmos to layout
#base = vector(self.width - 2*self.pmos.width + self.overlap_offset.x, 0)
self.lower_pmos_position = vector(self.bitcell.get_pin("bl").lx(),
self.pmos.active_offset.y)
def create_ptx(self):
"""
Create both the upper_pmos and lower_pmos to the module
"""
self.lower_pmos_inst=self.add_inst(name="lower_pmos",
mod=self.pmos,
offset=self.lower_pmos_position)
mod=self.pmos)
self.connect_inst(["bl", "en", "br", "vdd"])
self.upper_pmos1_inst=self.add_inst(name="upper_pmos1",
mod=self.pmos)
self.connect_inst(["bl", "en", "vdd", "vdd"])
self.upper_pmos2_inst=self.add_inst(name="upper_pmos2",
mod=self.pmos)
self.connect_inst(["br", "en", "vdd", "vdd"])
def place_ptx(self):
"""
Place both the upper_pmos and lower_pmos to the module
"""
# Compute the other pmos2 location, but determining offset to overlap the
# source and drain pins
overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll()
# This is how much the contact is placed inside the ptx active
contact_xdiff = self.pmos.get_pin("S").lx()
# adds the lower pmos to layout
bl_xoffset = self.bitcell.get_pin(self.bitcell_bl).lx()
self.lower_pmos_position = vector(max(bl_xoffset - contact_xdiff, self.well_enclose_active),
self.pmos.active_offset.y)
self.lower_pmos_inst.place(self.lower_pmos_position)
# adds the upper pmos(s) to layout
ydiff = self.pmos.height + 2*self.m1_space + contact.poly.width
self.upper_pmos1_pos = self.lower_pmos_position + vector(0, ydiff)
self.upper_pmos1_inst=self.add_inst(name="upper_pmos1",
mod=self.pmos,
offset=self.upper_pmos1_pos)
self.connect_inst(["bl", "en", "vdd", "vdd"])
upper_pmos2_pos = self.upper_pmos1_pos + self.overlap_offset
self.upper_pmos2_inst=self.add_inst(name="upper_pmos2",
mod=self.pmos,
offset=upper_pmos2_pos)
self.connect_inst(["br", "en", "vdd", "vdd"])
self.upper_pmos1_inst.place(self.upper_pmos1_pos)
upper_pmos2_pos = self.upper_pmos1_pos + overlap_offset
self.upper_pmos2_inst.place(upper_pmos2_pos)
def connect_poly(self):
"""Connects the upper and lower pmos together"""
"""
Connects the upper and lower pmos together
"""
offset = self.lower_pmos_inst.get_pin("G").ll()
# connects the top and bottom pmos' gates together
@ -122,8 +149,11 @@ class precharge(pgate.pgate):
width=xlength,
height=self.poly_width)
def add_en(self):
"""Adds the en input rail, en contact/vias, and connects to the pmos"""
def route_en(self):
"""
Adds the en input rail, en contact/vias, and connects to the pmos
"""
# adds the en contact to connect the gates to the en rail on metal1
offset = self.lower_pmos_inst.get_pin("G").ul() + vector(0,0.5*self.poly_space)
self.add_contact_center(layers=("poly", "contact", "metal1"),
@ -137,78 +167,99 @@ class precharge(pgate.pgate):
end=offset.scale(0,1)+vector(self.width,0))
def add_nwell_and_contact(self):
"""Adds a nwell tap to connect to the vdd rail"""
def place_nwell_and_contact(self):
"""
Adds a nwell tap to connect to the vdd rail
"""
# adds the contact from active to metal1
well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1,0) \
+ vector(0, self.upper_pmos1_inst.uy() + contact.well.height/2 + drc["well_extend_active"])
+ vector(0, self.upper_pmos1_inst.uy() + contact.well.height/2 + drc("well_extend_active"))
self.add_contact_center(layers=("active", "contact", "metal1"),
offset=well_contact_pos,
implant_type="n",
well_type="n")
# leave an extra pitch for the height
self.height = well_contact_pos.y + contact.well.height + self.m1_pitch
self.height = well_contact_pos.y + contact.well.height
# nwell should span the whole design since it is pmos only
self.add_rect(layer="nwell",
offset=vector(0,0),
width=self.width,
height=self.height)
def add_bitlines(self):
"""Adds both bit-line and bit-line-bar to the module"""
def route_bitlines(self):
"""
Adds both bit-line and bit-line-bar to the module
"""
# adds the BL on metal 2
offset = vector(self.bitcell.get_pin("bl").cx(),0) - vector(0.5 * self.m2_width,0)
self.add_layout_pin(text="bl",
layer="metal2",
offset=offset,
width=drc['minwidth_metal2'],
height=self.height)
offset = vector(self.bitcell.get_pin(self.bitcell_bl).cx(),0) - vector(0.5 * self.m2_width,0)
self.bl_pin = self.add_layout_pin(text="bl",
layer="metal2",
offset=offset,
width=drc("minwidth_metal2"),
height=self.height)
# adds the BR on metal 2
offset = vector(self.bitcell.get_pin("br").cx(),0) - vector(0.5 * self.m2_width,0)
self.add_layout_pin(text="br",
layer="metal2",
offset=offset,
width=drc['minwidth_metal2'],
height=self.height)
offset = vector(self.bitcell.get_pin(self.bitcell_br).cx(),0) - vector(0.5 * self.m2_width,0)
self.br_pin = self.add_layout_pin(text="br",
layer="metal2",
offset=offset,
width=drc("minwidth_metal2"),
height=self.height)
def connect_to_bitlines(self):
"""
Connect the bitlines to the devices
"""
self.add_bitline_contacts()
self.connect_pmos(self.lower_pmos_inst.get_pin("S"),self.get_pin("bl"))
self.connect_pmos(self.lower_pmos_inst.get_pin("D"),self.get_pin("br"))
self.connect_pmos(self.upper_pmos1_inst.get_pin("S"),self.get_pin("bl"))
self.connect_pmos(self.upper_pmos2_inst.get_pin("D"),self.get_pin("br"))
self.connect_pmos_m2(self.lower_pmos_inst.get_pin("S"),self.get_pin("bl"))
self.connect_pmos_m2(self.upper_pmos1_inst.get_pin("S"),self.get_pin("bl"))
self.connect_pmos_m1(self.lower_pmos_inst.get_pin("D"),self.get_pin("br"))
self.connect_pmos_m1(self.upper_pmos2_inst.get_pin("D"),self.get_pin("br"))
def add_bitline_contacts(self):
"""Adds contacts/via from metal1 to metal2 for bit-lines"""
"""
Adds contacts/via from metal1 to metal2 for bit-lines
"""
stack=("metal1", "via1", "metal2")
pos = self.lower_pmos_inst.get_pin("S").center()
self.add_contact_center(layers=stack,
offset=pos)
pos = self.lower_pmos_inst.get_pin("D").center()
self.add_contact_center(layers=stack,
offset=pos)
pos = self.upper_pmos1_inst.get_pin("S").center()
self.add_contact_center(layers=stack,
offset=pos)
pos = self.upper_pmos2_inst.get_pin("D").center()
self.add_contact_center(layers=stack,
offset=pos)
def connect_pmos(self, pmos_pin, bit_pin):
""" Connect pmos pin to bitline pin """
ll_pos = vector(min(pmos_pin.lx(),bit_pin.lx()), pmos_pin.by())
ur_pos = vector(max(pmos_pin.rx(),bit_pin.rx()), pmos_pin.uy())
width = ur_pos.x-ll_pos.x
height = ur_pos.y-ll_pos.y
self.add_rect(layer="metal2",
offset=ll_pos,
width=width,
height=height)
upper_pin = self.upper_pmos1_inst.get_pin("S")
lower_pin = self.lower_pmos_inst.get_pin("S")
# BL goes up to M2 at the transistor
self.bl_contact=self.add_contact_center(layers=stack,
offset=upper_pin.center())
self.add_contact_center(layers=stack,
offset=lower_pin.center())
# BR routes over on M1 first
self.add_contact_center(layers=stack,
offset = vector(self.br_pin.cx(), upper_pin.cy()))
self.add_contact_center(layers=stack,
offset = vector(self.br_pin.cx(), lower_pin.cy()))
def connect_pmos_m1(self, pmos_pin, bit_pin):
"""
Connect a pmos pin to bitline pin
"""
left_pos = vector(min(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy())
right_pos = vector(max(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy())
self.add_path("metal1", [ left_pos, right_pos] )
def connect_pmos_m2(self, pmos_pin, bit_pin):
"""
Connect a pmos pin to bitline pin
"""
left_pos = vector(min(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy())
right_pos = vector(max(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy())
self.add_path("metal2", [ left_pos, right_pos], self.bl_contact.height)

View File

@ -1,8 +1,9 @@
import design
import debug
from tech import drc, info, spice
from tech import drc, spice
from vector import vector
from contact import contact
from globals import OPTS
import path
class ptx(design.design):
@ -14,7 +15,7 @@ class ptx(design.design):
you to connect the fingered gates and active for parallel devices.
"""
def __init__(self, width=drc["minwidth_tx"], mults=1, tx_type="nmos", connect_active=False, connect_poly=False, num_contacts=None):
def __init__(self, width=drc("minwidth_tx"), mults=1, tx_type="nmos", connect_active=False, connect_poly=False, num_contacts=None):
# We need to keep unique names because outputting to GDSII
# will use the last record with a given name. I.e., you will
# over-write a design in GDS if one has and the other doesn't
@ -39,14 +40,11 @@ class ptx(design.design):
self.connect_poly = connect_poly
self.num_contacts = num_contacts
self.create_spice()
self.create_netlist()
# We must always create ptx layout for pbitcell
# some transistor sizes in other netlist depend on pbitcell
self.create_layout()
self.translate_all(self.active_offset)
# for run-time, we won't check every transitor DRC independently
# but this may be uncommented for debug purposes
#self.DRC()
def create_layout(self):
@ -56,19 +54,24 @@ class ptx(design.design):
self.add_well_implant()
self.add_poly()
self.add_active_contacts()
self.translate_all(self.active_offset)
def create_spice(self):
# for run-time, we won't check every transitor DRC independently
# but this may be uncommented for debug purposes
#self.DRC()
def create_netlist(self):
self.add_pin_list(["D", "G", "S", "B"])
# self.spice.append("\n.SUBCKT {0} {1}".format(self.name,
# " ".join(self.pins)))
# Just make a guess since these will actually be decided in the layout later.
area_sd = 2.5*drc["minwidth_poly"]*self.tx_width
perimeter_sd = 2*drc["minwidth_poly"] + 2*self.tx_width
area_sd = 2.5*drc("minwidth_poly")*self.tx_width
perimeter_sd = 2*drc("minwidth_poly") + 2*self.tx_width
self.spice_device="M{{0}} {{1}} {0} m={1} w={2}u l={3}u pd={4}u ps={4}u as={5}p ad={5}p".format(spice[self.tx_type],
self.mults,
self.tx_width,
drc["minwidth_poly"],
drc("minwidth_poly"),
perimeter_sd,
area_sd)
self.spice.append("\n* ptx " + self.spice_device)
@ -106,7 +109,7 @@ class ptx(design.design):
self.contact_pitch = 2*self.contact_to_gate + self.contact_width + self.poly_width
# The enclosure of an active contact. Not sure about second term.
active_enclose_contact = max(drc["active_enclosure_contact"],
active_enclose_contact = max(drc("active_enclosure_contact"),
(self.active_width - self.contact_width)/2)
# This is the distance from the edge of poly to the contacted end of active
self.end_to_poly = active_enclose_contact + self.contact_width + self.contact_to_gate
@ -126,7 +129,7 @@ class ptx(design.design):
self.active_offset = vector([self.well_enclose_active]*2)
# Well enclosure of active, ensure minwidth as well
if info["has_{}well".format(self.well_type)]:
if drc("has_{}well".format(self.well_type)):
self.cell_well_width = max(self.active_width + 2*self.well_enclose_active,
self.well_width)
self.cell_well_height = max(self.tx_width + 2*self.well_enclose_active,
@ -148,9 +151,9 @@ class ptx(design.design):
# Min area results are just flagged for now.
debug.check(self.active_width*self.active_height>=drc["minarea_active"],"Minimum active area violated.")
debug.check(self.active_width*self.active_height>=drc("minarea_active"),"Minimum active area violated.")
# We do not want to increase the poly dimensions to fix an area problem as it would cause an LVS issue.
debug.check(self.poly_width*self.poly_height>=drc["minarea_poly"],"Minimum poly area violated.")
debug.check(self.poly_width*self.poly_height>=drc("minarea_poly"),"Minimum poly area violated.")
def connect_fingered_poly(self, poly_positions):
"""
@ -178,7 +181,7 @@ class ptx(design.design):
layer="poly",
offset=poly_offset,
width=poly_width,
height=drc["minwidth_poly"])
height=drc("minwidth_poly"))
def connect_fingered_active(self, drain_positions, source_positions):
@ -266,7 +269,7 @@ class ptx(design.design):
height=self.active_height)
# If the implant must enclose the active, shift offset
# and increase width/height
enclose_width = drc["implant_enclosure_active"]
enclose_width = drc("implant_enclosure_active")
enclose_offset = [enclose_width]*2
self.add_rect(layer="{}implant".format(self.implant_type),
offset=self.active_offset - enclose_offset,
@ -277,7 +280,7 @@ class ptx(design.design):
"""
Add an (optional) well and implant for the type of transistor.
"""
if info["has_{}well".format(self.well_type)]:
if drc("has_{}well".format(self.well_type)):
self.add_rect(layer="{}well".format(self.well_type),
offset=(0,0),
width=self.cell_well_width,

View File

@ -1,6 +1,6 @@
import design
import debug
from tech import drc, info
from tech import drc
from vector import vector
import contact
from ptx import ptx
@ -9,26 +9,35 @@ from globals import OPTS
class single_level_column_mux(design.design):
"""
This module implements the columnmux bitline cell used in the design.
Creates a single columnmux cell.
Creates a single columnmux cell with the given integer size relative
to minimum size. Default is 8x. Per Samira and Hodges-Jackson book:
Column-mux transistors driven by the decoder must be sized for optimal speed
"""
def __init__(self, tx_size):
name="single_level_column_mux_{}".format(tx_size)
# This is needed for different bitline spacings
unique_id = 1
def __init__(self, tx_size=8, bitcell_bl="bl", bitcell_br="br"):
self.tx_size = int(tx_size)
name="single_level_column_mux_{}_{}".format(self.tx_size,single_level_column_mux.unique_id)
single_level_column_mux.unique_id += 1
design.design.__init__(self, name)
debug.info(2, "create single column mux cell: {0}".format(name))
from importlib import reload
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell = self.mod_bitcell()
self.bitcell_bl = bitcell_bl
self.bitcell_br = bitcell_br
self.ptx_width = tx_size * drc["minwidth_tx"]
self.add_pin_list(["bl", "br", "bl_out", "br_out", "sel", "gnd"])
self.create_layout()
def create_layout(self):
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.add_ptx()
def create_layout(self):
self.pin_height = 2*self.m2_width
self.width = self.bitcell.width
self.height = self.nmos_upper.uy() + self.pin_height
@ -36,12 +45,30 @@ class single_level_column_mux(design.design):
self.add_bitline_pins()
self.connect_bitlines()
self.add_wells()
def add_modules(self):
# This is just used for measurements,
# so don't add the module
from importlib import reload
c = reload(__import__(OPTS.bitcell))
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell = self.mod_bitcell()
# Adds nmos_lower,nmos_upper to the module
self.ptx_width = self.tx_size*drc("minwidth_tx")
self.nmos = ptx(width=self.ptx_width)
self.add_mod(self.nmos)
def add_pins(self):
self.add_pin_list(["bl", "br", "bl_out", "br_out", "sel", "gnd"])
def add_bitline_pins(self):
""" Add the top and bottom pins to this cell """
bl_pos = vector(self.bitcell.get_pin("bl").lx(), 0)
br_pos = vector(self.bitcell.get_pin("br").lx(), 0)
bl_pos = vector(self.bitcell.get_pin(self.bitcell_bl).lx(), 0)
br_pos = vector(self.bitcell.get_pin(self.bitcell_br).lx(), 0)
# bl and br
self.add_layout_pin(text="bl",
@ -67,10 +94,6 @@ class single_level_column_mux(design.design):
def add_ptx(self):
""" Create the two pass gate NMOS transistors to switch the bitlines"""
# Adds nmos_lower,nmos_upper to the module
self.nmos = ptx(width=self.ptx_width)
self.add_mod(self.nmos)
# Space it in the center
nmos_lower_position = self.nmos.active_offset.scale(0,1) + vector(0.5*self.bitcell.width-0.5*self.nmos.active_width,0)
self.nmos_lower=self.add_inst(name="mux_tx1",
@ -124,8 +147,8 @@ class single_level_column_mux(design.design):
# bl_out -> nmos_upper/S on metal2
self.add_path("metal1",[bl_pin.ll(), vector(nmos_upper_d_pin.cx(),bl_pin.by()), nmos_upper_d_pin.center()])
# halfway up, move over
mid1 = bl_out_pin.uc().scale(1,0.5)+nmos_upper_s_pin.bc().scale(0,0.5)
mid2 = bl_out_pin.uc().scale(0,0.5)+nmos_upper_s_pin.bc().scale(1,0.5)
mid1 = bl_out_pin.uc().scale(1,0.4)+nmos_upper_s_pin.bc().scale(0,0.4)
mid2 = bl_out_pin.uc().scale(0,0.4)+nmos_upper_s_pin.bc().scale(1,0.4)
self.add_path("metal2",[bl_out_pin.uc(), mid1, mid2, nmos_upper_s_pin.bc()])
# br -> nmos_lower/D on metal2
@ -144,7 +167,7 @@ class single_level_column_mux(design.design):
"""
# Add it to the right, aligned in between the two tx
active_pos = vector(self.bitcell.width,self.nmos_upper.by())
active_pos = vector(self.bitcell.width,self.nmos_upper.by() - 0.5*self.poly_space)
active_via = self.add_via_center(layers=("active", "contact", "metal1"),
offset=active_pos,
implant_type="p",

View File

@ -0,0 +1,67 @@
from enum import Enum
from vector3d import vector3d
class direction(Enum):
NORTH = 1
SOUTH = 2
EAST = 3
WEST = 4
UP = 5
DOWN = 6
NORTHEAST = 7
NORTHWEST = 8
SOUTHEAST = 9
SOUTHWEST = 10
def get_offset(direct):
"""
Returns the vector offset for a given direction.
"""
if direct==direction.NORTH:
offset = vector3d(0,1,0)
elif direct==direction.SOUTH:
offset = vector3d(0,-1,0)
elif direct==direction.EAST:
offset = vector3d(1,0,0)
elif direct==direction.WEST:
offset = vector3d(-1,0,0)
elif direct==direction.UP:
offset = vector3d(0,0,1)
elif direct==direction.DOWN:
offset = vector3d(0,0,-1)
elif direct==direction.NORTHEAST:
offset = vector3d(1,1,0)
elif direct==direction.NORTHWEST:
offset = vector3d(-1,1,0)
elif direct==direction.SOUTHEAST:
offset = vector3d(1,-1,0)
elif direct==direction.SOUTHWEST:
offset = vector3d(-1,-1,0)
else:
debug.error("Invalid direction {}".format(direct))
return offset
def cardinal_directions(up_down_too=False):
temp_dirs = [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST]
if up_down_too:
temp_dirs.extend([direction.UP, direction.DOWN])
return temp_dirs
def cardinal_offsets(up_down_too=False):
return [direction.get_offset(d) for d in direction.cardinal_directions(up_down_too)]
def all_directions():
return [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST,
direction.NORTHEAST, direction.NORTHWEST, direction.SOUTHEAST, direction.SOUTHWEST]
def all_offsets():
return [direction.get_offset(d) for d in direction.all_directions()]
def all_neighbors(cell):
return [cell+x for x in direction.all_offsets()]
def cardinal_neighbors(cell):
return [cell+x for x in direction.cardinal_offsets()]

View File

@ -1,167 +1,111 @@
import numpy as np
import string
from itertools import tee
import debug
from vector3d import vector3d
from cell import cell
import os
from grid_cell import grid_cell
try:
import Queue as Q # ver. < 3.0
except ImportError:
import queue as Q
class grid:
"""A two layer routing map. Each cell can be blocked in the vertical
or horizontal layer.
"""
A two layer routing map. Each cell can be blocked in the vertical
or horizontal layer.
"""
# costs are relative to a unit grid
# non-preferred cost allows an off-direction jog of 1 grid
# rather than 2 vias + preferred direction (cost 5)
VIA_COST = 2
NONPREFERRED_COST = 4
PREFERRED_COST = 1
def __init__(self):
""" Create a routing map of width x height cells and 2 in the z-axis. """
# costs are relative to a unit grid
# non-preferred cost allows an off-direction jog of 1 grid
# rather than 2 vias + preferred direction (cost 5)
self.VIA_COST = 2
self.NONPREFERRED_COST = 4
self.PREFERRED_COST = 1
def __init__(self, ll, ur, track_width):
""" Initialize the map and define the costs. """
# list of the source/target grid coordinates
self.source = []
self.target = []
self.track_width = track_width
self.track_widths = [self.track_width, self.track_width, 1.0]
self.track_factor = [1/self.track_width, 1/self.track_width, 1.0]
# The bounds are in grids for this
# This is really lower left bottom layer and upper right top layer in 3D.
self.ll = vector3d(ll.x,ll.y,0).scale(self.track_factor).round()
self.ur = vector3d(ur.x,ur.y,1).scale(self.track_factor).round()
# let's leave the map sparse, cells are created on demand to reduce memory
self.map={}
# priority queue for the maze routing
self.q = Q.PriorityQueue()
def set_blocked(self,n):
self.add_map(n)
self.map[n].blocked=True
def add_all_grids(self):
for x in range(self.ll.x, self.ur.x, 1):
for y in range(self.ll.y, self.ur.y, 1):
self.add_map(vector3d(x,y,0))
self.add_map(vector3d(x,y,1))
def set_blocked(self,n,value=True):
if isinstance(n, (list,tuple,set,frozenset)):
for item in n:
self.set_blocked(item,value)
else:
self.add_map(n)
self.map[n].blocked=value
def is_blocked(self,n):
self.add_map(n)
return self.map[n].blocked
if isinstance(n, (list,tuple,set,frozenset)):
for item in n:
if self.is_blocked(item):
return True
else:
return False
else:
self.add_map(n)
return self.map[n].blocked
def set_source(self,n):
self.add_map(n)
self.map[n].source=True
self.source.append(n)
def set_target(self,n):
self.add_map(n)
self.map[n].target=True
self.target.append(n)
def reinit(self):
""" Reinitialize everything for a new route. """
def set_path(self,n,value=True):
if isinstance(n, (list,tuple,set,frozenset)):
for item in n:
self.set_path(item,value)
else:
self.add_map(n)
self.map[n].path=value
def clear_blockages(self):
self.set_blocked(set(self.map.keys()),False)
def set_source(self,n,value=True):
if isinstance(n, (list,tuple,set,frozenset)):
for item in n:
self.set_source(item,value)
else:
self.add_map(n)
self.map[n].source=value
self.source.append(n)
self.reset_cells()
# clear source and target pins
self.source=[]
self.target=[]
# clear the queue
while (not self.q.empty()):
self.q.get(False)
def set_target(self,n,value=True):
if isinstance(n, (list,tuple,set,frozenset)):
for item in n:
self.set_target(item,value)
else:
self.add_map(n)
self.map[n].target=value
self.target.append(n)
def add_blockage_shape(self,ll,ur,z):
debug.info(3,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z))
for x in range(int(ll[0]),int(ur[0])+1):
for y in range(int(ll[1]),int(ur[1])+1):
n = vector3d(x,y,z)
self.set_blocked(n)
def add_blockage(self,block_list):
debug.info(3,"Adding blockage list={0}".format(str(block_list)))
for n in block_list:
self.set_blocked(n)
def add_source(self,track_list):
def add_source(self,track_list,value=True):
debug.info(3,"Adding source list={0}".format(str(track_list)))
for n in track_list:
if not self.is_blocked(n):
self.set_source(n)
debug.info(4,"Adding source ={0}".format(str(n)))
self.set_source(n,value)
self.set_blocked(n,False)
def add_target(self,track_list):
def add_target(self,track_list,value=True):
debug.info(3,"Adding target list={0}".format(str(track_list)))
for n in track_list:
if not self.is_blocked(n):
self.set_target(n)
def reset_cells(self):
"""
Reset the path and costs for all the grid cells.
"""
for p in self.map.values():
p.reset()
def add_path(self,path):
"""
Mark the path in the routing grid for visualization
"""
self.path=path
for p in path:
self.map[p].path=True
def route(self,detour_scale):
"""
This does the A* maze routing with preferred direction routing.
"""
# We set a cost bound of the HPWL for run-time. This can be
# over-ridden if the route fails due to pruning a feasible solution.
cost_bound = detour_scale*self.cost_to_target(self.source[0])*self.PREFERRED_COST
# Make sure the queue is empty if we run another route
while not self.q.empty():
self.q.get()
# Put the source items into the queue
self.init_queue()
cheapest_path = None
cheapest_cost = None
# Keep expanding and adding to the priority queue until we are done
while not self.q.empty():
# should we keep the path in the queue as well or just the final node?
(cost,path) = self.q.get()
debug.info(2,"Queue size: size=" + str(self.q.qsize()) + " " + str(cost))
debug.info(3,"Expanding: cost=" + str(cost) + " " + str(path))
# expand the last element
neighbors = self.expand_dirs(path)
debug.info(3,"Neighbors: " + str(neighbors))
for n in neighbors:
# node is added to the map by the expand routine
newpath = path + [n]
# check if we hit the target and are done
if self.is_target(n):
return (newpath,self.cost(newpath))
elif not self.map[n].visited:
# current path cost + predicted cost
current_cost = self.cost(newpath)
target_cost = self.cost_to_target(n)
predicted_cost = current_cost + target_cost
# only add the cost if it is less than our bound
if (predicted_cost < cost_bound):
if (self.map[n].min_cost==-1 or current_cost<self.map[n].min_cost):
self.map[n].visited=True
self.map[n].min_path = newpath
self.map[n].min_cost = predicted_cost
debug.info(3,"Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath))
# add the cost to get to this point if we haven't reached it yet
self.q.put((predicted_cost,newpath))
debug.warning("Unable to route path. Expand the detour_scale to allow detours.")
return (None,None)
debug.info(4,"Adding target ={0}".format(str(n)))
self.set_target(n,value)
self.set_blocked(n,False)
def is_target(self,point):
"""
@ -169,131 +113,27 @@ class grid:
"""
return point in self.target
def expand_dirs(self,path):
"""
Expand each of the four cardinal directions plus up or down
but not expanding to blocked cells. Expands in all directions
regardless of preferred directions.
"""
# expand from the last point
point = path[-1]
neighbors = []
east = point + vector3d(1,0,0)
if not self.is_blocked(east) and not east in path:
neighbors.append(east)
west= point + vector3d(-1,0,0)
if not self.is_blocked(west) and not west in path:
neighbors.append(west)
up = point + vector3d(0,0,1)
if up.z<2 and not self.is_blocked(up) and not up in path:
neighbors.append(up)
north = point + vector3d(0,1,0)
if not self.is_blocked(north) and not north in path:
neighbors.append(north)
south = point + vector3d(0,-1,0)
if not self.is_blocked(south) and not south in path:
neighbors.append(south)
down = point + vector3d(0,0,-1)
if down.z>=0 and not self.is_blocked(down) and not down in path:
neighbors.append(down)
return neighbors
def add_map(self,p):
def add_map(self,n):
"""
Add a point to the map if it doesn't exist.
"""
if p not in self.map.keys():
self.map[p]=cell()
def init_queue(self):
"""
Populate the queue with all the source pins with cost
to the target. Each item is a path of the grid cells.
We will use an A* search, so this cost must be pessimistic.
Cost so far will be the length of the path.
"""
debug.info(4,"Initializing queue.")
# uniquify the source (and target while we are at it)
self.source = list(set(self.source))
self.target = list(set(self.target))
for s in self.source:
cost = self.cost_to_target(s)
debug.info(4,"Init: cost=" + str(cost) + " " + str([s]))
self.q.put((cost,[s]))
def hpwl(self, src, dest):
"""
Return half perimeter wire length from point to another.
Either point can have positive or negative coordinates.
Include the via penalty if there is one.
"""
hpwl = max(abs(src.x-dest.x),abs(dest.x-src.x))
hpwl += max(abs(src.y-dest.y),abs(dest.y-src.y))
hpwl += max(abs(src.z-dest.z),abs(dest.z-src.z))
if src.x!=dest.x or src.y!=dest.y:
hpwl += self.VIA_COST
return hpwl
def cost_to_target(self,source):
"""
Find the cheapest HPWL distance to any target point ignoring
blockages for A* search.
"""
cost = self.hpwl(source,self.target[0])
for t in self.target:
cost = min(self.hpwl(source,t),cost)
return cost
def cost(self,path):
"""
The cost of the path is the length plus a penalty for the number
of vias. We assume that non-preferred direction is penalized.
"""
# Ignore the source pin layer change, FIXME?
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
plist = pairwise(path)
cost = 0
for p0,p1 in plist:
if p0.z != p1.z: # via
cost += self.VIA_COST
elif p0.x != p1.x: # horizontal
cost += self.NONPREFERRED_COST if (p0.z == 1) else self.PREFERRED_COST
elif p0.y != p1.y: # vertical
cost += self.NONPREFERRED_COST if (p0.z == 0) else self.PREFERRED_COST
else:
debug.error("Non-changing direction!")
return cost
def get_inertia(self,p0,p1):
"""
Sets the direction based on the previous direction we came from.
"""
# direction (index) of movement
if p0.x==p1.x:
return 1
elif p0.y==p1.y:
return 0
if isinstance(n, (list,tuple,set,frozenset)):
for item in n:
self.add_map(item)
else:
# z direction
return 2
if n not in self.map.keys():
self.map[n]=grid_cell()
def block_path(self,path):
"""
Mark the path in the routing grid as blocked.
Also unsets the path flag.
"""
path.set_path(False)
path.set_blocked(True)

View File

@ -1,10 +1,9 @@
class cell:
class grid_cell:
"""
A single cell that can be occupied in a given layer, blocked,
visited, etc.
"""
def __init__(self):
self.visited = False
self.path = False
self.blocked = False
self.source = False
@ -17,13 +16,17 @@ class cell:
Reset the dynamic info about routing. The pins/blockages are not reset so
that they can be reused.
"""
self.visited=False
self.min_cost=-1
self.min_path=None
self.blocked=False
self.source=False
self.target=False
def get_cost(self):
# We can display the cost of the frontier
if self.min_cost > 0:
return self.min_cost
def get_type(self):
if self.blocked:
@ -38,8 +41,4 @@ class cell:
if self.path:
return "P"
# We can display the cost of the frontier
if self.min_cost > 0:
return self.min_cost
return None

View File

@ -0,0 +1,212 @@
import debug
from vector3d import vector3d
from itertools import tee
from grid import grid
from grid_cell import grid_cell
from direction import direction
class grid_path:
"""
A grid path is a list of lists of grid cells.
It can have a width that is more than one cell.
All of the sublists will be the same dimension.
Cells should be continguous.
It can have a name to define pin shapes as well.
"""
def __init__(self, items=[], name=""):
self.name = name
if items:
self.pathlist = [items]
else:
self.pathlist = []
def __str__(self):
#import pprint
p = str(self.pathlist) #pprint.pformat(self.pathlist)
if self.name != "":
return (str(self.name) + " : " + p)
return p
def __setitem__(self, index, value):
"""
override setitem function
can set value by pathinstance[index]=value
"""
self.pathlist[index]=value
def __getitem__(self, index):
"""
override getitem function
can get value by value=pathinstance[index]
"""
return self.pathlist[index]
def __contains__(self, key):
"""
Determine if cell exists in this path
"""
# FIXME: Could maintain a hash to make in O(1)
for sublist in self.pathlist:
for item in sublist:
if item == key:
return True
else:
return False
def __add__(self, items):
"""
Override add to do append
"""
return self.pathlist.extend(items)
def __len__(self):
return len(self.pathlist)
def trim_last(self):
"""
Drop the last item
"""
if len(self.pathlist)>0:
self.pathlist.pop()
def trim_first(self):
"""
Drop the first item
"""
if len(self.pathlist)>0:
self.pathlist.pop(0)
def append(self,item):
"""
Append the list of items to the cells
"""
self.pathlist.append(item)
def extend(self,item):
"""
Extend the list of items to the cells
"""
self.pathlist.extend(item)
def set_path(self,value=True):
for sublist in self.pathlist:
for p in sublist:
p.path=value
def set_blocked(self,value=True):
for sublist in self.pathlist:
for p in sublist:
p.blocked=value
def get_grids(self):
"""
Return a set of all the grids in this path.
"""
newset = set()
for sublist in self.pathlist:
newset.update(sublist)
return newset
def get_wire_grids(self, start_index, end_index):
"""
Return a set of all the wire grids in this path.
These are the indices in the wave path in a certain range.
"""
newset = set()
for sublist in self.pathlist:
newset.update(sublist[start_index:end_index])
return newset
def cost(self):
"""
The cost of the path is the length plus a penalty for the number
of vias. We assume that non-preferred direction is penalized.
This cost only works with 1 wide tracks.
"""
# Ignore the source pin layer change, FIXME?
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
plist = list(pairwise(self.pathlist))
cost = 0
for p0list,p1list in plist:
# This is because they are "waves" so pick the first item
p0=p0list[0]
p1=p1list[0]
if p0.z != p1.z: # via
cost += grid.VIA_COST
elif p0.x != p1.x and p0.z==1: # horizontal on vertical layer
cost += grid.NONPREFERRED_COST
elif p0.y != p1.y and p0.z==0: # vertical on horizontal layer
cost += grid.NONPREFERRED_COST
else:
cost += grid.PREFERRED_COST
return cost
def expand_dirs(self):
"""
Expand from the end in each of the four cardinal directions plus up
or down but not expanding to blocked cells. Expands in all
directions regardless of preferred directions.
If the width is more than one, it can only expand in one direction
(for now). This is assumed for the supply router for now.
"""
neighbors = []
for d in direction.cardinal_directions(True):
n = self.neighbor(d)
if n:
neighbors.append(n)
return neighbors
def neighbor(self, d):
offset = direction.get_offset(d)
newwave = [point + offset for point in self.pathlist[-1]]
if newwave in self.pathlist:
return None
elif newwave[0].z>1 or newwave[0].z<0:
return None
return newwave
def set_layer(self, zindex):
new_pathlist = [vector3d(item.x, item.y, zindex) for wave in self.pathlist for item in wave]
self.pathlist = new_pathlist
def overlap(self, other):
"""
Return the overlap waves ignoring different layers
"""
my_zindex = self.pathlist[0][0].z
other_flat_cells = [vector3d(item.x,item.y,my_zindex) for wave in other.pathlist for item in wave]
# This keeps the wave structure of the self layer
shared_waves = []
for wave in self.pathlist:
for item in wave:
# If any item in the wave is not contained, skip it
if not item in other_flat_cells:
break
else:
shared_waves.append(wave)
if len(shared_waves)>0:
ll = shared_waves[0][0]
ur = shared_waves[-1][-1]
return [ll,ur]
return None

View File

@ -0,0 +1,141 @@
"""
Some utility functions for sets of grid cells.
"""
import debug
from direction import direction
from vector3d import vector3d
def increment_set(curset, direct):
"""
Return the cells incremented in given direction
"""
offset = direction.get_offset(direct)
newset = set()
for c in curset:
newc = c+offset
newset.add(newc)
return newset
def remove_border(curset, direct):
"""
Remove the cells on a given border.
"""
border = get_border(curset, direct)
curset.difference_update(border)
def get_upper_right(curset):
ur = None
for p in curset:
if ur == None or (p.x>=ur.x and p.y>=ur.y):
ur = p
return ur
def get_lower_left(curset):
ll = None
for p in curset:
if ll == None or (p.x<=ll.x and p.y<=ll.y):
ll = p
return ll
def get_border( curset, direct):
"""
Return the furthest cell(s) in a given direction.
"""
# find direction-most cell(s)
maxc = []
if direct==direction.NORTH:
for c in curset:
if len(maxc)==0 or c.y>maxc[0].y:
maxc = [c]
elif c.y==maxc[0].y:
maxc.append(c)
elif direct==direct.SOUTH:
for c in curset:
if len(maxc)==0 or c.y<maxc[0].y:
maxc = [c]
elif c.y==maxc[0].y:
maxc.append(c)
elif direct==direct.EAST:
for c in curset:
if len(maxc)==0 or c.x>maxc[0].x:
maxc = [c]
elif c.x==maxc[0].x:
maxc.append(c)
elif direct==direct.WEST:
for c in curset:
if len(maxc)==0 or c.x<maxc[0].x:
maxc = [c]
elif c.x==maxc[0].x:
maxc.append(c)
newset = set(maxc)
return newset
def expand_border(curset, direct):
"""
Expand the current set of sells in a given direction.
Only return the contiguous cells.
"""
border_set = get_border(curset, direct)
next_border_set = increment_set(border_set, direct)
return next_border_set
def expand_borders(curset):
"""
Return the expansions in planar directions.
"""
north_set=expand_border(curset,direction.NORTH)
south_set=expand_border(curset,direction.SOUTH)
east_set=expand_border(curset,direction.EAST)
west_set=expand_border(curset,direction.WEST)
return(north_set, east_set, south_set, west_set)
def inflate_cell(cell, distance):
"""
Expand the current cell in all directions and return the set.
"""
newset = set(cell)
if distance==0:
return(newset)
# recursively call this based on the distance
for offset in direction.all_offsets():
# FIXME: If distance is large this will be inefficient, but it is like 1 or 2
newset.update(inflate_cell(cell+offset,distance-1))
return newset
def inflate_set(curset, distance):
"""
Expand the set in all directions by the given number of grids.
"""
if distance<=0:
return curset
newset = curset.copy()
# Add all my neighbors
for c in curset:
newset.update(direction.all_neighbors(c))
# Recurse with less depth
return inflate_set(newset,distance-1)
def flatten_set(curset):
"""
Flatten until we have a set of vector3d objects.
"""
newset = set()
for c in curset:
if isinstance(c,vector3d):
newset.add(c)
else:
newset.update(flatten_set(c))
return newset

View File

@ -0,0 +1,665 @@
from direction import direction
from pin_layout import pin_layout
from vector3d import vector3d
from vector import vector
import grid_utils
from tech import drc
import debug
class pin_group:
"""
A class to represent a group of rectangular design pin.
It requires a router to define the track widths and blockages which
determine how pin shapes get mapped to tracks.
It is initially constructed with a single set of (touching) pins.
"""
def __init__(self, name, pin_set, router):
self.name = name
# Flag for when it is routed
self.routed = False
# Flag for when it is enclosed
self.enclosed = False
# Remove any redundant pins (i.e. contained in other pins)
irredundant_pin_set = self.remove_redundant_shapes(list(pin_set))
# This is a list because we can have a pin group of disconnected sets of pins
# and these are represented by separate lists
self.pins = [set(irredundant_pin_set)]
self.router = router
# These are the corresponding pin grids for each pin group.
self.grids = set()
# These are the secondary grids that could or could not be part of the pin
self.secondary_grids = set()
# The corresponding set of partially blocked grids for each pin group.
# These are blockages for other nets but unblocked for routing this group.
# These are also blockages if we used a simple enclosure to route to a rail.
self.blockages = set()
# This is a set of pin_layout shapes to cover the grids
self.enclosures = set()
def __str__(self):
""" override print function output """
total_string = "(pg {} ".format(self.name)
pin_string = "\n pins={}".format(self.pins)
total_string += pin_string
grids_string = "\n grids={}".format(self.grids)
total_string += grids_string
grids_string = "\n secondary={}".format(self.secondary_grids)
total_string += grids_string
if self.enclosed:
enlosure_string = "\n enclose={}".format(self.enclosures)
total_string += enclosure_string
total_string += ")"
return total_string
def __repr__(self):
""" override repr function output """
return str(self)
def size(self):
return len(self.grids)
def set_routed(self, value=True):
self.routed = value
def is_routed(self):
return self.routed
def pins_enclosed(self):
"""
Check if all of the pin shapes are enclosed.
Does not check if the DRC is correct, but just touching.
"""
for pin_list in self.pins:
pin_is_enclosed=False
for pin in pin_list:
if pin_is_enclosed:
break
for encosure in self.enclosures:
if pin.overlaps(enclosure):
pin_is_enclosed=True
break
else:
return False
return True
def remove_redundant_shapes(self, pin_list):
"""
Remove any pin layout that is contained within another.
Returns a new list without modifying pin_list.
"""
local_debug = False
if local_debug:
debug.info(0,"INITIAL: {}".format(pin_list))
# Make a copy of the list to start
new_pin_list = pin_list.copy()
remove_indices = set()
# This is n^2, but the number is small
for index1,pin1 in enumerate(pin_list):
# If we remove this pin, it can't contain other pins
if index1 in remove_indices:
continue
for index2,pin2 in enumerate(pin_list):
# Can't contain yourself, but compare the indices and not the pins
# so you can remove duplicate copies.
if index1==index2:
continue
# If we already removed it, can't remove it again...
if index2 in remove_indices:
continue
if pin1.contains(pin2):
if local_debug:
debug.info(0,"{0} contains {1}".format(pin1,pin2))
remove_indices.add(index2)
# Remove them in decreasing order to not invalidate the indices
for i in sorted(remove_indices, reverse=True):
del new_pin_list[i]
if local_debug:
debug.info(0,"FINAL : {}".format(new_pin_list))
return new_pin_list
# FIXME: This relies on some technology parameters from router which is not clean.
def compute_enclosures(self):
"""
Find the minimum rectangle enclosures of the given tracks.
"""
# Enumerate every possible enclosure
pin_list = []
for seed in self.grids:
(ll, ur) = self.enclose_pin_grids(seed, direction.NORTH, direction.EAST)
enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z)
pin_list.append(enclosure)
(ll, ur) = self.enclose_pin_grids(seed, direction.EAST, direction.NORTH)
enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z)
pin_list.append(enclosure)
# Now simplify the enclosure list
new_pin_list = self.remove_redundant_shapes(pin_list)
return new_pin_list
def compute_connector(self, pin, enclosure):
"""
Compute a shape to connect the pin to the enclosure shape.
This assumes the shape will be the dimension of the pin.
"""
if pin.xoverlaps(enclosure):
# Is it vertical overlap, extend pin shape to enclosure
plc = pin.lc()
prc = pin.rc()
elc = enclosure.lc()
erc = enclosure.rc()
ymin = min(plc.y,elc.y)
ymax = max(plc.y,elc.y)
ll = vector(plc.x, ymin)
ur = vector(prc.x, ymax)
p = pin_layout(pin.name, [ll, ur], pin.layer)
elif pin.yoverlaps(enclosure):
# Is it horizontal overlap, extend pin shape to enclosure
pbc = pin.bc()
puc = pin.uc()
ebc = enclosure.bc()
euc = enclosure.uc()
xmin = min(pbc.x,ebc.x)
xmax = max(pbc.x,ebc.x)
ll = vector(xmin, pbc.y)
ur = vector(xmax, puc.y)
p = pin_layout(pin.name, [ll, ur], pin.layer)
else:
# Neither, so we must do a corner-to corner
pc = pin.center()
ec = enclosure.center()
xmin = min(pc.x, ec.x)
xmax = max(pc.x, ec.x)
ymin = min(pc.y, ec.y)
ymax = max(pc.y, ec.y)
ll = vector(xmin, ymin)
ur = vector(xmax, ymax)
p = pin_layout(pin.name, [ll, ur], pin.layer)
return p
def find_above_connector(self, pin, enclosures):
"""
Find the enclosure that is to above the pin
and make a connector to it's upper edge.
"""
# Create the list of shapes that contain the pin edge
edge_list = []
for shape in enclosures:
if shape.xcontains(pin):
edge_list.append(shape)
# Sort them by their bottom edge
edge_list.sort(key=lambda x: x.by(), reverse=True)
# Find the bottom edge that is next to the pin's top edge
above_item = None
for item in edge_list:
if item.by()>=pin.uy():
above_item = item
else:
break
# There was nothing
if above_item==None:
return None
# If it already overlaps, no connector needed
if above_item.overlaps(pin):
return None
# Otherwise, make a connector to the item
p = self.compute_connector(pin, above_item)
return p
def find_below_connector(self, pin, enclosures):
"""
Find the enclosure that is below the pin
and make a connector to it's upper edge.
"""
# Create the list of shapes that contain the pin edge
edge_list = []
for shape in enclosures:
if shape.xcontains(pin):
edge_list.append(shape)
# Sort them by their upper edge
edge_list.sort(key=lambda x: x.uy())
# Find the upper edge that is next to the pin's bottom edge
bottom_item = None
for item in edge_list:
if item.uy()<=pin.by():
bottom_item = item
else:
break
# There was nothing to the left
if bottom_item==None:
return None
# If it already overlaps, no connector needed
if bottom_item.overlaps(pin):
return None
# Otherwise, make a connector to the item
p = self.compute_connector(pin, bottom_item)
return p
def find_left_connector(self, pin, enclosures):
"""
Find the enclosure that is to the left of the pin
and make a connector to it's right edge.
"""
# Create the list of shapes that contain the pin edge
edge_list = []
for shape in enclosures:
if shape.ycontains(pin):
edge_list.append(shape)
# Sort them by their right edge
edge_list.sort(key=lambda x: x.rx())
# Find the right edge that is to the pin's left edge
left_item = None
for item in edge_list:
if item.rx()<=pin.lx():
left_item = item
else:
break
# There was nothing to the left
if left_item==None:
return None
# If it already overlaps, no connector needed
if left_item.overlaps(pin):
return None
# Otherwise, make a connector to the item
p = self.compute_connector(pin, left_item)
return p
def find_right_connector(self, pin, enclosures):
"""
Find the enclosure that is to the right of the pin
and make a connector to it's left edge.
"""
# Create the list of shapes that contain the pin edge
edge_list = []
for shape in enclosures:
if shape.ycontains(pin):
edge_list.append(shape)
# Sort them by their right edge
edge_list.sort(key=lambda x: x.lx(), reverse=True)
# Find the left edge that is next to the pin's right edge
right_item = None
for item in edge_list:
if item.lx()>=pin.rx():
right_item = item
else:
break
# There was nothing to the right
if right_item==None:
return None
# If it already overlaps, no connector needed
if right_item.overlaps(pin):
return None
# Otherwise, make a connector to the item
p = self.compute_connector(pin, right_item)
return p
def find_smallest_connector(self, pin_list, shape_list):
"""
Compute all of the connectors between the overlapping pins and enclosure shape list..
Return the smallest.
"""
smallest = None
for pin in pin_list:
for enclosure in shape_list:
new_enclosure = self.compute_connector(pin, enclosure)
if smallest == None or new_enclosure.area()<smallest.area():
smallest = new_enclosure
return smallest
def find_smallest_overlapping(self, pin_list, shape_list):
"""
Find the smallest area shape in shape_list that overlaps with any
pin in pin_list by a min width.
"""
smallest_shape = None
for pin in pin_list:
overlap_shape = self.find_smallest_overlapping_pin(pin,shape_list)
if overlap_shape:
overlap_length = pin.overlap_length(overlap_shape)
if smallest_shape == None or overlap_shape.area()<smallest_shape.area():
smallest_shape = overlap_shape
return smallest_shape
def find_smallest_overlapping_pin(self, pin, shape_list):
"""
Find the smallest area shape in shape_list that overlaps with any
pin in pin_list by a min width.
"""
smallest_shape = None
zindex=self.router.get_zindex(pin.layer_num)
(min_width,min_space) = self.router.get_layer_width_space(zindex)
# Now compare it with every other shape to check how much they overlap
for other in shape_list:
overlap_length = pin.overlap_length(other)
if overlap_length > min_width:
if smallest_shape == None or other.area()<smallest_shape.area():
smallest_shape = other
return smallest_shape
def overlap_any_shape(self, pin_list, shape_list):
"""
Does the given pin overlap any of the shapes in the pin list.
"""
for pin in pin_list:
for other in shape_list:
if pin.overlaps(other):
return True
return False
def max_pin_layout(self, pin_list):
"""
Return the max area pin_layout
"""
biggest = pin_list[0]
for pin in pin_list:
if pin.area() > biggest.area():
biggest = pin
return pin
def enclose_pin_grids(self, ll, dir1=direction.NORTH, dir2=direction.EAST):
"""
This encloses a single pin component with a rectangle
starting with the seed and expanding right until blocked
and then up until blocked.
dir1 and dir2 should be two orthogonal directions.
"""
offset1= direction.get_offset(dir1)
offset2= direction.get_offset(dir2)
# We may have started with an empty set
if not self.grids:
return None
# Start with the ll and make the widest row
row = [ll]
# Move in dir1 while we can
while True:
next_cell = row[-1] + offset1
# Can't move if not in the pin shape
if next_cell in self.grids and next_cell not in self.router.blocked_grids:
row.append(next_cell)
else:
break
# Move in dir2 while we can
while True:
next_row = [x+offset2 for x in row]
for cell in next_row:
# Can't move if any cell is not in the pin shape
if cell not in self.grids or cell in self.router.blocked_grids:
break
else:
row = next_row
# Skips the second break
continue
# Breaks from the nested break
break
# Add a shape from ll to ur
ur = row[-1]
return (ll,ur)
def enclose_pin(self):
"""
If there is one set of connected pin shapes,
this will find the smallest rectangle enclosure that overlaps with any pin.
If there is not, it simply returns all the enclosures.
"""
self.enclosed = True
# Compute the enclosure pin_layout list of the set of tracks
self.enclosures = self.compute_enclosures()
for pin_list in self.pins:
for pin in pin_list:
# If it is contained, it won't need a connector
if pin.contained_by_any(self.enclosures):
continue
left_connector = self.find_left_connector(pin, self.enclosures)
right_connector = self.find_right_connector(pin, self.enclosures)
above_connector = self.find_above_connector(pin, self.enclosures)
below_connector = self.find_below_connector(pin, self.enclosures)
for connector in [left_connector, right_connector, above_connector, below_connector]:
if connector:
self.enclosures.append(connector)
# Now, make sure each pin touches an enclosure. If not, add a connector.
# This could only happen when there was no enclosure in any cardinal direction from a pin
for pin_list in self.pins:
if not self.overlap_any_shape(pin_list, self.enclosures):
connector = self.find_smallest_connector(pin_list, self.enclosures)
debug.check(connector!=None, "Could not find a connector for {} with {}".format(pin_list, self.enclosures))
self.enclosures.append(connector)
debug.info(3,"Computed enclosure(s) {0}\n {1}\n {2}\n {3}".format(self.name,
self.pins,
self.grids,
self.enclosures))
def combine_groups(self, pg1, pg2):
"""
Combine two pin groups into one.
"""
self.pins = [*pg1.pins, *pg2.pins] # Join the two lists of pins
self.grids = pg1.grids | pg2.grids # OR the set of grid locations
self.secondary_grids = pg1.secondary_grids | pg2.secondary_grids
def add_enclosure(self, cell):
"""
Add the enclosure shape to the given cell.
"""
for enclosure in self.enclosures:
debug.info(2,"Adding enclosure {0} {1}".format(self.name, enclosure))
cell.add_rect(layer=enclosure.layer,
offset=enclosure.ll(),
width=enclosure.width(),
height=enclosure.height())
def perimeter_grids(self):
"""
Return a list of the grids on the perimeter.
This assumes that we have a single contiguous shape.
"""
perimeter_set = set()
cardinal_offsets = direction.cardinal_offsets()
for g1 in self.grids:
neighbor_grids = [g1 + offset for offset in cardinal_offsets]
neighbor_count = sum([x in self.grids for x in neighbor_grids])
# If we aren't completely enclosed, we are on the perimeter
if neighbor_count < 4:
perimeter_set.add(g1)
return perimeter_set
def adjacent(self, other):
"""
Chck if the two pin groups have at least one adjacent pin grid.
"""
# We could optimize this to just check the boundaries
for g1 in self.perimeter_grids():
for g2 in other.perimeter_grids():
if g1.adjacent(g2):
return True
return False
def adjacent_grids(self, other, separation):
"""
Determine the sets of grids that are within a separation distance
of any grid in the other set.
"""
# We could optimize this to just check the boundaries
g1_grids = set()
g2_grids = set()
for g1 in self.grids:
for g2 in other.grids:
if g1.distance(g2) <= separation:
g1_grids.add(g1)
g2_grids.add(g2)
return g1_grids,g2_grids
def convert_pin(self):
"""
Convert the list of pin shapes into sets of routing grids.
The secondary set of grids are "optional" pin shapes that could be
should be either blocked or part of the pin.
"""
pin_set = set()
blockage_set = set()
for pin_list in self.pins:
for pin in pin_list:
debug.info(2," Converting {0}".format(pin))
# Determine which tracks the pin overlaps
pin_in_tracks=self.router.convert_pin_to_tracks(self.name, pin)
pin_set.update(pin_in_tracks)
# Blockages will be a super-set of pins since it uses the inflated pin shape.
blockage_in_tracks = self.router.convert_blockage(pin)
blockage_set.update(blockage_in_tracks)
# If we have a blockage, we must remove the grids
# Remember, this excludes the pin blockages already
shared_set = pin_set & self.router.blocked_grids
if len(shared_set)>0:
debug.info(2,"Removing pins {}".format(shared_set))
pin_set.difference_update(shared_set)
shared_set = blockage_set & self.router.blocked_grids
if len(shared_set)>0:
debug.info(2,"Removing blocks {}".format(shared_set))
blockage_set.difference_update(shared_set)
# At least one of the groups must have some valid tracks
if (len(pin_set)==0 and len(blockage_set)==0):
debug.warning("Pin is very close to metal blockage.\nAttempting to expand blocked pin {}".format(self.pins))
for pin_list in self.pins:
for pin in pin_list:
debug.info(2," Converting {0}".format(pin))
# Determine which tracks the pin overlaps
pin_in_tracks=self.router.convert_pin_to_tracks(self.name, pin, expansion=1)
pin_set.update(pin_in_tracks)
if len(pin_set)==0:
debug.error("Unable to find unblocked pin {} {}".format(self.name, self.pins))
self.router.write_debug_gds("blocked_pin.gds")
# We need to route each of the components, so don't combine the groups
self.grids = pin_set | blockage_set
# Remember the secondary grids for removing adjacent pins in wide metal spacing
self.secondary_grids = blockage_set - pin_set
debug.info(2," pins {}".format(self.grids))
debug.info(2," secondary {}".format(self.secondary_grids))
def recurse_simple_overlap_enclosure(self, start_set, direct):
"""
Recursive function to return set of tracks that connects to
the actual supply rail wire in a given direction (or terminating
when any track is no longer in the supply rail.
"""
next_set = grid_utils.expand_border(start_set, direct)
supply_tracks = self.router.supply_rail_tracks[self.name]
supply_wire_tracks = self.router.supply_rail_wire_tracks[self.name]
supply_overlap = next_set & supply_tracks
wire_overlap = next_set & supply_wire_tracks
# If the rail overlap is the same, we are done, since we connected to the actual wire
if len(wire_overlap)==len(start_set):
new_set = start_set | wire_overlap
# If the supply overlap is the same, keep expanding unti we hit the wire or move out of the rail region
elif len(supply_overlap)==len(start_set):
recurse_set = self.recurse_simple_overlap_enclosure(supply_overlap, direct)
new_set = start_set | supply_overlap | recurse_set
else:
# If we got no next set, we are done, can't expand!
new_set = set()
return new_set
def create_simple_overlap_enclosure(self, start_set):
"""
This takes a set of tracks that overlap a supply rail and creates an enclosure
that is ensured to overlap the supply rail wire.
It then adds rectangle(s) for the enclosure.
"""
additional_set = set()
# Check the layer of any element in the pin to determine which direction to route it
e = next(iter(start_set))
new_set = start_set.copy()
if e.z==0:
new_set = self.recurse_simple_overlap_enclosure(start_set, direction.NORTH)
if not new_set:
new_set = self.recurse_simple_overlap_enclosure(start_set, direction.SOUTH)
else:
new_set = self.recurse_simple_overlap_enclosure(start_set, direction.EAST)
if not new_set:
new_set = self.recurse_simple_overlap_enclosure(start_set, direction.WEST)
# Expand the pin grid set to include some extra grids that connect the supply rail
self.grids.update(new_set)
# Add the inflated set so we don't get wide metal spacing issues (if it exists)
self.blockages.update(grid_utils.inflate_set(new_set,self.router.supply_rail_space_width))
# Add the polygon enclosures and set this pin group as routed
self.set_routed()
self.enclosures = self.compute_enclosures()

View File

@ -1,3 +0,0 @@
#!/bin/bash
python tests/regress.py -t freepdk45
python tests/regress.py -t scn3me_subm

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,79 @@
from tech import drc,layer
from contact import contact
from pin_group import pin_group
from vector import vector
import debug
class router_tech:
"""
This is a class to hold the router tech constants.
"""
def __init__(self, layers):
"""
Allows us to change the layers that we are routing on. First layer
is always horizontal, middle is via, and last is always
vertical.
"""
self.layers = layers
(self.horiz_layer_name, self.via_layer_name, self.vert_layer_name) = self.layers
# This is the minimum routed track spacing
via_connect = contact(self.layers, (1, 1))
self.max_via_size = max(via_connect.width,via_connect.height)
self.vert_layer_minwidth = drc("minwidth_{0}".format(self.vert_layer_name))
self.vert_layer_spacing = drc(str(self.vert_layer_name)+"_to_"+str(self.vert_layer_name))
self.vert_layer_number = layer[self.vert_layer_name]
self.horiz_layer_minwidth = drc("minwidth_{0}".format(self.horiz_layer_name))
self.horiz_layer_spacing = drc(str(self.horiz_layer_name)+"_to_"+str(self.horiz_layer_name))
self.horiz_layer_number = layer[self.horiz_layer_name]
self.horiz_track_width = self.max_via_size + self.horiz_layer_spacing
self.vert_track_width = self.max_via_size + self.vert_layer_spacing
# We'll keep horizontal and vertical tracks the same for simplicity.
self.track_width = max(self.horiz_track_width,self.vert_track_width)
debug.info(1,"Track width: "+str(self.track_width))
self.track_widths = vector([self.track_width] * 2)
self.track_factor = vector([1/self.track_width] * 2)
debug.info(2,"Track factor: {0}".format(self.track_factor))
# When we actually create the routes, make them the width of the track (minus 1/2 spacing on each side)
self.layer_widths = [self.track_width - self.horiz_layer_spacing, 1, self.track_width - self.vert_layer_spacing]
def get_zindex(self,layer_num):
if layer_num==self.horiz_layer_number:
return 0
else:
return 1
def get_layer(self, zindex):
if zindex==1:
return self.vert_layer_name
elif zindex==0:
return self.horiz_layer_name
else:
debug.error("Invalid zindex {}".format(zindex),-1)
def get_layer_width_space(self, zindex, width=0, length=0):
"""
Return the width and spacing of a given layer
and wire of a given width and length.
"""
if zindex==1:
layer_name = self.vert_layer_name
elif zindex==0:
layer_name = self.horiz_layer_name
else:
debug.error("Invalid zindex for track", -1)
min_width = drc("minwidth_{0}".format(layer_name), width, length)
min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), width, length)
return (min_width,min_spacing)

View File

@ -0,0 +1,178 @@
from itertools import tee
import debug
from heapq import heappush,heappop
from copy import deepcopy
from grid import grid
from grid_path import grid_path
from vector3d import vector3d
class signal_grid(grid):
"""
Expand the two layer grid to include A* search functions for a source and target.
"""
def __init__(self, ll, ur, track_factor):
""" Create a routing map of width x height cells and 2 in the z-axis. """
grid.__init__(self, ll, ur, track_factor)
# priority queue for the maze routing
self.q = []
def reinit(self):
""" Reinitialize everything for a new route. """
# Reset all the cells in the map
for p in self.map.values():
p.reset()
# clear source and target pins
self.source=[]
self.target=[]
# Clear the queue
while len(self.q)>0:
heappop(self.q)
self.counter = 0
def init_queue(self):
"""
Populate the queue with all the source pins with cost
to the target. Each item is a path of the grid cells.
We will use an A* search, so this cost must be pessimistic.
Cost so far will be the length of the path.
"""
#debug.info(3,"Initializing queue.")
# Counter is used to not require data comparison in Python 3.x
# Items will be returned in order they are added during cost ties
self.counter = 0
for s in self.source:
cost = self.cost_to_target(s)
debug.info(3,"Init: cost=" + str(cost) + " " + str([s]))
heappush(self.q,(cost,self.counter,grid_path([vector3d(s)])))
self.counter+=1
def route(self,detour_scale):
"""
This does the A* maze routing with preferred direction routing.
This only works for 1 track wide routes!
"""
# We set a cost bound of the HPWL for run-time. This can be
# over-ridden if the route fails due to pruning a feasible solution.
cost_bound = detour_scale*self.cost_to_target(self.source[0])*grid.PREFERRED_COST
# Check if something in the queue is already a source and a target!
for s in self.source:
if self.is_target(s):
return((grid_path([vector3d(s)]),0))
# Make sure the queue is empty if we run another route
while len(self.q)>0:
heappop(self.q)
# Put the source items into the queue
self.init_queue()
cheapest_path = None
cheapest_cost = None
# Keep expanding and adding to the priority queue until we are done
while len(self.q)>0:
# should we keep the path in the queue as well or just the final node?
(cost,count,curpath) = heappop(self.q)
debug.info(3,"Queue size: size=" + str(len(self.q)) + " " + str(cost))
debug.info(4,"Expanding: cost=" + str(cost) + " " + str(curpath))
# expand the last element
neighbors = self.expand_dirs(curpath)
debug.info(4,"Neighbors: " + str(neighbors))
for n in neighbors:
# make a new copy of the path to not update the old ones
newpath = deepcopy(curpath)
# node is added to the map by the expand routine
newpath.append(n)
# check if we hit the target and are done
if self.is_target(n[0]): # This uses the [0] item because we are assuming 1-track wide
return (newpath,newpath.cost())
else:
# current path cost + predicted cost
current_cost = newpath.cost()
target_cost = self.cost_to_target(n[0])
predicted_cost = current_cost + target_cost
# only add the cost if it is less than our bound
if (predicted_cost < cost_bound):
if (self.map[n[0]].min_cost==-1 or predicted_cost<self.map[n[0]].min_cost):
self.map[n[0]].min_path = newpath
self.map[n[0]].min_cost = predicted_cost
debug.info(4,"Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath))
# add the cost to get to this point if we haven't reached it yet
heappush(self.q,(predicted_cost,self.counter,newpath))
self.counter += 1
#else:
# print("Better previous cost.")
#else:
# print("Cost bounded")
debug.warning("Unable to route path. Expand the detour_scale to allow detours.")
return (None,None)
def expand_dirs(self,curpath):
"""
Expand each of the four cardinal directions plus up or down
but not expanding to blocked cells. Expands in all directions
regardless of preferred directions.
"""
# Expand all directions.
neighbors = curpath.expand_dirs()
# Filter the blocked ones
unblocked_neighbors = [x for x in neighbors if not self.is_blocked(x)]
return unblocked_neighbors
def hpwl(self, src, dest):
"""
Return half perimeter wire length from point to another.
Either point can have positive or negative coordinates.
Include the via penalty if there is one.
"""
hpwl = max(abs(src.x-dest.x),abs(dest.x-src.x))
hpwl += max(abs(src.y-dest.y),abs(dest.y-src.y))
hpwl += max(abs(src.z-dest.z),abs(dest.z-src.z))
if src.x!=dest.x or src.y!=dest.y:
hpwl += grid.VIA_COST
return hpwl
def cost_to_target(self,source):
"""
Find the cheapest HPWL distance to any target point ignoring
blockages for A* search.
"""
cost = self.hpwl(source,self.target[0])
for t in self.target:
cost = min(self.hpwl(source,t),cost)
return cost
def get_inertia(self,p0,p1):
"""
Sets the direction based on the previous direction we came from.
"""
# direction (index) of movement
if p0.x==p1.x:
return 1
elif p0.y==p1.y:
return 0
else:
# z direction
return 2

View File

@ -0,0 +1,81 @@
import gdsMill
import tech
from contact import contact
import math
import debug
from pin_layout import pin_layout
from globals import OPTS
from router import router
class signal_router(router):
"""
A router class to read an obstruction map from a gds and plan a
route on a given layer. This is limited to two layer routes.
"""
def __init__(self, layers, design, gds_filename=None):
"""
This will route on layers in design. It will get the blockages from
either the gds file name or the design itself (by saving to a gds file).
"""
router.__init__(self, layers, design, gds_filename)
def create_routing_grid(self):
"""
Create a sprase routing grid with A* expansion functions.
"""
# We will add a halo around the boundary
# of this many tracks
size = self.ur - self.ll
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
import signal_grid
self.rg = signal_grid.signal_grid(self.ll, self.ur, self.track_width)
def route(self, src, dest, detour_scale=5):
"""
Route a single source-destination net and return
the simplified rectilinear path. Cost factor is how sub-optimal to explore for a feasible route.
This is used to speed up the routing when there is not much detouring needed.
"""
debug.info(1,"Running signal router from {0} to {1}...".format(src,dest))
self.pins[src] = []
self.pins[dest] = []
# Clear the pins if we have previously routed
if (hasattr(self,'rg')):
self.clear_pins()
else:
# Creat a routing grid over the entire area
# FIXME: This could be created only over the routing region,
# but this is simplest for now.
self.create_routing_grid()
# Get the pin shapes
self.find_pins_and_blockages([src, dest])
# Block everything
self.prepare_blockages()
# Clear the pins we are routing
self.set_blockages(self.pin_components[src],False)
self.set_blockages(self.pin_components[dest],False)
# Now add the src/tgt if they are not blocked by other shapes
self.add_source(src)
self.add_target(dest)
if not self.run_router(detour_scale=detour_scale):
self.write_debug_gds(stop_program=False)
return False
self.write_debug_gds(stop_program=False)
return True

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