mirror of https://github.com/VLSIDA/OpenRAM.git
Fixed merge conflicts with sram.py
This commit is contained in:
commit
62439bdac6
29
README.md
29
README.md
|
|
@ -7,8 +7,9 @@ https://github.com/mguthaus/OpenRAM/blob/master/OpenRAM_ICCAD_2016_presentation.
|
||||||
The OpenRAM compiler has very few dependencies:
|
The OpenRAM compiler has very few dependencies:
|
||||||
* ngspice-26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later)
|
* ngspice-26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later)
|
||||||
* Python 3.5 and higher
|
* Python 3.5 and higher
|
||||||
* Python numpy
|
* Python numpy (pip3 install numpy to install)
|
||||||
* a setup script for each technology
|
* 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
|
* a technology directory for each technology with the base cells
|
||||||
|
|
||||||
If you want to perform DRC and LVS, you will need either:
|
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.
|
technology directory that contains subdirs of all other technologies.
|
||||||
For example, in bash, add to your .bashrc:
|
For example, in bash, add to your .bashrc:
|
||||||
```
|
```
|
||||||
export OPENRAM_HOME="$HOME/OpenRAM/compiler"
|
export OPENRAM_HOME="$HOME/openram/compiler"
|
||||||
export OPENRAM_TECH="$HOME/OpenRAM/technology"
|
export OPENRAM_TECH="$HOME/openram/technology"
|
||||||
```
|
```
|
||||||
For example, in csh/tcsh, add to your .cshrc/.tcshrc:
|
For example, in csh/tcsh, add to your .cshrc/.tcshrc:
|
||||||
```
|
```
|
||||||
setenv OPENRAM_HOME "$HOME/OpenRAM/compiler"
|
setenv OPENRAM_HOME "$HOME/openram/compiler"
|
||||||
setenv OPENRAM_TECH "$HOME/OpenRAM/technology"
|
setenv OPENRAM_TECH "$HOME/openram/technology"
|
||||||
```
|
```
|
||||||
|
|
||||||
We include the tech files necessary for FreePDK and SCMOS. The SCMOS
|
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:
|
If you are using SCMOS, you should install Magic and netgen from:
|
||||||
http://opencircuitdesign.com/magic/
|
http://opencircuitdesign.com/magic/
|
||||||
http://opencircuitdesign.com/netgen/
|
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/
|
http://opencircuitdesign.com/qflow/
|
||||||
|
|
||||||
# DIRECTORY STRUCTURE
|
# 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 - openram compiler itself (pointed to by OPENRAM_HOME)
|
||||||
* compiler/base - base data structure modules
|
* compiler/base - base data structure modules
|
||||||
* compiler/pgates - parameterized cells (e.g. logic gates)
|
* compiler/pgates - parameterized cells (e.g. logic gates)
|
||||||
|
* compiler/bitcells - various bitcell styles
|
||||||
* compiler/modules - high-level modules (e.g. decoders, etc.)
|
* 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/characterizer - timing characterization code
|
||||||
* compiler/gdsMill - GDSII reader/writer
|
* compiler/gdsMill - GDSII reader/writer
|
||||||
* compiler/router - detailed router
|
* compiler/router - router for signals and power supplies
|
||||||
* compiler/tests - unit tests
|
* compiler/tests - unit tests
|
||||||
* technology - openram technology directory (pointed to by OPENRAM_TECH)
|
* technology - openram technology directory (pointed to by OPENRAM_TECH)
|
||||||
* technology/freepdk45 - example configuration library for freepdk45 technology node
|
* 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
|
* 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
|
* lib - IP library of pregenerated memories
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -89,8 +92,8 @@ To increase the verbosity of the test, add one (or more) -v options:
|
||||||
python tests/00_code_format_check_test.py -v -t freepdk45
|
python tests/00_code_format_check_test.py -v -t freepdk45
|
||||||
```
|
```
|
||||||
To specify a particular technology use "-t <techname>" such as
|
To specify a particular technology use "-t <techname>" such as
|
||||||
"-t scn3me_subm". The default for a unit test is freepdk45 whereas
|
"-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.
|
The default for openram.py is specified in the configuration file.
|
||||||
|
|
||||||
|
|
||||||
# CREATING CUSTOM TECHNOLOGIES
|
# CREATING CUSTOM TECHNOLOGIES
|
||||||
|
|
|
||||||
|
|
@ -73,21 +73,21 @@ class contact(hierarchy_design.hierarchy_design):
|
||||||
self.second_layer_name = second_layer
|
self.second_layer_name = second_layer
|
||||||
|
|
||||||
def setup_layout_constants(self):
|
def setup_layout_constants(self):
|
||||||
self.contact_width = drc["minwidth_{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)]
|
contact_to_contact = drc("{0}_to_{0}".format(self.via_layer_name))
|
||||||
self.contact_pitch = self.contact_width + contact_to_contact
|
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_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
|
self.contact_array_height = self.contact_width + (self.dimensions[1] - 1) * self.contact_pitch
|
||||||
|
|
||||||
# DRC rules
|
# DRC rules
|
||||||
first_layer_minwidth = drc["minwidth_{0}".format(self.first_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_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_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)]
|
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_minwidth = drc("minwidth_{0}".format(self.second_layer_name))
|
||||||
second_layer_minarea = drc["minarea_{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_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)]
|
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,
|
self.first_layer_horizontal_enclosure = max((first_layer_minwidth - self.contact_array_width) / 2,
|
||||||
first_layer_enclosure)
|
first_layer_enclosure)
|
||||||
|
|
@ -145,16 +145,16 @@ class contact(hierarchy_design.hierarchy_design):
|
||||||
height=self.second_layer_height)
|
height=self.second_layer_height)
|
||||||
|
|
||||||
def create_implant_well_enclosures(self):
|
def create_implant_well_enclosures(self):
|
||||||
implant_position = self.first_layer_position - [drc["implant_enclosure_active"]]*2
|
implant_position = self.first_layer_position - [drc("implant_enclosure_active")]*2
|
||||||
implant_width = self.first_layer_width + 2*drc["implant_enclosure_active"]
|
implant_width = self.first_layer_width + 2*drc("implant_enclosure_active")
|
||||||
implant_height = self.first_layer_height + 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),
|
self.add_rect(layer="{}implant".format(self.implant_type),
|
||||||
offset=implant_position,
|
offset=implant_position,
|
||||||
width=implant_width,
|
width=implant_width,
|
||||||
height=implant_height)
|
height=implant_height)
|
||||||
well_position = self.first_layer_position - [drc["well_enclosure_active"]]*2
|
well_position = self.first_layer_position - [drc("well_enclosure_active")]*2
|
||||||
well_width = self.first_layer_width + 2*drc["well_enclosure_active"]
|
well_width = self.first_layer_width + 2*drc("well_enclosure_active")
|
||||||
well_height = self.first_layer_height + 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),
|
self.add_rect(layer="{}well".format(self.well_type),
|
||||||
offset=well_position,
|
offset=well_position,
|
||||||
width=well_width,
|
width=well_width,
|
||||||
|
|
|
||||||
|
|
@ -29,24 +29,25 @@ class design(hierarchy_design):
|
||||||
def setup_drc_constants(self):
|
def setup_drc_constants(self):
|
||||||
""" These are some DRC constants used in many places in the compiler."""
|
""" These are some DRC constants used in many places in the compiler."""
|
||||||
from tech import drc
|
from tech import drc
|
||||||
self.well_width = drc["minwidth_well"]
|
self.well_width = drc("minwidth_well")
|
||||||
self.poly_width = drc["minwidth_poly"]
|
self.poly_width = drc("minwidth_poly")
|
||||||
self.poly_space = drc["poly_to_poly"]
|
self.poly_space = drc("poly_to_poly")
|
||||||
self.m1_width = drc["minwidth_metal1"]
|
self.m1_width = drc("minwidth_metal1")
|
||||||
self.m1_space = drc["metal1_to_metal1"]
|
self.m1_space = drc("metal1_to_metal1")
|
||||||
self.m2_width = drc["minwidth_metal2"]
|
self.m2_width = drc("minwidth_metal2")
|
||||||
self.m2_space = drc["metal2_to_metal2"]
|
self.m2_space = drc("metal2_to_metal2")
|
||||||
self.m3_width = drc["minwidth_metal3"]
|
self.m3_width = drc("minwidth_metal3")
|
||||||
self.m3_space = drc["metal3_to_metal3"]
|
self.m3_space = drc("metal3_to_metal3")
|
||||||
self.active_width = drc["minwidth_active"]
|
self.active_width = drc("minwidth_active")
|
||||||
self.contact_width = drc["minwidth_contact"]
|
self.contact_width = drc("minwidth_contact")
|
||||||
|
|
||||||
self.poly_to_active = drc["poly_to_active"]
|
self.poly_to_active = drc("poly_to_active")
|
||||||
self.poly_extend_active = drc["poly_extend_active"]
|
self.poly_extend_active = drc("poly_extend_active")
|
||||||
self.contact_to_gate = drc["contact_to_gate"]
|
self.poly_to_polycontact = drc("poly_to_polycontact")
|
||||||
self.well_enclose_active = drc["well_enclosure_active"]
|
self.contact_to_gate = drc("contact_to_gate")
|
||||||
self.implant_enclose_active = drc["implant_enclosure_active"]
|
self.well_enclose_active = drc("well_enclosure_active")
|
||||||
self.implant_space = drc["implant_to_implant"]
|
self.implant_enclose_active = drc("implant_enclosure_active")
|
||||||
|
self.implant_space = drc("implant_to_implant")
|
||||||
|
|
||||||
def setup_multiport_constants(self):
|
def setup_multiport_constants(self):
|
||||||
""" These are contants and lists that aid multiport design """
|
""" These are contants and lists that aid multiport design """
|
||||||
|
|
@ -82,3 +83,14 @@ class design(hierarchy_design):
|
||||||
for inst in self.insts:
|
for inst in self.insts:
|
||||||
total_module_power += inst.mod.analytical_power(proc, vdd, temp, load)
|
total_module_power += inst.mod.analytical_power(proc, vdd, temp, load)
|
||||||
return total_module_power
|
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
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from vector import vector
|
||||||
import tech
|
import tech
|
||||||
import math
|
import math
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
from utils import round_to_grid
|
||||||
|
|
||||||
class geometry:
|
class geometry:
|
||||||
"""
|
"""
|
||||||
|
|
@ -46,8 +47,8 @@ class geometry:
|
||||||
def normalize(self):
|
def normalize(self):
|
||||||
""" Re-find the LL and UR points after a transform """
|
""" Re-find the LL and UR points after a transform """
|
||||||
(first,second)=self.boundary
|
(first,second)=self.boundary
|
||||||
ll = vector(min(first[0],second[0]),min(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]))
|
ur = vector(max(first[0],second[0]),max(first[1],second[1])).snap_to_grid()
|
||||||
self.boundary=[ll,ur]
|
self.boundary=[ll,ur]
|
||||||
|
|
||||||
def update_boundary(self):
|
def update_boundary(self):
|
||||||
|
|
@ -142,8 +143,8 @@ class instance(geometry):
|
||||||
self.rotate = rotate
|
self.rotate = rotate
|
||||||
self.offset = vector(offset).snap_to_grid()
|
self.offset = vector(offset).snap_to_grid()
|
||||||
self.mirror = mirror
|
self.mirror = mirror
|
||||||
self.width = mod.width
|
self.width = round_to_grid(mod.width)
|
||||||
self.height = mod.height
|
self.height = round_to_grid(mod.height)
|
||||||
self.compute_boundary(offset,mirror,rotate)
|
self.compute_boundary(offset,mirror,rotate)
|
||||||
|
|
||||||
debug.info(4, "creating instance: " + self.name)
|
debug.info(4, "creating instance: " + self.name)
|
||||||
|
|
@ -191,15 +192,15 @@ class instance(geometry):
|
||||||
self.mod.gds_write_file(self.gds)
|
self.mod.gds_write_file(self.gds)
|
||||||
# now write an instance of my module/structure
|
# now write an instance of my module/structure
|
||||||
new_layout.addInstance(self.gds,
|
new_layout.addInstance(self.gds,
|
||||||
offsetInMicrons=self.offset,
|
offsetInMicrons=self.offset,
|
||||||
mirror=self.mirror,
|
mirror=self.mirror,
|
||||||
rotate=self.rotate)
|
rotate=self.rotate)
|
||||||
|
|
||||||
def place(self, offset, mirror="R0", rotate=0):
|
def place(self, offset, mirror="R0", rotate=0):
|
||||||
""" This updates the placement of an instance. """
|
""" This updates the placement of an instance. """
|
||||||
debug.info(3, "placing instance {}".format(self.name))
|
debug.info(3, "placing instance {}".format(self.name))
|
||||||
# Update the placement of an already added instance
|
# Update the placement of an already added instance
|
||||||
self.offset = vector(offset)
|
self.offset = vector(offset).snap_to_grid()
|
||||||
self.mirror = mirror
|
self.mirror = mirror
|
||||||
self.rotate = rotate
|
self.rotate = rotate
|
||||||
self.update_boundary()
|
self.update_boundary()
|
||||||
|
|
@ -238,7 +239,7 @@ class instance(geometry):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
""" override print function output """
|
""" 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):
|
def __repr__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
|
|
@ -260,13 +261,13 @@ class path(geometry):
|
||||||
# supported right now. It might not work in gdsMill.
|
# supported right now. It might not work in gdsMill.
|
||||||
assert(0)
|
assert(0)
|
||||||
|
|
||||||
def gds_write_file(self, newLayout):
|
def gds_write_file(self, new_layout):
|
||||||
"""Writes the path to GDS"""
|
"""Writes the path to GDS"""
|
||||||
debug.info(4, "writing path (" + str(self.layerNumber) + "): " + self.coordinates)
|
debug.info(4, "writing path (" + str(self.layerNumber) + "): " + self.coordinates)
|
||||||
newLayout.addPath(layerNumber=self.layerNumber,
|
new_layout.addPath(layerNumber=self.layerNumber,
|
||||||
purposeNumber=0,
|
purposeNumber=0,
|
||||||
coordinates=self.coordinates,
|
coordinates=self.coordinates,
|
||||||
width=self.path_width)
|
width=self.path_width)
|
||||||
|
|
||||||
def get_blockages(self, layer):
|
def get_blockages(self, layer):
|
||||||
""" Fail since we don't support paths yet. """
|
""" Fail since we don't support paths yet. """
|
||||||
|
|
@ -301,15 +302,15 @@ class label(geometry):
|
||||||
|
|
||||||
debug.info(4,"creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset))
|
debug.info(4,"creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset))
|
||||||
|
|
||||||
def gds_write_file(self, newLayout):
|
def gds_write_file(self, new_layout):
|
||||||
"""Writes the text label to GDS"""
|
"""Writes the text label to GDS"""
|
||||||
debug.info(4, "writing label (" + str(self.layerNumber) + "): " + self.text)
|
debug.info(4, "writing label (" + str(self.layerNumber) + "): " + self.text)
|
||||||
newLayout.addText(text=self.text,
|
new_layout.addText(text=self.text,
|
||||||
layerNumber=self.layerNumber,
|
layerNumber=self.layerNumber,
|
||||||
purposeNumber=0,
|
purposeNumber=0,
|
||||||
offsetInMicrons=self.offset,
|
offsetInMicrons=self.offset,
|
||||||
magnification=self.zoom,
|
magnification=self.zoom,
|
||||||
rotate=None)
|
rotate=None)
|
||||||
|
|
||||||
def get_blockages(self, layer):
|
def get_blockages(self, layer):
|
||||||
""" Returns an empty list since text cannot be blockages. """
|
""" Returns an empty list since text cannot be blockages. """
|
||||||
|
|
@ -321,7 +322,7 @@ class label(geometry):
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
""" override print function output """
|
""" 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):
|
class rectangle(geometry):
|
||||||
"""Represents a rectangular shape"""
|
"""Represents a rectangular shape"""
|
||||||
|
|
@ -333,8 +334,8 @@ class rectangle(geometry):
|
||||||
self.layerNumber = layerNumber
|
self.layerNumber = layerNumber
|
||||||
self.offset = vector(offset).snap_to_grid()
|
self.offset = vector(offset).snap_to_grid()
|
||||||
self.size = vector(width, height).snap_to_grid()
|
self.size = vector(width, height).snap_to_grid()
|
||||||
self.width = self.size.x
|
self.width = round_to_grid(self.size.x)
|
||||||
self.height = self.size.y
|
self.height = round_to_grid(self.size.y)
|
||||||
self.compute_boundary(offset,"",0)
|
self.compute_boundary(offset,"",0)
|
||||||
|
|
||||||
debug.info(4, "creating rectangle (" + str(self.layerNumber) + "): "
|
debug.info(4, "creating rectangle (" + str(self.layerNumber) + "): "
|
||||||
|
|
@ -348,16 +349,16 @@ class rectangle(geometry):
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def gds_write_file(self, newLayout):
|
def gds_write_file(self, new_layout):
|
||||||
"""Writes the rectangular shape to GDS"""
|
"""Writes the rectangular shape to GDS"""
|
||||||
debug.info(4, "writing rectangle (" + str(self.layerNumber) + "):"
|
debug.info(4, "writing rectangle (" + str(self.layerNumber) + "):"
|
||||||
+ str(self.width) + "x" + str(self.height) + " @ " + str(self.offset))
|
+ str(self.width) + "x" + str(self.height) + " @ " + str(self.offset))
|
||||||
newLayout.addBox(layerNumber=self.layerNumber,
|
new_layout.addBox(layerNumber=self.layerNumber,
|
||||||
purposeNumber=0,
|
purposeNumber=0,
|
||||||
offsetInMicrons=self.offset,
|
offsetInMicrons=self.offset,
|
||||||
width=self.width,
|
width=self.width,
|
||||||
height=self.height,
|
height=self.height,
|
||||||
center=False)
|
center=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class layout(lef.lef):
|
||||||
self.insts = [] # Holds module/cell layout instances
|
self.insts = [] # Holds module/cell layout instances
|
||||||
self.objs = [] # Holds all other objects (labels, geometries, etc)
|
self.objs = [] # Holds all other objects (labels, geometries, etc)
|
||||||
self.pin_map = {} # Holds name->pin_layout map for all pins
|
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.is_library_cell = False # Flag for library cells
|
||||||
self.gds_read()
|
self.gds_read()
|
||||||
|
|
||||||
|
|
@ -134,11 +134,13 @@ class layout(lef.lef):
|
||||||
return inst
|
return inst
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def add_rect(self, layer, offset, width=0, height=0):
|
def add_rect(self, layer, offset, width=None, height=None):
|
||||||
"""Adds a rectangle on a given layer,offset with width and height"""
|
"""
|
||||||
if width==0:
|
Adds a rectangle on a given layer,offset with width and height
|
||||||
|
"""
|
||||||
|
if not width:
|
||||||
width=drc["minwidth_{}".format(layer)]
|
width=drc["minwidth_{}".format(layer)]
|
||||||
if height==0:
|
if not height:
|
||||||
height=drc["minwidth_{}".format(layer)]
|
height=drc["minwidth_{}".format(layer)]
|
||||||
# negative layers indicate "unused" layers in a given technology
|
# negative layers indicate "unused" layers in a given technology
|
||||||
layer_num = techlayer[layer]
|
layer_num = techlayer[layer]
|
||||||
|
|
@ -147,11 +149,13 @@ class layout(lef.lef):
|
||||||
return self.objs[-1]
|
return self.objs[-1]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def add_rect_center(self, layer, offset, width=0, height=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 width==0:
|
Adds a rectangle on a given layer at the center point with width and height
|
||||||
|
"""
|
||||||
|
if not width:
|
||||||
width=drc["minwidth_{}".format(layer)]
|
width=drc["minwidth_{}".format(layer)]
|
||||||
if height==0:
|
if not height:
|
||||||
height=drc["minwidth_{}".format(layer)]
|
height=drc["minwidth_{}".format(layer)]
|
||||||
# negative layers indicate "unused" layers in a given technology
|
# negative layers indicate "unused" layers in a given technology
|
||||||
layer_num = techlayer[layer]
|
layer_num = techlayer[layer]
|
||||||
|
|
@ -163,7 +167,9 @@ class layout(lef.lef):
|
||||||
|
|
||||||
|
|
||||||
def add_segment_center(self, layer, start, end):
|
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)]
|
minwidth_layer = drc["minwidth_{}".format(layer)]
|
||||||
if start.x!=end.x and start.y!=end.y:
|
if start.x!=end.x and start.y!=end.y:
|
||||||
debug.error("Nonrectilinear center rect!",-1)
|
debug.error("Nonrectilinear center rect!",-1)
|
||||||
|
|
@ -177,7 +183,9 @@ class layout(lef.lef):
|
||||||
|
|
||||||
|
|
||||||
def get_pin(self, text):
|
def get_pin(self, text):
|
||||||
""" Return the pin or list of pins """
|
"""
|
||||||
|
Return the pin or list of pins
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
if len(self.pin_map[text])>1:
|
if len(self.pin_map[text])>1:
|
||||||
debug.error("Should use a pin iterator since more than one pin {}".format(text),-1)
|
debug.error("Should use a pin iterator since more than one pin {}".format(text),-1)
|
||||||
|
|
@ -192,8 +200,13 @@ class layout(lef.lef):
|
||||||
|
|
||||||
|
|
||||||
def get_pins(self, text):
|
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=""):
|
def copy_layout_pin(self, instance, pin_name, new_name=""):
|
||||||
"""
|
"""
|
||||||
|
|
@ -207,7 +220,9 @@ class layout(lef.lef):
|
||||||
self.add_layout_pin(new_name, pin.layer, pin.ll(), pin.width(), pin.height())
|
self.add_layout_pin(new_name, pin.layer, pin.ll(), pin.width(), pin.height())
|
||||||
|
|
||||||
def add_layout_pin_segment_center(self, text, layer, start, end):
|
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.")
|
debug.check(start.x==end.x or start.y==end.y,"Cannot have a non-manhatten layout pin.")
|
||||||
|
|
||||||
|
|
@ -232,9 +247,9 @@ class layout(lef.lef):
|
||||||
|
|
||||||
def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None):
|
def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None):
|
||||||
""" Creates a path like pin with center-line convention """
|
""" Creates a path like pin with center-line convention """
|
||||||
if width==None:
|
if not width:
|
||||||
width=drc["minwidth_{0}".format(layer)]
|
width=drc["minwidth_{0}".format(layer)]
|
||||||
if height==None:
|
if not height:
|
||||||
height=drc["minwidth_{0}".format(layer)]
|
height=drc["minwidth_{0}".format(layer)]
|
||||||
|
|
||||||
ll_offset = offset - vector(0.5*width,0.5*height)
|
ll_offset = offset - vector(0.5*width,0.5*height)
|
||||||
|
|
@ -243,14 +258,18 @@ class layout(lef.lef):
|
||||||
|
|
||||||
|
|
||||||
def remove_layout_pin(self, text):
|
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]=[]
|
self.pin_map[text]=[]
|
||||||
|
|
||||||
def add_layout_pin(self, text, layer, offset, width=None, height=None):
|
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)]
|
width=drc["minwidth_{0}".format(layer)]
|
||||||
if height==None:
|
if not height:
|
||||||
height=drc["minwidth_{0}".format(layer)]
|
height=drc["minwidth_{0}".format(layer)]
|
||||||
|
|
||||||
new_pin = pin_layout(text, [offset,offset+vector(width,height)], layer)
|
new_pin = pin_layout(text, [offset,offset+vector(width,height)], layer)
|
||||||
|
|
@ -270,13 +289,14 @@ class layout(lef.lef):
|
||||||
return new_pin
|
return new_pin
|
||||||
|
|
||||||
def add_label_pin(self, text, layer, offset, width=None, height=None):
|
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
|
actual pin but a named net so that we can add a correspondence point
|
||||||
in LVS.
|
in LVS.
|
||||||
"""
|
"""
|
||||||
if width==None:
|
if not width:
|
||||||
width=drc["minwidth_{0}".format(layer)]
|
width=drc["minwidth_{0}".format(layer)]
|
||||||
if height==None:
|
if not height:
|
||||||
height=drc["minwidth_{0}".format(layer)]
|
height=drc["minwidth_{0}".format(layer)]
|
||||||
self.add_rect(layer=layer,
|
self.add_rect(layer=layer,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
|
|
@ -313,7 +333,7 @@ class layout(lef.lef):
|
||||||
position_list=coordinates,
|
position_list=coordinates,
|
||||||
width=width)
|
width=width)
|
||||||
|
|
||||||
def add_route(self, layers, coordinates):
|
def add_route(self, layers, coordinates, layer_widths):
|
||||||
"""Connects a routing path on given layer,coordinates,width. The
|
"""Connects a routing path on given layer,coordinates,width. The
|
||||||
layers are the (horizontal, via, vertical). add_wire assumes
|
layers are the (horizontal, via, vertical). add_wire assumes
|
||||||
preferred direction routing whereas this includes layers in
|
preferred direction routing whereas this includes layers in
|
||||||
|
|
@ -324,7 +344,8 @@ class layout(lef.lef):
|
||||||
# add an instance of our path that breaks down into rectangles and contacts
|
# add an instance of our path that breaks down into rectangles and contacts
|
||||||
route.route(obj=self,
|
route.route(obj=self,
|
||||||
layer_stack=layers,
|
layer_stack=layers,
|
||||||
path=coordinates)
|
path=coordinates,
|
||||||
|
layer_widths=layer_widths)
|
||||||
|
|
||||||
|
|
||||||
def add_wire(self, layers, coordinates):
|
def add_wire(self, layers, coordinates):
|
||||||
|
|
@ -432,59 +453,66 @@ class layout(lef.lef):
|
||||||
|
|
||||||
# open the gds file if it exists or else create a blank layout
|
# open the gds file if it exists or else create a blank layout
|
||||||
if os.path.isfile(self.gds_file):
|
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.is_library_cell=True
|
||||||
self.gds = gdsMill.VlsiLayout(units=GDS["unit"])
|
self.gds = gdsMill.VlsiLayout(units=GDS["unit"])
|
||||||
reader = gdsMill.Gds2reader(self.gds)
|
reader = gdsMill.Gds2reader(self.gds)
|
||||||
reader.loadFromFile(self.gds_file)
|
reader.loadFromFile(self.gds_file)
|
||||||
else:
|
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"])
|
self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"])
|
||||||
|
|
||||||
def print_gds(self, gds_file=None):
|
def print_gds(self, gds_file=None):
|
||||||
"""Print the gds file (not the vlsi class) to the terminal """
|
"""Print the gds file (not the vlsi class) to the terminal """
|
||||||
if gds_file == None:
|
if gds_file == None:
|
||||||
gds_file = self.gds_file
|
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"])
|
arrayCellLayout = gdsMill.VlsiLayout(units=GDS["unit"])
|
||||||
reader = gdsMill.Gds2reader(arrayCellLayout, debugToTerminal=1)
|
reader = gdsMill.Gds2reader(arrayCellLayout, debugToTerminal=1)
|
||||||
reader.loadFromFile(gds_file)
|
reader.loadFromFile(gds_file)
|
||||||
|
|
||||||
def clear_visited(self):
|
def clear_visited(self):
|
||||||
""" Recursively clear the visited flag """
|
""" Recursively clear the visited flag """
|
||||||
if not self.visited:
|
self.visited = []
|
||||||
for i in self.insts:
|
|
||||||
i.mod.clear_visited()
|
|
||||||
self.visited = False
|
|
||||||
|
|
||||||
def gds_write_file(self, newLayout):
|
def gds_write_file(self, gds_layout):
|
||||||
"""Recursive GDS write function"""
|
"""Recursive GDS write function"""
|
||||||
# Visited means that we already prepared self.gds for this subtree
|
# Visited means that we already prepared self.gds for this subtree
|
||||||
if self.visited:
|
if self.name in self.visited:
|
||||||
return
|
return
|
||||||
for i in self.insts:
|
for i in self.insts:
|
||||||
i.gds_write_file(newLayout)
|
i.gds_write_file(gds_layout)
|
||||||
for i in self.objs:
|
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_name in self.pin_map.keys():
|
||||||
for pin in self.pin_map[pin_name]:
|
for pin in self.pin_map[pin_name]:
|
||||||
pin.gds_write_file(newLayout)
|
pin.gds_write_file(gds_layout)
|
||||||
self.visited = True
|
self.visited.append(self.name)
|
||||||
|
|
||||||
def gds_write(self, gds_name):
|
def gds_write(self, gds_name):
|
||||||
"""Write the entire gds of the object to the file."""
|
"""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)
|
writer = gdsMill.Gds2writer(self.gds)
|
||||||
# MRG: 3/2/18 We don't want to clear the visited flag since
|
# 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
|
# this would result in duplicates of all instances being placed in self.gds
|
||||||
# which may have been previously processed!
|
# 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
|
# recursively create all the remaining objects
|
||||||
self.gds_write_file(self.gds)
|
self.gds_write_file(self.gds)
|
||||||
|
|
||||||
# populates the xyTree data structure for gds
|
# populates the xyTree data structure for gds
|
||||||
# self.gds.prepareForWrite()
|
# self.gds.prepareForWrite()
|
||||||
writer.writeToFile(gds_name)
|
writer.writeToFile(gds_name)
|
||||||
|
debug.info(3, "Done writing to {}".format(gds_name))
|
||||||
|
|
||||||
def get_boundary(self):
|
def get_boundary(self):
|
||||||
""" Return the lower-left and upper-right coordinates of boundary """
|
""" Return the lower-left and upper-right coordinates of boundary """
|
||||||
|
|
@ -875,6 +903,22 @@ class layout(lef.lef):
|
||||||
width=xmax-xmin,
|
width=xmax-xmin,
|
||||||
height=ymax-ymin)
|
height=ymax-ymin)
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
def add_power_pin(self, name, loc, rotate=90):
|
||||||
"""
|
"""
|
||||||
Add a single power pin from M3 down to M1 at the given center location
|
Add a single power pin from M3 down to M1 at the given center location
|
||||||
|
|
@ -1008,7 +1052,7 @@ class layout(lef.lef):
|
||||||
def pdf_write(self, pdf_name):
|
def pdf_write(self, pdf_name):
|
||||||
# NOTE: Currently does not work (Needs further research)
|
# NOTE: Currently does not work (Needs further research)
|
||||||
#self.pdf_name = self.name + ".pdf"
|
#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)
|
pdf = gdsMill.pdfLayout(self.gds)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -91,9 +91,9 @@ class spice(verilog.verilog):
|
||||||
group of modules are generated."""
|
group of modules are generated."""
|
||||||
|
|
||||||
if (check and (len(self.insts[-1].mod.pins) != len(args))):
|
if (check and (len(self.insts[-1].mod.pins) != len(args))):
|
||||||
import pprint
|
from pprint import pformat
|
||||||
modpins_string=pprint.pformat(self.insts[-1].mod.pins)
|
modpins_string=pformat(self.insts[-1].mod.pins)
|
||||||
argpins_string=pprint.pformat(args)
|
argpins_string=pformat(args)
|
||||||
debug.error("Connections: {}".format(modpins_string))
|
debug.error("Connections: {}".format(modpins_string))
|
||||||
debug.error("Connections: {}".format(argpins_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),
|
debug.error("Number of net connections ({0}) does not match last instance ({1})".format(len(self.insts[-1].mod.pins),
|
||||||
|
|
@ -101,9 +101,9 @@ class spice(verilog.verilog):
|
||||||
self.conns.append(args)
|
self.conns.append(args)
|
||||||
|
|
||||||
if check and (len(self.insts)!=len(self.conns)):
|
if check and (len(self.insts)!=len(self.conns)):
|
||||||
import pprint
|
from pprint import pformat
|
||||||
insts_string=pprint.pformat(self.insts)
|
insts_string=pformat(self.insts)
|
||||||
conns_string=pprint.pformat(self.conns)
|
conns_string=pformat(self.conns)
|
||||||
|
|
||||||
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
|
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
|
||||||
len(self.insts),
|
len(self.insts),
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,26 @@ class pin_layout:
|
||||||
return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1])
|
return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1])
|
||||||
|
|
||||||
def __repr__(self):
|
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):
|
def __eq__(self, other):
|
||||||
""" Check if these are the same pins for duplicate checks """
|
""" Check if these are the same pins for duplicate checks """
|
||||||
if isinstance(other, self.__class__):
|
if isinstance(other, self.__class__):
|
||||||
|
|
@ -46,7 +63,7 @@ class pin_layout:
|
||||||
and return the new rectangle.
|
and return the new rectangle.
|
||||||
"""
|
"""
|
||||||
if not spacing:
|
if not spacing:
|
||||||
spacing = drc["{0}_to_{0}".format(self.layer)]
|
spacing = 0.5*drc("{0}_to_{0}".format(self.layer))
|
||||||
|
|
||||||
(ll,ur) = self.rect
|
(ll,ur) = self.rect
|
||||||
spacing = vector(spacing, spacing)
|
spacing = vector(spacing, spacing)
|
||||||
|
|
@ -54,21 +71,39 @@ class pin_layout:
|
||||||
newur = ur + spacing
|
newur = ur + spacing
|
||||||
|
|
||||||
return (newll, newur)
|
return (newll, newur)
|
||||||
|
|
||||||
def overlaps(self, other):
|
def intersection(self, other):
|
||||||
""" Check if a shape overlaps with a rectangle """
|
""" Check if a shape overlaps with a rectangle """
|
||||||
(ll,ur) = self.rect
|
(ll,ur) = self.rect
|
||||||
(oll,our) = other.rect
|
(oll,our) = other.rect
|
||||||
# Start assuming no overlaps
|
|
||||||
|
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
|
x_overlaps = False
|
||||||
y_overlaps = False
|
|
||||||
# check if self is within other x range
|
# 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):
|
if (ll.x >= oll.x and ll.x <= our.x) or (ur.x >= oll.x and ur.x <= our.x):
|
||||||
x_overlaps = True
|
x_overlaps = True
|
||||||
# check if other is within self x range
|
# 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):
|
if (oll.x >= ll.x and oll.x <= ur.x) or (our.x >= ll.x and our.x <= ur.x):
|
||||||
x_overlaps = True
|
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
|
# 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):
|
if (ll.y >= oll.y and ll.y <= our.y) or (ur.y >= oll.y and ur.y <= our.y):
|
||||||
y_overlaps = True
|
y_overlaps = True
|
||||||
|
|
@ -76,7 +111,42 @@ class pin_layout:
|
||||||
if (oll.y >= ll.y and oll.y <= ur.y) or (our.y >= ll.y and our.y <= ur.y):
|
if (oll.y >= ll.y and oll.y <= ur.y) or (our.y >= ll.y and our.y <= ur.y):
|
||||||
y_overlaps = True
|
y_overlaps = True
|
||||||
|
|
||||||
|
return y_overlaps
|
||||||
|
|
||||||
|
def contains(self, other):
|
||||||
|
""" Check if a shape contains another rectangle """
|
||||||
|
# Can only overlap on the same layer
|
||||||
|
if self.layer != other.layer:
|
||||||
|
return False
|
||||||
|
|
||||||
|
(ll,ur) = self.rect
|
||||||
|
(oll,our) = other.rect
|
||||||
|
|
||||||
|
|
||||||
|
if not (oll.y >= ll.y and oll.y <= ur.y):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not (oll.x >= ll.x and oll.x <= ur.x):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
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
|
return x_overlaps and y_overlaps
|
||||||
|
|
||||||
|
def area(self):
|
||||||
|
""" Return the area. """
|
||||||
|
return self.height()*self.width()
|
||||||
|
|
||||||
def height(self):
|
def height(self):
|
||||||
""" Return height. Abs is for pre-normalized value."""
|
""" Return height. Abs is for pre-normalized value."""
|
||||||
return abs(self.rect[1].y-self.rect[0].y)
|
return abs(self.rect[1].y-self.rect[0].y)
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,17 @@ class route(design):
|
||||||
"""
|
"""
|
||||||
Object route (used by the router module)
|
Object route (used by the router module)
|
||||||
Add a route of minimium metal width between a set of points.
|
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
|
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.
|
The points are the center of the wire.
|
||||||
This can have non-preferred direction routing.
|
This can have non-preferred direction routing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
unique_route_id = 0
|
unique_route_id = 0
|
||||||
|
|
||||||
def __init__(self, obj, layer_stack, path):
|
def __init__(self, obj, layer_stack, path, layer_widths=[None,1,None]):
|
||||||
name = "route_{0}".format(route.unique_route_id)
|
name = "route_{0}".format(route.unique_route_id)
|
||||||
route.unique_route_id += 1
|
route.unique_route_id += 1
|
||||||
design.__init__(self, name)
|
design.__init__(self, name)
|
||||||
|
|
@ -26,6 +28,7 @@ class route(design):
|
||||||
|
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
self.layer_stack = layer_stack
|
self.layer_stack = layer_stack
|
||||||
|
self.layer_widths = layer_widths
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
self.setup_layers()
|
self.setup_layers()
|
||||||
|
|
@ -33,16 +36,16 @@ class route(design):
|
||||||
|
|
||||||
|
|
||||||
def setup_layers(self):
|
def setup_layers(self):
|
||||||
(horiz_layer, via_layer, vert_layer) = self.layer_stack
|
(self.horiz_layer_name, self.via_layer, self.vert_layer_name) = self.layer_stack
|
||||||
self.via_layer_name = via_layer
|
(self.horiz_layer_width, self.num_vias, self.vert_layer_width) = self.layer_widths
|
||||||
|
|
||||||
self.vert_layer_name = vert_layer
|
if not self.vert_layer_width:
|
||||||
self.vert_layer_width = drc["minwidth_{0}".format(vert_layer)]
|
self.vert_layer_width = drc("minwidth_{0}".format(self.vert_layer_name))
|
||||||
|
if not self.horiz_layer_width:
|
||||||
self.horiz_layer_name = horiz_layer
|
self.horiz_layer_width = drc("minwidth_{0}".format(self.horiz_layer_name))
|
||||||
self.horiz_layer_width = drc["minwidth_{0}".format(horiz_layer)]
|
|
||||||
# offset this by 1/2 the via size
|
# 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):
|
def create_wires(self):
|
||||||
|
|
@ -63,7 +66,8 @@ class route(design):
|
||||||
#via_offset = vector(p0.x+0.5*self.c.width,p0.y+0.5*self.c.height)
|
#via_offset = vector(p0.x+0.5*self.c.width,p0.y+0.5*self.c.height)
|
||||||
# offset if rotated
|
# offset if rotated
|
||||||
via_offset = vector(p0.x+0.5*self.c.height,p0.y-0.5*self.c.width)
|
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(self.layer_stack,via_offset,size=via_size,rotate=90)
|
||||||
elif p0.x != p1.x and p0.y != p1.y: # diagonal!
|
elif p0.x != p1.x and p0.y != p1.y: # diagonal!
|
||||||
debug.error("Non-changing direction!")
|
debug.error("Non-changing direction!")
|
||||||
else:
|
else:
|
||||||
|
|
@ -79,14 +83,36 @@ class route(design):
|
||||||
self.draw_corner_wire(plist[-1][1])
|
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):
|
def draw_wire(self, p0, p1):
|
||||||
"""
|
"""
|
||||||
This draws a straight wire with layer_minwidth
|
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
|
# always route left to right or bottom to top
|
||||||
if p0.z != p1.z:
|
if p0.z != p1.z:
|
||||||
|
|
@ -120,8 +146,8 @@ class route(design):
|
||||||
""" This function adds the corner squares since the center
|
""" This function adds the corner squares since the center
|
||||||
line convention only draws to the center of the corner."""
|
line convention only draws to the center of the corner."""
|
||||||
|
|
||||||
layer_name = self.layer_stack[2*p0.z]
|
layer_width = self.get_layer_width(p0.z)
|
||||||
layer_width = drc["minwidth_{0}".format(layer_name)]
|
layer_name = self.get_layer_name(p0.z)
|
||||||
offset = vector(p0.x-0.5*layer_width,p0.y-0.5*layer_width)
|
offset = vector(p0.x-0.5*layer_width,p0.y-0.5*layer_width)
|
||||||
self.obj.add_rect(layer=layer_name,
|
self.obj.add_rect(layer=layer_name,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
|
|
|
||||||
|
|
@ -11,24 +11,24 @@ class vector():
|
||||||
concise vector operations, output, and other more complex
|
concise vector operations, output, and other more complex
|
||||||
data structures like lists.
|
data structures like lists.
|
||||||
"""
|
"""
|
||||||
def __init__(self, x, y=None):
|
def __init__(self, x, y=0):
|
||||||
""" init function support two init method"""
|
""" init function support two init method"""
|
||||||
# will take single input as a coordinate
|
# will take single input as a coordinate
|
||||||
if y==None:
|
if isinstance(x, (list,tuple,vector)):
|
||||||
self.x = x[0]
|
self.x = float(x[0])
|
||||||
self.y = x[1]
|
self.y = float(x[1])
|
||||||
#will take two inputs as the values of a coordinate
|
#will take two inputs as the values of a coordinate
|
||||||
else:
|
else:
|
||||||
self.x = x
|
self.x = float(x)
|
||||||
self.y = y
|
self.y = float(y)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
return "["+str(self.x)+","+str(self.y)+"]"
|
return "v["+str(self.x)+","+str(self.y)+"]"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
return "["+str(self.x)+","+str(self.y)+"]"
|
return "v["+str(self.x)+","+str(self.y)+"]"
|
||||||
|
|
||||||
def __setitem__(self, index, value):
|
def __setitem__(self, index, value):
|
||||||
"""
|
"""
|
||||||
|
|
@ -36,12 +36,12 @@ class vector():
|
||||||
can set value by vector[index]=value
|
can set value by vector[index]=value
|
||||||
"""
|
"""
|
||||||
if index==0:
|
if index==0:
|
||||||
self.x=value
|
self.x=float(value)
|
||||||
elif index==1:
|
elif index==1:
|
||||||
self.y=value
|
self.y=float(value)
|
||||||
else:
|
else:
|
||||||
self.x=value[0]
|
self.x=float(value[0])
|
||||||
self.y=value[1]
|
self.y=float(value[1])
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
"""
|
"""
|
||||||
|
|
@ -84,6 +84,14 @@ class vector():
|
||||||
"""
|
"""
|
||||||
return vector(other[0]- self.x, other[1] - self.y)
|
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):
|
def snap_to_grid(self):
|
||||||
self.x = self.snap_offset_to_grid(self.x)
|
self.x = self.snap_offset_to_grid(self.x)
|
||||||
self.y = self.snap_offset_to_grid(self.y)
|
self.y = self.snap_offset_to_grid(self.y)
|
||||||
|
|
|
||||||
|
|
@ -33,14 +33,14 @@ class wire(path):
|
||||||
self.via_layer_name = via_layer
|
self.via_layer_name = via_layer
|
||||||
|
|
||||||
self.vert_layer_name = vert_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_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,
|
via_connect = contact(self.layer_stack,
|
||||||
(1, 1))
|
(1, 1))
|
||||||
self.node_to_node = [drc["minwidth_" + str(self.horiz_layer_name)] + via_connect.width,
|
self.node_to_node = [drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.width,
|
||||||
drc["minwidth_" + str(self.horiz_layer_name)] + via_connect.height]
|
drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.height]
|
||||||
|
|
||||||
# create a 1x1 contact
|
# create a 1x1 contact
|
||||||
def create_vias(self):
|
def create_vias(self):
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,9 @@ class bitcell(design.design):
|
||||||
|
|
||||||
def list_bitcell_pins(self, col, row):
|
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 """
|
""" 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),
|
bitcell_pins = ["bl_{0}".format(col),
|
||||||
"br[{0}]".format(col),
|
"br_{0}".format(col),
|
||||||
"wl[{0}]".format(row),
|
"wl_{0}".format(row),
|
||||||
"vdd",
|
"vdd",
|
||||||
"gnd"]
|
"gnd"]
|
||||||
return bitcell_pins
|
return bitcell_pins
|
||||||
|
|
@ -0,0 +1,886 @@
|
||||||
|
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
|
||||||
|
self.bitline_offset = -self.active_width + 0.5*contact.m1m2.height + self.m2_space + self.m2_width
|
||||||
|
self.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.port_spacing) \
|
||||||
|
- self.num_w_ports*(self.write_nmos.active_width + self.port_spacing) \
|
||||||
|
- self.num_r_ports*(self.read_port_width + self.port_spacing) \
|
||||||
|
- self.bitline_offset - 0.5*self.m2_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.port_spacing \
|
||||||
|
- (k+1)*self.readwrite_nmos.active_width
|
||||||
|
|
||||||
|
right_readwrite_transistor_xpos = self.right_building_edge \
|
||||||
|
+ (k+1)*self.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 + 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 - 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.port_spacing \
|
||||||
|
- (k+1)*self.write_nmos.active_width
|
||||||
|
|
||||||
|
right_write_transistor_xpos = self.right_building_edge \
|
||||||
|
+ (k+1)*self.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 + 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 - 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.port_spacing \
|
||||||
|
- (k+1)*self.read_port_width
|
||||||
|
|
||||||
|
right_read_transistor_xpos = self.right_building_edge \
|
||||||
|
+ (k+1)*self.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 + 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 - 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)
|
||||||
|
|
||||||
|
supply_offset = vector(position.x, 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 list_read_bl_names(self):
|
||||||
|
""" Creates a list of bl pin names associated with read ports """
|
||||||
|
bl_pins = self.rw_bl_names + self.r_bl_names
|
||||||
|
return bl_pins
|
||||||
|
|
||||||
|
def list_read_br_names(self):
|
||||||
|
""" Creates a list of br pin names associated with read ports """
|
||||||
|
br_pins = self.rw_br_names + self.r_br_names
|
||||||
|
return br_pins
|
||||||
|
|
||||||
|
def list_write_bl_names(self):
|
||||||
|
""" Creates a list of bl pin names associated with write ports """
|
||||||
|
bl_pins = self.rw_bl_names + self.w_bl_names
|
||||||
|
return bl_pins
|
||||||
|
|
||||||
|
def list_write_br_names(self):
|
||||||
|
""" Creates a list of br pin names asscociated with write ports"""
|
||||||
|
br_pins = self.rw_br_names + self.w_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])
|
||||||
|
|
@ -56,14 +56,14 @@ class functional(simulation):
|
||||||
check = 0
|
check = 0
|
||||||
|
|
||||||
# First cycle idle
|
# First cycle idle
|
||||||
self.add_noop_all_ports("Idle at time {0}n".format(self.t_current),
|
debug_comment = self.cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, 0, self.t_current)
|
||||||
"0"*self.addr_size, "0"*self.word_size)
|
self.add_noop_all_ports(debug_comment, "0"*self.addr_size, "0"*self.word_size)
|
||||||
|
|
||||||
# Write at least once
|
# Write at least once
|
||||||
addr = self.gen_addr()
|
addr = self.gen_addr()
|
||||||
word = self.gen_data()
|
word = self.gen_data()
|
||||||
self.add_write("Writing {0} to address {1} (from port {2}) at time {3}n".format(word, addr, 0, self.t_current),
|
debug_comment = self.cycle_comment("write", word, addr, 0, self.t_current)
|
||||||
addr, word, 0)
|
self.add_write(debug_comment, addr, word, 0)
|
||||||
self.stored_words[addr] = word
|
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.
|
# 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.
|
||||||
|
|
@ -72,8 +72,8 @@ class functional(simulation):
|
||||||
if self.port_id[port] == "w":
|
if self.port_id[port] == "w":
|
||||||
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
|
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
|
||||||
else:
|
else:
|
||||||
self.add_read_one_port("Reading {0} from address {1} (from port {2}) at time {3}n".format(word, addr, port, self.t_current),
|
debug_comment = self.cycle_comment("read", word, addr, port, self.t_current)
|
||||||
addr, rw_read_data, port)
|
self.add_read_one_port(debug_comment, addr, rw_read_data, port)
|
||||||
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
|
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
|
||||||
check += 1
|
check += 1
|
||||||
self.cycle_times.append(self.t_current)
|
self.cycle_times.append(self.t_current)
|
||||||
|
|
@ -101,8 +101,8 @@ class functional(simulation):
|
||||||
if addr in w_addrs:
|
if addr in w_addrs:
|
||||||
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
|
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
|
||||||
else:
|
else:
|
||||||
self.add_write_one_port("Writing {0} to address {1} (from port {2}) at time {3}n".format(word, addr, port, self.t_current),
|
debug_comment = self.cycle_comment("write", word, addr, port, self.t_current)
|
||||||
addr, word, port)
|
self.add_write_one_port(debug_comment, addr, word, port)
|
||||||
self.stored_words[addr] = word
|
self.stored_words[addr] = word
|
||||||
w_addrs.append(addr)
|
w_addrs.append(addr)
|
||||||
else:
|
else:
|
||||||
|
|
@ -111,8 +111,8 @@ class functional(simulation):
|
||||||
if addr in w_addrs:
|
if addr in w_addrs:
|
||||||
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
|
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
|
||||||
else:
|
else:
|
||||||
self.add_read_one_port("Reading {0} from address {1} (from port {2}) at time {3}n".format(word, addr, port, self.t_current),
|
debug_comment = self.cycle_comment("read", word, addr, port, self.t_current)
|
||||||
addr, rw_read_data, port)
|
self.add_read_one_port(debug_comment, addr, rw_read_data, port)
|
||||||
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
|
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
|
||||||
check += 1
|
check += 1
|
||||||
|
|
||||||
|
|
@ -120,8 +120,8 @@ class functional(simulation):
|
||||||
self.t_current += self.period
|
self.t_current += self.period
|
||||||
|
|
||||||
# Last cycle idle needed to correctly measure the value on the second to last clock edge
|
# Last cycle idle needed to correctly measure the value on the second to last clock edge
|
||||||
self.add_noop_all_ports("Idle at time {0}n".format(self.t_current),
|
debug_comment = self.cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, 0, self.t_current)
|
||||||
"0"*self.addr_size, "0"*self.word_size)
|
self.add_noop_all_ports(debug_comment, "0"*self.addr_size, "0"*self.word_size)
|
||||||
|
|
||||||
def read_stim_results(self):
|
def read_stim_results(self):
|
||||||
# Extrat DOUT values from spice timing.lis
|
# Extrat DOUT values from spice timing.lis
|
||||||
|
|
@ -148,10 +148,11 @@ class functional(simulation):
|
||||||
def check_stim_results(self):
|
def check_stim_results(self):
|
||||||
for i in range(len(self.write_check)):
|
for i in range(len(self.write_check)):
|
||||||
if self.write_check[i][0] != self.read_check[i][0]:
|
if self.write_check[i][0] != self.read_check[i][0]:
|
||||||
error = "FAILED: {0} value {1} does not match written value {2} read at time {3}n".format(self.read_check[i][1],
|
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.read_check[i][0],
|
||||||
self.write_check[i][0],
|
self.write_check[i][0],
|
||||||
self.read_check[i][2])
|
int((self.read_check[i][2]-self.period)/self.period),
|
||||||
|
self.read_check[i][2])
|
||||||
return(0, error)
|
return(0, error)
|
||||||
return(1, "SUCCESS")
|
return(1, "SUCCESS")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ class lib:
|
||||||
|
|
||||||
self.characterize_corners()
|
self.characterize_corners()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def gen_port_names(self):
|
def gen_port_names(self):
|
||||||
"""Generates the port names to be written to the lib file"""
|
"""Generates the port names to be written to the lib file"""
|
||||||
#This is basically a copy and paste of whats in delay.py as well. Something more efficient should be done here.
|
#This is basically a copy and paste of whats in delay.py as well. Something more efficient should be done here.
|
||||||
|
|
@ -96,7 +98,7 @@ class lib:
|
||||||
debug.info(1,"Writing to {0}".format(lib_name))
|
debug.info(1,"Writing to {0}".format(lib_name))
|
||||||
self.characterize()
|
self.characterize()
|
||||||
self.lib.close()
|
self.lib.close()
|
||||||
|
self.parse_info()
|
||||||
def characterize(self):
|
def characterize(self):
|
||||||
""" Characterize the current corner. """
|
""" Characterize the current corner. """
|
||||||
|
|
||||||
|
|
@ -516,3 +518,37 @@ class lib:
|
||||||
else:
|
else:
|
||||||
self.times = self.sh.analyze(self.slews,self.slews)
|
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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -199,4 +199,25 @@ class simulation():
|
||||||
if port in self.write_index:
|
if port in self.write_index:
|
||||||
self.add_data(data,port)
|
self.add_data(data,port)
|
||||||
self.add_address(address, port)
|
self.add_address(address, port)
|
||||||
|
|
||||||
|
def 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 cylce {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 cylce {3} ({4}ns - {5}ns)".format(word,
|
||||||
|
addr,
|
||||||
|
port,
|
||||||
|
int(t_current/self.period),
|
||||||
|
t_current,
|
||||||
|
t_current+self.period)
|
||||||
|
return comment
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,8 @@ class trim_spice():
|
||||||
else:
|
else:
|
||||||
col_address = 0
|
col_address = 0
|
||||||
# 1. Keep cells in the bitcell array based on WL and BL
|
# 1. Keep cells in the bitcell array based on WL and BL
|
||||||
wl_name = "wl[{}]".format(wl_address)
|
wl_name = "wl_{}".format(wl_address)
|
||||||
bl_name = "bl[{}]".format(int(self.words_per_row*data_bit + col_address))
|
bl_name = "bl_{}".format(int(self.words_per_row*data_bit + col_address))
|
||||||
|
|
||||||
# Prepend info about the trimming
|
# Prepend info about the trimming
|
||||||
addr_msg = "Keeping {} address".format(address)
|
addr_msg = "Keeping {} address".format(address)
|
||||||
|
|
@ -75,8 +75,8 @@ class trim_spice():
|
||||||
self.sp_buffer.insert(0, "* WARNING: This is a TRIMMED NETLIST.")
|
self.sp_buffer.insert(0, "* WARNING: This is a TRIMMED NETLIST.")
|
||||||
|
|
||||||
|
|
||||||
wl_regex = r"wl\d*\[{}\]".format(wl_address)
|
wl_regex = r"wl\d*_{}".format(wl_address)
|
||||||
bl_regex = r"bl\d*\[{}\]".format(int(self.words_per_row*data_bit + col_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])
|
self.remove_insts("bitcell_array",[wl_regex,bl_regex])
|
||||||
|
|
||||||
# 2. Keep sense amps basd on BL
|
# 2. Keep sense amps basd on BL
|
||||||
|
|
@ -87,7 +87,7 @@ class trim_spice():
|
||||||
self.remove_insts("column_mux_array",[bl_regex])
|
self.remove_insts("column_mux_array",[bl_regex])
|
||||||
|
|
||||||
# 4. Keep write driver based on DATA
|
# 4. Keep write driver based on DATA
|
||||||
data_regex = r"data\[{}\]".format(data_bit)
|
data_regex = r"data_{}".format(data_bit)
|
||||||
self.remove_insts("write_driver_array",[data_regex])
|
self.remove_insts("write_driver_array",[data_regex])
|
||||||
|
|
||||||
# 5. Keep wordline driver based on WL
|
# 5. Keep wordline driver based on WL
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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('<','<').replace('"','"').replace('>',">")
|
||||||
|
|
||||||
|
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('<','<').replace('"','"').replace('>',">")
|
||||||
|
|
||||||
|
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" />'
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ supply_voltages = [1.0]
|
||||||
temperatures = [25]
|
temperatures = [25]
|
||||||
|
|
||||||
output_path = "temp"
|
output_path = "temp"
|
||||||
output_name = "sram_{0}_{1}_{2}_{3}".format(word_size,num_words,num_banks,tech_name)
|
output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name)
|
||||||
|
|
||||||
#Setting for multiport
|
#Setting for multiport
|
||||||
# netlist_only = True
|
# netlist_only = True
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,4 @@ supply_voltages = [ 5.0 ]
|
||||||
temperatures = [ 25 ]
|
temperatures = [ 25 ]
|
||||||
|
|
||||||
output_path = "temp"
|
output_path = "temp"
|
||||||
output_name = "sram_{0}_{1}_{2}_{3}".format(word_size,num_words,num_banks,tech_name)
|
output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name)
|
||||||
|
|
||||||
#Setting for multiport
|
|
||||||
# netlist_only = True
|
|
||||||
# bitcell = "pbitcell"
|
|
||||||
# replica_bitcell="replica_pbitcell"
|
|
||||||
# num_rw_ports = 1
|
|
||||||
# num_r_ports = 1
|
|
||||||
# num_w_ports = 0
|
|
||||||
|
|
|
||||||
|
|
@ -619,7 +619,8 @@ class VlsiLayout:
|
||||||
|
|
||||||
def updateBoundary(self,thisBoundary,cellBoundary):
|
def updateBoundary(self,thisBoundary,cellBoundary):
|
||||||
[left_bott_X,left_bott_Y,right_top_X,right_top_Y]=thisBoundary
|
[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
|
cellBoundary=thisBoundary
|
||||||
else:
|
else:
|
||||||
if cellBoundary[0]>left_bott_X:
|
if cellBoundary[0]>left_bott_X:
|
||||||
|
|
@ -713,9 +714,9 @@ class VlsiLayout:
|
||||||
# Convert to user units
|
# Convert to user units
|
||||||
new_boundaries = []
|
new_boundaries = []
|
||||||
for pin_boundary in pin_boundaries:
|
for pin_boundary in pin_boundaries:
|
||||||
new_boundaries.append([pin_boundary[0]*self.units[0],pin_boundary[1]*self.units[0],
|
new_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]])
|
pin_boundary[2]*self.units[0],pin_boundary[3]*self.units[0]]
|
||||||
|
new_boundaries.append(["p"+str(coordinate)+"_"+str(layer), layer, new_pin_boundary])
|
||||||
return new_boundaries
|
return new_boundaries
|
||||||
|
|
||||||
def getPinShapeByLabel(self,label_name):
|
def getPinShapeByLabel(self,label_name):
|
||||||
|
|
|
||||||
|
|
@ -100,9 +100,17 @@ def check_versions():
|
||||||
minor_required = 5
|
minor_required = 5
|
||||||
if not (major_python_version == major_required and minor_python_version >= minor_required):
|
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)
|
debug.error("Python {0}.{1} or greater is required.".format(major_required,minor_required),-1)
|
||||||
|
|
||||||
# FIXME: Check versions of other tools here??
|
# FIXME: Check versions of other tools here??
|
||||||
# or, this could be done in each module (e.g. verify, characterizer, etc.)
|
# 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):
|
def init_openram(config_file, is_unit_test=True):
|
||||||
"""Initialize the technology, paths, simulators, etc."""
|
"""Initialize the technology, paths, simulators, etc."""
|
||||||
|
|
@ -287,7 +295,8 @@ def setup_paths():
|
||||||
|
|
||||||
# Add all of the subdirs to the python path
|
# Add all of the subdirs to the python path
|
||||||
# These subdirs are modules and don't need to be added: characterizer, verify
|
# These subdirs are modules and don't need to be added: characterizer, verify
|
||||||
for subdir in ["gdsMill", "tests", "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)
|
full_path = "{0}/{1}".format(OPENRAM_HOME,subdir)
|
||||||
debug.check(os.path.isdir(full_path),
|
debug.check(os.path.isdir(full_path),
|
||||||
"$OPENRAM_HOME/{0} does not exist: {1}".format(subdir,full_path))
|
"$OPENRAM_HOME/{0} does not exist: {1}".format(subdir,full_path))
|
||||||
|
|
|
||||||
|
|
@ -68,13 +68,13 @@ class bank(design.design):
|
||||||
""" Adding pins for Bank module"""
|
""" Adding pins for Bank module"""
|
||||||
for port in range(self.total_read):
|
for port in range(self.total_read):
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
self.add_pin("dout{0}[{1}]".format(self.read_index[port],bit),"OUT")
|
self.add_pin("dout{0}_{1}".format(self.read_index[port],bit),"OUT")
|
||||||
for port in range(self.total_write):
|
for port in range(self.total_write):
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
self.add_pin("din{0}[{1}]".format(port,bit),"IN")
|
self.add_pin("din{0}_{1}".format(port,bit),"IN")
|
||||||
for port in range(self.total_ports):
|
for port in range(self.total_ports):
|
||||||
for bit in range(self.addr_size):
|
for bit in range(self.addr_size):
|
||||||
self.add_pin("addr{0}[{1}]".format(port,bit),"INPUT")
|
self.add_pin("addr{0}_{1}".format(port,bit),"INPUT")
|
||||||
|
|
||||||
# For more than one bank, we have a bank select and name
|
# For more than one bank, we have a bank select and name
|
||||||
# the signals gated_*.
|
# the signals gated_*.
|
||||||
|
|
@ -107,8 +107,7 @@ class bank(design.design):
|
||||||
if self.num_banks > 1:
|
if self.num_banks > 1:
|
||||||
self.route_bank_select()
|
self.route_bank_select()
|
||||||
|
|
||||||
self.route_vdd_gnd()
|
self.route_supplies()
|
||||||
|
|
||||||
|
|
||||||
def create_modules(self):
|
def create_modules(self):
|
||||||
""" Add modules. The order should not matter! """
|
""" Add modules. The order should not matter! """
|
||||||
|
|
@ -201,7 +200,7 @@ class bank(design.design):
|
||||||
self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width
|
self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width
|
||||||
|
|
||||||
# A space for wells or jogging m2
|
# A space for wells or jogging m2
|
||||||
self.m2_gap = max(2*drc["pwell_to_nwell"] + drc["well_enclosure_active"],
|
self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclosure_active"),
|
||||||
2*self.m2_pitch)
|
2*self.m2_pitch)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -286,10 +285,10 @@ class bank(design.design):
|
||||||
temp = []
|
temp = []
|
||||||
for col in range(self.num_cols):
|
for col in range(self.num_cols):
|
||||||
for bitline in self.total_bitline_list:
|
for bitline in self.total_bitline_list:
|
||||||
temp.append(bitline+"[{0}]".format(col))
|
temp.append(bitline+"_{0}".format(col))
|
||||||
for row in range(self.num_rows):
|
for row in range(self.num_rows):
|
||||||
for wordline in self.total_wl_list:
|
for wordline in self.total_wl_list:
|
||||||
temp.append(wordline+"[{0}]".format(row))
|
temp.append(wordline+"_{0}".format(row))
|
||||||
temp.append("vdd")
|
temp.append("vdd")
|
||||||
temp.append("gnd")
|
temp.append("gnd")
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
@ -309,8 +308,8 @@ class bank(design.design):
|
||||||
mod=self.precharge_array[port]))
|
mod=self.precharge_array[port]))
|
||||||
temp = []
|
temp = []
|
||||||
for i in range(self.num_cols):
|
for i in range(self.num_cols):
|
||||||
temp.append(self.read_bl_list[port]+"[{0}]".format(i))
|
temp.append(self.read_bl_list[port]+"_{0}".format(i))
|
||||||
temp.append(self.read_br_list[port]+"[{0}]".format(i))
|
temp.append(self.read_br_list[port]+"_{0}".format(i))
|
||||||
temp.extend([self.prefix+"clk_buf_bar{0}".format(self.read_index[port]), "vdd"])
|
temp.extend([self.prefix+"clk_buf_bar{0}".format(self.read_index[port]), "vdd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
@ -338,13 +337,13 @@ class bank(design.design):
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
for col in range(self.num_cols):
|
for col in range(self.num_cols):
|
||||||
temp.append(self.total_bl_list[port]+"[{0}]".format(col))
|
temp.append(self.total_bl_list[port]+"_{0}".format(col))
|
||||||
temp.append(self.total_br_list[port]+"[{0}]".format(col))
|
temp.append(self.total_br_list[port]+"_{0}".format(col))
|
||||||
for word in range(self.words_per_row):
|
for word in range(self.words_per_row):
|
||||||
temp.append("sel{0}[{1}]".format(port,word))
|
temp.append("sel{0}_{1}".format(port,word))
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
temp.append(self.total_bl_list[port]+"_out[{0}]".format(bit))
|
temp.append(self.total_bl_list[port]+"_out_{0}".format(bit))
|
||||||
temp.append(self.total_br_list[port]+"_out[{0}]".format(bit))
|
temp.append(self.total_br_list[port]+"_out_{0}".format(bit))
|
||||||
temp.append("gnd")
|
temp.append("gnd")
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
@ -372,13 +371,13 @@ class bank(design.design):
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
temp.append("dout{0}[{1}]".format(self.read_index[port],bit))
|
temp.append("dout{0}_{1}".format(self.read_index[port],bit))
|
||||||
if self.words_per_row == 1:
|
if self.words_per_row == 1:
|
||||||
temp.append(self.read_bl_list[port]+"[{0}]".format(bit))
|
temp.append(self.read_bl_list[port]+"_{0}".format(bit))
|
||||||
temp.append(self.read_br_list[port]+"[{0}]".format(bit))
|
temp.append(self.read_br_list[port]+"_{0}".format(bit))
|
||||||
else:
|
else:
|
||||||
temp.append(self.read_bl_list[port]+"_out[{0}]".format(bit))
|
temp.append(self.read_bl_list[port]+"_out_{0}".format(bit))
|
||||||
temp.append(self.read_br_list[port]+"_out[{0}]".format(bit))
|
temp.append(self.read_br_list[port]+"_out_{0}".format(bit))
|
||||||
|
|
||||||
temp.extend([self.prefix+"s_en{}".format(self.read_index[port]), "vdd", "gnd"])
|
temp.extend([self.prefix+"s_en{}".format(self.read_index[port]), "vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
@ -403,14 +402,14 @@ class bank(design.design):
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
temp.append("din{0}[{1}]".format(port,bit))
|
temp.append("din{0}_{1}".format(port,bit))
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
if (self.words_per_row == 1):
|
if (self.words_per_row == 1):
|
||||||
temp.append(self.write_bl_list[port]+"[{0}]".format(bit))
|
temp.append(self.write_bl_list[port]+"_{0}".format(bit))
|
||||||
temp.append(self.write_br_list[port]+"[{0}]".format(bit))
|
temp.append(self.write_br_list[port]+"_{0}".format(bit))
|
||||||
else:
|
else:
|
||||||
temp.append(self.write_bl_list[port]+"_out[{0}]".format(bit))
|
temp.append(self.write_bl_list[port]+"_out_{0}".format(bit))
|
||||||
temp.append(self.write_br_list[port]+"_out[{0}]".format(bit))
|
temp.append(self.write_br_list[port]+"_out_{0}".format(bit))
|
||||||
temp.extend([self.prefix+"w_en{0}".format(port), "vdd", "gnd"])
|
temp.extend([self.prefix+"w_en{0}".format(port), "vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
@ -435,9 +434,9 @@ class bank(design.design):
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
for bit in range(self.row_addr_size):
|
for bit in range(self.row_addr_size):
|
||||||
temp.append("addr{0}[{1}]".format(port,bit+self.col_addr_size))
|
temp.append("addr{0}_{1}".format(port,bit+self.col_addr_size))
|
||||||
for row in range(self.num_rows):
|
for row in range(self.num_rows):
|
||||||
temp.append("dec_out{0}[{1}]".format(port,row))
|
temp.append("dec_out{0}_{1}".format(port,row))
|
||||||
temp.extend(["vdd", "gnd"])
|
temp.extend(["vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
@ -467,9 +466,9 @@ class bank(design.design):
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
for row in range(self.num_rows):
|
for row in range(self.num_rows):
|
||||||
temp.append("dec_out{0}[{1}]".format(port,row))
|
temp.append("dec_out{0}_{1}".format(port,row))
|
||||||
for row in range(self.num_rows):
|
for row in range(self.num_rows):
|
||||||
temp.append(self.total_wl_list[port]+"[{0}]".format(row))
|
temp.append(self.total_wl_list[port]+"_{0}".format(row))
|
||||||
temp.append(self.prefix+"clk_buf{0}".format(port))
|
temp.append(self.prefix+"clk_buf{0}".format(port))
|
||||||
temp.append("vdd")
|
temp.append("vdd")
|
||||||
temp.append("gnd")
|
temp.append("gnd")
|
||||||
|
|
@ -511,9 +510,9 @@ class bank(design.design):
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
for bit in range(self.col_addr_size):
|
for bit in range(self.col_addr_size):
|
||||||
temp.append("addr{0}[{1}]".format(port,bit))
|
temp.append("addr{0}_{1}".format(port,bit))
|
||||||
for bit in range(self.num_col_addr_lines):
|
for bit in range(self.num_col_addr_lines):
|
||||||
temp.append("sel{0}[{1}]".format(port,bit))
|
temp.append("sel{0}_{1}".format(port,bit))
|
||||||
temp.extend(["vdd", "gnd"])
|
temp.extend(["vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
@ -527,10 +526,13 @@ class bank(design.design):
|
||||||
|
|
||||||
# FIXME: place for multiport
|
# FIXME: place for multiport
|
||||||
for port in range(self.total_ports):
|
for port in range(self.total_ports):
|
||||||
|
col_decoder_inst = self.col_decoder_inst[port]
|
||||||
|
|
||||||
# Place the col decoder right aligned with row decoder
|
# Place the col decoder right aligned with row decoder
|
||||||
x_off = -(self.central_bus_width + self.wordline_driver.width + self.col_decoder.width)
|
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"])
|
y_off = -(self.col_decoder.height + 2*drc("well_to_well"))
|
||||||
self.col_decoder_inst[port].place(vector(x_off,y_off))
|
col_decoder_inst.place(vector(x_off,y_off))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_bank_select(self):
|
def create_bank_select(self):
|
||||||
|
|
@ -565,47 +567,17 @@ class bank(design.design):
|
||||||
y_off = min(self.col_decoder_inst[port].by(), self.col_mux_array_inst[port].by())
|
y_off = min(self.col_decoder_inst[port].by(), self.col_mux_array_inst[port].by())
|
||||||
else:
|
else:
|
||||||
y_off = self.row_decoder_inst[port].by()
|
y_off = self.row_decoder_inst[port].by()
|
||||||
y_off -= (self.bank_select.height + drc["well_to_well"])
|
y_off -= (self.bank_select.height + drc("well_to_well"))
|
||||||
self.bank_select_pos = vector(x_off,y_off)
|
self.bank_select_pos = vector(x_off,y_off)
|
||||||
self.bank_select_inst[port].place(self.bank_select_pos)
|
self.bank_select_inst[port].place(self.bank_select_pos)
|
||||||
|
|
||||||
|
|
||||||
def route_vdd_gnd(self):
|
def route_supplies(self):
|
||||||
""" Propagate all vdd/gnd pins up to this level for all modules """
|
""" 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")
|
||||||
|
|
||||||
# These are the instances that every bank has
|
|
||||||
top_instances = [self.bitcell_array_inst]
|
|
||||||
for port in range(self.total_read):
|
|
||||||
#top_instances.append(self.precharge_array_inst[port])
|
|
||||||
top_instances.append(self.sense_amp_array_inst[port])
|
|
||||||
for port in range(self.total_write):
|
|
||||||
top_instances.append(self.write_driver_array_inst[port])
|
|
||||||
for port in range(self.total_ports):
|
|
||||||
top_instances.extend([self.row_decoder_inst[port],
|
|
||||||
self.wordline_driver_inst[port]])
|
|
||||||
# Add these if we use the part...
|
|
||||||
if self.col_addr_size > 0:
|
|
||||||
top_instances.append(self.col_decoder_inst[port])
|
|
||||||
#top_instances.append(self.col_mux_array_inst[port])
|
|
||||||
|
|
||||||
if self.num_banks > 1:
|
|
||||||
top_instances.append(self.bank_select_inst[port])
|
|
||||||
|
|
||||||
if self.col_addr_size > 0:
|
|
||||||
for port in range(self.total_ports):
|
|
||||||
self.copy_layout_pin(self.col_mux_array_inst[port], "gnd")
|
|
||||||
for port in range(self.total_read):
|
|
||||||
self.copy_layout_pin(self.precharge_array_inst[port], "vdd")
|
|
||||||
|
|
||||||
for inst in top_instances:
|
|
||||||
# Column mux has no vdd
|
|
||||||
#if self.col_addr_size==0 or (self.col_addr_size>0 and inst != self.col_mux_array_inst[0]):
|
|
||||||
self.copy_layout_pin(inst, "vdd")
|
|
||||||
# Precharge has no gnd
|
|
||||||
#if inst != self.precharge_array_inst[port]:
|
|
||||||
self.copy_layout_pin(inst, "gnd")
|
|
||||||
|
|
||||||
|
|
||||||
def route_bank_select(self):
|
def route_bank_select(self):
|
||||||
""" Route the bank select logic. """
|
""" Route the bank select logic. """
|
||||||
|
|
||||||
|
|
@ -707,10 +679,10 @@ class bank(design.design):
|
||||||
# FIXME: Update for multiport
|
# FIXME: Update for multiport
|
||||||
for port in range(self.total_read):
|
for port in range(self.total_read):
|
||||||
for col in range(self.num_cols):
|
for col in range(self.num_cols):
|
||||||
precharge_bl = self.precharge_array_inst[port].get_pin("bl[{}]".format(col)).bc()
|
precharge_bl = self.precharge_array_inst[port].get_pin("bl_{}".format(col)).bc()
|
||||||
precharge_br = self.precharge_array_inst[port].get_pin("br[{}]".format(col)).bc()
|
precharge_br = self.precharge_array_inst[port].get_pin("br_{}".format(col)).bc()
|
||||||
bitcell_bl = self.bitcell_array_inst.get_pin(self.read_bl_list[port]+"[{}]".format(col)).uc()
|
bitcell_bl = self.bitcell_array_inst.get_pin(self.read_bl_list[port]+"_{}".format(col)).uc()
|
||||||
bitcell_br = self.bitcell_array_inst.get_pin(self.read_br_list[port]+"[{}]".format(col)).uc()
|
bitcell_br = self.bitcell_array_inst.get_pin(self.read_br_list[port]+"_{}".format(col)).uc()
|
||||||
|
|
||||||
yoffset = 0.5*(precharge_bl.y+bitcell_bl.y)
|
yoffset = 0.5*(precharge_bl.y+bitcell_bl.y)
|
||||||
self.add_path("metal2",[precharge_bl, vector(precharge_bl.x,yoffset),
|
self.add_path("metal2",[precharge_bl, vector(precharge_bl.x,yoffset),
|
||||||
|
|
@ -729,10 +701,10 @@ class bank(design.design):
|
||||||
# FIXME: Update for multiport
|
# FIXME: Update for multiport
|
||||||
for port in range(self.total_ports):
|
for port in range(self.total_ports):
|
||||||
for col in range(self.num_cols):
|
for col in range(self.num_cols):
|
||||||
col_mux_bl = self.col_mux_array_inst[port].get_pin("bl[{}]".format(col)).uc()
|
col_mux_bl = self.col_mux_array_inst[port].get_pin("bl_{}".format(col)).uc()
|
||||||
col_mux_br = self.col_mux_array_inst[port].get_pin("br[{}]".format(col)).uc()
|
col_mux_br = self.col_mux_array_inst[port].get_pin("br_{}".format(col)).uc()
|
||||||
bitcell_bl = self.bitcell_array_inst.get_pin(self.total_bl_list[port]+"[{}]".format(col)).bc()
|
bitcell_bl = self.bitcell_array_inst.get_pin(self.total_bl_list[port]+"_{}".format(col)).bc()
|
||||||
bitcell_br = self.bitcell_array_inst.get_pin(self.total_br_list[port]+"[{}]".format(col)).bc()
|
bitcell_br = self.bitcell_array_inst.get_pin(self.total_br_list[port]+"_{}".format(col)).bc()
|
||||||
|
|
||||||
yoffset = 0.5*(col_mux_bl.y+bitcell_bl.y)
|
yoffset = 0.5*(col_mux_bl.y+bitcell_bl.y)
|
||||||
self.add_path("metal2",[col_mux_bl, vector(col_mux_bl.x,yoffset),
|
self.add_path("metal2",[col_mux_bl, vector(col_mux_bl.x,yoffset),
|
||||||
|
|
@ -746,17 +718,17 @@ class bank(design.design):
|
||||||
|
|
||||||
for port in range(self.total_read):
|
for port in range(self.total_read):
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
sense_amp_bl = self.sense_amp_array_inst[port].get_pin("bl[{}]".format(bit)).uc()
|
sense_amp_bl = self.sense_amp_array_inst[port].get_pin("bl_{}".format(bit)).uc()
|
||||||
sense_amp_br = self.sense_amp_array_inst[port].get_pin("br[{}]".format(bit)).uc()
|
sense_amp_br = self.sense_amp_array_inst[port].get_pin("br_{}".format(bit)).uc()
|
||||||
|
|
||||||
if self.col_addr_size>0:
|
if self.col_addr_size>0:
|
||||||
# Sense amp is connected to the col mux
|
# Sense amp is connected to the col mux
|
||||||
connect_bl = self.col_mux_array_inst[port].get_pin("bl_out[{}]".format(bit)).bc()
|
connect_bl = self.col_mux_array_inst[port].get_pin("bl_out_{}".format(bit)).bc()
|
||||||
connect_br = self.col_mux_array_inst[port].get_pin("br_out[{}]".format(bit)).bc()
|
connect_br = self.col_mux_array_inst[port].get_pin("br_out_{}".format(bit)).bc()
|
||||||
else:
|
else:
|
||||||
# Sense amp is directly connected to the bitcell array
|
# Sense amp is directly connected to the bitcell array
|
||||||
connect_bl = self.bitcell_array_inst.get_pin(self.read_bl_list[port]+"[{}]".format(bit)).bc()
|
connect_bl = self.bitcell_array_inst.get_pin(self.read_bl_list[port]+"_{}".format(bit)).bc()
|
||||||
connect_br = self.bitcell_array_inst.get_pin(self.read_br_list[port]+"[{}]".format(bit)).bc()
|
connect_br = self.bitcell_array_inst.get_pin(self.read_br_list[port]+"_{}".format(bit)).bc()
|
||||||
|
|
||||||
|
|
||||||
yoffset = 0.5*(sense_amp_bl.y+connect_bl.y)
|
yoffset = 0.5*(sense_amp_bl.y+connect_bl.y)
|
||||||
|
|
@ -772,8 +744,8 @@ class bank(design.design):
|
||||||
# FIXME: Update for multiport
|
# FIXME: Update for multiport
|
||||||
for port in range(self.total_read):
|
for port in range(self.total_read):
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
data_pin = self.sense_amp_array_inst[port].get_pin("data[{}]".format(bit))
|
data_pin = self.sense_amp_array_inst[port].get_pin("data_{}".format(bit))
|
||||||
self.add_layout_pin_rect_center(text="dout{0}[{1}]".format(self.read_index[port],bit),
|
self.add_layout_pin_rect_center(text="dout{0}_{1}".format(self.read_index[port],bit),
|
||||||
layer=data_pin.layer,
|
layer=data_pin.layer,
|
||||||
offset=data_pin.center(),
|
offset=data_pin.center(),
|
||||||
height=data_pin.height(),
|
height=data_pin.height(),
|
||||||
|
|
@ -788,8 +760,8 @@ class bank(design.design):
|
||||||
for port in range(self.total_ports):
|
for port in range(self.total_ports):
|
||||||
for row in range(self.row_addr_size):
|
for row in range(self.row_addr_size):
|
||||||
addr_idx = row + self.col_addr_size
|
addr_idx = row + self.col_addr_size
|
||||||
decoder_name = "addr[{}]".format(row)
|
decoder_name = "addr_{}".format(row)
|
||||||
addr_name = "addr{0}[{1}]".format(port,addr_idx)
|
addr_name = "addr{0}_{1}".format(port,addr_idx)
|
||||||
self.copy_layout_pin(self.row_decoder_inst[port], decoder_name, addr_name)
|
self.copy_layout_pin(self.row_decoder_inst[port], decoder_name, addr_name)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -797,8 +769,8 @@ class bank(design.design):
|
||||||
""" Connecting write driver """
|
""" Connecting write driver """
|
||||||
for port in range(self.total_ports):
|
for port in range(self.total_ports):
|
||||||
for row in range(self.word_size):
|
for row in range(self.word_size):
|
||||||
data_name = "data[{}]".format(row)
|
data_name = "data_{}".format(row)
|
||||||
din_name = "din{0}[{1}]".format(port,row)
|
din_name = "din{0}_{1}".format(port,row)
|
||||||
self.copy_layout_pin(self.write_driver_array_inst[port], data_name, din_name)
|
self.copy_layout_pin(self.write_driver_array_inst[port], data_name, din_name)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -807,15 +779,15 @@ class bank(design.design):
|
||||||
for port in range(self.total_ports):
|
for port in range(self.total_ports):
|
||||||
for row in range(self.num_rows):
|
for row in range(self.num_rows):
|
||||||
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
||||||
decoder_out_pos = self.row_decoder_inst[port].get_pin("decode[{}]".format(row)).rc()
|
decoder_out_pos = self.row_decoder_inst[port].get_pin("decode_{}".format(row)).rc()
|
||||||
driver_in_pos = self.wordline_driver_inst[port].get_pin("in[{}]".format(row)).lc()
|
driver_in_pos = self.wordline_driver_inst[port].get_pin("in_{}".format(row)).lc()
|
||||||
mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0)
|
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)
|
mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1)
|
||||||
self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
||||||
|
|
||||||
# The mid guarantees we exit the input cell to the right.
|
# The mid guarantees we exit the input cell to the right.
|
||||||
driver_wl_pos = self.wordline_driver_inst[port].get_pin("wl[{}]".format(row)).rc()
|
driver_wl_pos = self.wordline_driver_inst[port].get_pin("wl_{}".format(row)).rc()
|
||||||
bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.total_wl_list[port]+"[{}]".format(row)).lc()
|
bitcell_wl_pos = self.bitcell_array_inst.get_pin(self.total_wl_list[port]+"_{}".format(row)).lc()
|
||||||
mid1 = driver_wl_pos.scale(0.5,1)+bitcell_wl_pos.scale(0.5,0)
|
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)
|
mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1)
|
||||||
self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
||||||
|
|
@ -833,25 +805,25 @@ class bank(design.design):
|
||||||
decode_names = ["Zb", "Z"]
|
decode_names = ["Zb", "Z"]
|
||||||
|
|
||||||
# The Address LSB
|
# The Address LSB
|
||||||
self.copy_layout_pin(self.col_decoder_inst[port], "A", "addr{}[0]".format(port))
|
self.copy_layout_pin(self.col_decoder_inst[port], "A", "addr{}_0".format(port))
|
||||||
|
|
||||||
elif self.col_addr_size > 1:
|
elif self.col_addr_size > 1:
|
||||||
decode_names = []
|
decode_names = []
|
||||||
for i in range(self.num_col_addr_lines):
|
for i in range(self.num_col_addr_lines):
|
||||||
decode_names.append("out[{}]".format(i))
|
decode_names.append("out_{}".format(i))
|
||||||
|
|
||||||
for i in range(self.col_addr_size):
|
for i in range(self.col_addr_size):
|
||||||
decoder_name = "in[{}]".format(i)
|
decoder_name = "in_{}".format(i)
|
||||||
addr_name = "addr{0}[{1}]".format(port,i)
|
addr_name = "addr{0}_{1}".format(port,i)
|
||||||
self.copy_layout_pin(self.col_decoder_inst[port], decoder_name, addr_name)
|
self.copy_layout_pin(self.col_decoder_inst[port], decoder_name, addr_name)
|
||||||
|
|
||||||
|
|
||||||
# This will do a quick "river route" on two layers.
|
# This will do a quick "river route" on two layers.
|
||||||
# When above the top select line it will offset "inward" again to prevent conflicts.
|
# 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.
|
# 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[port].get_pin("sel[{}]".format(self.num_col_addr_lines-1)).cy()
|
top_y_offset = self.col_mux_array_inst[port].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)):
|
for (decode_name,i) in zip(decode_names,range(self.num_col_addr_lines)):
|
||||||
mux_name = "sel[{}]".format(i)
|
mux_name = "sel_{}".format(i)
|
||||||
mux_addr_pos = self.col_mux_array_inst[port].get_pin(mux_name).lc()
|
mux_addr_pos = self.col_mux_array_inst[port].get_pin(mux_name).lc()
|
||||||
|
|
||||||
decode_out_pos = self.col_decoder_inst[port].get_pin(decode_name).center()
|
decode_out_pos = self.col_decoder_inst[port].get_pin(decode_name).center()
|
||||||
|
|
@ -874,7 +846,7 @@ class bank(design.design):
|
||||||
"""
|
"""
|
||||||
# Add the wordline names
|
# Add the wordline names
|
||||||
for i in range(self.num_rows):
|
for i in range(self.num_rows):
|
||||||
wl_name = "wl[{}]".format(i)
|
wl_name = "wl_{}".format(i)
|
||||||
wl_pin = self.bitcell_array_inst.get_pin(wl_name)
|
wl_pin = self.bitcell_array_inst.get_pin(wl_name)
|
||||||
self.add_label(text=wl_name,
|
self.add_label(text=wl_name,
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
|
|
@ -882,8 +854,8 @@ class bank(design.design):
|
||||||
|
|
||||||
# Add the bitline names
|
# Add the bitline names
|
||||||
for i in range(self.num_cols):
|
for i in range(self.num_cols):
|
||||||
bl_name = "bl[{}]".format(i)
|
bl_name = "bl_{}".format(i)
|
||||||
br_name = "br[{}]".format(i)
|
br_name = "br_{}".format(i)
|
||||||
bl_pin = self.bitcell_array_inst.get_pin(bl_name)
|
bl_pin = self.bitcell_array_inst.get_pin(bl_name)
|
||||||
br_pin = self.bitcell_array_inst.get_pin(br_name)
|
br_pin = self.bitcell_array_inst.get_pin(br_name)
|
||||||
self.add_label(text=bl_name,
|
self.add_label(text=bl_name,
|
||||||
|
|
@ -895,16 +867,16 @@ class bank(design.design):
|
||||||
|
|
||||||
# # Add the data output names to the sense amp output
|
# # Add the data output names to the sense amp output
|
||||||
# for i in range(self.word_size):
|
# for i in range(self.word_size):
|
||||||
# data_name = "data[{}]".format(i)
|
# data_name = "data_{}".format(i)
|
||||||
# data_pin = self.sense_amp_array_inst.get_pin(data_name)
|
# data_pin = self.sense_amp_array_inst.get_pin(data_name)
|
||||||
# self.add_label(text="sa_out[{}]".format(i),
|
# self.add_label(text="sa_out_{}".format(i),
|
||||||
# layer="metal2",
|
# layer="metal2",
|
||||||
# offset=data_pin.center())
|
# offset=data_pin.center())
|
||||||
|
|
||||||
# Add labels on the decoder
|
# Add labels on the decoder
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
data_name = "dec_out[{}]".format(i)
|
data_name = "dec_out_{}".format(i)
|
||||||
pin_name = "in[{}]".format(i)
|
pin_name = "in_{}".format(i)
|
||||||
data_pin = self.wordline_driver_inst[0].get_pin(pin_name)
|
data_pin = self.wordline_driver_inst[0].get_pin(pin_name)
|
||||||
self.add_label(text=data_name,
|
self.add_label(text=data_name,
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ class bank_select(design.design):
|
||||||
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
||||||
self.bitcell = self.mod_bitcell()
|
self.bitcell = self.mod_bitcell()
|
||||||
|
|
||||||
height = self.bitcell.height + drc["poly_to_active"]
|
height = self.bitcell.height + drc("poly_to_active")
|
||||||
|
|
||||||
# 1x Inverter
|
# 1x Inverter
|
||||||
self.inv_sel = pinv(height=height)
|
self.inv_sel = pinv(height=height)
|
||||||
|
|
@ -88,8 +88,8 @@ class bank_select(design.design):
|
||||||
|
|
||||||
def calculate_module_offsets(self):
|
def calculate_module_offsets(self):
|
||||||
|
|
||||||
self.xoffset_nand = 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_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_inv = max(self.xoffset_nand + self.nand2.width, self.xoffset_nor + self.nor2.width)
|
||||||
self.xoffset_bank_sel_inv = 0
|
self.xoffset_bank_sel_inv = 0
|
||||||
self.xoffset_inputs = 0
|
self.xoffset_inputs = 0
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ class bitcell_array(design.design):
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
|
|
||||||
# We increase it by a well enclosure so the precharges don't overlap our wells
|
# 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.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.width = self.column_size*self.cell.width + self.m1_width
|
||||||
|
|
||||||
xoffset = 0.0
|
xoffset = 0.0
|
||||||
|
|
@ -69,10 +69,10 @@ class bitcell_array(design.design):
|
||||||
column_list = self.cell.list_all_bitline_names()
|
column_list = self.cell.list_all_bitline_names()
|
||||||
for col in range(self.column_size):
|
for col in range(self.column_size):
|
||||||
for cell_column in column_list:
|
for cell_column in column_list:
|
||||||
self.add_pin(cell_column+"[{0}]".format(col))
|
self.add_pin(cell_column+"_{0}".format(col))
|
||||||
for row in range(self.row_size):
|
for row in range(self.row_size):
|
||||||
for cell_row in row_list:
|
for cell_row in row_list:
|
||||||
self.add_pin(cell_row+"[{0}]".format(row))
|
self.add_pin(cell_row+"_{0}".format(row))
|
||||||
self.add_pin("vdd")
|
self.add_pin("vdd")
|
||||||
self.add_pin("gnd")
|
self.add_pin("gnd")
|
||||||
|
|
||||||
|
|
@ -105,7 +105,7 @@ class bitcell_array(design.design):
|
||||||
for col in range(self.column_size):
|
for col in range(self.column_size):
|
||||||
for cell_column in column_list:
|
for cell_column in column_list:
|
||||||
bl_pin = self.cell_inst[0,col].get_pin(cell_column)
|
bl_pin = self.cell_inst[0,col].get_pin(cell_column)
|
||||||
self.add_layout_pin(text=cell_column+"[{0}]".format(col),
|
self.add_layout_pin(text=cell_column+"_{0}".format(col),
|
||||||
layer="metal2",
|
layer="metal2",
|
||||||
offset=bl_pin.ll(),
|
offset=bl_pin.ll(),
|
||||||
width=bl_pin.width(),
|
width=bl_pin.width(),
|
||||||
|
|
@ -118,7 +118,7 @@ class bitcell_array(design.design):
|
||||||
for row in range(self.row_size):
|
for row in range(self.row_size):
|
||||||
for cell_row in row_list:
|
for cell_row in row_list:
|
||||||
wl_pin = self.cell_inst[row,0].get_pin(cell_row)
|
wl_pin = self.cell_inst[row,0].get_pin(cell_row)
|
||||||
self.add_layout_pin(text=cell_row+"[{0}]".format(row),
|
self.add_layout_pin(text=cell_row+"_{0}".format(row),
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
offset=wl_pin.ll(),
|
offset=wl_pin.ll(),
|
||||||
width=self.width,
|
width=self.width,
|
||||||
|
|
@ -199,13 +199,13 @@ class bitcell_array(design.design):
|
||||||
return total_power
|
return total_power
|
||||||
|
|
||||||
def gen_wl_wire(self):
|
def gen_wl_wire(self):
|
||||||
wl_wire = self.generate_rc_net(int(self.column_size), self.width, drc["minwidth_metal1"])
|
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
|
wl_wire.wire_c = 2*spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell
|
||||||
return wl_wire
|
return wl_wire
|
||||||
|
|
||||||
def gen_bl_wire(self):
|
def gen_bl_wire(self):
|
||||||
bl_pos = 0
|
bl_pos = 0
|
||||||
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), self.height, drc["minwidth_metal1"])
|
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
|
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
|
||||||
return bl_wire
|
return bl_wire
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ class control_logic(design.design):
|
||||||
# FIXME: These should be tuned according to the size!
|
# FIXME: These should be tuned according to the size!
|
||||||
delay_stages = 4 # Must be non-inverting
|
delay_stages = 4 # Must be non-inverting
|
||||||
delay_fanout = 3 # This can be anything >=2
|
delay_fanout = 3 # This can be anything >=2
|
||||||
bitcell_loads = int(math.ceil(self.num_rows / 5.0))
|
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.replica_bitline = replica_bitline(delay_stages, delay_fanout, bitcell_loads, name="replica_bitline_"+self.port_type)
|
||||||
self.add_mod(self.replica_bitline)
|
self.add_mod(self.replica_bitline)
|
||||||
|
|
||||||
|
|
@ -299,7 +299,7 @@ class control_logic(design.design):
|
||||||
control_inputs = ["cs"]
|
control_inputs = ["cs"]
|
||||||
else:
|
else:
|
||||||
control_inputs = ["cs", "we"]
|
control_inputs = ["cs", "we"]
|
||||||
dff_out_map = zip(["dout_bar[{}]".format(i) for i in range(2*self.num_control_signals - 1)], control_inputs)
|
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)
|
self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets)
|
||||||
|
|
||||||
# Connect the clock rail to the other clock rail
|
# Connect the clock rail to the other clock rail
|
||||||
|
|
@ -311,9 +311,9 @@ class control_logic(design.design):
|
||||||
offset=rail_pos,
|
offset=rail_pos,
|
||||||
rotate=90)
|
rotate=90)
|
||||||
|
|
||||||
self.copy_layout_pin(self.ctrl_dff_inst, "din[0]", "csb")
|
self.copy_layout_pin(self.ctrl_dff_inst, "din_0", "csb")
|
||||||
if (self.port_type == "rw"):
|
if (self.port_type == "rw"):
|
||||||
self.copy_layout_pin(self.ctrl_dff_inst, "din[1]", "web")
|
self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web")
|
||||||
|
|
||||||
|
|
||||||
def create_dffs(self):
|
def create_dffs(self):
|
||||||
|
|
|
||||||
|
|
@ -83,21 +83,21 @@ class dff_array(design.design):
|
||||||
|
|
||||||
def get_din_name(self, row, col):
|
def get_din_name(self, row, col):
|
||||||
if self.columns == 1:
|
if self.columns == 1:
|
||||||
din_name = "din[{0}]".format(row)
|
din_name = "din_{0}".format(row)
|
||||||
elif self.rows == 1:
|
elif self.rows == 1:
|
||||||
din_name = "din[{0}]".format(col)
|
din_name = "din_{0}".format(col)
|
||||||
else:
|
else:
|
||||||
din_name = "din[{0}][{1}]".format(row,col)
|
din_name = "din_{0}_{1}".format(row,col)
|
||||||
|
|
||||||
return din_name
|
return din_name
|
||||||
|
|
||||||
def get_dout_name(self, row, col):
|
def get_dout_name(self, row, col):
|
||||||
if self.columns == 1:
|
if self.columns == 1:
|
||||||
dout_name = "dout[{0}]".format(row)
|
dout_name = "dout_{0}".format(row)
|
||||||
elif self.rows == 1:
|
elif self.rows == 1:
|
||||||
dout_name = "dout[{0}]".format(col)
|
dout_name = "dout_{0}".format(col)
|
||||||
else:
|
else:
|
||||||
dout_name = "dout[{0}][{1}]".format(row,col)
|
dout_name = "dout_{0}_{1}".format(row,col)
|
||||||
|
|
||||||
return dout_name
|
return dout_name
|
||||||
|
|
||||||
|
|
@ -136,11 +136,12 @@ class dff_array(design.design):
|
||||||
|
|
||||||
# Create vertical spines to a single horizontal rail
|
# Create vertical spines to a single horizontal rail
|
||||||
clk_pin = self.dff_insts[0,0].get_pin("clk")
|
clk_pin = self.dff_insts[0,0].get_pin("clk")
|
||||||
|
clk_ypos = 2*self.m3_pitch+self.m3_width
|
||||||
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
||||||
self.add_layout_pin_segment_center(text="clk",
|
self.add_layout_pin_segment_center(text="clk",
|
||||||
layer="metal3",
|
layer="metal3",
|
||||||
start=vector(0,self.m3_pitch+self.m3_width),
|
start=vector(0,clk_ypos),
|
||||||
end=vector(self.width,self.m3_pitch+self.m3_width))
|
end=vector(self.width,clk_ypos))
|
||||||
for col in range(self.columns):
|
for col in range(self.columns):
|
||||||
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
||||||
# Make a vertical strip for each column
|
# Make a vertical strip for each column
|
||||||
|
|
@ -150,7 +151,7 @@ class dff_array(design.design):
|
||||||
height=self.height)
|
height=self.height)
|
||||||
# Drop a via to the M3 pin
|
# Drop a via to the M3 pin
|
||||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,31 +84,31 @@ class dff_buf_array(design.design):
|
||||||
|
|
||||||
def get_din_name(self, row, col):
|
def get_din_name(self, row, col):
|
||||||
if self.columns == 1:
|
if self.columns == 1:
|
||||||
din_name = "din[{0}]".format(row)
|
din_name = "din_{0}".format(row)
|
||||||
elif self.rows == 1:
|
elif self.rows == 1:
|
||||||
din_name = "din[{0}]".format(col)
|
din_name = "din_{0}".format(col)
|
||||||
else:
|
else:
|
||||||
din_name = "din[{0}][{1}]".format(row,col)
|
din_name = "din_{0}_{1}".format(row,col)
|
||||||
|
|
||||||
return din_name
|
return din_name
|
||||||
|
|
||||||
def get_dout_name(self, row, col):
|
def get_dout_name(self, row, col):
|
||||||
if self.columns == 1:
|
if self.columns == 1:
|
||||||
dout_name = "dout[{0}]".format(row)
|
dout_name = "dout_{0}".format(row)
|
||||||
elif self.rows == 1:
|
elif self.rows == 1:
|
||||||
dout_name = "dout[{0}]".format(col)
|
dout_name = "dout_{0}".format(col)
|
||||||
else:
|
else:
|
||||||
dout_name = "dout[{0}][{1}]".format(row,col)
|
dout_name = "dout_{0}_{1}".format(row,col)
|
||||||
|
|
||||||
return dout_name
|
return dout_name
|
||||||
|
|
||||||
def get_dout_bar_name(self, row, col):
|
def get_dout_bar_name(self, row, col):
|
||||||
if self.columns == 1:
|
if self.columns == 1:
|
||||||
dout_bar_name = "dout_bar[{0}]".format(row)
|
dout_bar_name = "dout_bar_{0}".format(row)
|
||||||
elif self.rows == 1:
|
elif self.rows == 1:
|
||||||
dout_bar_name = "dout_bar[{0}]".format(col)
|
dout_bar_name = "dout_bar_{0}".format(col)
|
||||||
else:
|
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
|
return dout_bar_name
|
||||||
|
|
||||||
|
|
@ -153,6 +153,7 @@ class dff_buf_array(design.design):
|
||||||
|
|
||||||
# Create vertical spines to a single horizontal rail
|
# Create vertical spines to a single horizontal rail
|
||||||
clk_pin = self.dff_insts[0,0].get_pin("clk")
|
clk_pin = self.dff_insts[0,0].get_pin("clk")
|
||||||
|
clk_ypos = 2*self.m3_pitch+self.m3_width
|
||||||
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
||||||
if self.columns==1:
|
if self.columns==1:
|
||||||
self.add_layout_pin(text="clk",
|
self.add_layout_pin(text="clk",
|
||||||
|
|
@ -163,8 +164,8 @@ class dff_buf_array(design.design):
|
||||||
else:
|
else:
|
||||||
self.add_layout_pin_segment_center(text="clk",
|
self.add_layout_pin_segment_center(text="clk",
|
||||||
layer="metal3",
|
layer="metal3",
|
||||||
start=vector(0,self.m3_pitch+self.m3_width),
|
start=vector(0,clk_ypos),
|
||||||
end=vector(self.width,self.m3_pitch+self.m3_width))
|
end=vector(self.width,clk_ypos))
|
||||||
for col in range(self.columns):
|
for col in range(self.columns):
|
||||||
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
||||||
|
|
||||||
|
|
@ -175,7 +176,7 @@ class dff_buf_array(design.design):
|
||||||
height=self.height)
|
height=self.height)
|
||||||
# Drop a via to the M3 pin
|
# Drop a via to the M3 pin
|
||||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,31 +84,31 @@ class dff_inv_array(design.design):
|
||||||
|
|
||||||
def get_din_name(self, row, col):
|
def get_din_name(self, row, col):
|
||||||
if self.columns == 1:
|
if self.columns == 1:
|
||||||
din_name = "din[{0}]".format(row)
|
din_name = "din_{0}".format(row)
|
||||||
elif self.rows == 1:
|
elif self.rows == 1:
|
||||||
din_name = "din[{0}]".format(col)
|
din_name = "din_{0}".format(col)
|
||||||
else:
|
else:
|
||||||
din_name = "din[{0}][{1}]".format(row,col)
|
din_name = "din_{0}_{1}".format(row,col)
|
||||||
|
|
||||||
return din_name
|
return din_name
|
||||||
|
|
||||||
def get_dout_name(self, row, col):
|
def get_dout_name(self, row, col):
|
||||||
if self.columns == 1:
|
if self.columns == 1:
|
||||||
dout_name = "dout[{0}]".format(row)
|
dout_name = "dout_{0}".format(row)
|
||||||
elif self.rows == 1:
|
elif self.rows == 1:
|
||||||
dout_name = "dout[{0}]".format(col)
|
dout_name = "dout_{0}".format(col)
|
||||||
else:
|
else:
|
||||||
dout_name = "dout[{0}][{1}]".format(row,col)
|
dout_name = "dout_{0}_{1}".format(row,col)
|
||||||
|
|
||||||
return dout_name
|
return dout_name
|
||||||
|
|
||||||
def get_dout_bar_name(self, row, col):
|
def get_dout_bar_name(self, row, col):
|
||||||
if self.columns == 1:
|
if self.columns == 1:
|
||||||
dout_bar_name = "dout_bar[{0}]".format(row)
|
dout_bar_name = "dout_bar_{0}".format(row)
|
||||||
elif self.rows == 1:
|
elif self.rows == 1:
|
||||||
dout_bar_name = "dout_bar[{0}]".format(col)
|
dout_bar_name = "dout_bar_{0}".format(col)
|
||||||
else:
|
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
|
return dout_bar_name
|
||||||
|
|
||||||
|
|
@ -153,6 +153,7 @@ class dff_inv_array(design.design):
|
||||||
|
|
||||||
# Create vertical spines to a single horizontal rail
|
# Create vertical spines to a single horizontal rail
|
||||||
clk_pin = self.dff_insts[0,0].get_pin("clk")
|
clk_pin = self.dff_insts[0,0].get_pin("clk")
|
||||||
|
clk_ypos = 2*self.m3_pitch+self.m3_width
|
||||||
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
||||||
if self.columns==1:
|
if self.columns==1:
|
||||||
self.add_layout_pin(text="clk",
|
self.add_layout_pin(text="clk",
|
||||||
|
|
@ -163,8 +164,8 @@ class dff_inv_array(design.design):
|
||||||
else:
|
else:
|
||||||
self.add_layout_pin_segment_center(text="clk",
|
self.add_layout_pin_segment_center(text="clk",
|
||||||
layer="metal3",
|
layer="metal3",
|
||||||
start=vector(0,self.m3_pitch+self.m3_width),
|
start=vector(0,clk_ypos),
|
||||||
end=vector(self.width,self.m3_pitch+self.m3_width))
|
end=vector(self.width,clk_ypos))
|
||||||
for col in range(self.columns):
|
for col in range(self.columns):
|
||||||
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
||||||
# Make a vertical strip for each column
|
# Make a vertical strip for each column
|
||||||
|
|
@ -174,7 +175,7 @@ class dff_inv_array(design.design):
|
||||||
height=self.height)
|
height=self.height)
|
||||||
# Drop a via to the M3 pin
|
# Drop a via to the M3 pin
|
||||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ class hierarchical_decoder(design.design):
|
||||||
b = self.mod_bitcell()
|
b = self.mod_bitcell()
|
||||||
self.bitcell_height = b.height
|
self.bitcell_height = b.height
|
||||||
|
|
||||||
self.NAND_FORMAT = "DEC_NAND[{0}]"
|
self.NAND_FORMAT = "DEC_NAND_{0}"
|
||||||
self.INV_FORMAT = "DEC_INV_[{0}]"
|
self.INV_FORMAT = "DEC_INV_{0}"
|
||||||
|
|
||||||
self.pre2x4_inst = []
|
self.pre2x4_inst = []
|
||||||
self.pre3x8_inst = []
|
self.pre3x8_inst = []
|
||||||
|
|
@ -168,7 +168,7 @@ class hierarchical_decoder(design.design):
|
||||||
min_x = min(min_x, -self.pre3_8.width)
|
min_x = min(min_x, -self.pre3_8.width)
|
||||||
input_offset=vector(min_x - self.input_routing_width,0)
|
input_offset=vector(min_x - self.input_routing_width,0)
|
||||||
|
|
||||||
input_bus_names = ["addr[{0}]".format(i) for i in range(self.num_inputs)]
|
input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)]
|
||||||
self.input_rails = self.create_vertical_pin_bus(layer="metal2",
|
self.input_rails = self.create_vertical_pin_bus(layer="metal2",
|
||||||
pitch=self.m2_pitch,
|
pitch=self.m2_pitch,
|
||||||
offset=input_offset,
|
offset=input_offset,
|
||||||
|
|
@ -184,9 +184,9 @@ class hierarchical_decoder(design.design):
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
index = pre_num * 2 + i
|
index = pre_num * 2 + i
|
||||||
|
|
||||||
input_pos = self.input_rails["addr[{}]".format(index)]
|
input_pos = self.input_rails["addr_{}".format(index)]
|
||||||
|
|
||||||
in_name = "in[{}]".format(i)
|
in_name = "in_{}".format(i)
|
||||||
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
|
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
|
||||||
|
|
||||||
# To prevent conflicts, we will offset each input connect so
|
# To prevent conflicts, we will offset each input connect so
|
||||||
|
|
@ -201,9 +201,9 @@ class hierarchical_decoder(design.design):
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
index = pre_num * 3 + i + self.no_of_pre2x4 * 2
|
index = pre_num * 3 + i + self.no_of_pre2x4 * 2
|
||||||
|
|
||||||
input_pos = self.input_rails["addr[{}]".format(index)]
|
input_pos = self.input_rails["addr_{}".format(index)]
|
||||||
|
|
||||||
in_name = "in[{}]".format(i)
|
in_name = "in_{}".format(i)
|
||||||
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
|
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
|
||||||
|
|
||||||
# To prevent conflicts, we will offset each input connect so
|
# To prevent conflicts, we will offset each input connect so
|
||||||
|
|
@ -230,10 +230,10 @@ class hierarchical_decoder(design.design):
|
||||||
""" Add the module pins """
|
""" Add the module pins """
|
||||||
|
|
||||||
for i in range(self.num_inputs):
|
for i in range(self.num_inputs):
|
||||||
self.add_pin("addr[{0}]".format(i))
|
self.add_pin("addr_{0}".format(i))
|
||||||
|
|
||||||
for j in range(self.rows):
|
for j in range(self.rows):
|
||||||
self.add_pin("decode[{0}]".format(j))
|
self.add_pin("decode_{0}".format(j))
|
||||||
self.add_pin("vdd")
|
self.add_pin("vdd")
|
||||||
self.add_pin("gnd")
|
self.add_pin("gnd")
|
||||||
|
|
||||||
|
|
@ -258,12 +258,12 @@ class hierarchical_decoder(design.design):
|
||||||
|
|
||||||
pins = []
|
pins = []
|
||||||
for input_index in range(2):
|
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):
|
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"])
|
pins.extend(["vdd", "gnd"])
|
||||||
|
|
||||||
self.pre2x4_inst.append(self.add_inst(name="pre[{0}]".format(num),
|
self.pre2x4_inst.append(self.add_inst(name="pre_{0}".format(num),
|
||||||
mod=self.pre2_4))
|
mod=self.pre2_4))
|
||||||
self.connect_inst(pins)
|
self.connect_inst(pins)
|
||||||
|
|
||||||
|
|
@ -277,12 +277,12 @@ class hierarchical_decoder(design.design):
|
||||||
|
|
||||||
pins = []
|
pins = []
|
||||||
for input_index in range(3):
|
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):
|
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"])
|
pins.extend(["vdd", "gnd"])
|
||||||
|
|
||||||
self.pre3x8_inst.append(self.add_inst(name="pre3x8[{0}]".format(num),
|
self.pre3x8_inst.append(self.add_inst(name="pre3x8_{0}".format(num),
|
||||||
mod=self.pre3_8))
|
mod=self.pre3_8))
|
||||||
self.connect_inst(pins)
|
self.connect_inst(pins)
|
||||||
|
|
||||||
|
|
@ -340,9 +340,9 @@ class hierarchical_decoder(design.design):
|
||||||
name = self.NAND_FORMAT.format(row)
|
name = self.NAND_FORMAT.format(row)
|
||||||
self.nand_inst.append(self.add_inst(name=name,
|
self.nand_inst.append(self.add_inst(name=name,
|
||||||
mod=self.nand2))
|
mod=self.nand2))
|
||||||
pins =["out[{0}]".format(i),
|
pins =["out_{0}".format(i),
|
||||||
"out[{0}]".format(j + len(self.predec_groups[0])),
|
"out_{0}".format(j + len(self.predec_groups[0])),
|
||||||
"Z[{0}]".format(row),
|
"Z_{0}".format(row),
|
||||||
"vdd", "gnd"]
|
"vdd", "gnd"]
|
||||||
self.connect_inst(pins)
|
self.connect_inst(pins)
|
||||||
|
|
||||||
|
|
@ -359,10 +359,10 @@ class hierarchical_decoder(design.design):
|
||||||
self.nand_inst.append(self.add_inst(name=name,
|
self.nand_inst.append(self.add_inst(name=name,
|
||||||
mod=self.nand3))
|
mod=self.nand3))
|
||||||
|
|
||||||
pins = ["out[{0}]".format(i),
|
pins = ["out_{0}".format(i),
|
||||||
"out[{0}]".format(j + len(self.predec_groups[0])),
|
"out_{0}".format(j + len(self.predec_groups[0])),
|
||||||
"out[{0}]".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])),
|
"out_{0}".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])),
|
||||||
"Z[{0}]".format(row),
|
"Z_{0}".format(row),
|
||||||
"vdd", "gnd"]
|
"vdd", "gnd"]
|
||||||
self.connect_inst(pins)
|
self.connect_inst(pins)
|
||||||
|
|
||||||
|
|
@ -377,8 +377,8 @@ class hierarchical_decoder(design.design):
|
||||||
name = self.INV_FORMAT.format(row)
|
name = self.INV_FORMAT.format(row)
|
||||||
self.inv_inst.append(self.add_inst(name=name,
|
self.inv_inst.append(self.add_inst(name=name,
|
||||||
mod=self.inv))
|
mod=self.inv))
|
||||||
self.connect_inst(args=["Z[{0}]".format(row),
|
self.connect_inst(args=["Z_{0}".format(row),
|
||||||
"decode[{0}]".format(row),
|
"decode_{0}".format(row),
|
||||||
"vdd", "gnd"])
|
"vdd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -466,7 +466,7 @@ class hierarchical_decoder(design.design):
|
||||||
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
|
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
|
||||||
|
|
||||||
z_pin = self.inv_inst[row].get_pin("Z")
|
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",
|
layer="metal1",
|
||||||
offset=z_pin.ll(),
|
offset=z_pin.ll(),
|
||||||
width=z_pin.width(),
|
width=z_pin.width(),
|
||||||
|
|
@ -480,7 +480,7 @@ class hierarchical_decoder(design.design):
|
||||||
# This is not needed for inputs <4 since they have no pre/decode stages.
|
# This is not needed for inputs <4 since they have no pre/decode stages.
|
||||||
if (self.num_inputs >= 4):
|
if (self.num_inputs >= 4):
|
||||||
input_offset = vector(0.5*self.m2_width,0)
|
input_offset = vector(0.5*self.m2_width,0)
|
||||||
input_bus_names = ["predecode[{0}]".format(i) for i in range(self.total_number_of_predecoder_outputs)]
|
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",
|
self.predecode_rails = self.create_vertical_pin_bus(layer="metal2",
|
||||||
pitch=self.m2_pitch,
|
pitch=self.m2_pitch,
|
||||||
offset=input_offset,
|
offset=input_offset,
|
||||||
|
|
@ -497,8 +497,8 @@ class hierarchical_decoder(design.design):
|
||||||
# FIXME: convert to connect_bus
|
# FIXME: convert to connect_bus
|
||||||
for pre_num in range(self.no_of_pre2x4):
|
for pre_num in range(self.no_of_pre2x4):
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
predecode_name = "predecode[{}]".format(pre_num * 4 + i)
|
predecode_name = "predecode_{}".format(pre_num * 4 + i)
|
||||||
out_name = "out[{}]".format(i)
|
out_name = "out_{}".format(i)
|
||||||
pin = self.pre2x4_inst[pre_num].get_pin(out_name)
|
pin = self.pre2x4_inst[pre_num].get_pin(out_name)
|
||||||
self.route_predecode_rail_m3(predecode_name, pin)
|
self.route_predecode_rail_m3(predecode_name, pin)
|
||||||
|
|
||||||
|
|
@ -506,8 +506,8 @@ class hierarchical_decoder(design.design):
|
||||||
# FIXME: convert to connect_bus
|
# FIXME: convert to connect_bus
|
||||||
for pre_num in range(self.no_of_pre3x8):
|
for pre_num in range(self.no_of_pre3x8):
|
||||||
for i in range(8):
|
for i in range(8):
|
||||||
predecode_name = "predecode[{}]".format(pre_num * 8 + i + self.no_of_pre2x4 * 4)
|
predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4)
|
||||||
out_name = "out[{}]".format(i)
|
out_name = "out_{}".format(i)
|
||||||
pin = self.pre3x8_inst[pre_num].get_pin(out_name)
|
pin = self.pre3x8_inst[pre_num].get_pin(out_name)
|
||||||
self.route_predecode_rail_m3(predecode_name, pin)
|
self.route_predecode_rail_m3(predecode_name, pin)
|
||||||
|
|
||||||
|
|
@ -526,9 +526,9 @@ class hierarchical_decoder(design.design):
|
||||||
for index_A in self.predec_groups[0]:
|
for index_A in self.predec_groups[0]:
|
||||||
for index_B in self.predec_groups[1]:
|
for index_B in self.predec_groups[1]:
|
||||||
# FIXME: convert to connect_bus?
|
# FIXME: convert to connect_bus?
|
||||||
predecode_name = "predecode[{}]".format(index_A)
|
predecode_name = "predecode_{}".format(index_A)
|
||||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("A"))
|
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("A"))
|
||||||
predecode_name = "predecode[{}]".format(index_B)
|
predecode_name = "predecode_{}".format(index_B)
|
||||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B"))
|
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B"))
|
||||||
row_index = row_index + 1
|
row_index = row_index + 1
|
||||||
|
|
||||||
|
|
@ -537,41 +537,38 @@ class hierarchical_decoder(design.design):
|
||||||
for index_B in self.predec_groups[1]:
|
for index_B in self.predec_groups[1]:
|
||||||
for index_C in self.predec_groups[2]:
|
for index_C in self.predec_groups[2]:
|
||||||
# FIXME: convert to connect_bus?
|
# FIXME: convert to connect_bus?
|
||||||
predecode_name = "predecode[{}]".format(index_A)
|
predecode_name = "predecode_{}".format(index_A)
|
||||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("A"))
|
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("A"))
|
||||||
predecode_name = "predecode[{}]".format(index_B)
|
predecode_name = "predecode_{}".format(index_B)
|
||||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B"))
|
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B"))
|
||||||
predecode_name = "predecode[{}]".format(index_C)
|
predecode_name = "predecode_{}".format(index_C)
|
||||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("C"))
|
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("C"))
|
||||||
row_index = row_index + 1
|
row_index = row_index + 1
|
||||||
|
|
||||||
def route_vdd_gnd(self):
|
def route_vdd_gnd(self):
|
||||||
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
|
""" 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
|
# The vias will be placed in the center and right of the cells, respectively.
|
||||||
a_xoffset = self.inv_inst[0].lx()
|
xoffset = self.nand_inst[0].cx()
|
||||||
b_xoffset = self.inv_inst[0].rx()
|
|
||||||
|
|
||||||
for num in range(0,self.rows):
|
for num in range(0,self.rows):
|
||||||
# this will result in duplicate polygons for rails, but who cares
|
for pin_name in ["vdd", "gnd"]:
|
||||||
|
# The nand and inv are the same height rows...
|
||||||
# Route both supplies
|
supply_pin = self.nand_inst[num].get_pin(pin_name)
|
||||||
for n in ["vdd", "gnd"]:
|
pin_pos = vector(xoffset, supply_pin.cy())
|
||||||
supply_pin = self.inv_inst[num].get_pin(n)
|
self.add_power_pin(name=pin_name,
|
||||||
|
loc=pin_pos)
|
||||||
# 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)
|
|
||||||
|
|
||||||
|
# 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
|
# Copy the pins from the predecoders
|
||||||
for pre in self.pre2x4_inst + self.pre3x8_inst:
|
for pre in self.pre2x4_inst + self.pre3x8_inst:
|
||||||
self.copy_layout_pin(pre, "vdd")
|
self.copy_layout_pin(pre, "vdd")
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ class hierarchical_predecode(design.design):
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
for k in range(self.number_of_inputs):
|
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):
|
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("vdd")
|
||||||
self.add_pin("gnd")
|
self.add_pin("gnd")
|
||||||
|
|
||||||
|
|
@ -67,7 +67,7 @@ class hierarchical_predecode(design.design):
|
||||||
|
|
||||||
def route_rails(self):
|
def route_rails(self):
|
||||||
""" Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """
|
""" 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)
|
offset = vector(0.5*self.m2_width,2*self.m1_width)
|
||||||
self.input_rails = self.create_vertical_pin_bus(layer="metal2",
|
self.input_rails = self.create_vertical_pin_bus(layer="metal2",
|
||||||
pitch=self.m2_pitch,
|
pitch=self.m2_pitch,
|
||||||
|
|
@ -75,8 +75,8 @@ class hierarchical_predecode(design.design):
|
||||||
names=input_names,
|
names=input_names,
|
||||||
length=self.height - 2*self.m1_width)
|
length=self.height - 2*self.m1_width)
|
||||||
|
|
||||||
invert_names = ["Abar[{}]".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)]
|
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
|
||||||
decode_names = invert_names + non_invert_names
|
decode_names = invert_names + non_invert_names
|
||||||
offset = vector(self.x_off_inv_1 + self.inv.width + 2*self.m2_pitch, 2*self.m1_width)
|
offset = vector(self.x_off_inv_1 + self.inv.width + 2*self.m2_pitch, 2*self.m1_width)
|
||||||
self.decode_rails = self.create_vertical_bus(layer="metal2",
|
self.decode_rails = self.create_vertical_bus(layer="metal2",
|
||||||
|
|
@ -90,11 +90,11 @@ class hierarchical_predecode(design.design):
|
||||||
""" Create the input inverters to invert input signals for the decode stage. """
|
""" Create the input inverters to invert input signals for the decode stage. """
|
||||||
self.in_inst = []
|
self.in_inst = []
|
||||||
for inv_num in range(self.number_of_inputs):
|
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,
|
self.in_inst.append(self.add_inst(name=name,
|
||||||
mod=self.inv))
|
mod=self.inv))
|
||||||
self.connect_inst(["in[{0}]".format(inv_num),
|
self.connect_inst(["in_{0}".format(inv_num),
|
||||||
"inbar[{0}]".format(inv_num),
|
"inbar_{0}".format(inv_num),
|
||||||
"vdd", "gnd"])
|
"vdd", "gnd"])
|
||||||
|
|
||||||
def place_input_inverters(self):
|
def place_input_inverters(self):
|
||||||
|
|
@ -114,11 +114,11 @@ class hierarchical_predecode(design.design):
|
||||||
""" Create inverters for the inverted output decode signals. """
|
""" Create inverters for the inverted output decode signals. """
|
||||||
self.inv_inst = []
|
self.inv_inst = []
|
||||||
for inv_num in range(self.number_of_outputs):
|
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,
|
self.inv_inst.append(self.add_inst(name=name,
|
||||||
mod=self.inv))
|
mod=self.inv))
|
||||||
self.connect_inst(["Z[{}]".format(inv_num),
|
self.connect_inst(["Z_{}".format(inv_num),
|
||||||
"out[{}]".format(inv_num),
|
"out_{}".format(inv_num),
|
||||||
"vdd", "gnd"])
|
"vdd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -140,7 +140,7 @@ class hierarchical_predecode(design.design):
|
||||||
self.nand_inst = []
|
self.nand_inst = []
|
||||||
for nand_input in range(self.number_of_outputs):
|
for nand_input in range(self.number_of_outputs):
|
||||||
inout = str(self.number_of_inputs)+"x"+str(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,
|
self.nand_inst.append(self.add_inst(name=name,
|
||||||
mod=self.nand))
|
mod=self.nand))
|
||||||
self.connect_inst(connections[nand_input])
|
self.connect_inst(connections[nand_input])
|
||||||
|
|
@ -175,8 +175,8 @@ class hierarchical_predecode(design.design):
|
||||||
# typically where the p/n devices are and there are no
|
# typically where the p/n devices are and there are no
|
||||||
# pins in the nand gates.
|
# pins in the nand gates.
|
||||||
y_offset = (num+self.number_of_inputs) * self.inv.height + contact.m1m2.width + self.m1_space
|
y_offset = (num+self.number_of_inputs) * self.inv.height + contact.m1m2.width + self.m1_space
|
||||||
in_pin = "in[{}]".format(num)
|
in_pin = "in_{}".format(num)
|
||||||
a_pin = "A[{}]".format(num)
|
a_pin = "A_{}".format(num)
|
||||||
in_pos = vector(self.input_rails[in_pin].x,y_offset)
|
in_pos = vector(self.input_rails[in_pin].x,y_offset)
|
||||||
a_pos = vector(self.decode_rails[a_pin].x,y_offset)
|
a_pos = vector(self.decode_rails[a_pin].x,y_offset)
|
||||||
self.add_path("metal1",[in_pos, a_pos])
|
self.add_path("metal1",[in_pos, a_pos])
|
||||||
|
|
@ -202,7 +202,7 @@ class hierarchical_predecode(design.design):
|
||||||
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
|
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
|
||||||
|
|
||||||
z_pin = self.inv_inst[num].get_pin("Z")
|
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",
|
layer="metal1",
|
||||||
offset=z_pin.ll(),
|
offset=z_pin.ll(),
|
||||||
height=z_pin.height(),
|
height=z_pin.height(),
|
||||||
|
|
@ -214,8 +214,8 @@ class hierarchical_predecode(design.design):
|
||||||
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd]
|
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd]
|
||||||
"""
|
"""
|
||||||
for inv_num in range(self.number_of_inputs):
|
for inv_num in range(self.number_of_inputs):
|
||||||
out_pin = "Abar[{}]".format(inv_num)
|
out_pin = "Abar_{}".format(inv_num)
|
||||||
in_pin = "in[{}]".format(inv_num)
|
in_pin = "in_{}".format(inv_num)
|
||||||
|
|
||||||
#add output so that it is just below the vdd or gnd rail
|
#add output so that it is just below the vdd or gnd rail
|
||||||
# since this is where the p/n devices are and there are no
|
# since this is where the p/n devices are and there are no
|
||||||
|
|
@ -282,15 +282,7 @@ class hierarchical_predecode(design.design):
|
||||||
# Add pins in two locations
|
# Add pins in two locations
|
||||||
for xoffset in [in_xoffset, out_xoffset]:
|
for xoffset in [in_xoffset, out_xoffset]:
|
||||||
pin_pos = vector(xoffset, nand_pin.cy())
|
pin_pos = vector(xoffset, nand_pin.cy())
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_power_pin(n, pin_pos)
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ class hierarchical_predecode2x4(hierarchical_predecode):
|
||||||
self.create_modules()
|
self.create_modules()
|
||||||
self.create_input_inverters()
|
self.create_input_inverters()
|
||||||
self.create_output_inverters()
|
self.create_output_inverters()
|
||||||
connections =[["inbar[0]", "inbar[1]", "Z[0]", "vdd", "gnd"],
|
connections =[["inbar_0", "inbar_1", "Z_0", "vdd", "gnd"],
|
||||||
["in[0]", "inbar[1]", "Z[1]", "vdd", "gnd"],
|
["in_0", "inbar_1", "Z_1", "vdd", "gnd"],
|
||||||
["inbar[0]", "in[1]", "Z[2]", "vdd", "gnd"],
|
["inbar_0", "in_1", "Z_2", "vdd", "gnd"],
|
||||||
["in[0]", "in[1]", "Z[3]", "vdd", "gnd"]]
|
["in_0", "in_1", "Z_3", "vdd", "gnd"]]
|
||||||
self.create_nand_array(connections)
|
self.create_nand_array(connections)
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
|
|
@ -44,10 +44,10 @@ class hierarchical_predecode2x4(hierarchical_predecode):
|
||||||
|
|
||||||
def get_nand_input_line_combination(self):
|
def get_nand_input_line_combination(self):
|
||||||
""" These are the decoder connections of the NAND gates to the A,B pins """
|
""" These are the decoder connections of the NAND gates to the A,B pins """
|
||||||
combination = [["Abar[0]", "Abar[1]"],
|
combination = [["Abar_0", "Abar_1"],
|
||||||
["A[0]", "Abar[1]"],
|
["A_0", "Abar_1"],
|
||||||
["Abar[0]", "A[1]"],
|
["Abar_0", "A_1"],
|
||||||
["A[0]", "A[1]"]]
|
["A_0", "A_1"]]
|
||||||
return combination
|
return combination
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,14 @@ class hierarchical_predecode3x8(hierarchical_predecode):
|
||||||
self.create_modules()
|
self.create_modules()
|
||||||
self.create_input_inverters()
|
self.create_input_inverters()
|
||||||
self.create_output_inverters()
|
self.create_output_inverters()
|
||||||
connections=[["inbar[0]", "inbar[1]", "inbar[2]", "Z[0]", "vdd", "gnd"],
|
connections=[["inbar_0", "inbar_1", "inbar_2", "Z_0", "vdd", "gnd"],
|
||||||
["in[0]", "inbar[1]", "inbar[2]", "Z[1]", "vdd", "gnd"],
|
["in_0", "inbar_1", "inbar_2", "Z_1", "vdd", "gnd"],
|
||||||
["inbar[0]", "in[1]", "inbar[2]", "Z[2]", "vdd", "gnd"],
|
["inbar_0", "in_1", "inbar_2", "Z_2", "vdd", "gnd"],
|
||||||
["in[0]", "in[1]", "inbar[2]", "Z[3]", "vdd", "gnd"],
|
["in_0", "in_1", "inbar_2", "Z_3", "vdd", "gnd"],
|
||||||
["inbar[0]", "inbar[1]", "in[2]", "Z[4]", "vdd", "gnd"],
|
["inbar_0", "inbar_1", "in_2", "Z_4", "vdd", "gnd"],
|
||||||
["in[0]", "inbar[1]", "in[2]", "Z[5]", "vdd", "gnd"],
|
["in_0", "inbar_1", "in_2", "Z_5", "vdd", "gnd"],
|
||||||
["inbar[0]", "in[1]", "in[2]", "Z[6]", "vdd", "gnd"],
|
["inbar_0", "in_1", "in_2", "Z_6", "vdd", "gnd"],
|
||||||
["in[0]", "in[1]", "in[2]", "Z[7]", "vdd", "gnd"]]
|
["in_0", "in_1", "in_2", "Z_7", "vdd", "gnd"]]
|
||||||
self.create_nand_array(connections)
|
self.create_nand_array(connections)
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
|
|
@ -49,14 +49,14 @@ class hierarchical_predecode3x8(hierarchical_predecode):
|
||||||
|
|
||||||
def get_nand_input_line_combination(self):
|
def get_nand_input_line_combination(self):
|
||||||
""" These are the decoder connections of the NAND gates to the A,B,C pins """
|
""" These are the decoder connections of the NAND gates to the A,B,C pins """
|
||||||
combination = [["Abar[0]", "Abar[1]", "Abar[2]"],
|
combination = [["Abar_0", "Abar_1", "Abar_2"],
|
||||||
["A[0]", "Abar[1]", "Abar[2]"],
|
["A_0", "Abar_1", "Abar_2"],
|
||||||
["Abar[0]", "A[1]", "Abar[2]"],
|
["Abar_0", "A_1", "Abar_2"],
|
||||||
["A[0]", "A[1]", "Abar[2]"],
|
["A_0", "A_1", "Abar_2"],
|
||||||
["Abar[0]", "Abar[1]", "A[2]"],
|
["Abar_0", "Abar_1", "A_2"],
|
||||||
["A[0]", "Abar[1]", "A[2]"],
|
["A_0", "Abar_1", "A_2"],
|
||||||
["Abar[0]", "A[1]", "A[2]"],
|
["Abar_0", "A_1", "A_2"],
|
||||||
["A[0]", "A[1]", "A[2]"]]
|
["A_0", "A_1", "A_2"]]
|
||||||
return combination
|
return combination
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,11 +75,11 @@ class multibank(design.design):
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
""" Adding pins for Bank module"""
|
""" Adding pins for Bank module"""
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
self.add_pin("DOUT[{0}]".format(i),"OUT")
|
self.add_pin("DOUT_{0}".format(i),"OUT")
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
self.add_pin("BANK_DIN[{0}]".format(i),"IN")
|
self.add_pin("BANK_DIN_{0}".format(i),"IN")
|
||||||
for i in range(self.addr_size):
|
for i in range(self.addr_size):
|
||||||
self.add_pin("A[{0}]".format(i),"INPUT")
|
self.add_pin("A_{0}".format(i),"INPUT")
|
||||||
|
|
||||||
# For more than one bank, we have a bank select and name
|
# For more than one bank, we have a bank select and name
|
||||||
# the signals gated_*.
|
# the signals gated_*.
|
||||||
|
|
@ -109,7 +109,7 @@ class multibank(design.design):
|
||||||
if self.num_banks > 1:
|
if self.num_banks > 1:
|
||||||
self.route_bank_select()
|
self.route_bank_select()
|
||||||
|
|
||||||
self.route_vdd_gnd()
|
self.route_supplies()
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
""" Add modules. The order should not matter! """
|
""" Add modules. The order should not matter! """
|
||||||
|
|
@ -170,7 +170,7 @@ class multibank(design.design):
|
||||||
self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width
|
self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width
|
||||||
|
|
||||||
# A space for wells or jogging m2
|
# A space for wells or jogging m2
|
||||||
self.m2_gap = max(2*drc["pwell_to_nwell"] + drc["well_enclosure_active"],
|
self.m2_gap = max(2*drc("pwell_to_nwell"] + drc["well_enclosure_active"),
|
||||||
2*self.m2_pitch)
|
2*self.m2_pitch)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -227,10 +227,10 @@ class multibank(design.design):
|
||||||
offset=vector(0,0))
|
offset=vector(0,0))
|
||||||
temp = []
|
temp = []
|
||||||
for i in range(self.num_cols):
|
for i in range(self.num_cols):
|
||||||
temp.append("bl[{0}]".format(i))
|
temp.append("bl_{0}".format(i))
|
||||||
temp.append("br[{0}]".format(i))
|
temp.append("br_{0}".format(i))
|
||||||
for j in range(self.num_rows):
|
for j in range(self.num_rows):
|
||||||
temp.append("wl[{0}]".format(j))
|
temp.append("wl_{0}".format(j))
|
||||||
temp.extend(["vdd", "gnd"])
|
temp.extend(["vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
@ -246,8 +246,8 @@ class multibank(design.design):
|
||||||
offset=vector(0,y_offset))
|
offset=vector(0,y_offset))
|
||||||
temp = []
|
temp = []
|
||||||
for i in range(self.num_cols):
|
for i in range(self.num_cols):
|
||||||
temp.append("bl[{0}]".format(i))
|
temp.append("bl_{0}".format(i))
|
||||||
temp.append("br[{0}]".format(i))
|
temp.append("br_{0}".format(i))
|
||||||
temp.extend([self.prefix+"clk_buf_bar", "vdd"])
|
temp.extend([self.prefix+"clk_buf_bar", "vdd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
@ -265,13 +265,13 @@ class multibank(design.design):
|
||||||
offset=vector(0,y_offset).scale(-1,-1))
|
offset=vector(0,y_offset).scale(-1,-1))
|
||||||
temp = []
|
temp = []
|
||||||
for i in range(self.num_cols):
|
for i in range(self.num_cols):
|
||||||
temp.append("bl[{0}]".format(i))
|
temp.append("bl_{0}".format(i))
|
||||||
temp.append("br[{0}]".format(i))
|
temp.append("br_{0}".format(i))
|
||||||
for k in range(self.words_per_row):
|
for k in range(self.words_per_row):
|
||||||
temp.append("sel[{0}]".format(k))
|
temp.append("sel_{0}".format(k))
|
||||||
for j in range(self.word_size):
|
for j in range(self.word_size):
|
||||||
temp.append("bl_out[{0}]".format(j))
|
temp.append("bl_out_{0}".format(j))
|
||||||
temp.append("br_out[{0}]".format(j))
|
temp.append("br_out_{0}".format(j))
|
||||||
temp.append("gnd")
|
temp.append("gnd")
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
@ -284,13 +284,13 @@ class multibank(design.design):
|
||||||
offset=vector(0,y_offset).scale(-1,-1))
|
offset=vector(0,y_offset).scale(-1,-1))
|
||||||
temp = []
|
temp = []
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
temp.append("sa_out[{0}]".format(i))
|
temp.append("sa_out_{0}".format(i))
|
||||||
if self.words_per_row == 1:
|
if self.words_per_row == 1:
|
||||||
temp.append("bl[{0}]".format(i))
|
temp.append("bl_{0}".format(i))
|
||||||
temp.append("br[{0}]".format(i))
|
temp.append("br_{0}".format(i))
|
||||||
else:
|
else:
|
||||||
temp.append("bl_out[{0}]".format(i))
|
temp.append("bl_out_{0}".format(i))
|
||||||
temp.append("br_out[{0}]".format(i))
|
temp.append("br_out_{0}".format(i))
|
||||||
|
|
||||||
temp.extend([self.prefix+"s_en", "vdd", "gnd"])
|
temp.extend([self.prefix+"s_en", "vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
@ -306,14 +306,14 @@ class multibank(design.design):
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
temp.append("BANK_DIN[{0}]".format(i))
|
temp.append("BANK_DIN_{0}".format(i))
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
if (self.words_per_row == 1):
|
if (self.words_per_row == 1):
|
||||||
temp.append("bl[{0}]".format(i))
|
temp.append("bl_{0}".format(i))
|
||||||
temp.append("br[{0}]".format(i))
|
temp.append("br_{0}".format(i))
|
||||||
else:
|
else:
|
||||||
temp.append("bl_out[{0}]".format(i))
|
temp.append("bl_out_{0}".format(i))
|
||||||
temp.append("br_out[{0}]".format(i))
|
temp.append("br_out_{0}".format(i))
|
||||||
temp.extend([self.prefix+"w_en", "vdd", "gnd"])
|
temp.extend([self.prefix+"w_en", "vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
@ -327,9 +327,9 @@ class multibank(design.design):
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
temp.append("sa_out[{0}]".format(i))
|
temp.append("sa_out_{0}".format(i))
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
temp.append("DOUT[{0}]".format(i))
|
temp.append("DOUT_{0}".format(i))
|
||||||
temp.extend([self.prefix+"tri_en", self.prefix+"tri_en_bar", "vdd", "gnd"])
|
temp.extend([self.prefix+"tri_en", self.prefix+"tri_en_bar", "vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
@ -350,9 +350,9 @@ class multibank(design.design):
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
for i in range(self.row_addr_size):
|
for i in range(self.row_addr_size):
|
||||||
temp.append("A[{0}]".format(i+self.col_addr_size))
|
temp.append("A_{0}".format(i+self.col_addr_size))
|
||||||
for j in range(self.num_rows):
|
for j in range(self.num_rows):
|
||||||
temp.append("dec_out[{0}]".format(j))
|
temp.append("dec_out_{0}".format(j))
|
||||||
temp.extend(["vdd", "gnd"])
|
temp.extend(["vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
@ -367,9 +367,9 @@ class multibank(design.design):
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
for i in range(self.num_rows):
|
for i in range(self.num_rows):
|
||||||
temp.append("dec_out[{0}]".format(i))
|
temp.append("dec_out_{0}".format(i))
|
||||||
for i in range(self.num_rows):
|
for i in range(self.num_rows):
|
||||||
temp.append("wl[{0}]".format(i))
|
temp.append("wl_{0}".format(i))
|
||||||
temp.append(self.prefix+"clk_buf")
|
temp.append(self.prefix+"clk_buf")
|
||||||
temp.append("vdd")
|
temp.append("vdd")
|
||||||
temp.append("gnd")
|
temp.append("gnd")
|
||||||
|
|
@ -382,16 +382,16 @@ class multibank(design.design):
|
||||||
"""
|
"""
|
||||||
# Place the col decoder right aligned with row decoder
|
# Place the col decoder right aligned with row decoder
|
||||||
x_off = -(self.central_bus_width + self.wordline_driver.width + self.col_decoder.width)
|
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"])
|
y_off = -(self.col_decoder.height + 2*drc("well_to_well"))
|
||||||
self.col_decoder_inst=self.add_inst(name="col_address_decoder",
|
self.col_decoder_inst=self.add_inst(name="col_address_decoder",
|
||||||
mod=self.col_decoder,
|
mod=self.col_decoder,
|
||||||
offset=vector(x_off,y_off))
|
offset=vector(x_off,y_off))
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
for i in range(self.col_addr_size):
|
for i in range(self.col_addr_size):
|
||||||
temp.append("A[{0}]".format(i))
|
temp.append("A_{0}".format(i))
|
||||||
for j in range(self.num_col_addr_lines):
|
for j in range(self.num_col_addr_lines):
|
||||||
temp.append("sel[{0}]".format(j))
|
temp.append("sel_{0}".format(j))
|
||||||
temp.extend(["vdd", "gnd"])
|
temp.extend(["vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
@ -427,7 +427,7 @@ class multibank(design.design):
|
||||||
y_off = min(self.col_decoder_inst.by(), self.col_mux_array_inst.by())
|
y_off = min(self.col_decoder_inst.by(), self.col_mux_array_inst.by())
|
||||||
else:
|
else:
|
||||||
y_off = self.row_decoder_inst.by()
|
y_off = self.row_decoder_inst.by()
|
||||||
y_off -= (self.bank_select.height + drc["well_to_well"])
|
y_off -= (self.bank_select.height + drc("well_to_well"))
|
||||||
self.bank_select_pos = vector(x_off,y_off)
|
self.bank_select_pos = vector(x_off,y_off)
|
||||||
self.bank_select_inst = self.add_inst(name="bank_select",
|
self.bank_select_inst = self.add_inst(name="bank_select",
|
||||||
mod=self.bank_select,
|
mod=self.bank_select,
|
||||||
|
|
@ -440,33 +440,11 @@ class multibank(design.design):
|
||||||
temp.extend(["vdd", "gnd"])
|
temp.extend(["vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
def route_vdd_gnd(self):
|
def route_supplies(self):
|
||||||
""" Propagate all vdd/gnd pins up to this level for all modules """
|
""" Propagate all vdd/gnd pins up to this level for all modules """
|
||||||
|
for inst in self.insts:
|
||||||
# These are the instances that every bank has
|
self.copy_power_pins(inst,"vdd")
|
||||||
top_instances = [self.bitcell_array_inst,
|
self.copy_power_pins(inst,"gnd")
|
||||||
self.precharge_array_inst,
|
|
||||||
self.sense_amp_array_inst,
|
|
||||||
self.write_driver_array_inst,
|
|
||||||
# self.tri_gate_array_inst,
|
|
||||||
self.row_decoder_inst,
|
|
||||||
self.wordline_driver_inst]
|
|
||||||
# Add these if we use the part...
|
|
||||||
if self.col_addr_size > 0:
|
|
||||||
top_instances.append(self.col_decoder_inst)
|
|
||||||
top_instances.append(self.col_mux_array_inst)
|
|
||||||
|
|
||||||
if self.num_banks > 1:
|
|
||||||
top_instances.append(self.bank_select_inst)
|
|
||||||
|
|
||||||
|
|
||||||
for inst in top_instances:
|
|
||||||
# Column mux has no vdd
|
|
||||||
if self.col_addr_size==0 or (self.col_addr_size>0 and inst != self.col_mux_array_inst):
|
|
||||||
self.copy_layout_pin(inst, "vdd")
|
|
||||||
# Precharge has no gnd
|
|
||||||
if inst != self.precharge_array_inst:
|
|
||||||
self.copy_layout_pin(inst, "gnd")
|
|
||||||
|
|
||||||
def route_bank_select(self):
|
def route_bank_select(self):
|
||||||
""" Route the bank select logic. """
|
""" Route the bank select logic. """
|
||||||
|
|
@ -550,10 +528,10 @@ class multibank(design.design):
|
||||||
""" Routing of BL and BR between pre-charge and bitcell array """
|
""" Routing of BL and BR between pre-charge and bitcell array """
|
||||||
|
|
||||||
for i in range(self.num_cols):
|
for i in range(self.num_cols):
|
||||||
precharge_bl = self.precharge_array_inst.get_pin("bl[{}]".format(i)).bc()
|
precharge_bl = self.precharge_array_inst.get_pin("bl_{}".format(i)).bc()
|
||||||
precharge_br = self.precharge_array_inst.get_pin("br[{}]".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_bl = self.bitcell_array_inst.get_pin("bl_{}".format(i)).uc()
|
||||||
bitcell_br = self.bitcell_array_inst.get_pin("br[{}]".format(i)).uc()
|
bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).uc()
|
||||||
|
|
||||||
yoffset = 0.5*(precharge_bl.y+bitcell_bl.y)
|
yoffset = 0.5*(precharge_bl.y+bitcell_bl.y)
|
||||||
self.add_path("metal2",[precharge_bl, vector(precharge_bl.x,yoffset),
|
self.add_path("metal2",[precharge_bl, vector(precharge_bl.x,yoffset),
|
||||||
|
|
@ -570,10 +548,10 @@ class multibank(design.design):
|
||||||
return
|
return
|
||||||
|
|
||||||
for i in range(self.num_cols):
|
for i in range(self.num_cols):
|
||||||
col_mux_bl = self.col_mux_array_inst.get_pin("bl[{}]".format(i)).uc()
|
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()
|
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_bl = self.bitcell_array_inst.get_pin("bl_{}".format(i)).bc()
|
||||||
bitcell_br = self.bitcell_array_inst.get_pin("br[{}]".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)
|
yoffset = 0.5*(col_mux_bl.y+bitcell_bl.y)
|
||||||
self.add_path("metal2",[col_mux_bl, vector(col_mux_bl.x,yoffset),
|
self.add_path("metal2",[col_mux_bl, vector(col_mux_bl.x,yoffset),
|
||||||
|
|
@ -585,17 +563,17 @@ class multibank(design.design):
|
||||||
""" Routing of BL and BR between sense_amp and column mux or bitcell array """
|
""" Routing of BL and BR between sense_amp and column mux or bitcell array """
|
||||||
|
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
sense_amp_bl = self.sense_amp_array_inst.get_pin("bl[{}]".format(i)).uc()
|
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()
|
sense_amp_br = self.sense_amp_array_inst.get_pin("br_{}".format(i)).uc()
|
||||||
|
|
||||||
if self.col_addr_size>0:
|
if self.col_addr_size>0:
|
||||||
# Sense amp is connected to the col mux
|
# Sense amp is connected to the col mux
|
||||||
connect_bl = self.col_mux_array_inst.get_pin("bl_out[{}]".format(i)).bc()
|
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()
|
connect_br = self.col_mux_array_inst.get_pin("br_out_{}".format(i)).bc()
|
||||||
else:
|
else:
|
||||||
# Sense amp is directly connected to the bitcell array
|
# Sense amp is directly connected to the bitcell array
|
||||||
connect_bl = self.bitcell_array_inst.get_pin("bl[{}]".format(i)).bc()
|
connect_bl = self.bitcell_array_inst.get_pin("bl_{}".format(i)).bc()
|
||||||
connect_br = self.bitcell_array_inst.get_pin("br[{}]".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)
|
yoffset = 0.5*(sense_amp_bl.y+connect_bl.y)
|
||||||
|
|
@ -609,8 +587,8 @@ class multibank(design.design):
|
||||||
|
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
# Connection of data_out of sense amp to data_in
|
# Connection of data_out of sense amp to data_in
|
||||||
tri_gate_in = self.tri_gate_array_inst.get_pin("in[{}]".format(i)).lc()
|
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()
|
sa_data_out = self.sense_amp_array_inst.get_pin("data_{}".format(i)).bc()
|
||||||
|
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||||
offset=tri_gate_in)
|
offset=tri_gate_in)
|
||||||
|
|
@ -621,8 +599,8 @@ class multibank(design.design):
|
||||||
def route_sense_amp_out(self):
|
def route_sense_amp_out(self):
|
||||||
""" Add pins for the sense amp output """
|
""" Add pins for the sense amp output """
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
data_pin = self.sense_amp_array_inst.get_pin("data[{}]".format(i))
|
data_pin = self.sense_amp_array_inst.get_pin("data_{}".format(i))
|
||||||
self.add_layout_pin_rect_center(text="DOUT[{}]".format(i),
|
self.add_layout_pin_rect_center(text="DOUT_{}".format(i),
|
||||||
layer=data_pin.layer,
|
layer=data_pin.layer,
|
||||||
offset=data_pin.center(),
|
offset=data_pin.center(),
|
||||||
height=data_pin.height(),
|
height=data_pin.height(),
|
||||||
|
|
@ -631,8 +609,8 @@ class multibank(design.design):
|
||||||
def route_tri_gate_out(self):
|
def route_tri_gate_out(self):
|
||||||
""" Metal 3 routing of tri_gate output data """
|
""" Metal 3 routing of tri_gate output data """
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
data_pin = self.tri_gate_array_inst.get_pin("out[{}]".format(i))
|
data_pin = self.tri_gate_array_inst.get_pin("out_{}".format(i))
|
||||||
self.add_layout_pin_rect_center(text="DOUT[{}]".format(i),
|
self.add_layout_pin_rect_center(text="DOUT_{}".format(i),
|
||||||
layer=data_pin.layer,
|
layer=data_pin.layer,
|
||||||
offset=data_pin.center(),
|
offset=data_pin.center(),
|
||||||
height=data_pin.height(),
|
height=data_pin.height(),
|
||||||
|
|
@ -645,8 +623,8 @@ class multibank(design.design):
|
||||||
# Create inputs for the row address lines
|
# Create inputs for the row address lines
|
||||||
for i in range(self.row_addr_size):
|
for i in range(self.row_addr_size):
|
||||||
addr_idx = i + self.col_addr_size
|
addr_idx = i + self.col_addr_size
|
||||||
decoder_name = "A[{}]".format(i)
|
decoder_name = "A_{}".format(i)
|
||||||
addr_name = "A[{}]".format(addr_idx)
|
addr_name = "A_{}".format(addr_idx)
|
||||||
self.copy_layout_pin(self.row_decoder_inst, decoder_name, addr_name)
|
self.copy_layout_pin(self.row_decoder_inst, decoder_name, addr_name)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -654,8 +632,8 @@ class multibank(design.design):
|
||||||
""" Connecting write driver """
|
""" Connecting write driver """
|
||||||
|
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
data_name = "data[{}]".format(i)
|
data_name = "data_{}".format(i)
|
||||||
din_name = "BANK_DIN[{}]".format(i)
|
din_name = "BANK_DIN_{}".format(i)
|
||||||
self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name)
|
self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -666,15 +644,15 @@ class multibank(design.design):
|
||||||
# we don't care about bends after connecting to the input pin, so let the path code decide.
|
# 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):
|
for i in range(self.num_rows):
|
||||||
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
# 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()
|
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()
|
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)
|
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)
|
mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1)
|
||||||
self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
||||||
|
|
||||||
# The mid guarantees we exit the input cell to the right.
|
# The mid guarantees we exit the input cell to the right.
|
||||||
driver_wl_pos = self.wordline_driver_inst.get_pin("wl[{}]".format(i)).rc()
|
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()
|
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)
|
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)
|
mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1)
|
||||||
self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
||||||
|
|
@ -699,20 +677,20 @@ class multibank(design.design):
|
||||||
elif self.col_addr_size > 1:
|
elif self.col_addr_size > 1:
|
||||||
decode_names = []
|
decode_names = []
|
||||||
for i in range(self.num_col_addr_lines):
|
for i in range(self.num_col_addr_lines):
|
||||||
decode_names.append("out[{}]".format(i))
|
decode_names.append("out_{}".format(i))
|
||||||
|
|
||||||
for i in range(self.col_addr_size):
|
for i in range(self.col_addr_size):
|
||||||
decoder_name = "in[{}]".format(i)
|
decoder_name = "in_{}".format(i)
|
||||||
addr_name = "A[{}]".format(i)
|
addr_name = "A_{}".format(i)
|
||||||
self.copy_layout_pin(self.col_decoder_inst, decoder_name, addr_name)
|
self.copy_layout_pin(self.col_decoder_inst, decoder_name, addr_name)
|
||||||
|
|
||||||
|
|
||||||
# This will do a quick "river route" on two layers.
|
# This will do a quick "river route" on two layers.
|
||||||
# When above the top select line it will offset "inward" again to prevent conflicts.
|
# 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.
|
# 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()
|
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)):
|
for (decode_name,i) in zip(decode_names,range(self.num_col_addr_lines)):
|
||||||
mux_name = "sel[{}]".format(i)
|
mux_name = "sel_{}".format(i)
|
||||||
mux_addr_pos = self.col_mux_array_inst.get_pin(mux_name).lc()
|
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()
|
decode_out_pos = self.col_decoder_inst.get_pin(decode_name).center()
|
||||||
|
|
@ -738,7 +716,7 @@ class multibank(design.design):
|
||||||
"""
|
"""
|
||||||
# Add the wordline names
|
# Add the wordline names
|
||||||
for i in range(self.num_rows):
|
for i in range(self.num_rows):
|
||||||
wl_name = "wl[{}]".format(i)
|
wl_name = "wl_{}".format(i)
|
||||||
wl_pin = self.bitcell_array_inst.get_pin(wl_name)
|
wl_pin = self.bitcell_array_inst.get_pin(wl_name)
|
||||||
self.add_label(text=wl_name,
|
self.add_label(text=wl_name,
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
|
|
@ -746,8 +724,8 @@ class multibank(design.design):
|
||||||
|
|
||||||
# Add the bitline names
|
# Add the bitline names
|
||||||
for i in range(self.num_cols):
|
for i in range(self.num_cols):
|
||||||
bl_name = "bl[{}]".format(i)
|
bl_name = "bl_{}".format(i)
|
||||||
br_name = "br[{}]".format(i)
|
br_name = "br_{}".format(i)
|
||||||
bl_pin = self.bitcell_array_inst.get_pin(bl_name)
|
bl_pin = self.bitcell_array_inst.get_pin(bl_name)
|
||||||
br_pin = self.bitcell_array_inst.get_pin(br_name)
|
br_pin = self.bitcell_array_inst.get_pin(br_name)
|
||||||
self.add_label(text=bl_name,
|
self.add_label(text=bl_name,
|
||||||
|
|
@ -759,16 +737,16 @@ class multibank(design.design):
|
||||||
|
|
||||||
# # Add the data output names to the sense amp output
|
# # Add the data output names to the sense amp output
|
||||||
# for i in range(self.word_size):
|
# for i in range(self.word_size):
|
||||||
# data_name = "data[{}]".format(i)
|
# data_name = "data_{}".format(i)
|
||||||
# data_pin = self.sense_amp_array_inst.get_pin(data_name)
|
# data_pin = self.sense_amp_array_inst.get_pin(data_name)
|
||||||
# self.add_label(text="sa_out[{}]".format(i),
|
# self.add_label(text="sa_out_{}".format(i),
|
||||||
# layer="metal2",
|
# layer="metal2",
|
||||||
# offset=data_pin.center())
|
# offset=data_pin.center())
|
||||||
|
|
||||||
# Add labels on the decoder
|
# Add labels on the decoder
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
data_name = "dec_out[{}]".format(i)
|
data_name = "dec_out_{}".format(i)
|
||||||
pin_name = "in[{}]".format(i)
|
pin_name = "in_{}".format(i)
|
||||||
data_pin = self.wordline_driver_inst.get_pin(pin_name)
|
data_pin = self.wordline_driver_inst.get_pin(pin_name)
|
||||||
self.add_label(text=data_name,
|
self.add_label(text=data_name,
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ class precharge_array(design.design):
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
"""Adds pins for spice file"""
|
"""Adds pins for spice file"""
|
||||||
for i in range(self.columns):
|
for i in range(self.columns):
|
||||||
self.add_pin("bl[{0}]".format(i))
|
self.add_pin("bl_{0}".format(i))
|
||||||
self.add_pin("br[{0}]".format(i))
|
self.add_pin("br_{0}".format(i))
|
||||||
self.add_pin("en")
|
self.add_pin("en")
|
||||||
self.add_pin("vdd")
|
self.add_pin("vdd")
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ class precharge_array(design.design):
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
offset=self.pc_cell.get_pin("en").ll(),
|
offset=self.pc_cell.get_pin("en").ll(),
|
||||||
width=self.width,
|
width=self.width,
|
||||||
height=drc["minwidth_metal1"])
|
height=drc("minwidth_metal1"))
|
||||||
|
|
||||||
for inst in self.local_insts:
|
for inst in self.local_insts:
|
||||||
self.copy_layout_pin(inst, "vdd")
|
self.copy_layout_pin(inst, "vdd")
|
||||||
|
|
@ -71,16 +71,16 @@ class precharge_array(design.design):
|
||||||
for i in range(len(self.local_insts)):
|
for i in range(len(self.local_insts)):
|
||||||
inst = self.local_insts[i]
|
inst = self.local_insts[i]
|
||||||
bl_pin = inst.get_pin("bl")
|
bl_pin = inst.get_pin("bl")
|
||||||
self.add_layout_pin(text="bl[{0}]".format(i),
|
self.add_layout_pin(text="bl_{0}".format(i),
|
||||||
layer="metal2",
|
layer="metal2",
|
||||||
offset=bl_pin.ll(),
|
offset=bl_pin.ll(),
|
||||||
width=drc["minwidth_metal2"],
|
width=drc("minwidth_metal2"),
|
||||||
height=bl_pin.height())
|
height=bl_pin.height())
|
||||||
br_pin = inst.get_pin("br")
|
br_pin = inst.get_pin("br")
|
||||||
self.add_layout_pin(text="br[{0}]".format(i),
|
self.add_layout_pin(text="br_{0}".format(i),
|
||||||
layer="metal2",
|
layer="metal2",
|
||||||
offset=br_pin.ll(),
|
offset=br_pin.ll(),
|
||||||
width=drc["minwidth_metal2"],
|
width=drc("minwidth_metal2"),
|
||||||
height=bl_pin.height())
|
height=bl_pin.height())
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -94,7 +94,7 @@ class precharge_array(design.design):
|
||||||
mod=self.pc_cell,
|
mod=self.pc_cell,
|
||||||
offset=offset)
|
offset=offset)
|
||||||
self.local_insts.append(inst)
|
self.local_insts.append(inst)
|
||||||
self.connect_inst(["bl[{0}]".format(i), "br[{0}]".format(i), "en", "vdd"])
|
self.connect_inst(["bl_{0}".format(i), "br_{0}".format(i), "en", "vdd"])
|
||||||
|
|
||||||
|
|
||||||
def place_insts(self):
|
def place_insts(self):
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,6 @@ class replica_bitline(design.design):
|
||||||
self.access_tx_offset = vector(-gap_width-self.access_tx.width-self.inv.width, 0.5*self.inv.height)
|
self.access_tx_offset = vector(-gap_width-self.access_tx.width-self.inv.width, 0.5*self.inv.height)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
""" Add the modules for later usage """
|
""" Add the modules for later usage """
|
||||||
|
|
||||||
|
|
@ -111,12 +110,12 @@ class replica_bitline(design.design):
|
||||||
# This is the threshold detect inverter on the output of the RBL
|
# This is the threshold detect inverter on the output of the RBL
|
||||||
self.rbl_inv_inst=self.add_inst(name="rbl_inv",
|
self.rbl_inv_inst=self.add_inst(name="rbl_inv",
|
||||||
mod=self.inv)
|
mod=self.inv)
|
||||||
self.connect_inst(["bl0[0]", "out", "vdd", "gnd"])
|
self.connect_inst(["bl0_0", "out", "vdd", "gnd"])
|
||||||
|
|
||||||
self.tx_inst=self.add_inst(name="rbl_access_tx",
|
self.tx_inst=self.add_inst(name="rbl_access_tx",
|
||||||
mod=self.access_tx)
|
mod=self.access_tx)
|
||||||
# D, G, S, B
|
# D, G, S, B
|
||||||
self.connect_inst(["vdd", "delayed_en", "bl0[0]", "vdd"])
|
self.connect_inst(["vdd", "delayed_en", "bl0_0", "vdd"])
|
||||||
# add the well and poly contact
|
# add the well and poly contact
|
||||||
|
|
||||||
self.dc_inst=self.add_inst(name="delay_chain",
|
self.dc_inst=self.add_inst(name="delay_chain",
|
||||||
|
|
@ -127,22 +126,22 @@ class replica_bitline(design.design):
|
||||||
mod=self.replica_bitcell)
|
mod=self.replica_bitcell)
|
||||||
temp = []
|
temp = []
|
||||||
for port in range(self.total_ports):
|
for port in range(self.total_ports):
|
||||||
temp.append("bl{}[0]".format(port))
|
temp.append("bl{}_0".format(port))
|
||||||
temp.append("br{}[0]".format(port))
|
temp.append("br{}_0".format(port))
|
||||||
for port in range(self.total_ports):
|
for port in range(self.total_ports):
|
||||||
temp.append("delayed_en")
|
temp.append("delayed_en")
|
||||||
temp.append("vdd")
|
temp.append("vdd")
|
||||||
temp.append("gnd")
|
temp.append("gnd")
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
#self.connect_inst(["bl[0]", "br[0]", "delayed_en", "vdd", "gnd"])
|
#self.connect_inst(["bl_0", "br_0", "delayed_en", "vdd", "gnd"])
|
||||||
|
|
||||||
self.rbl_inst=self.add_inst(name="load",
|
self.rbl_inst=self.add_inst(name="load",
|
||||||
mod=self.rbl)
|
mod=self.rbl)
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
for port in range(self.total_ports):
|
for port in range(self.total_ports):
|
||||||
temp.append("bl{}[0]".format(port))
|
temp.append("bl{}_0".format(port))
|
||||||
temp.append("br{}[0]".format(port))
|
temp.append("br{}_0".format(port))
|
||||||
for wl in range(self.bitcell_loads):
|
for wl in range(self.bitcell_loads):
|
||||||
for port in range(self.total_ports):
|
for port in range(self.total_ports):
|
||||||
temp.append("gnd")
|
temp.append("gnd")
|
||||||
|
|
@ -180,29 +179,24 @@ class replica_bitline(design.design):
|
||||||
""" Connect the RBL word lines to gnd """
|
""" Connect the RBL word lines to gnd """
|
||||||
# Connect the WL and gnd pins directly to the center and right gnd rails
|
# Connect the WL and gnd pins directly to the center and right gnd rails
|
||||||
for row in range(self.bitcell_loads):
|
for row in range(self.bitcell_loads):
|
||||||
wl = self.wl_list[0]+"[{}]".format(row)
|
wl = self.wl_list[0]+"_{}".format(row)
|
||||||
pin = self.rbl_inst.get_pin(wl)
|
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
|
# Wordlines may be close to each other when tiled, so gnd connections are routed in opposite directions
|
||||||
if row % 2 == 0:
|
|
||||||
vertical_extension = vector(0, 1.5*drc["minwidth_metal1"] + 0.5*contact.m1m2.height)
|
|
||||||
else:
|
|
||||||
vertical_extension = vector(0, -1.5*drc["minwidth_metal1"] - 1.5*contact.m1m2.height)
|
|
||||||
|
|
||||||
pin_right = pin.rc()
|
pin_right = pin.rc()
|
||||||
pin_extension1 = pin_right + vector(self.m3_pitch,0)
|
pin_extension = pin_right + vector(self.m3_pitch,0)
|
||||||
pin_extension2 = pin_extension1 + vertical_extension
|
|
||||||
if pin.layer != "metal1":
|
if pin.layer != "metal1":
|
||||||
continue
|
continue
|
||||||
self.add_path("metal1", [pin_right, pin_extension1, pin_extension2])
|
self.add_path("metal1", [pin_right, pin_extension])
|
||||||
self.add_power_pin("gnd", pin_extension2)
|
self.add_power_pin("gnd", pin_extension)
|
||||||
|
|
||||||
# for multiport, need to short wordlines to each other so they all connect to gnd
|
# for multiport, need to short wordlines to each other so they all connect to gnd
|
||||||
wl_last = self.wl_list[self.total_ports-1]+"[{}]".format(row)
|
wl_last = self.wl_list[self.total_ports-1]+"_{}".format(row)
|
||||||
pin_last = self.rbl_inst.get_pin(wl_last)
|
pin_last = self.rbl_inst.get_pin(wl_last)
|
||||||
|
|
||||||
correct = vector(0.5*drc["minwidth_metal1"], 0)
|
correct = vector(0.5*drc("minwidth_metal1"), 0)
|
||||||
self.add_path("metal1", [pin.rc()-correct, pin_last.rc()-correct])
|
self.add_path("metal1", [pin.rc()-correct, pin_last.rc()-correct])
|
||||||
|
|
||||||
def route_supplies(self):
|
def route_supplies(self):
|
||||||
|
|
@ -261,7 +255,7 @@ class replica_bitline(design.design):
|
||||||
# 3. Route the contact of previous route to the bitcell WL
|
# 3. Route the contact of previous route to the bitcell WL
|
||||||
# route bend of previous net to bitcell WL
|
# route bend of previous net to bitcell WL
|
||||||
wl_offset = self.rbc_inst.get_pin(self.wl_list[0]).lc()
|
wl_offset = self.rbc_inst.get_pin(self.wl_list[0]).lc()
|
||||||
wl_mid1 = wl_offset - vector(1.5*drc["minwidth_metal1"], 0)
|
wl_mid1 = wl_offset - vector(1.5*drc("minwidth_metal1"), 0)
|
||||||
wl_mid2 = vector(wl_mid1.x, contact_offset.y)
|
wl_mid2 = vector(wl_mid1.x, contact_offset.y)
|
||||||
#xmid_point= 0.5*(wl_offset.x+contact_offset.x)
|
#xmid_point= 0.5*(wl_offset.x+contact_offset.x)
|
||||||
#wl_mid1 = vector(xmid_point,contact_offset.y)
|
#wl_mid1 = vector(xmid_point,contact_offset.y)
|
||||||
|
|
@ -274,13 +268,13 @@ class replica_bitline(design.design):
|
||||||
pin = self.rbc_inst.get_pin(wl)
|
pin = self.rbc_inst.get_pin(wl)
|
||||||
pin_last = self.rbc_inst.get_pin(wl_last)
|
pin_last = self.rbc_inst.get_pin(wl_last)
|
||||||
|
|
||||||
correct = vector(0.5*drc["minwidth_metal1"], 0)
|
correct = vector(0.5*drc("minwidth_metal1"), 0)
|
||||||
self.add_path("metal1", [pin.lc()+correct, pin_last.lc()+correct])
|
self.add_path("metal1", [pin.lc()+correct, pin_last.lc()+correct])
|
||||||
|
|
||||||
# DRAIN ROUTE
|
# DRAIN ROUTE
|
||||||
# Route the drain to the vdd rail
|
# Route the drain to the vdd rail
|
||||||
drain_offset = self.tx_inst.get_pin("D").center()
|
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
|
# SOURCE ROUTE
|
||||||
# Route the drain to the RBL inverter input
|
# Route the drain to the RBL inverter input
|
||||||
|
|
@ -414,7 +408,7 @@ class replica_bitline(design.design):
|
||||||
|
|
||||||
# Connect the WL and gnd pins directly to the center and right gnd rails
|
# Connect the WL and gnd pins directly to the center and right gnd rails
|
||||||
for row in range(self.bitcell_loads):
|
for row in range(self.bitcell_loads):
|
||||||
wl = self.wl_list[0]+"[{}]".format(row)
|
wl = self.wl_list[0]+"_{}".format(row)
|
||||||
pin = self.rbl_inst.get_pin(wl)
|
pin = self.rbl_inst.get_pin(wl)
|
||||||
if pin.layer != "metal1":
|
if pin.layer != "metal1":
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,9 @@ class sense_amp_array(design.design):
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
for i in range(0,self.word_size):
|
for i in range(0,self.word_size):
|
||||||
self.add_pin("data[{0}]".format(i))
|
self.add_pin("data_{0}".format(i))
|
||||||
self.add_pin("bl[{0}]".format(i))
|
self.add_pin("bl_{0}".format(i))
|
||||||
self.add_pin("br[{0}]".format(i))
|
self.add_pin("br_{0}".format(i))
|
||||||
self.add_pin("en")
|
self.add_pin("en")
|
||||||
self.add_pin("vdd")
|
self.add_pin("vdd")
|
||||||
self.add_pin("gnd")
|
self.add_pin("gnd")
|
||||||
|
|
@ -70,9 +70,9 @@ class sense_amp_array(design.design):
|
||||||
name = "sa_d{0}".format(i)
|
name = "sa_d{0}".format(i)
|
||||||
self.local_insts.append(self.add_inst(name=name,
|
self.local_insts.append(self.add_inst(name=name,
|
||||||
mod=self.amp))
|
mod=self.amp))
|
||||||
self.connect_inst(["bl[{0}]".format(i),
|
self.connect_inst(["bl_{0}".format(i),
|
||||||
"br[{0}]".format(i),
|
"br_{0}".format(i),
|
||||||
"data[{0}]".format(i),
|
"data_{0}".format(i),
|
||||||
"en", "vdd", "gnd"])
|
"en", "vdd", "gnd"])
|
||||||
|
|
||||||
def place_sense_amp_array(self):
|
def place_sense_amp_array(self):
|
||||||
|
|
@ -107,18 +107,18 @@ class sense_amp_array(design.design):
|
||||||
br_pin = inst.get_pin("br")
|
br_pin = inst.get_pin("br")
|
||||||
dout_pin = inst.get_pin("dout")
|
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",
|
layer="metal2",
|
||||||
offset=bl_pin.ll(),
|
offset=bl_pin.ll(),
|
||||||
width=bl_pin.width(),
|
width=bl_pin.width(),
|
||||||
height=bl_pin.height())
|
height=bl_pin.height())
|
||||||
self.add_layout_pin(text="br[{0}]".format(i),
|
self.add_layout_pin(text="br_{0}".format(i),
|
||||||
layer="metal2",
|
layer="metal2",
|
||||||
offset=br_pin.ll(),
|
offset=br_pin.ll(),
|
||||||
width=br_pin.width(),
|
width=br_pin.width(),
|
||||||
height=br_pin.height())
|
height=br_pin.height())
|
||||||
|
|
||||||
self.add_layout_pin(text="data[{0}]".format(i),
|
self.add_layout_pin(text="data_{0}".format(i),
|
||||||
layer="metal2",
|
layer="metal2",
|
||||||
offset=dout_pin.ll(),
|
offset=dout_pin.ll(),
|
||||||
width=dout_pin.width(),
|
width=dout_pin.width(),
|
||||||
|
|
@ -132,7 +132,7 @@ class sense_amp_array(design.design):
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
offset=sclk_offset,
|
offset=sclk_offset,
|
||||||
width=self.width,
|
width=self.width,
|
||||||
height=drc["minwidth_metal1"])
|
height=drc("minwidth_metal1"))
|
||||||
|
|
||||||
def analytical_delay(self, slew, load=0.0):
|
def analytical_delay(self, slew, load=0.0):
|
||||||
return self.amp.analytical_delay(slew=slew, load=load)
|
return self.amp.analytical_delay(slew=slew, load=load)
|
||||||
|
|
|
||||||
|
|
@ -50,13 +50,13 @@ class single_level_column_mux_array(design.design):
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
for i in range(self.columns):
|
for i in range(self.columns):
|
||||||
self.add_pin("bl[{}]".format(i))
|
self.add_pin("bl_{}".format(i))
|
||||||
self.add_pin("br[{}]".format(i))
|
self.add_pin("br_{}".format(i))
|
||||||
for i in range(self.words_per_row):
|
for i in range(self.words_per_row):
|
||||||
self.add_pin("sel[{}]".format(i))
|
self.add_pin("sel_{}".format(i))
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
self.add_pin("bl_out[{}]".format(i))
|
self.add_pin("bl_out_{}".format(i))
|
||||||
self.add_pin("br_out[{}]".format(i))
|
self.add_pin("br_out_{}".format(i))
|
||||||
self.add_pin("gnd")
|
self.add_pin("gnd")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -83,11 +83,11 @@ class single_level_column_mux_array(design.design):
|
||||||
self.mux_inst.append(self.add_inst(name=name,
|
self.mux_inst.append(self.add_inst(name=name,
|
||||||
mod=self.mux))
|
mod=self.mux))
|
||||||
|
|
||||||
self.connect_inst(["bl[{}]".format(col_num),
|
self.connect_inst(["bl_{}".format(col_num),
|
||||||
"br[{}]".format(col_num),
|
"br_{}".format(col_num),
|
||||||
"bl_out[{}]".format(int(col_num/self.words_per_row)),
|
"bl_out_{}".format(int(col_num/self.words_per_row)),
|
||||||
"br_out[{}]".format(int(col_num/self.words_per_row)),
|
"br_out_{}".format(int(col_num/self.words_per_row)),
|
||||||
"sel[{}]".format(col_num % self.words_per_row),
|
"sel_{}".format(col_num % self.words_per_row),
|
||||||
"gnd"])
|
"gnd"])
|
||||||
|
|
||||||
def place_array(self):
|
def place_array(self):
|
||||||
|
|
@ -104,13 +104,13 @@ class single_level_column_mux_array(design.design):
|
||||||
for col_num in range(self.columns):
|
for col_num in range(self.columns):
|
||||||
mux_inst = self.mux_inst[col_num]
|
mux_inst = self.mux_inst[col_num]
|
||||||
offset = mux_inst.get_pin("bl").ll()
|
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",
|
layer="metal2",
|
||||||
offset=offset,
|
offset=offset,
|
||||||
height=self.height-offset.y)
|
height=self.height-offset.y)
|
||||||
|
|
||||||
offset = mux_inst.get_pin("br").ll()
|
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",
|
layer="metal2",
|
||||||
offset=offset,
|
offset=offset,
|
||||||
height=self.height-offset.y)
|
height=self.height-offset.y)
|
||||||
|
|
@ -128,7 +128,7 @@ class single_level_column_mux_array(design.design):
|
||||||
""" Create address input rails on M1 below the mux transistors """
|
""" Create address input rails on M1 below the mux transistors """
|
||||||
for j in range(self.words_per_row):
|
for j in range(self.words_per_row):
|
||||||
offset = vector(0, self.route_height + (j-self.words_per_row)*self.m1_pitch)
|
offset = vector(0, self.route_height + (j-self.words_per_row)*self.m1_pitch)
|
||||||
self.add_layout_pin(text="sel[{}]".format(j),
|
self.add_layout_pin(text="sel_{}".format(j),
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
offset=offset,
|
offset=offset,
|
||||||
width=self.mux.width * self.columns,
|
width=self.mux.width * self.columns,
|
||||||
|
|
@ -144,9 +144,9 @@ class single_level_column_mux_array(design.design):
|
||||||
# Add the column x offset to find the right select bit
|
# Add the column x offset to find the right select bit
|
||||||
gate_offset = self.mux_inst[col].get_pin("sel").bc()
|
gate_offset = self.mux_inst[col].get_pin("sel").bc()
|
||||||
# height to connect the gate to the correct horizontal row
|
# 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
|
# 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
|
# Add the poly contact with a shift to account for the rotation
|
||||||
self.add_via_center(layers=("metal1", "contact", "poly"),
|
self.add_via_center(layers=("metal1", "contact", "poly"),
|
||||||
offset=offset,
|
offset=offset,
|
||||||
|
|
@ -170,23 +170,23 @@ class single_level_column_mux_array(design.design):
|
||||||
self.add_rect(layer="metal1",
|
self.add_rect(layer="metal1",
|
||||||
offset=bl_out_offset,
|
offset=bl_out_offset,
|
||||||
width=width,
|
width=width,
|
||||||
height=drc["minwidth_metal2"])
|
height=drc("minwidth_metal2"))
|
||||||
self.add_rect(layer="metal1",
|
self.add_rect(layer="metal1",
|
||||||
offset=br_out_offset,
|
offset=br_out_offset,
|
||||||
width=width,
|
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
|
# 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",
|
layer="metal2",
|
||||||
offset=bl_out_offset.scale(1,0),
|
offset=bl_out_offset.scale(1,0),
|
||||||
width=drc['minwidth_metal2'],
|
width=drc('minwidth_metal2'),
|
||||||
height=self.route_height)
|
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",
|
layer="metal2",
|
||||||
offset=br_out_offset.scale(1,0),
|
offset=br_out_offset.scale(1,0),
|
||||||
width=drc['minwidth_metal2'],
|
width=drc('minwidth_metal2'),
|
||||||
height=self.route_height)
|
height=self.route_height)
|
||||||
|
|
||||||
# This via is on the right of the wire
|
# This via is on the right of the wire
|
||||||
|
|
@ -202,7 +202,7 @@ class single_level_column_mux_array(design.design):
|
||||||
|
|
||||||
self.add_rect(layer="metal2",
|
self.add_rect(layer="metal2",
|
||||||
offset=bl_out_offset,
|
offset=bl_out_offset,
|
||||||
width=drc['minwidth_metal2'],
|
width=drc('minwidth_metal2'),
|
||||||
height=self.route_height-bl_out_offset.y)
|
height=self.route_height-bl_out_offset.y)
|
||||||
# This via is on the right of the wire
|
# This via is on the right of the wire
|
||||||
self.add_via(layers=("metal1", "via1", "metal2"),
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
||||||
|
|
@ -210,7 +210,7 @@ class single_level_column_mux_array(design.design):
|
||||||
rotate=90)
|
rotate=90)
|
||||||
self.add_rect(layer="metal2",
|
self.add_rect(layer="metal2",
|
||||||
offset=br_out_offset,
|
offset=br_out_offset,
|
||||||
width=drc['minwidth_metal2'],
|
width=drc('minwidth_metal2'),
|
||||||
height=self.route_height-br_out_offset.y)
|
height=self.route_height-br_out_offset.y)
|
||||||
# This via is on the left of the wire
|
# This via is on the left of the wire
|
||||||
self.add_via(layers=("metal1", "via1", "metal2"),
|
self.add_via(layers=("metal1", "via1", "metal2"),
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,9 @@ class tri_gate_array(design.design):
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
"""create the name of pins depend on the word size"""
|
"""create the name of pins depend on the word size"""
|
||||||
for i in range(self.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):
|
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"]:
|
for pin in ["en", "en_bar", "vdd", "gnd"]:
|
||||||
self.add_pin(pin)
|
self.add_pin(pin)
|
||||||
|
|
||||||
|
|
@ -59,8 +59,8 @@ class tri_gate_array(design.design):
|
||||||
self.tri_inst[i]=self.add_inst(name=name,
|
self.tri_inst[i]=self.add_inst(name=name,
|
||||||
mod=self.tri)
|
mod=self.tri)
|
||||||
index = int(i/self.words_per_row)
|
index = int(i/self.words_per_row)
|
||||||
self.connect_inst(["in[{0}]".format(index),
|
self.connect_inst(["in_{0}".format(index),
|
||||||
"out[{0}]".format(index),
|
"out_{0}".format(index),
|
||||||
"en", "en_bar", "vdd", "gnd"])
|
"en", "en_bar", "vdd", "gnd"])
|
||||||
|
|
||||||
def place_array(self):
|
def place_array(self):
|
||||||
|
|
@ -76,14 +76,14 @@ class tri_gate_array(design.design):
|
||||||
index = int(i/self.words_per_row)
|
index = int(i/self.words_per_row)
|
||||||
|
|
||||||
in_pin = self.tri_inst[i].get_pin("in")
|
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",
|
layer="metal2",
|
||||||
offset=in_pin.ll(),
|
offset=in_pin.ll(),
|
||||||
width=in_pin.width(),
|
width=in_pin.width(),
|
||||||
height=in_pin.height())
|
height=in_pin.height())
|
||||||
|
|
||||||
out_pin = self.tri_inst[i].get_pin("out")
|
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",
|
layer="metal2",
|
||||||
offset=out_pin.ll(),
|
offset=out_pin.ll(),
|
||||||
width=out_pin.width(),
|
width=out_pin.width(),
|
||||||
|
|
@ -107,14 +107,14 @@ class tri_gate_array(design.design):
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
offset=en_pin.ll().scale(0, 1),
|
offset=en_pin.ll().scale(0, 1),
|
||||||
width=width,
|
width=width,
|
||||||
height=drc["minwidth_metal1"])
|
height=drc("minwidth_metal1"))
|
||||||
|
|
||||||
enbar_pin = self.tri_inst[0].get_pin("en_bar")
|
enbar_pin = self.tri_inst[0].get_pin("en_bar")
|
||||||
self.add_layout_pin(text="en_bar",
|
self.add_layout_pin(text="en_bar",
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
offset=enbar_pin.ll().scale(0, 1),
|
offset=enbar_pin.ll().scale(0, 1),
|
||||||
width=width,
|
width=width,
|
||||||
height=drc["minwidth_metal1"])
|
height=drc("minwidth_metal1"))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,10 @@ class wordline_driver(design.design):
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
# inputs to wordline_driver.
|
# inputs to wordline_driver.
|
||||||
for i in range(self.rows):
|
for i in range(self.rows):
|
||||||
self.add_pin("in[{0}]".format(i))
|
self.add_pin("in_{0}".format(i))
|
||||||
# Outputs from wordline_driver.
|
# Outputs from wordline_driver.
|
||||||
for i in range(self.rows):
|
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("en")
|
||||||
self.add_pin("vdd")
|
self.add_pin("vdd")
|
||||||
self.add_pin("gnd")
|
self.add_pin("gnd")
|
||||||
|
|
@ -107,20 +107,20 @@ class wordline_driver(design.design):
|
||||||
self.inv1_inst.append(self.add_inst(name=name_inv1,
|
self.inv1_inst.append(self.add_inst(name=name_inv1,
|
||||||
mod=self.inv_no_output))
|
mod=self.inv_no_output))
|
||||||
self.connect_inst(["en",
|
self.connect_inst(["en",
|
||||||
"en_bar[{0}]".format(row),
|
"en_bar_{0}".format(row),
|
||||||
"vdd", "gnd"])
|
"vdd", "gnd"])
|
||||||
# add nand 2
|
# add nand 2
|
||||||
self.nand_inst.append(self.add_inst(name=name_nand,
|
self.nand_inst.append(self.add_inst(name=name_nand,
|
||||||
mod=self.nand2))
|
mod=self.nand2))
|
||||||
self.connect_inst(["en_bar[{0}]".format(row),
|
self.connect_inst(["en_bar_{0}".format(row),
|
||||||
"in[{0}]".format(row),
|
"in_{0}".format(row),
|
||||||
"wl_bar[{0}]".format(row),
|
"wl_bar_{0}".format(row),
|
||||||
"vdd", "gnd"])
|
"vdd", "gnd"])
|
||||||
# add inv2
|
# add inv2
|
||||||
self.inv2_inst.append(self.add_inst(name=name_inv2,
|
self.inv2_inst.append(self.add_inst(name=name_inv2,
|
||||||
mod=self.inv))
|
mod=self.inv))
|
||||||
self.connect_inst(["wl_bar[{0}]".format(row),
|
self.connect_inst(["wl_bar_{0}".format(row),
|
||||||
"wl[{0}]".format(row),
|
"wl_{0}".format(row),
|
||||||
"vdd", "gnd"])
|
"vdd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -205,7 +205,7 @@ class wordline_driver(design.design):
|
||||||
input_offset = vector(0,b_pos.y + up_or_down)
|
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)
|
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
|
# 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",
|
layer="metal1",
|
||||||
start=input_offset,
|
start=input_offset,
|
||||||
end=mid_via_offset)
|
end=mid_via_offset)
|
||||||
|
|
@ -221,7 +221,7 @@ class wordline_driver(design.design):
|
||||||
|
|
||||||
# output each WL on the right
|
# output each WL on the right
|
||||||
wl_offset = inv2_inst.get_pin("Z").rc()
|
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",
|
layer="metal1",
|
||||||
start=wl_offset,
|
start=wl_offset,
|
||||||
end=wl_offset-vector(self.m1_width,0))
|
end=wl_offset-vector(self.m1_width,0))
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,10 @@ class write_driver_array(design.design):
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
self.add_pin("data[{0}]".format(i))
|
self.add_pin("data_{0}".format(i))
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
self.add_pin("bl[{0}]".format(i))
|
self.add_pin("bl_{0}".format(i))
|
||||||
self.add_pin("br[{0}]".format(i))
|
self.add_pin("br_{0}".format(i))
|
||||||
self.add_pin("en")
|
self.add_pin("en")
|
||||||
self.add_pin("vdd")
|
self.add_pin("vdd")
|
||||||
self.add_pin("gnd")
|
self.add_pin("gnd")
|
||||||
|
|
@ -73,9 +73,9 @@ class write_driver_array(design.design):
|
||||||
self.driver_insts[index]=self.add_inst(name=name,
|
self.driver_insts[index]=self.add_inst(name=name,
|
||||||
mod=self.driver)
|
mod=self.driver)
|
||||||
|
|
||||||
self.connect_inst(["data[{0}]".format(index),
|
self.connect_inst(["data_{0}".format(index),
|
||||||
"bl[{0}]".format(index),
|
"bl_{0}".format(index),
|
||||||
"br[{0}]".format(index),
|
"br_{0}".format(index),
|
||||||
"en", "vdd", "gnd"])
|
"en", "vdd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -94,20 +94,20 @@ class write_driver_array(design.design):
|
||||||
def add_layout_pins(self):
|
def add_layout_pins(self):
|
||||||
for i in range(self.word_size):
|
for i in range(self.word_size):
|
||||||
din_pin = self.driver_insts[i].get_pin("din")
|
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",
|
layer="metal2",
|
||||||
offset=din_pin.ll(),
|
offset=din_pin.ll(),
|
||||||
width=din_pin.width(),
|
width=din_pin.width(),
|
||||||
height=din_pin.height())
|
height=din_pin.height())
|
||||||
bl_pin = self.driver_insts[i].get_pin("bl")
|
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",
|
layer="metal2",
|
||||||
offset=bl_pin.ll(),
|
offset=bl_pin.ll(),
|
||||||
width=bl_pin.width(),
|
width=bl_pin.width(),
|
||||||
height=bl_pin.height())
|
height=bl_pin.height())
|
||||||
|
|
||||||
br_pin = self.driver_insts[i].get_pin("br")
|
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",
|
layer="metal2",
|
||||||
offset=br_pin.ll(),
|
offset=br_pin.ll(),
|
||||||
width=br_pin.width(),
|
width=br_pin.width(),
|
||||||
|
|
@ -130,7 +130,7 @@ class write_driver_array(design.design):
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
offset=self.driver_insts[0].get_pin("en").ll().scale(0,1),
|
offset=self.driver_insts[0].get_pin("en").ll().scale(0,1),
|
||||||
width=self.width,
|
width=self.width,
|
||||||
height=drc['minwidth_metal1'])
|
height=drc('minwidth_metal1'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ a spice (.sp) file for circuit simulation
|
||||||
a GDS2 (.gds) file containing the layout
|
a GDS2 (.gds) file containing the layout
|
||||||
a LEF (.lef) file for preliminary P&R (real one should be from layout)
|
a LEF (.lef) file for preliminary P&R (real one should be from layout)
|
||||||
a Liberty (.lib) file for timing analysis/optimization
|
a Liberty (.lib) file for timing analysis/optimization
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys,os
|
import sys,os
|
||||||
|
|
@ -27,7 +26,6 @@ if len(args) != 1:
|
||||||
# These depend on arguments, so don't load them until now.
|
# These depend on arguments, so don't load them until now.
|
||||||
import debug
|
import debug
|
||||||
|
|
||||||
|
|
||||||
init_openram(config_file=args[0], is_unit_test=False)
|
init_openram(config_file=args[0], is_unit_test=False)
|
||||||
|
|
||||||
# Only print banner here so it's not in unit tests
|
# Only print banner here so it's not in unit tests
|
||||||
|
|
@ -40,8 +38,10 @@ report_status()
|
||||||
import verify
|
import verify
|
||||||
from sram import sram
|
from sram import sram
|
||||||
from sram_config import sram_config
|
from sram_config import sram_config
|
||||||
|
#from parser import *
|
||||||
output_extensions = ["sp","v","lib"]
|
output_extensions = ["sp","v","lib"]
|
||||||
|
if OPTS.datasheet_gen:
|
||||||
|
output_extensions.append("html")
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
output_extensions.extend(["gds","lef"])
|
output_extensions.extend(["gds","lef"])
|
||||||
output_files = ["{0}.{1}".format(OPTS.output_name,x) for x in output_extensions]
|
output_files = ["{0}.{1}".format(OPTS.output_name,x) for x in output_extensions]
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,7 +1,7 @@
|
||||||
import contact
|
import contact
|
||||||
import design
|
import design
|
||||||
import debug
|
import debug
|
||||||
from tech import drc, parameter, spice, info
|
from tech import drc, parameter, spice
|
||||||
from ptx import ptx
|
from ptx import ptx
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
@ -110,7 +110,7 @@ class pgate(design.design):
|
||||||
max_y_offset = self.height + 0.5*self.m1_width
|
max_y_offset = self.height + 0.5*self.m1_width
|
||||||
self.nwell_position = middle_position
|
self.nwell_position = middle_position
|
||||||
nwell_height = max_y_offset - middle_position.y
|
nwell_height = max_y_offset - middle_position.y
|
||||||
if info["has_nwell"]:
|
if drc("has_nwell"):
|
||||||
self.add_rect(layer="nwell",
|
self.add_rect(layer="nwell",
|
||||||
offset=middle_position,
|
offset=middle_position,
|
||||||
width=self.well_width,
|
width=self.well_width,
|
||||||
|
|
@ -122,7 +122,7 @@ class pgate(design.design):
|
||||||
|
|
||||||
pwell_position = vector(0,-0.5*self.m1_width)
|
pwell_position = vector(0,-0.5*self.m1_width)
|
||||||
pwell_height = middle_position.y-pwell_position.y
|
pwell_height = middle_position.y-pwell_position.y
|
||||||
if info["has_pwell"]:
|
if drc("has_pwell"):
|
||||||
self.add_rect(layer="pwell",
|
self.add_rect(layer="pwell",
|
||||||
offset=pwell_position,
|
offset=pwell_position,
|
||||||
width=self.well_width,
|
width=self.well_width,
|
||||||
|
|
@ -138,7 +138,7 @@ class pgate(design.design):
|
||||||
layer_stack = ("active", "contact", "metal1")
|
layer_stack = ("active", "contact", "metal1")
|
||||||
|
|
||||||
# To the right a spacing away from the pmos right active edge
|
# 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
|
# 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.
|
# OR align the active with the top of PMOS active.
|
||||||
max_y_offset = self.height + 0.5*self.m1_width
|
max_y_offset = self.height + 0.5*self.m1_width
|
||||||
|
|
@ -185,7 +185,7 @@ class pgate(design.design):
|
||||||
pwell_position = vector(0,-0.5*self.m1_width)
|
pwell_position = vector(0,-0.5*self.m1_width)
|
||||||
|
|
||||||
# To the right a spacing away from the nmos right active edge
|
# 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
|
# Must be at least an well enclosure of active up from the bottom of the well
|
||||||
contact_yoffset = max(nmos_pos.y,
|
contact_yoffset = max(nmos_pos.y,
|
||||||
self.well_enclose_active - nmos.active_contact.first_layer_height/2)
|
self.well_enclose_active - nmos.active_contact.first_layer_height/2)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import contact
|
import contact
|
||||||
import pgate
|
import pgate
|
||||||
import debug
|
import debug
|
||||||
from tech import drc, parameter, spice, info
|
from tech import drc, parameter, spice
|
||||||
from ptx import ptx
|
from ptx import ptx
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from math import ceil
|
from math import ceil
|
||||||
|
|
@ -76,8 +76,8 @@ class pinv(pgate.pgate):
|
||||||
# This may make the result differ when the layout is created...
|
# This may make the result differ when the layout is created...
|
||||||
if OPTS.netlist_only:
|
if OPTS.netlist_only:
|
||||||
self.tx_mults = 1
|
self.tx_mults = 1
|
||||||
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
|
self.nmos_width = self.nmos_size*drc("minwidth_tx")
|
||||||
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
|
self.pmos_width = self.pmos_size*drc("minwidth_tx")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Do a quick sanity check and bail if unlikely feasible height
|
# Do a quick sanity check and bail if unlikely feasible height
|
||||||
|
|
@ -85,16 +85,16 @@ class pinv(pgate.pgate):
|
||||||
# Assume we need 3 metal 1 pitches (2 power rails, one between the tx for the drain)
|
# Assume we need 3 metal 1 pitches (2 power rails, one between the tx for the drain)
|
||||||
# plus the tx height
|
# plus the tx height
|
||||||
nmos = ptx(tx_type="nmos")
|
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
|
tx_height = nmos.poly_height + pmos.poly_height
|
||||||
# rotated m1 pitch or poly to active spacing
|
# rotated m1 pitch or poly to active spacing
|
||||||
min_channel = max(contact.poly.width + self.m1_space,
|
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
|
# This is the extra space needed to ensure DRC rules to the active contacts
|
||||||
extra_contact_space = max(-nmos.get_pin("D").by(),0)
|
extra_contact_space = max(-nmos.get_pin("D").by(),0)
|
||||||
# This is a poly-to-poly of a flipped cell
|
# 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,
|
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
|
total_height = tx_height + min_channel + 2*self.top_bottom_space
|
||||||
debug.check(self.height> total_height,"Cell height {0} too small for simple min height {1}.".format(self.height,total_height))
|
debug.check(self.height> total_height,"Cell height {0} too small for simple min height {1}.".format(self.height,total_height))
|
||||||
|
|
||||||
|
|
@ -103,16 +103,16 @@ class pinv(pgate.pgate):
|
||||||
# Divide the height in half. Could divide proportional to beta, but this makes
|
# Divide the height in half. Could divide proportional to beta, but this makes
|
||||||
# connecting wells of multiple cells easier.
|
# connecting wells of multiple cells easier.
|
||||||
# Subtract the poly space under the rail of the tx
|
# Subtract the poly space under the rail of the tx
|
||||||
nmos_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"]
|
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,
|
debug.info(2,"Height avail {0:.4f} PMOS {1:.4f} NMOS {2:.4f}".format(tx_height_available,
|
||||||
nmos_height_available,
|
nmos_height_available,
|
||||||
pmos_height_available))
|
pmos_height_available))
|
||||||
|
|
||||||
# Determine the number of mults for each to fit width into available space
|
# Determine the number of mults for each to fit width into available space
|
||||||
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
|
self.nmos_width = self.nmos_size*drc("minwidth_tx")
|
||||||
self.pmos_width = self.pmos_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)
|
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)
|
pmos_required_mults = max(int(ceil(self.pmos_width/pmos_height_available)),1)
|
||||||
# The mults must be the same for easy connection of poly
|
# The mults must be the same for easy connection of poly
|
||||||
|
|
@ -124,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
|
# 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.
|
# 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)
|
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)
|
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):
|
def setup_layout_constants(self):
|
||||||
|
|
@ -137,7 +137,7 @@ class pinv(pgate.pgate):
|
||||||
# the well width is determined the multi-finger PMOS device width plus
|
# the well width is determined the multi-finger PMOS device width plus
|
||||||
# the well contact width and half well enclosure on both sides
|
# the well contact width and half well enclosure on both sides
|
||||||
self.well_width = self.pmos.active_width + self.pmos.active_contact.width \
|
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
|
self.width = self.well_width
|
||||||
# Height is an input parameter, so it is not recomputed.
|
# Height is an input parameter, so it is not recomputed.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,16 +94,16 @@ class pinvbuf(design.design):
|
||||||
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
|
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
|
||||||
|
|
||||||
def place_modules(self):
|
def place_modules(self):
|
||||||
# Add INV1 to the right (capacitance shield)
|
# Add INV1 to the left (capacitance shield)
|
||||||
self.inv1_inst.place(vector(0,0))
|
self.inv1_inst.place(vector(0,0))
|
||||||
|
|
||||||
# Add INV2 to the right
|
# Add INV2 to the right of INV1
|
||||||
self.inv2_inst.place(vector(self.inv1_inst.rx(),0))
|
self.inv2_inst.place(vector(self.inv1_inst.rx(),0))
|
||||||
|
|
||||||
# Add INV3 to the right
|
# Add INV3 to the right of INV2
|
||||||
self.inv3_inst.place(vector(self.inv2_inst.rx(),0))
|
self.inv3_inst.place(vector(self.inv2_inst.rx(),0))
|
||||||
|
|
||||||
# Add INV4 to the bottom
|
# Add INV4 flipped to the bottom aligned with INV2
|
||||||
self.inv4_inst.place(offset=vector(self.inv2_inst.rx(),2*self.inv2.height),
|
self.inv4_inst.place(offset=vector(self.inv2_inst.rx(),2*self.inv2.height),
|
||||||
mirror = "MX")
|
mirror = "MX")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ class pnand2(pgate.pgate):
|
||||||
|
|
||||||
self.nmos_size = 2*size
|
self.nmos_size = 2*size
|
||||||
self.pmos_size = parameter["beta"]*size
|
self.pmos_size = parameter["beta"]*size
|
||||||
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
|
self.nmos_width = self.nmos_size*drc("minwidth_tx")
|
||||||
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
|
self.pmos_width = self.pmos_size*drc("minwidth_tx")
|
||||||
|
|
||||||
# FIXME: Allow these to be sized
|
# FIXME: Allow these to be sized
|
||||||
debug.check(size==1,"Size 1 pnand2 is only supported now.")
|
debug.check(size==1,"Size 1 pnand2 is only supported now.")
|
||||||
|
|
@ -91,7 +91,7 @@ class pnand2(pgate.pgate):
|
||||||
# Two PMOS devices and a well contact. Separation between each.
|
# Two PMOS devices and a well contact. Separation between each.
|
||||||
# Enclosure space on the sides.
|
# Enclosure space on the sides.
|
||||||
self.well_width = 2*self.pmos.active_width + contact.active.width \
|
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
|
self.width = self.well_width
|
||||||
# Height is an input parameter, so it is not recomputed.
|
# Height is an input parameter, so it is not recomputed.
|
||||||
|
|
@ -100,7 +100,7 @@ class pnand2(pgate.pgate):
|
||||||
extra_contact_space = max(-self.nmos.get_pin("D").by(),0)
|
extra_contact_space = max(-self.nmos.get_pin("D").by(),0)
|
||||||
# This is a poly-to-poly of a flipped cell
|
# 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,
|
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 route_supply_rails(self):
|
def route_supply_rails(self):
|
||||||
""" Add vdd/gnd rails to the top and bottom. """
|
""" Add vdd/gnd rails to the top and bottom. """
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ class pnand3(pgate.pgate):
|
||||||
# If we relax this, we could size this better.
|
# If we relax this, we could size this better.
|
||||||
self.nmos_size = 2*size
|
self.nmos_size = 2*size
|
||||||
self.pmos_size = parameter["beta"]*size
|
self.pmos_size = parameter["beta"]*size
|
||||||
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
|
self.nmos_width = self.nmos_size*drc("minwidth_tx")
|
||||||
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
|
self.pmos_width = self.pmos_size*drc("minwidth_tx")
|
||||||
|
|
||||||
# FIXME: Allow these to be sized
|
# FIXME: Allow these to be sized
|
||||||
debug.check(size==1,"Size 1 pnand3 is only supported now.")
|
debug.check(size==1,"Size 1 pnand3 is only supported now.")
|
||||||
|
|
@ -83,7 +83,7 @@ class pnand3(pgate.pgate):
|
||||||
# Two PMOS devices and a well contact. Separation between each.
|
# Two PMOS devices and a well contact. Separation between each.
|
||||||
# Enclosure space on the sides.
|
# Enclosure space on the sides.
|
||||||
self.well_width = 3*self.pmos.active_width + self.pmos.active_contact.width \
|
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.overlap_offset.x
|
||||||
self.width = self.well_width
|
self.width = self.well_width
|
||||||
# Height is an input parameter, so it is not recomputed.
|
# Height is an input parameter, so it is not recomputed.
|
||||||
|
|
@ -96,7 +96,7 @@ class pnand3(pgate.pgate):
|
||||||
extra_contact_space = max(-nmos.get_pin("D").by(),0)
|
extra_contact_space = max(-nmos.get_pin("D").by(),0)
|
||||||
# This is a poly-to-poly of a flipped cell
|
# 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,
|
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 route_supply_rails(self):
|
def route_supply_rails(self):
|
||||||
""" Add vdd/gnd rails to the top and bottom. """
|
""" Add vdd/gnd rails to the top and bottom. """
|
||||||
|
|
@ -191,7 +191,7 @@ class pnand3(pgate.pgate):
|
||||||
metal_spacing = max(self.m1_space + self.m1_width, self.m2_space + self.m2_width,
|
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)
|
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
|
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")
|
self.route_input_gate(self.pmos3_inst, self.nmos3_inst, inputC_yoffset, "C", position="center")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ class pnor2(pgate.pgate):
|
||||||
self.nmos_size = size
|
self.nmos_size = size
|
||||||
# We will just make this 1.5 times for now. NORs are not ideal anyhow.
|
# We will just make this 1.5 times for now. NORs are not ideal anyhow.
|
||||||
self.pmos_size = 1.5*parameter["beta"]*size
|
self.pmos_size = 1.5*parameter["beta"]*size
|
||||||
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
|
self.nmos_width = self.nmos_size*drc("minwidth_tx")
|
||||||
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
|
self.pmos_width = self.pmos_size*drc("minwidth_tx")
|
||||||
|
|
||||||
# FIXME: Allow these to be sized
|
# FIXME: Allow these to be sized
|
||||||
debug.check(size==1,"Size 1 pnor2 is only supported now.")
|
debug.check(size==1,"Size 1 pnor2 is only supported now.")
|
||||||
|
|
@ -92,7 +92,7 @@ class pnor2(pgate.pgate):
|
||||||
# Two PMOS devices and a well contact. Separation between each.
|
# Two PMOS devices and a well contact. Separation between each.
|
||||||
# Enclosure space on the sides.
|
# Enclosure space on the sides.
|
||||||
self.well_width = 2*self.pmos.active_width + self.pmos.active_contact.width \
|
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
|
self.width = self.well_width
|
||||||
# Height is an input parameter, so it is not recomputed.
|
# Height is an input parameter, so it is not recomputed.
|
||||||
|
|
@ -101,7 +101,7 @@ class pnor2(pgate.pgate):
|
||||||
extra_contact_space = max(-self.nmos.get_pin("D").by(),0)
|
extra_contact_space = max(-self.nmos.get_pin("D").by(),0)
|
||||||
# This is a poly-to-poly of a flipped cell
|
# 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,
|
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 add_supply_rails(self):
|
||||||
""" Add vdd/gnd rails to the top and bottom. """
|
""" Add vdd/gnd rails to the top and bottom. """
|
||||||
|
|
|
||||||
|
|
@ -78,13 +78,14 @@ class precharge(pgate.pgate):
|
||||||
self.add_path("metal1", [pmos_pin.uc(), vdd_pos])
|
self.add_path("metal1", [pmos_pin.uc(), vdd_pos])
|
||||||
|
|
||||||
# Add the M1->M2->M3 stack at the left edge
|
# Add the M1->M2->M3 stack at the left edge
|
||||||
|
vdd_contact_pos = vector(0.5*self.width, vdd_position.y + 0.5*self.m1_width)
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||||
offset=vdd_pos.scale(0,1))
|
offset=vdd_contact_pos)
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||||
offset=vdd_pos.scale(0,1))
|
offset=vdd_contact_pos)
|
||||||
self.add_layout_pin_rect_center(text="vdd",
|
self.add_layout_pin_rect_center(text="vdd",
|
||||||
layer="metal3",
|
layer="metal3",
|
||||||
offset=vdd_pos.scale(0,1))
|
offset=vdd_contact_pos)
|
||||||
|
|
||||||
|
|
||||||
def create_ptx(self):
|
def create_ptx(self):
|
||||||
|
|
@ -112,7 +113,7 @@ class precharge(pgate.pgate):
|
||||||
|
|
||||||
# adds the lower pmos to layout
|
# adds the lower pmos to layout
|
||||||
#base = vector(self.width - 2*self.pmos.width + self.overlap_offset.x, 0)
|
#base = vector(self.width - 2*self.pmos.width + self.overlap_offset.x, 0)
|
||||||
self.lower_pmos_position = vector(self.bitcell.get_pin(self.bitcell_bl).lx(),
|
self.lower_pmos_position = vector(max(self.bitcell.get_pin(self.bitcell_bl).lx(), self.well_enclose_active),
|
||||||
self.pmos.active_offset.y)
|
self.pmos.active_offset.y)
|
||||||
self.lower_pmos_inst.place(self.lower_pmos_position)
|
self.lower_pmos_inst.place(self.lower_pmos_position)
|
||||||
|
|
||||||
|
|
@ -162,7 +163,7 @@ class precharge(pgate.pgate):
|
||||||
"""Adds a nwell tap to connect to the vdd rail"""
|
"""Adds a nwell tap to connect to the vdd rail"""
|
||||||
# adds the contact from active to metal1
|
# adds the contact from active to metal1
|
||||||
well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1,0) \
|
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"),
|
self.add_contact_center(layers=("active", "contact", "metal1"),
|
||||||
offset=well_contact_pos,
|
offset=well_contact_pos,
|
||||||
implant_type="n",
|
implant_type="n",
|
||||||
|
|
@ -184,7 +185,7 @@ class precharge(pgate.pgate):
|
||||||
self.add_layout_pin(text="bl",
|
self.add_layout_pin(text="bl",
|
||||||
layer="metal2",
|
layer="metal2",
|
||||||
offset=offset,
|
offset=offset,
|
||||||
width=drc['minwidth_metal2'],
|
width=drc("minwidth_metal2"),
|
||||||
height=self.height)
|
height=self.height)
|
||||||
|
|
||||||
# adds the BR on metal 2
|
# adds the BR on metal 2
|
||||||
|
|
@ -192,7 +193,7 @@ class precharge(pgate.pgate):
|
||||||
self.add_layout_pin(text="br",
|
self.add_layout_pin(text="br",
|
||||||
layer="metal2",
|
layer="metal2",
|
||||||
offset=offset,
|
offset=offset,
|
||||||
width=drc['minwidth_metal2'],
|
width=drc("minwidth_metal2"),
|
||||||
height=self.height)
|
height=self.height)
|
||||||
|
|
||||||
def connect_to_bitlines(self):
|
def connect_to_bitlines(self):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import design
|
import design
|
||||||
import debug
|
import debug
|
||||||
from tech import drc, info, spice
|
from tech import drc, spice
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from contact import contact
|
from contact import contact
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
@ -15,7 +15,7 @@ class ptx(design.design):
|
||||||
you to connect the fingered gates and active for parallel devices.
|
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
|
# We need to keep unique names because outputting to GDSII
|
||||||
# will use the last record with a given name. I.e., you will
|
# 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
|
# over-write a design in GDS if one has and the other doesn't
|
||||||
|
|
@ -66,12 +66,12 @@ class ptx(design.design):
|
||||||
# self.spice.append("\n.SUBCKT {0} {1}".format(self.name,
|
# self.spice.append("\n.SUBCKT {0} {1}".format(self.name,
|
||||||
# " ".join(self.pins)))
|
# " ".join(self.pins)))
|
||||||
# Just make a guess since these will actually be decided in the layout later.
|
# Just make a guess since these will actually be decided in the layout later.
|
||||||
area_sd = 2.5*drc["minwidth_poly"]*self.tx_width
|
area_sd = 2.5*drc("minwidth_poly")*self.tx_width
|
||||||
perimeter_sd = 2*drc["minwidth_poly"] + 2*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.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.mults,
|
||||||
self.tx_width,
|
self.tx_width,
|
||||||
drc["minwidth_poly"],
|
drc("minwidth_poly"),
|
||||||
perimeter_sd,
|
perimeter_sd,
|
||||||
area_sd)
|
area_sd)
|
||||||
self.spice.append("\n* ptx " + self.spice_device)
|
self.spice.append("\n* ptx " + self.spice_device)
|
||||||
|
|
@ -109,7 +109,7 @@ class ptx(design.design):
|
||||||
self.contact_pitch = 2*self.contact_to_gate + self.contact_width + self.poly_width
|
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.
|
# 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)
|
(self.active_width - self.contact_width)/2)
|
||||||
# This is the distance from the edge of poly to the contacted end of active
|
# 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
|
self.end_to_poly = active_enclose_contact + self.contact_width + self.contact_to_gate
|
||||||
|
|
@ -129,7 +129,7 @@ class ptx(design.design):
|
||||||
self.active_offset = vector([self.well_enclose_active]*2)
|
self.active_offset = vector([self.well_enclose_active]*2)
|
||||||
|
|
||||||
# Well enclosure of active, ensure minwidth as well
|
# 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.cell_well_width = max(self.active_width + 2*self.well_enclose_active,
|
||||||
self.well_width)
|
self.well_width)
|
||||||
self.cell_well_height = max(self.tx_width + 2*self.well_enclose_active,
|
self.cell_well_height = max(self.tx_width + 2*self.well_enclose_active,
|
||||||
|
|
@ -151,9 +151,9 @@ class ptx(design.design):
|
||||||
|
|
||||||
|
|
||||||
# Min area results are just flagged for now.
|
# 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.
|
# 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):
|
def connect_fingered_poly(self, poly_positions):
|
||||||
"""
|
"""
|
||||||
|
|
@ -181,7 +181,7 @@ class ptx(design.design):
|
||||||
layer="poly",
|
layer="poly",
|
||||||
offset=poly_offset,
|
offset=poly_offset,
|
||||||
width=poly_width,
|
width=poly_width,
|
||||||
height=drc["minwidth_poly"])
|
height=drc("minwidth_poly"))
|
||||||
|
|
||||||
|
|
||||||
def connect_fingered_active(self, drain_positions, source_positions):
|
def connect_fingered_active(self, drain_positions, source_positions):
|
||||||
|
|
@ -269,7 +269,7 @@ class ptx(design.design):
|
||||||
height=self.active_height)
|
height=self.active_height)
|
||||||
# If the implant must enclose the active, shift offset
|
# If the implant must enclose the active, shift offset
|
||||||
# and increase width/height
|
# and increase width/height
|
||||||
enclose_width = drc["implant_enclosure_active"]
|
enclose_width = drc("implant_enclosure_active")
|
||||||
enclose_offset = [enclose_width]*2
|
enclose_offset = [enclose_width]*2
|
||||||
self.add_rect(layer="{}implant".format(self.implant_type),
|
self.add_rect(layer="{}implant".format(self.implant_type),
|
||||||
offset=self.active_offset - enclose_offset,
|
offset=self.active_offset - enclose_offset,
|
||||||
|
|
@ -280,7 +280,7 @@ class ptx(design.design):
|
||||||
"""
|
"""
|
||||||
Add an (optional) well and implant for the type of transistor.
|
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),
|
self.add_rect(layer="{}well".format(self.well_type),
|
||||||
offset=(0,0),
|
offset=(0,0),
|
||||||
width=self.cell_well_width,
|
width=self.cell_well_width,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import design
|
import design
|
||||||
import debug
|
import debug
|
||||||
from tech import drc, info
|
from tech import drc
|
||||||
from vector import vector
|
from vector import vector
|
||||||
import contact
|
import contact
|
||||||
from ptx import ptx
|
from ptx import ptx
|
||||||
|
|
@ -52,7 +52,7 @@ class single_level_column_mux(design.design):
|
||||||
self.bitcell = self.mod_bitcell()
|
self.bitcell = self.mod_bitcell()
|
||||||
|
|
||||||
# Adds nmos_lower,nmos_upper to the module
|
# Adds nmos_lower,nmos_upper to the module
|
||||||
self.ptx_width = self.tx_size * drc["minwidth_tx"]
|
self.ptx_width = self.tx_size * drc("minwidth_tx")
|
||||||
self.nmos = ptx(width=self.ptx_width)
|
self.nmos = ptx(width=self.ptx_width)
|
||||||
self.add_mod(self.nmos)
|
self.add_mod(self.nmos)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class direction(Enum):
|
||||||
|
NORTH = 1
|
||||||
|
SOUTH = 2
|
||||||
|
EAST = 3
|
||||||
|
WEST = 4
|
||||||
|
UP = 5
|
||||||
|
DOWN = 6
|
||||||
|
|
@ -1,95 +1,138 @@
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import string
|
import string
|
||||||
from itertools import tee
|
|
||||||
import debug
|
import debug
|
||||||
from vector3d import vector3d
|
from vector3d import vector3d
|
||||||
from cell import cell
|
from grid_cell import grid_cell
|
||||||
import os
|
|
||||||
|
|
||||||
class grid:
|
class grid:
|
||||||
"""
|
"""
|
||||||
A two layer routing map. Each cell can be blocked in the vertical
|
A two layer routing map. Each cell can be blocked in the vertical
|
||||||
or horizontal layer.
|
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):
|
|
||||||
|
def __init__(self, ll, ur, track_width):
|
||||||
""" Initialize the map and define the costs. """
|
""" Initialize the map and define the costs. """
|
||||||
|
|
||||||
# costs are relative to a unit grid
|
# list of the source/target grid coordinates
|
||||||
# non-preferred cost allows an off-direction jog of 1 grid
|
self.source = []
|
||||||
# rather than 2 vias + preferred direction (cost 5)
|
self.target = []
|
||||||
self.VIA_COST = 2
|
|
||||||
self.NONPREFERRED_COST = 4
|
|
||||||
self.PREFERRED_COST = 1
|
|
||||||
|
|
||||||
|
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
|
# let's leave the map sparse, cells are created on demand to reduce memory
|
||||||
self.map={}
|
self.map={}
|
||||||
|
|
||||||
def set_blocked(self,n):
|
def add_all_grids(self):
|
||||||
self.add_map(n)
|
for x in range(self.ll.x, self.ur.x, 1):
|
||||||
self.map[n].blocked=True
|
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):
|
def is_blocked(self,n):
|
||||||
self.add_map(n)
|
if isinstance(n, (list,tuple,set,frozenset)):
|
||||||
return self.map[n].blocked
|
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 add_blockage_shape(self,ll,ur,z):
|
|
||||||
debug.info(3,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z))
|
|
||||||
|
|
||||||
block_list = []
|
def set_path(self,n,value=True):
|
||||||
for x in range(int(ll[0]),int(ur[0])+1):
|
if isinstance(n, (list,tuple,set,frozenset)):
|
||||||
for y in range(int(ll[1]),int(ur[1])+1):
|
for item in n:
|
||||||
block_list.append(vector3d(x,y,z))
|
self.set_path(item,value)
|
||||||
|
else:
|
||||||
|
self.add_map(n)
|
||||||
|
self.map[n].path=value
|
||||||
|
|
||||||
self.add_blockage(block_list)
|
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)
|
||||||
|
|
||||||
|
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(self,block_list):
|
|
||||||
debug.info(2,"Adding blockage list={0}".format(str(block_list)))
|
def add_source(self,track_list,value=True):
|
||||||
for n in block_list:
|
debug.info(3,"Adding source list={0}".format(str(track_list)))
|
||||||
self.set_blocked(n)
|
for n in track_list:
|
||||||
|
debug.info(4,"Adding source ={0}".format(str(n)))
|
||||||
|
self.set_source(n,value)
|
||||||
|
self.set_blocked(n,False)
|
||||||
|
|
||||||
def add_map(self,p):
|
|
||||||
|
def add_target(self,track_list,value=True):
|
||||||
|
debug.info(3,"Adding target list={0}".format(str(track_list)))
|
||||||
|
for n in track_list:
|
||||||
|
debug.info(4,"Adding target ={0}".format(str(n)))
|
||||||
|
self.set_target(n,value)
|
||||||
|
self.set_blocked(n,False)
|
||||||
|
|
||||||
|
def is_target(self,point):
|
||||||
|
"""
|
||||||
|
Point is in the target set, so we are done.
|
||||||
|
"""
|
||||||
|
return point in self.target
|
||||||
|
|
||||||
|
def add_map(self,n):
|
||||||
"""
|
"""
|
||||||
Add a point to the map if it doesn't exist.
|
Add a point to the map if it doesn't exist.
|
||||||
"""
|
"""
|
||||||
if p not in self.map.keys():
|
if isinstance(n, (list,tuple,set,frozenset)):
|
||||||
self.map[p]=cell()
|
for item in n:
|
||||||
|
self.add_map(item)
|
||||||
|
else:
|
||||||
|
if n not in self.map.keys():
|
||||||
|
self.map[n]=grid_cell()
|
||||||
|
|
||||||
def add_path(self,path):
|
|
||||||
|
def block_path(self,path):
|
||||||
"""
|
"""
|
||||||
Mark the path in the routing grid for visualization
|
Mark the path in the routing grid as blocked.
|
||||||
|
Also unsets the path flag.
|
||||||
"""
|
"""
|
||||||
self.path=path
|
path.set_path(False)
|
||||||
for p in path:
|
path.set_blocked(True)
|
||||||
self.map[p].path=True
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
class cell:
|
class grid_cell:
|
||||||
"""
|
"""
|
||||||
A single cell that can be occupied in a given layer, blocked,
|
A single cell that can be occupied in a given layer, blocked,
|
||||||
visited, etc.
|
visited, etc.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.visited = False
|
|
||||||
self.path = False
|
self.path = False
|
||||||
self.blocked = False
|
self.blocked = False
|
||||||
self.source = False
|
self.source = False
|
||||||
|
|
@ -17,7 +16,6 @@ class cell:
|
||||||
Reset the dynamic info about routing. The pins/blockages are not reset so
|
Reset the dynamic info about routing. The pins/blockages are not reset so
|
||||||
that they can be reused.
|
that they can be reused.
|
||||||
"""
|
"""
|
||||||
self.visited=False
|
|
||||||
self.min_cost=-1
|
self.min_cost=-1
|
||||||
self.min_path=None
|
self.min_path=None
|
||||||
self.blocked=False
|
self.blocked=False
|
||||||
|
|
@ -0,0 +1,227 @@
|
||||||
|
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,up_down_too=True):
|
||||||
|
"""
|
||||||
|
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 list(direction):
|
||||||
|
if not up_down_too and (d==direction.UP or d==direction.DOWN):
|
||||||
|
continue
|
||||||
|
n = self.neighbor(d)
|
||||||
|
if n:
|
||||||
|
neighbors.append(n)
|
||||||
|
|
||||||
|
return neighbors
|
||||||
|
|
||||||
|
def neighbor(self, d):
|
||||||
|
if d==direction.EAST:
|
||||||
|
offset = vector3d(1,0,0)
|
||||||
|
elif d==direction.WEST:
|
||||||
|
offset = vector3d(-1,0,0)
|
||||||
|
elif d==direction.NORTH:
|
||||||
|
offset = vector3d(0,1,0)
|
||||||
|
elif d==direction.SOUTH:
|
||||||
|
offset = vector3d(0,-1,0)
|
||||||
|
elif d==direction.UP:
|
||||||
|
offset = vector3d(0,0,1)
|
||||||
|
elif d==direction.DOWN:
|
||||||
|
offset = vector3d(0,0,-1)
|
||||||
|
else:
|
||||||
|
debug.error("Invalid direction {}".format(d),-1)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
debug.error("Invalid direction {}".format(dirct))
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,55 +1,24 @@
|
||||||
from itertools import tee
|
from itertools import tee
|
||||||
import debug
|
import debug
|
||||||
from vector3d import vector3d
|
|
||||||
import grid
|
|
||||||
from heapq import heappush,heappop
|
from heapq import heappush,heappop
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
class astar_grid(grid.grid):
|
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.
|
Expand the two layer grid to include A* search functions for a source and target.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, ll, ur, track_factor):
|
||||||
""" Create a routing map of width x height cells and 2 in the z-axis. """
|
""" Create a routing map of width x height cells and 2 in the z-axis. """
|
||||||
grid.grid.__init__(self)
|
grid.__init__(self, ll, ur, track_factor)
|
||||||
|
|
||||||
# list of the source/target grid coordinates
|
|
||||||
self.source = []
|
|
||||||
self.target = []
|
|
||||||
|
|
||||||
# priority queue for the maze routing
|
# priority queue for the maze routing
|
||||||
self.q = []
|
self.q = []
|
||||||
|
|
||||||
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 add_source(self,track_list):
|
|
||||||
debug.info(2,"Adding source list={0}".format(str(track_list)))
|
|
||||||
for n in track_list:
|
|
||||||
if not self.is_blocked(n):
|
|
||||||
debug.info(3,"Adding source ={0}".format(str(n)))
|
|
||||||
self.set_source(n)
|
|
||||||
|
|
||||||
def add_target(self,track_list):
|
|
||||||
debug.info(2,"Adding target list={0}".format(str(track_list)))
|
|
||||||
for n in track_list:
|
|
||||||
if not self.is_blocked(n):
|
|
||||||
self.set_target(n)
|
|
||||||
|
|
||||||
def is_target(self,point):
|
|
||||||
"""
|
|
||||||
Point is in the target set, so we are done.
|
|
||||||
"""
|
|
||||||
return point in self.target
|
|
||||||
|
|
||||||
def reinit(self):
|
def reinit(self):
|
||||||
""" Reinitialize everything for a new route. """
|
""" Reinitialize everything for a new route. """
|
||||||
|
|
||||||
|
|
@ -73,112 +42,98 @@ class astar_grid(grid.grid):
|
||||||
We will use an A* search, so this cost must be pessimistic.
|
We will use an A* search, so this cost must be pessimistic.
|
||||||
Cost so far will be the length of the path.
|
Cost so far will be the length of the path.
|
||||||
"""
|
"""
|
||||||
debug.info(4,"Initializing queue.")
|
#debug.info(3,"Initializing queue.")
|
||||||
|
|
||||||
# uniquify the source (and target while we are at it)
|
|
||||||
self.source = list(set(self.source))
|
|
||||||
self.target = list(set(self.target))
|
|
||||||
|
|
||||||
# Counter is used to not require data comparison in Python 3.x
|
# Counter is used to not require data comparison in Python 3.x
|
||||||
# Items will be returned in order they are added during cost ties
|
# Items will be returned in order they are added during cost ties
|
||||||
self.counter = 0
|
self.counter = 0
|
||||||
for s in self.source:
|
for s in self.source:
|
||||||
cost = self.cost_to_target(s)
|
cost = self.cost_to_target(s)
|
||||||
debug.info(1,"Init: cost=" + str(cost) + " " + str([s]))
|
debug.info(3,"Init: cost=" + str(cost) + " " + str([s]))
|
||||||
heappush(self.q,(cost,self.counter,[s]))
|
heappush(self.q,(cost,self.counter,grid_path([vector3d(s)])))
|
||||||
self.counter+=1
|
self.counter+=1
|
||||||
|
|
||||||
|
|
||||||
def route(self,detour_scale):
|
def route(self,detour_scale):
|
||||||
"""
|
"""
|
||||||
This does the A* maze routing with preferred direction routing.
|
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
|
# 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.
|
# 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
|
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
|
# Make sure the queue is empty if we run another route
|
||||||
while len(self.q)>0:
|
while len(self.q)>0:
|
||||||
heappop(self.q)
|
heappop(self.q)
|
||||||
|
|
||||||
# Put the source items into the queue
|
# Put the source items into the queue
|
||||||
self.init_queue()
|
self.init_queue()
|
||||||
cheapest_path = None
|
cheapest_path = None
|
||||||
cheapest_cost = None
|
cheapest_cost = None
|
||||||
|
|
||||||
|
|
||||||
# Keep expanding and adding to the priority queue until we are done
|
# Keep expanding and adding to the priority queue until we are done
|
||||||
while len(self.q)>0:
|
while len(self.q)>0:
|
||||||
# should we keep the path in the queue as well or just the final node?
|
# should we keep the path in the queue as well or just the final node?
|
||||||
(cost,count,path) = heappop(self.q)
|
(cost,count,curpath) = heappop(self.q)
|
||||||
debug.info(2,"Queue size: size=" + str(len(self.q)) + " " + str(cost))
|
debug.info(3,"Queue size: size=" + str(len(self.q)) + " " + str(cost))
|
||||||
debug.info(3,"Expanding: cost=" + str(cost) + " " + str(path))
|
debug.info(4,"Expanding: cost=" + str(cost) + " " + str(curpath))
|
||||||
|
|
||||||
# expand the last element
|
# expand the last element
|
||||||
neighbors = self.expand_dirs(path)
|
neighbors = self.expand_dirs(curpath)
|
||||||
debug.info(3,"Neighbors: " + str(neighbors))
|
debug.info(4,"Neighbors: " + str(neighbors))
|
||||||
|
|
||||||
for n in 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
|
# node is added to the map by the expand routine
|
||||||
newpath = path + [n]
|
newpath.append(n)
|
||||||
# check if we hit the target and are done
|
# check if we hit the target and are done
|
||||||
if self.is_target(n):
|
if self.is_target(n[0]): # This uses the [0] item because we are assuming 1-track wide
|
||||||
return (newpath,self.cost(newpath))
|
return (newpath,newpath.cost())
|
||||||
elif not self.map[n].visited:
|
else:
|
||||||
# current path cost + predicted cost
|
# current path cost + predicted cost
|
||||||
current_cost = self.cost(newpath)
|
current_cost = newpath.cost()
|
||||||
target_cost = self.cost_to_target(n)
|
target_cost = self.cost_to_target(n[0])
|
||||||
predicted_cost = current_cost + target_cost
|
predicted_cost = current_cost + target_cost
|
||||||
# only add the cost if it is less than our bound
|
# only add the cost if it is less than our bound
|
||||||
if (predicted_cost < cost_bound):
|
if (predicted_cost < cost_bound):
|
||||||
if (self.map[n].min_cost==-1 or current_cost<self.map[n].min_cost):
|
if (self.map[n[0]].min_cost==-1 or predicted_cost<self.map[n[0]].min_cost):
|
||||||
self.map[n].visited=True
|
self.map[n[0]].min_path = newpath
|
||||||
self.map[n].min_path = newpath
|
self.map[n[0]].min_cost = predicted_cost
|
||||||
self.map[n].min_cost = predicted_cost
|
debug.info(4,"Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath))
|
||||||
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
|
# add the cost to get to this point if we haven't reached it yet
|
||||||
heappush(self.q,(predicted_cost,self.counter,newpath))
|
heappush(self.q,(predicted_cost,self.counter,newpath))
|
||||||
self.counter += 1
|
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.")
|
debug.warning("Unable to route path. Expand the detour_scale to allow detours.")
|
||||||
return (None,None)
|
return (None,None)
|
||||||
|
|
||||||
def expand_dirs(self,path):
|
def expand_dirs(self,curpath):
|
||||||
"""
|
"""
|
||||||
Expand each of the four cardinal directions plus up or down
|
Expand each of the four cardinal directions plus up or down
|
||||||
but not expanding to blocked cells. Expands in all directions
|
but not expanding to blocked cells. Expands in all directions
|
||||||
regardless of preferred directions.
|
regardless of preferred directions.
|
||||||
"""
|
"""
|
||||||
# expand from the last point
|
|
||||||
point = path[-1]
|
|
||||||
|
|
||||||
neighbors = []
|
|
||||||
|
|
||||||
east = point + vector3d(1,0,0)
|
# Expand all directions.
|
||||||
if not self.is_blocked(east) and not east in path:
|
neighbors = curpath.expand_dirs()
|
||||||
neighbors.append(east)
|
|
||||||
|
|
||||||
west= point + vector3d(-1,0,0)
|
# Filter the blocked ones
|
||||||
if not self.is_blocked(west) and not west in path:
|
unblocked_neighbors = [x for x in neighbors if not self.is_blocked(x)]
|
||||||
neighbors.append(west)
|
|
||||||
|
|
||||||
up = point + vector3d(0,0,1)
|
return unblocked_neighbors
|
||||||
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 hpwl(self, src, dest):
|
def hpwl(self, src, dest):
|
||||||
|
|
@ -191,7 +146,7 @@ class astar_grid(grid.grid):
|
||||||
hpwl += max(abs(src.y-dest.y),abs(dest.y-src.y))
|
hpwl += max(abs(src.y-dest.y),abs(dest.y-src.y))
|
||||||
hpwl += max(abs(src.z-dest.z),abs(dest.z-src.z))
|
hpwl += max(abs(src.z-dest.z),abs(dest.z-src.z))
|
||||||
if src.x!=dest.x or src.y!=dest.y:
|
if src.x!=dest.x or src.y!=dest.y:
|
||||||
hpwl += self.VIA_COST
|
hpwl += grid.VIA_COST
|
||||||
return hpwl
|
return hpwl
|
||||||
|
|
||||||
def cost_to_target(self,source):
|
def cost_to_target(self,source):
|
||||||
|
|
@ -202,37 +157,10 @@ class astar_grid(grid.grid):
|
||||||
cost = self.hpwl(source,self.target[0])
|
cost = self.hpwl(source,self.target[0])
|
||||||
for t in self.target:
|
for t in self.target:
|
||||||
cost = min(self.hpwl(source,t),cost)
|
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
|
return cost
|
||||||
|
|
||||||
|
|
||||||
def get_inertia(self,p0,p1):
|
def get_inertia(self,p0,p1):
|
||||||
"""
|
"""
|
||||||
Sets the direction based on the previous direction we came from.
|
Sets the direction based on the previous direction we came from.
|
||||||
|
|
@ -4,24 +4,21 @@ from contact import contact
|
||||||
import math
|
import math
|
||||||
import debug
|
import debug
|
||||||
from pin_layout import pin_layout
|
from pin_layout import pin_layout
|
||||||
from vector import vector
|
|
||||||
from vector3d import vector3d
|
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
from router import router
|
from router import router
|
||||||
|
|
||||||
class signal_router(router):
|
class signal_router(router):
|
||||||
"""A router class to read an obstruction map from a gds and plan a
|
"""
|
||||||
|
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.
|
route on a given layer. This is limited to two layer routes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, gds_name=None, module=None):
|
def __init__(self, layers, design, gds_filename=None):
|
||||||
"""Use the gds file for the blockages with the top module topName and
|
|
||||||
layers for the layers to route on
|
|
||||||
"""
|
"""
|
||||||
router.__init__(self, gds_name, module)
|
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).
|
||||||
# all the paths we've routed so far (to supplement the blockages)
|
"""
|
||||||
self.paths = []
|
router.__init__(self, layers, design, gds_filename)
|
||||||
|
|
||||||
|
|
||||||
def create_routing_grid(self):
|
def create_routing_grid(self):
|
||||||
|
|
@ -33,130 +30,51 @@ class signal_router(router):
|
||||||
size = self.ur - self.ll
|
size = self.ur - self.ll
|
||||||
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
|
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
|
||||||
|
|
||||||
import astar_grid
|
import signal_grid
|
||||||
self.rg = astar_grid.astar_grid()
|
self.rg = signal_grid.signal_grid(self.ll, self.ur, self.track_width)
|
||||||
|
|
||||||
|
|
||||||
def route(self, cell, layers, src, dest, detour_scale=5):
|
def route(self, src, dest, detour_scale=5):
|
||||||
"""
|
"""
|
||||||
Route a single source-destination net and return
|
Route a single source-destination net and return
|
||||||
the simplified rectilinear path. Cost factor is how sub-optimal to explore for a feasible route.
|
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.
|
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))
|
debug.info(1,"Running signal router from {0} to {1}...".format(src,dest))
|
||||||
self.cell = cell
|
|
||||||
self.source_pin_name = src
|
|
||||||
self.target_pin_name = dest
|
|
||||||
|
|
||||||
|
self.pins[src] = []
|
||||||
|
self.pins[dest] = []
|
||||||
|
|
||||||
# Clear the pins if we have previously routed
|
# Clear the pins if we have previously routed
|
||||||
if (hasattr(self,'rg')):
|
if (hasattr(self,'rg')):
|
||||||
self.clear_pins()
|
self.clear_pins()
|
||||||
else:
|
else:
|
||||||
# Set up layers and track sizes
|
|
||||||
self.set_layers(layers)
|
|
||||||
# Creat a routing grid over the entire area
|
# Creat a routing grid over the entire area
|
||||||
# FIXME: This could be created only over the routing region,
|
# FIXME: This could be created only over the routing region,
|
||||||
# but this is simplest for now.
|
# but this is simplest for now.
|
||||||
self.create_routing_grid()
|
self.create_routing_grid()
|
||||||
# This will get all shapes as blockages
|
|
||||||
self.find_blockages()
|
|
||||||
|
|
||||||
# Get the pin shapes
|
# Get the pin shapes
|
||||||
self.get_pin(src)
|
self.find_pins_and_blockages([src, dest])
|
||||||
self.get_pin(dest)
|
|
||||||
|
|
||||||
# Now add the blockages (all shapes except the src/tgt pins)
|
# Block everything
|
||||||
self.add_blockages()
|
self.prepare_blockages()
|
||||||
# Add blockages from previous paths
|
# Clear the pins we are routing
|
||||||
self.add_path_blockages()
|
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
|
# Now add the src/tgt if they are not blocked by other shapes
|
||||||
self.add_pin(src,True)
|
self.add_source(src)
|
||||||
self.add_pin(dest,False)
|
self.add_target(dest)
|
||||||
|
|
||||||
|
if not self.run_router(detour_scale=detour_scale):
|
||||||
# returns the path in tracks
|
self.write_debug_gds(stop_program=False)
|
||||||
(path,cost) = self.rg.route(detour_scale)
|
return False
|
||||||
if path:
|
|
||||||
debug.info(1,"Found path: cost={0} ".format(cost))
|
self.write_debug_gds(stop_program=False)
|
||||||
debug.info(2,str(path))
|
return True
|
||||||
self.add_route(path)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
self.write_debug_gds()
|
|
||||||
# clean up so we can try a reroute
|
|
||||||
self.clear_pins()
|
|
||||||
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def add_route(self,path):
|
|
||||||
"""
|
|
||||||
Add the current wire route to the given design instance.
|
|
||||||
"""
|
|
||||||
debug.info(3,"Set path: " + str(path))
|
|
||||||
|
|
||||||
# Keep track of path for future blockages
|
|
||||||
self.paths.append(path)
|
|
||||||
|
|
||||||
# This is marked for debug
|
|
||||||
self.rg.add_path(path)
|
|
||||||
|
|
||||||
# For debugging... if the path failed to route.
|
|
||||||
if False or path==None:
|
|
||||||
self.write_debug_gds()
|
|
||||||
|
|
||||||
|
|
||||||
# First, simplify the path for
|
|
||||||
#debug.info(1,str(self.path))
|
|
||||||
contracted_path = self.contract_path(path)
|
|
||||||
debug.info(1,str(contracted_path))
|
|
||||||
|
|
||||||
# convert the path back to absolute units from tracks
|
|
||||||
abs_path = map(self.convert_point_to_units,contracted_path)
|
|
||||||
debug.info(1,str(abs_path))
|
|
||||||
self.cell.add_route(self.layers,abs_path)
|
|
||||||
|
|
||||||
|
|
||||||
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 0
|
|
||||||
elif p0.y!=p1.y:
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
# z direction
|
|
||||||
return 2
|
|
||||||
|
|
||||||
def contract_path(self,path):
|
|
||||||
"""
|
|
||||||
Remove intermediate points in a rectilinear path.
|
|
||||||
"""
|
|
||||||
newpath = [path[0]]
|
|
||||||
for i in range(1,len(path)-1):
|
|
||||||
prev_inertia=self.get_inertia(path[i-1],path[i])
|
|
||||||
next_inertia=self.get_inertia(path[i],path[i+1])
|
|
||||||
# if we switch directions, add the point, otherwise don't
|
|
||||||
if prev_inertia!=next_inertia:
|
|
||||||
newpath.append(path[i])
|
|
||||||
|
|
||||||
# always add the last path
|
|
||||||
newpath.append(path[-1])
|
|
||||||
return newpath
|
|
||||||
|
|
||||||
|
|
||||||
def add_path_blockages(self):
|
|
||||||
"""
|
|
||||||
Go through all of the past paths and add them as blockages.
|
|
||||||
This is so we don't have to write/reload the GDS.
|
|
||||||
"""
|
|
||||||
for path in self.paths:
|
|
||||||
for grid in path:
|
|
||||||
self.rg.set_blocked(grid)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,79 @@
|
||||||
import debug
|
import debug
|
||||||
from vector3d import vector3d
|
from vector3d import vector3d
|
||||||
import grid
|
from grid import grid
|
||||||
|
from signal_grid import signal_grid
|
||||||
|
from grid_path import grid_path
|
||||||
|
from direction import direction
|
||||||
|
|
||||||
|
|
||||||
class supply_grid(grid.grid):
|
class supply_grid(signal_grid):
|
||||||
"""
|
"""
|
||||||
A two layer routing map. Each cell can be blocked in the vertical
|
This routes a supply grid. It is derived from a signal grid because it still
|
||||||
or horizontal layer.
|
routes the pins to the supply rails using the same routines.
|
||||||
|
It has a few extra routines to support "waves" which are multiple track wide
|
||||||
|
directional routes (no bends).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, ll, ur, track_width):
|
||||||
""" Create a routing map of width x height cells and 2 in the z-axis. """
|
""" Create a routing map of width x height cells and 2 in the z-axis. """
|
||||||
grid.grid.__init__(self)
|
signal_grid.__init__(self, ll, ur, track_width)
|
||||||
|
|
||||||
# list of the vdd/gnd rail cells
|
|
||||||
self.vdd_rails = []
|
|
||||||
self.gnd_rails = []
|
|
||||||
|
|
||||||
def reinit(self):
|
def reinit(self):
|
||||||
""" Reinitialize everything for a new route. """
|
""" Reinitialize everything for a new route. """
|
||||||
|
self.source = []
|
||||||
|
self.target = []
|
||||||
# Reset all the cells in the map
|
# Reset all the cells in the map
|
||||||
for p in self.map.values():
|
for p in self.map.values():
|
||||||
p.reset()
|
p.reset()
|
||||||
|
|
||||||
|
|
||||||
|
def find_start_wave(self, wave, width, direct):
|
||||||
|
"""
|
||||||
|
Finds the first loc starting at loc and up that is open.
|
||||||
|
Returns None if it reaches max size first.
|
||||||
|
"""
|
||||||
|
# Don't expand outside the bounding box
|
||||||
|
if wave[0].x > self.ur.x:
|
||||||
|
return None
|
||||||
|
if wave[-1].y > self.ur.y:
|
||||||
|
return None
|
||||||
|
|
||||||
|
while wave and self.is_wave_blocked(wave):
|
||||||
|
wf=grid_path(wave)
|
||||||
|
wave=wf.neighbor(direct)
|
||||||
|
# Bail out if we couldn't increment futher
|
||||||
|
if wave[0].x > self.ur.x or wave[-1].y > self.ur.y:
|
||||||
|
return None
|
||||||
|
# Return a start if it isn't blocked
|
||||||
|
if not self.is_wave_blocked(wave):
|
||||||
|
return wave
|
||||||
|
|
||||||
|
return wave
|
||||||
|
|
||||||
|
|
||||||
|
def is_wave_blocked(self, wave):
|
||||||
|
"""
|
||||||
|
Checks if any of the locations are blocked
|
||||||
|
"""
|
||||||
|
for v in wave:
|
||||||
|
if self.is_blocked(v):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def probe(self, wave, direct):
|
||||||
|
"""
|
||||||
|
Expand the wave until there is a blockage and return
|
||||||
|
the wave path.
|
||||||
|
"""
|
||||||
|
wave_path = grid_path()
|
||||||
|
while wave and not self.is_wave_blocked(wave):
|
||||||
|
if wave[0].x > self.ur.x or wave[-1].y > self.ur.y:
|
||||||
|
break
|
||||||
|
wave_path.append(wave)
|
||||||
|
wave = wave_path.neighbor(direct)
|
||||||
|
|
||||||
|
return wave_path
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ import tech
|
||||||
from contact import contact
|
from contact import contact
|
||||||
import math
|
import math
|
||||||
import debug
|
import debug
|
||||||
import grid
|
|
||||||
from pin_layout import pin_layout
|
|
||||||
from vector import vector
|
|
||||||
from vector3d import vector3d
|
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
from pin_layout import pin_layout
|
||||||
|
from vector3d import vector3d
|
||||||
from router import router
|
from router import router
|
||||||
|
from direction import direction
|
||||||
|
import grid
|
||||||
|
import grid_utils
|
||||||
|
|
||||||
class supply_router(router):
|
class supply_router(router):
|
||||||
"""
|
"""
|
||||||
|
|
@ -16,131 +17,504 @@ class supply_router(router):
|
||||||
routes a grid to connect the supply on the two layers.
|
routes a grid to connect the supply on the two layers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, gds_name=None, module=None):
|
def __init__(self, layers, design, gds_filename=None):
|
||||||
"""Use the gds file for the blockages with the top module topName and
|
|
||||||
layers for the layers to route on
|
|
||||||
"""
|
"""
|
||||||
router.__init__(self, gds_name, module)
|
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).
|
||||||
self.pins = {}
|
"""
|
||||||
|
router.__init__(self, layers, design, gds_filename)
|
||||||
|
|
||||||
|
# We over-ride the regular router costs to allow
|
||||||
def clear_pins(self):
|
# more off-direction router in the supply grid
|
||||||
"""
|
grid.VIA_COST = 1
|
||||||
Convert the routed path to blockages.
|
grid.NONPREFERRED_COST = 1
|
||||||
Keep the other blockages unchanged.
|
grid.PREFERRED_COST = 1
|
||||||
"""
|
|
||||||
self.pins = {}
|
|
||||||
self.rg.reinit()
|
|
||||||
|
|
||||||
|
|
||||||
def route(self, cell, layers, vdd_name="vdd", gnd_name="gnd"):
|
# The list of supply rails (grid sets) that may be routed
|
||||||
|
self.supply_rails = {}
|
||||||
|
self.supply_rail_wires = {}
|
||||||
|
# This is the same as above but as a sigle set for the all the rails
|
||||||
|
self.supply_rail_tracks = {}
|
||||||
|
self.supply_rail_wire_tracks = {}
|
||||||
|
|
||||||
|
# Power rail width in grid units.
|
||||||
|
self.rail_track_width = 2
|
||||||
|
|
||||||
|
|
||||||
|
def create_routing_grid(self):
|
||||||
"""
|
"""
|
||||||
Route a single source-destination net and return
|
Create a sprase routing grid with A* expansion functions.
|
||||||
the simplified rectilinear path.
|
"""
|
||||||
|
size = self.ur - self.ll
|
||||||
|
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
|
||||||
|
|
||||||
|
import supply_grid
|
||||||
|
self.rg = supply_grid.supply_grid(self.ll, self.ur, self.track_width)
|
||||||
|
|
||||||
|
def route(self, vdd_name="vdd", gnd_name="gnd"):
|
||||||
|
"""
|
||||||
|
Add power supply rails and connect all pins to these rails.
|
||||||
"""
|
"""
|
||||||
debug.info(1,"Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
|
debug.info(1,"Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
|
||||||
self.cell = cell
|
self.vdd_name = vdd_name
|
||||||
self.pins[vdd_name] = []
|
self.gnd_name = gnd_name
|
||||||
self.pins[gnd_name] = []
|
|
||||||
|
|
||||||
# Clear the pins if we have previously routed
|
# Clear the pins if we have previously routed
|
||||||
if (hasattr(self,'rg')):
|
if (hasattr(self,'rg')):
|
||||||
self.clear_pins()
|
self.clear_pins()
|
||||||
else:
|
else:
|
||||||
# Set up layers and track sizes
|
|
||||||
self.set_layers(layers)
|
|
||||||
# Creat a routing grid over the entire area
|
# Creat a routing grid over the entire area
|
||||||
# FIXME: This could be created only over the routing region,
|
# FIXME: This could be created only over the routing region,
|
||||||
# but this is simplest for now.
|
# but this is simplest for now.
|
||||||
self.create_routing_grid()
|
self.create_routing_grid()
|
||||||
# This will get all shapes as blockages
|
|
||||||
self.find_blockages()
|
|
||||||
|
|
||||||
# Get the pin shapes
|
# Get the pin shapes
|
||||||
self.get_pin(vdd_name)
|
self.find_pins_and_blockages([self.vdd_name, self.gnd_name])
|
||||||
self.get_pin(gnd_name)
|
#self.write_debug_gds("pin_enclosures.gds",stop_program=False)
|
||||||
|
|
||||||
# Now add the blockages (all shapes except the src/tgt pins)
|
|
||||||
self.add_blockages()
|
|
||||||
# Add blockages from previous routes
|
|
||||||
self.add_path_blockages()
|
|
||||||
|
|
||||||
# source pin will be a specific layout pin
|
# Add the supply rails in a mesh network and connect H/V with vias
|
||||||
# target pin will be the rails only
|
# Block everything
|
||||||
|
self.prepare_blockages(self.gnd_name)
|
||||||
|
# Determine the rail locations
|
||||||
|
self.route_supply_rails(self.gnd_name,0)
|
||||||
|
|
||||||
|
# Block everything
|
||||||
|
self.prepare_blockages(self.vdd_name)
|
||||||
|
# Determine the rail locations
|
||||||
|
self.route_supply_rails(self.vdd_name,1)
|
||||||
|
#self.write_debug_gds("debug_rails.gds",stop_program=True)
|
||||||
|
|
||||||
|
remaining_vdd_pin_indices = self.route_simple_overlaps(vdd_name)
|
||||||
|
remaining_gnd_pin_indices = self.route_simple_overlaps(gnd_name)
|
||||||
|
#self.write_debug_gds("debug_simple_route.gds",stop_program=True)
|
||||||
|
|
||||||
|
# Route the supply pins to the supply rails
|
||||||
|
# Route vdd first since we want it to be shorter
|
||||||
|
self.route_pins_to_rails(vdd_name, remaining_vdd_pin_indices)
|
||||||
|
self.route_pins_to_rails(gnd_name, remaining_gnd_pin_indices)
|
||||||
|
#self.write_debug_gds("debug_pin_routes.gds",stop_program=True)
|
||||||
|
|
||||||
|
#self.write_debug_gds("final.gds")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def route_simple_overlaps(self, pin_name):
|
||||||
|
"""
|
||||||
|
This checks for simple cases where a pin component already overlaps a supply rail.
|
||||||
|
It will add an enclosure to ensure the overlap in wide DRC rule cases.
|
||||||
|
"""
|
||||||
|
num_components = self.num_pin_components(pin_name)
|
||||||
|
remaining_pins = []
|
||||||
|
supply_tracks = self.supply_rail_tracks[pin_name]
|
||||||
|
|
||||||
|
for index in range(num_components):
|
||||||
|
pin_in_tracks = self.pin_grids[pin_name][index]
|
||||||
|
common_set = supply_tracks & pin_in_tracks
|
||||||
|
|
||||||
|
if len(common_set)==0:
|
||||||
|
# if no overlap, add it to the complex route pins
|
||||||
|
remaining_pins.append(index)
|
||||||
|
else:
|
||||||
|
self.create_simple_overlap_enclosure(pin_name, common_set)
|
||||||
|
|
||||||
# returns the path in tracks
|
return remaining_pins
|
||||||
# (path,cost) = self.rg.route(detour_scale)
|
|
||||||
# if path:
|
def recurse_simple_overlap_enclosure(self, pin_name, start_set, direct):
|
||||||
# debug.info(1,"Found path: cost={0} ".format(cost))
|
"""
|
||||||
# debug.info(2,str(path))
|
Recursive function to return set of tracks that connects to
|
||||||
# self.add_route(path)
|
the actual supply rail wire in a given direction (or terminating
|
||||||
# return True
|
when any track is no longer in the supply rail.
|
||||||
# else:
|
"""
|
||||||
# self.write_debug_gds()
|
next_set = grid_utils.expand_border(start_set, direct)
|
||||||
# # clean up so we can try a reroute
|
|
||||||
# self.clear_pins()
|
supply_tracks = self.supply_rail_tracks[pin_name]
|
||||||
|
supply_wire_tracks = self.supply_rail_wire_tracks[pin_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(pin_name, 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()
|
||||||
|
|
||||||
self.write_debug_gds()
|
return new_set
|
||||||
|
|
||||||
|
def create_simple_overlap_enclosure(self, pin_name, 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(pin_name, start_set, direction.NORTH)
|
||||||
|
if not new_set:
|
||||||
|
new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.SOUTH)
|
||||||
|
else:
|
||||||
|
new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.EAST)
|
||||||
|
if not new_set:
|
||||||
|
new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.WEST)
|
||||||
|
|
||||||
|
enclosure_list = self.compute_enclosures(new_set)
|
||||||
|
for pin in enclosure_list:
|
||||||
|
debug.info(2,"Adding simple overlap enclosure {0} {1}".format(pin_name, pin))
|
||||||
|
self.cell.add_rect(layer=pin.layer,
|
||||||
|
offset=pin.ll(),
|
||||||
|
width=pin.width(),
|
||||||
|
height=pin.height())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def finalize_supply_rails(self, name):
|
||||||
|
"""
|
||||||
|
Determine which supply rails overlap and can accomodate a via.
|
||||||
|
Remove any supply rails that do not have a via since they are disconnected.
|
||||||
|
NOTE: It is still possible though unlikely that there are disconnected groups of rails.
|
||||||
|
"""
|
||||||
|
|
||||||
|
all_rails = self.supply_rail_wires[name]
|
||||||
|
|
||||||
|
connections = set()
|
||||||
|
via_areas = []
|
||||||
|
for i1,r1 in enumerate(all_rails):
|
||||||
|
# Only consider r1 horizontal rails
|
||||||
|
e = next(iter(r1))
|
||||||
|
if e.z==1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# We need to move this rail to the other layer for the z indices to match
|
||||||
|
# during the intersection. This also makes a copy.
|
||||||
|
new_r1 = {vector3d(i.x,i.y,1) for i in r1}
|
||||||
|
|
||||||
|
# If horizontal, subtract off the left/right track to prevent end of rail via
|
||||||
|
#ll = grid_utils.get_lower_left(new_r1)
|
||||||
|
#ur = grid_utils.get_upper_right(new_r1)
|
||||||
|
grid_utils.remove_border(new_r1, direction.EAST)
|
||||||
|
grid_utils.remove_border(new_r1, direction.WEST)
|
||||||
|
|
||||||
|
for i2,r2 in enumerate(all_rails):
|
||||||
|
# Never compare to yourself
|
||||||
|
if i1==i2:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Only consider r2 vertical rails
|
||||||
|
e = next(iter(r2))
|
||||||
|
if e.z==0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Need to maek a copy to consider via overlaps to ignore the end-caps
|
||||||
|
new_r2 = r2.copy()
|
||||||
|
grid_utils.remove_border(new_r2, direction.NORTH)
|
||||||
|
grid_utils.remove_border(new_r2, direction.SOUTH)
|
||||||
|
|
||||||
|
# Determine if we hhave sufficient overlap and, if so,
|
||||||
|
# remember:
|
||||||
|
# the indices to determine a rail is connected to another
|
||||||
|
# the overlap area for placement of a via
|
||||||
|
overlap = new_r1 & new_r2
|
||||||
|
if len(overlap) >= self.supply_rail_wire_width**2:
|
||||||
|
debug.info(2,"Via overlap {0} {1} {2}".format(len(overlap),self.supply_rail_wire_width**2,overlap))
|
||||||
|
connections.add(i1)
|
||||||
|
connections.add(i2)
|
||||||
|
via_areas.append(overlap)
|
||||||
|
|
||||||
|
# Go through and add the vias at the center of the intersection
|
||||||
|
for area in via_areas:
|
||||||
|
ll = grid_utils.get_lower_left(area)
|
||||||
|
ur = grid_utils.get_upper_right(area)
|
||||||
|
center = (ll + ur).scale(0.5,0.5,0)
|
||||||
|
self.add_via(center,self.rail_track_width)
|
||||||
|
|
||||||
|
# Determien which indices were not connected to anything above
|
||||||
|
all_indices = set([x for x in range(len(self.supply_rails[name]))])
|
||||||
|
missing_indices = all_indices ^ connections
|
||||||
|
# Go through and remove those disconnected indices
|
||||||
|
# (No via was added, so that doesn't need to be removed)
|
||||||
|
for rail_index in missing_indices:
|
||||||
|
ll = grid_utils.get_lower_left(all_rails[rail_index])
|
||||||
|
ur = grid_utils.get_upper_right(all_rails[rail_index])
|
||||||
|
debug.info(1,"Removing disconnected supply rail {0} .. {1}".format(ll,ur))
|
||||||
|
self.supply_rails[name].pop(rail_index)
|
||||||
|
self.supply_rail_wires[name].pop(rail_index)
|
||||||
|
|
||||||
|
# Make the supply rails into a big giant set of grids for easy blockages.
|
||||||
|
# Must be done after we determine which ones are connected.
|
||||||
|
self.create_supply_track_set(name)
|
||||||
|
|
||||||
|
|
||||||
|
def add_supply_rails(self, name):
|
||||||
|
"""
|
||||||
|
Add the shapes that represent the routed supply rails.
|
||||||
|
This is after the paths have been pruned and only include rails that are
|
||||||
|
connected with vias.
|
||||||
|
"""
|
||||||
|
for rail in self.supply_rails[name]:
|
||||||
|
ll = grid_utils.get_lower_left(rail)
|
||||||
|
ur = grid_utils.get_upper_right(rail)
|
||||||
|
z = ll.z
|
||||||
|
pin = self.compute_wide_enclosure(ll, ur, z, name)
|
||||||
|
debug.info(1,"Adding supply rail {0} {1}->{2} {3}".format(name,ll,ur,pin))
|
||||||
|
self.cell.add_layout_pin(text=name,
|
||||||
|
layer=pin.layer,
|
||||||
|
offset=pin.ll(),
|
||||||
|
width=pin.width(),
|
||||||
|
height=pin.height())
|
||||||
|
|
||||||
|
def compute_supply_rail_dimensions(self):
|
||||||
|
"""
|
||||||
|
Compute the supply rail dimensions including wide metal spacing rules.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.max_yoffset = self.rg.ur.y
|
||||||
|
self.max_xoffset = self.rg.ur.x
|
||||||
|
|
||||||
|
# Longest length is conservative
|
||||||
|
rail_length = max(self.max_yoffset,self.max_xoffset)
|
||||||
|
# Convert the number of tracks to dimensions to get the design rule spacing
|
||||||
|
rail_width = self.track_width*self.rail_track_width
|
||||||
|
|
||||||
|
# Get the conservative width and spacing of the top rails
|
||||||
|
(horizontal_width, horizontal_space) = self.get_layer_width_space(0, rail_width, rail_length)
|
||||||
|
(vertical_width, vertical_space) = self.get_layer_width_space(1, rail_width, rail_length)
|
||||||
|
width = max(horizontal_width, vertical_width)
|
||||||
|
space = max(horizontal_space, vertical_space)
|
||||||
|
|
||||||
|
# This is the supply rail pitch in terms of routing grids
|
||||||
|
# i.e. a rail of self.rail_track_width needs this many tracks including
|
||||||
|
# space
|
||||||
|
track_pitch = self.rail_track_width*width + space
|
||||||
|
|
||||||
|
# Determine the pitch (in tracks) of the rail wire + spacing
|
||||||
|
self.supply_rail_width = math.ceil(track_pitch/self.track_width)
|
||||||
|
debug.info(1,"Rail step: {}".format(self.supply_rail_width))
|
||||||
|
|
||||||
|
# Conservatively determine the number of tracks that the rail actually occupies
|
||||||
|
space_tracks = math.ceil(space/self.track_width)
|
||||||
|
self.supply_rail_wire_width = self.supply_rail_width - space_tracks
|
||||||
|
debug.info(1,"Rail wire tracks: {}".format(self.supply_rail_wire_width))
|
||||||
|
total_space = self.supply_rail_width - self.supply_rail_wire_width
|
||||||
|
self.supply_rail_space_width = math.floor(0.5*total_space)
|
||||||
|
debug.info(1,"Rail space tracks: {} (on both sides)".format(self.supply_rail_space_width))
|
||||||
|
|
||||||
|
|
||||||
|
def compute_supply_rails(self, name, supply_number):
|
||||||
|
"""
|
||||||
|
Compute the unblocked locations for the horizontal and vertical supply rails.
|
||||||
|
Go in a raster order from bottom to the top (for horizontal) and left to right
|
||||||
|
(for vertical). Start with an initial start_offset in x and y direction.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.supply_rails[name]=[]
|
||||||
|
self.supply_rail_wires[name]=[]
|
||||||
|
|
||||||
|
start_offset = supply_number*self.supply_rail_width
|
||||||
|
|
||||||
|
# Horizontal supply rails
|
||||||
|
for offset in range(start_offset, self.max_yoffset, 2*self.supply_rail_width):
|
||||||
|
# Seed the function at the location with the given width
|
||||||
|
wave = [vector3d(0,offset+i,0) for i in range(self.supply_rail_width)]
|
||||||
|
# While we can keep expanding east in this horizontal track
|
||||||
|
while wave and wave[0].x < self.max_xoffset:
|
||||||
|
added_rail = self.find_supply_rail(name, wave, direction.EAST)
|
||||||
|
if added_rail:
|
||||||
|
wave = added_rail.neighbor(direction.EAST)
|
||||||
|
else:
|
||||||
|
wave = None
|
||||||
|
|
||||||
|
|
||||||
|
# Vertical supply rails
|
||||||
|
max_offset = self.rg.ur.x
|
||||||
|
for offset in range(start_offset, self.max_xoffset, 2*self.supply_rail_width):
|
||||||
|
# Seed the function at the location with the given width
|
||||||
|
wave = [vector3d(offset+i,0,1) for i in range(self.supply_rail_width)]
|
||||||
|
# While we can keep expanding north in this vertical track
|
||||||
|
while wave and wave[0].y < self.max_yoffset:
|
||||||
|
added_rail = self.find_supply_rail(name, wave, direction.NORTH)
|
||||||
|
if added_rail:
|
||||||
|
wave = added_rail.neighbor(direction.NORTH)
|
||||||
|
else:
|
||||||
|
wave = None
|
||||||
|
|
||||||
|
def find_supply_rail(self, name, seed_wave, direct):
|
||||||
|
"""
|
||||||
|
Find a start location, probe in the direction, and see if the rail is big enough
|
||||||
|
to contain a via, and, if so, add it.
|
||||||
|
"""
|
||||||
|
start_wave = self.find_supply_rail_start(name, seed_wave, direct)
|
||||||
|
if not start_wave:
|
||||||
|
return None
|
||||||
|
|
||||||
|
wave_path = self.probe_supply_rail(name, start_wave, direct)
|
||||||
|
|
||||||
|
if self.approve_supply_rail(name, wave_path):
|
||||||
|
return wave_path
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_supply_rail_start(self, name, seed_wave, direct):
|
||||||
|
"""
|
||||||
|
This finds the first valid starting location and routes a supply rail
|
||||||
|
in the given direction.
|
||||||
|
It returns the space after the end of the rail to seed another call for multiple
|
||||||
|
supply rails in the same "track" when there is a blockage.
|
||||||
|
"""
|
||||||
|
# Sweep to find an initial unblocked valid wave
|
||||||
|
start_wave = self.rg.find_start_wave(seed_wave, len(seed_wave), direct)
|
||||||
|
|
||||||
|
return start_wave
|
||||||
|
|
||||||
|
def probe_supply_rail(self, name, start_wave, direct):
|
||||||
|
"""
|
||||||
|
This finds the first valid starting location and routes a supply rail
|
||||||
|
in the given direction.
|
||||||
|
It returns the space after the end of the rail to seed another call for multiple
|
||||||
|
supply rails in the same "track" when there is a blockage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Expand the wave to the right
|
||||||
|
wave_path = self.rg.probe(start_wave, direct)
|
||||||
|
|
||||||
|
if not wave_path:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# drop the first and last steps to leave escape routing room
|
||||||
|
# around the blockage that stopped the probe
|
||||||
|
# except, don't drop the first if it is the first in a row/column
|
||||||
|
if (direct==direction.NORTH and start_wave[0].y>0):
|
||||||
|
wave_path.trim_first()
|
||||||
|
elif (direct == direction.EAST and start_wave[0].x>0):
|
||||||
|
wave_path.trim_first()
|
||||||
|
|
||||||
|
wave_path.trim_last()
|
||||||
|
|
||||||
|
return wave_path
|
||||||
|
|
||||||
|
def approve_supply_rail(self, name, wave_path):
|
||||||
|
"""
|
||||||
|
Check if the supply rail is sufficient (big enough) and add it to the
|
||||||
|
data structure. Return whether it was added or not.
|
||||||
|
"""
|
||||||
|
# We must have at least 2 tracks to drop plus 2 tracks for a via
|
||||||
|
if len(wave_path)>=4*self.rail_track_width:
|
||||||
|
grid_set = wave_path.get_grids()
|
||||||
|
self.supply_rails[name].append(grid_set)
|
||||||
|
|
||||||
|
start_wire_index = self.supply_rail_space_width
|
||||||
|
end_wire_index = self.supply_rail_width - self.supply_rail_space_width
|
||||||
|
wire_set = wave_path.get_wire_grids(start_wire_index,end_wire_index)
|
||||||
|
self.supply_rail_wires[name].append(wire_set)
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def add_route(self,path):
|
|
||||||
"""
|
|
||||||
Add the current wire route to the given design instance.
|
|
||||||
|
def route_supply_rails(self, name, supply_number):
|
||||||
|
"""
|
||||||
|
Route the horizontal and vertical supply rails across the entire design.
|
||||||
|
Must be done with lower left at 0,0
|
||||||
"""
|
"""
|
||||||
debug.info(3,"Set path: " + str(path))
|
|
||||||
|
|
||||||
# Keep track of path for future blockages
|
# Compute the grid dimensions
|
||||||
self.paths.append(path)
|
self.compute_supply_rail_dimensions()
|
||||||
|
|
||||||
# This is marked for debug
|
# Compute the grid locations of the supply rails
|
||||||
self.rg.add_path(path)
|
self.compute_supply_rails(name, supply_number)
|
||||||
|
|
||||||
|
# Add the supply rail vias (and prune disconnected rails)
|
||||||
|
self.finalize_supply_rails(name)
|
||||||
|
|
||||||
# For debugging... if the path failed to route.
|
# Add the rails themselves
|
||||||
if False or path==None:
|
self.add_supply_rails(name)
|
||||||
self.write_debug_gds()
|
|
||||||
|
|
||||||
# First, simplify the path for
|
|
||||||
#debug.info(1,str(self.path))
|
def create_supply_track_set(self, pin_name):
|
||||||
contracted_path = self.contract_path(path)
|
"""
|
||||||
debug.info(1,str(contracted_path))
|
Make a single set of all the tracks for the rail and wire itself.
|
||||||
|
"""
|
||||||
|
rail_set = set()
|
||||||
|
for rail in self.supply_rails[pin_name]:
|
||||||
|
rail_set.update(rail)
|
||||||
|
self.supply_rail_tracks[pin_name] = rail_set
|
||||||
|
|
||||||
# convert the path back to absolute units from tracks
|
wire_set = set()
|
||||||
abs_path = map(self.convert_point_to_units,contracted_path)
|
for rail in self.supply_rail_wires[pin_name]:
|
||||||
debug.info(1,str(abs_path))
|
wire_set.update(rail)
|
||||||
self.cell.add_route(self.layers,abs_path)
|
self.supply_rail_wire_tracks[pin_name] = wire_set
|
||||||
|
|
||||||
|
|
||||||
|
def route_pins_to_rails(self, pin_name, remaining_component_indices):
|
||||||
|
"""
|
||||||
|
This will route each of the remaining pin components to the supply rails.
|
||||||
|
After it is done, the cells are added to the pin blockage list.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
debug.info(1,"Pin {0} has {1} remaining components to route.".format(pin_name,
|
||||||
|
len(remaining_component_indices)))
|
||||||
|
|
||||||
|
recent_paths = []
|
||||||
|
# For every component
|
||||||
|
for index in remaining_component_indices:
|
||||||
|
debug.info(2,"Routing component {0} {1}".format(pin_name, index))
|
||||||
|
|
||||||
|
self.rg.reinit()
|
||||||
|
|
||||||
|
self.prepare_blockages(pin_name)
|
||||||
|
|
||||||
|
# Add the single component of the pin as the source
|
||||||
|
# which unmarks it as a blockage too
|
||||||
|
self.add_pin_component_source(pin_name,index)
|
||||||
|
|
||||||
|
# Add all of the rails as targets
|
||||||
|
# Don't add the other pins, but we could?
|
||||||
|
self.add_supply_rail_target(pin_name)
|
||||||
|
|
||||||
|
# Add the previous paths as targets too
|
||||||
|
#self.add_path_target(recent_paths)
|
||||||
|
|
||||||
|
#print(self.rg.target)
|
||||||
|
|
||||||
|
# Actually run the A* router
|
||||||
|
if not self.run_router(detour_scale=5):
|
||||||
|
self.write_debug_gds()
|
||||||
|
|
||||||
|
recent_paths.append(self.paths[-1])
|
||||||
|
|
||||||
|
|
||||||
def create_routing_grid(self):
|
def add_supply_rail_target(self, pin_name):
|
||||||
"""
|
|
||||||
Create a sprase routing grid with A* expansion functions.
|
|
||||||
"""
|
"""
|
||||||
# We will add a halo around the boundary
|
Add the supply rails of given name as a routing target.
|
||||||
# of this many tracks
|
"""
|
||||||
size = self.ur - self.ll
|
debug.info(2,"Add supply rail target {}".format(pin_name))
|
||||||
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
|
# Add the wire itself as the target
|
||||||
|
self.rg.set_target(self.supply_rail_wire_tracks[pin_name])
|
||||||
|
# But unblock all the rail tracks including the space
|
||||||
|
self.rg.set_blocked(self.supply_rail_tracks[pin_name],False)
|
||||||
|
|
||||||
import supply_grid
|
|
||||||
self.rg = supply_grid.supply_grid()
|
def set_supply_rail_blocked(self, value=True):
|
||||||
|
"""
|
||||||
|
Add the supply rails of given name as a routing target.
|
||||||
##########################
|
"""
|
||||||
# Gridded supply route functions
|
debug.info(3,"Blocking supply rail")
|
||||||
##########################
|
for rail_name in self.supply_rail_tracks:
|
||||||
def create_grid(self, ll, ur):
|
self.rg.set_blocked(self.supply_rail_tracks[rail_name])
|
||||||
""" Create alternating vdd/gnd lines horizontally """
|
|
||||||
|
|
||||||
self.create_horizontal_grid()
|
|
||||||
self.create_vertical_grid()
|
|
||||||
|
|
||||||
|
|
||||||
def create_horizontal_grid(self):
|
|
||||||
""" Create alternating vdd/gnd lines horizontally """
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create_vertical_grid(self):
|
|
||||||
""" Create alternating vdd/gnd lines horizontally """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,9 @@ class no_blockages_test(openram_test):
|
||||||
offset=[0,0])
|
offset=[0,0])
|
||||||
self.connect_inst([])
|
self.connect_inst([])
|
||||||
|
|
||||||
r=router(gds_file)
|
|
||||||
layer_stack =("metal1","via1","metal2")
|
layer_stack =("metal1","via1","metal2")
|
||||||
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
|
r=router(layer_stack,self,gds_file)
|
||||||
|
self.assertTrue(r.route(src="A",dest="B"))
|
||||||
|
|
||||||
r=routing("01_no_blockages_test_{0}".format(OPTS.tech_name))
|
r=routing("01_no_blockages_test_{0}".format(OPTS.tech_name))
|
||||||
self.local_drc_check(r)
|
self.local_drc_check(r)
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,9 @@ class blockages_test(openram_test):
|
||||||
offset=[0,0])
|
offset=[0,0])
|
||||||
self.connect_inst([])
|
self.connect_inst([])
|
||||||
|
|
||||||
r=router(gds_file)
|
|
||||||
layer_stack =("metal1","via1","metal2")
|
layer_stack =("metal1","via1","metal2")
|
||||||
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
|
r=router(layer_stack,self,gds_file)
|
||||||
|
self.assertTrue(r.route(src="A",dest="B"))
|
||||||
|
|
||||||
r=routing("02_blockages_test_{0}".format(OPTS.tech_name))
|
r=routing("02_blockages_test_{0}".format(OPTS.tech_name))
|
||||||
self.local_drc_check(r)
|
self.local_drc_check(r)
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,9 @@ class same_layer_pins_test(openram_test):
|
||||||
offset=[0,0])
|
offset=[0,0])
|
||||||
self.connect_inst([])
|
self.connect_inst([])
|
||||||
|
|
||||||
r=router(gds_file)
|
|
||||||
layer_stack =("metal1","via1","metal2")
|
layer_stack =("metal1","via1","metal2")
|
||||||
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
|
r=router(layer_stack,self,gds_file)
|
||||||
|
self.assertTrue(r.route(src="A",dest="B"))
|
||||||
|
|
||||||
r = routing("03_same_layer_pins_test_{0}".format(OPTS.tech_name))
|
r = routing("03_same_layer_pins_test_{0}".format(OPTS.tech_name))
|
||||||
self.local_drc_check(r)
|
self.local_drc_check(r)
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,9 @@ class diff_layer_pins_test(openram_test):
|
||||||
offset=[0,0])
|
offset=[0,0])
|
||||||
self.connect_inst([])
|
self.connect_inst([])
|
||||||
|
|
||||||
r=router(gds_file)
|
|
||||||
layer_stack =("metal1","via1","metal2")
|
layer_stack =("metal1","via1","metal2")
|
||||||
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
|
r=router(layer_stack,self,gds_file)
|
||||||
|
self.assertTrue(r.route(src="A",dest="B"))
|
||||||
|
|
||||||
r = routing("04_diff_layer_pins_test_{0}".format(OPTS.tech_name))
|
r = routing("04_diff_layer_pins_test_{0}".format(OPTS.tech_name))
|
||||||
self.local_drc_check(r)
|
self.local_drc_check(r)
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,10 @@ class two_nets_test(openram_test):
|
||||||
offset=[0,0])
|
offset=[0,0])
|
||||||
self.connect_inst([])
|
self.connect_inst([])
|
||||||
|
|
||||||
r=router(gds_file)
|
|
||||||
layer_stack =("metal1","via1","metal2")
|
layer_stack =("metal1","via1","metal2")
|
||||||
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
|
r=router(layer_stack,self,gds_file)
|
||||||
self.assertTrue(r.route(self,layer_stack,src="C",dest="D"))
|
self.assertTrue(r.route(src="A",dest="B"))
|
||||||
|
self.assertTrue(r.route(src="C",dest="D"))
|
||||||
|
|
||||||
r = routing("05_two_nets_test_{0}".format(OPTS.tech_name))
|
r = routing("05_two_nets_test_{0}".format(OPTS.tech_name))
|
||||||
self.local_drc_check(r)
|
self.local_drc_check(r)
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,13 @@ class pin_location_test(openram_test):
|
||||||
offset=[0,0])
|
offset=[0,0])
|
||||||
self.connect_inst([])
|
self.connect_inst([])
|
||||||
|
|
||||||
r=router(gds_file)
|
|
||||||
layer_stack =("metal1","via1","metal2")
|
layer_stack =("metal1","via1","metal2")
|
||||||
|
r=router(layer_stack,self,gds_file)
|
||||||
# these are user coordinates and layers
|
# these are user coordinates and layers
|
||||||
src_pin = [[0.52, 4.099],11]
|
src_pin = [[0.52, 4.099],11]
|
||||||
tgt_pin = [[3.533, 1.087],11]
|
tgt_pin = [[3.533, 1.087],11]
|
||||||
#r.route(layer_stack,src="A",dest="B")
|
#r.route(layer_stack,src="A",dest="B")
|
||||||
self.assertTrue(r.route(self,layer_stack,src=src_pin,dest=tgt_pin))
|
self.assertTrue(r.route(src=src_pin,dest=tgt_pin))
|
||||||
|
|
||||||
# This only works for freepdk45 since the coordinates are hard coded
|
# This only works for freepdk45 since the coordinates are hard coded
|
||||||
if OPTS.tech_name == "freepdk45":
|
if OPTS.tech_name == "freepdk45":
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ class big_test(openram_test):
|
||||||
offset=[0,0])
|
offset=[0,0])
|
||||||
self.connect_inst([])
|
self.connect_inst([])
|
||||||
|
|
||||||
r=router(gds_file)
|
|
||||||
layer_stack =("metal1","via1","metal2")
|
layer_stack =("metal1","via1","metal2")
|
||||||
|
r=router(layer_stack,self,gds_file)
|
||||||
connections=[('out_0_2', 'a_0_0'),
|
connections=[('out_0_2', 'a_0_0'),
|
||||||
('out_0_3', 'b_0_0'),
|
('out_0_3', 'b_0_0'),
|
||||||
('out_0_0', 'a_0_1'),
|
('out_0_0', 'a_0_1'),
|
||||||
|
|
@ -61,7 +61,7 @@ class big_test(openram_test):
|
||||||
('out_4_1', 'a_4_3'),
|
('out_4_1', 'a_4_3'),
|
||||||
('out_4_5', 'b_4_3')]
|
('out_4_5', 'b_4_3')]
|
||||||
for (src,tgt) in connections:
|
for (src,tgt) in connections:
|
||||||
self.assertTrue(r.route(self,layer_stack,src=src,dest=tgt))
|
self.assertTrue(r.route(src=src,dest=tgt))
|
||||||
|
|
||||||
# This test only runs on scn3me_subm tech
|
# This test only runs on scn3me_subm tech
|
||||||
if OPTS.tech_name=="scn3me_subm":
|
if OPTS.tech_name=="scn3me_subm":
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,12 @@ class expand_region_test(openram_test):
|
||||||
offset=[0,0])
|
offset=[0,0])
|
||||||
self.connect_inst([])
|
self.connect_inst([])
|
||||||
|
|
||||||
r=router(gds_file)
|
|
||||||
layer_stack =("metal1","via1","metal2")
|
layer_stack =("metal1","via1","metal2")
|
||||||
|
r=router(layer_stack,self,gds_file)
|
||||||
# This should be infeasible because it is blocked without a detour.
|
# This should be infeasible because it is blocked without a detour.
|
||||||
self.assertFalse(r.route(self,layer_stack,src="A",dest="B",detour_scale=1))
|
self.assertFalse(r.route(src="A",dest="B",detour_scale=1))
|
||||||
# This should be feasible because we allow it to detour
|
# This should be feasible because we allow it to detour
|
||||||
self.assertTrue(r.route(self,layer_stack,src="A",dest="B",detour_scale=3))
|
self.assertTrue(r.route(src="A",dest="B",detour_scale=3))
|
||||||
|
|
||||||
r = routing("08_expand_region_test_{0}".format(OPTS.tech_name))
|
r = routing("08_expand_region_test_{0}".format(OPTS.tech_name))
|
||||||
self.local_drc_check(r)
|
self.local_drc_check(r)
|
||||||
|
|
|
||||||
|
|
@ -17,36 +17,26 @@ class no_blockages_test(openram_test):
|
||||||
|
|
||||||
def runTest(self):
|
def runTest(self):
|
||||||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||||
from gds_cell import gds_cell
|
|
||||||
from design import design
|
|
||||||
from supply_router import supply_router as router
|
from supply_router import supply_router as router
|
||||||
|
|
||||||
class routing(design, openram_test):
|
if False:
|
||||||
"""
|
from control_logic import control_logic
|
||||||
A generic GDS design that we can route on.
|
cell = control_logic(16)
|
||||||
"""
|
else:
|
||||||
def __init__(self, name):
|
from sram import sram
|
||||||
design.__init__(self, "top")
|
from sram_config import sram_config
|
||||||
|
c = sram_config(word_size=4,
|
||||||
|
num_words=32,
|
||||||
|
num_banks=1)
|
||||||
|
|
||||||
|
c.words_per_row=1
|
||||||
|
sram = sram(c, "sram1")
|
||||||
|
cell = sram.s
|
||||||
|
|
||||||
# Instantiate a GDS cell with the design
|
layer_stack =("metal3","via3","metal4")
|
||||||
globals.setup_paths()
|
rtr=router(layer_stack, cell)
|
||||||
from control_logic import control_logic
|
self.assertTrue(rtr.route())
|
||||||
cell = control_logic(16)
|
self.local_check(cell,True)
|
||||||
#from pinv import pinv
|
|
||||||
#cell = pinv()
|
|
||||||
#gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),"control_logic")
|
|
||||||
#cell = gds_cell(name, gds_file)
|
|
||||||
self.add_inst(name=name,
|
|
||||||
mod=cell,
|
|
||||||
offset=[0,0])
|
|
||||||
self.connect_inst(cell.pin_map.keys())
|
|
||||||
|
|
||||||
r=router(module=cell)
|
|
||||||
layer_stack =("metal3","via2","metal2")
|
|
||||||
self.assertTrue(r.route(self,layer_stack))
|
|
||||||
|
|
||||||
r=routing("10_supply_grid_test_{0}".format(OPTS.tech_name))
|
|
||||||
self.local_drc_check(r)
|
|
||||||
|
|
||||||
# fails if there are any DRC errors on any cells
|
# fails if there are any DRC errors on any cells
|
||||||
globals.end_openram()
|
globals.end_openram()
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
||||||
word_size = 1
|
word_size = 1
|
||||||
num_words = 16
|
num_words = 16
|
||||||
|
|
||||||
tech_name = "scn3me_subm"
|
tech_name = "scn4m_subm"
|
||||||
process_corners = ["TT"]
|
process_corners = ["TT"]
|
||||||
supply_voltages = [5.0]
|
supply_voltages = [5.0]
|
||||||
temperatures = [25]
|
temperatures = [25]
|
||||||
|
|
@ -4,7 +4,6 @@ import re
|
||||||
import unittest
|
import unittest
|
||||||
import sys,os
|
import sys,os
|
||||||
sys.path.append(os.path.join(sys.path[0],"../../compiler"))
|
sys.path.append(os.path.join(sys.path[0],"../../compiler"))
|
||||||
print(sys.path)
|
|
||||||
import globals
|
import globals
|
||||||
|
|
||||||
(OPTS, args) = globals.parse_args()
|
(OPTS, args) = globals.parse_args()
|
||||||
|
|
|
||||||
|
|
@ -15,20 +15,20 @@ class vector3d():
|
||||||
self.x = x[0]
|
self.x = x[0]
|
||||||
self.y = x[1]
|
self.y = x[1]
|
||||||
self.z = x[2]
|
self.z = x[2]
|
||||||
#will take two inputs as the values of a coordinate
|
#will take inputs as the values of a coordinate
|
||||||
else:
|
else:
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
self.z = z
|
self.z = z
|
||||||
self.tpl=(x,y,z)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
return "vector3d:["+str(self.x)+", "+str(self.y)+", "+str(self.z)+"]"
|
return "v3d["+str(self.x)+", "+str(self.y)+", "+str(self.z)+"]"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
return "["+str(self.x)+", "+str(self.y)+", "+str(self.z)+"]"
|
return "v3d["+str(self.x)+", "+str(self.y)+", "+str(self.z)+"]"
|
||||||
|
|
||||||
def __setitem__(self, index, value):
|
def __setitem__(self, index, value):
|
||||||
"""
|
"""
|
||||||
|
|
@ -89,7 +89,7 @@ class vector3d():
|
||||||
Note: This assumes that you DON'T CHANGE THE VECTOR or it will
|
Note: This assumes that you DON'T CHANGE THE VECTOR or it will
|
||||||
break things.
|
break things.
|
||||||
"""
|
"""
|
||||||
return hash(self.tpl)
|
return hash((self.x,self.y,self.z))
|
||||||
|
|
||||||
|
|
||||||
def __rsub__(self, other):
|
def __rsub__(self, other):
|
||||||
|
|
@ -118,12 +118,39 @@ class vector3d():
|
||||||
x_factor=x_factor[0]
|
x_factor=x_factor[0]
|
||||||
return vector3d(self.y*x_factor,self.x*y_factor,self.z*z_factor)
|
return vector3d(self.y*x_factor,self.x*y_factor,self.z*z_factor)
|
||||||
|
|
||||||
|
def floor(self):
|
||||||
|
"""
|
||||||
|
Override floor function
|
||||||
|
"""
|
||||||
|
return vector3d(int(math.floor(self.x)),int(math.floor(self.y)), self.z)
|
||||||
|
|
||||||
|
def ceil(self):
|
||||||
|
"""
|
||||||
|
Override ceil function
|
||||||
|
"""
|
||||||
|
return vector3d(int(math.ceil(self.x)),int(math.ceil(self.y)), self.z)
|
||||||
|
|
||||||
|
def round(self):
|
||||||
|
"""
|
||||||
|
Override round function
|
||||||
|
"""
|
||||||
|
return vector3d(int(round(self.x)),int(round(self.y)), self.z)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
"""Override the default Equals behavior"""
|
"""Override the default Equals behavior"""
|
||||||
if isinstance(other, self.__class__):
|
if isinstance(other, self.__class__):
|
||||||
return self.__dict__ == other.__dict__
|
return self.x==other.x and self.y==other.y and self.z==other.z
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
"""Override the default less than behavior"""
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
if self.x<other.x:
|
||||||
|
return True
|
||||||
|
if self.x==other.x and self.y<other.y:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
"""Override the default non-equality behavior"""
|
"""Override the default non-equality behavior"""
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class sram():
|
||||||
def verilog_write(self,name):
|
def verilog_write(self,name):
|
||||||
self.s.verilog_write(name)
|
self.s.verilog_write(name)
|
||||||
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
""" Save all the output files while reporting time to do it as well. """
|
""" Save all the output files while reporting time to do it as well. """
|
||||||
|
|
||||||
|
|
@ -108,6 +108,14 @@ class sram():
|
||||||
print("Trimming netlist to speed up characterization.")
|
print("Trimming netlist to speed up characterization.")
|
||||||
lib(out_dir=OPTS.output_path, sram=self.s, sp_file=sp_file)
|
lib(out_dir=OPTS.output_path, sram=self.s, sp_file=sp_file)
|
||||||
print_time("Characterization", datetime.datetime.now(), start_time)
|
print_time("Characterization", datetime.datetime.now(), start_time)
|
||||||
|
|
||||||
|
# Write the datasheet
|
||||||
|
start_time = datetime.datetime.now()
|
||||||
|
from datasheet_gen import datasheet_gen
|
||||||
|
dname = OPTS.output_path + self.s.name + ".html"
|
||||||
|
print("Datasheet: writing to {0}".format(dname))
|
||||||
|
datasheet_gen.datasheet_write(dname)
|
||||||
|
print_time("Datasheet", datetime.datetime.now(), start_time)
|
||||||
|
|
||||||
# Write a verilog model
|
# Write a verilog model
|
||||||
start_time = datetime.datetime.now()
|
start_time = datetime.datetime.now()
|
||||||
|
|
|
||||||
|
|
@ -100,25 +100,23 @@ class sram_1bank(sram_base):
|
||||||
self.copy_layout_pin(self.control_logic_inst[port], signal, signal+"{}".format(port))
|
self.copy_layout_pin(self.control_logic_inst[port], signal, signal+"{}".format(port))
|
||||||
|
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
self.copy_layout_pin(self.bank_inst, "dout{0}[{1}]".format(port,bit), "DOUT{0}[{1}]".format(port,bit))
|
self.copy_layout_pin(self.bank_inst, "dout{0}_{1}".format(port,bit), "DOUT{0}[{1}]".format(port,bit))
|
||||||
|
|
||||||
# Lower address bits
|
# Lower address bits
|
||||||
for bit in range(self.col_addr_size):
|
for bit in range(self.col_addr_size):
|
||||||
self.copy_layout_pin(self.col_addr_dff_inst[port], "din[{}]".format(bit),"ADDR{0}[{1}]".format(port,bit))
|
self.copy_layout_pin(self.col_addr_dff_inst[port], "din_{}".format(bit),"ADDR{0}[{1}]".format(port,bit))
|
||||||
# Upper address bits
|
# Upper address bits
|
||||||
for bit in range(self.row_addr_size):
|
for bit in range(self.row_addr_size):
|
||||||
self.copy_layout_pin(self.row_addr_dff_inst[port], "din[{}]".format(bit),"ADDR{0}[{1}]".format(port,bit+self.col_addr_size))
|
self.copy_layout_pin(self.row_addr_dff_inst[port], "din_{}".format(bit),"ADDR{0}[{1}]".format(port,bit+self.col_addr_size))
|
||||||
|
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
self.copy_layout_pin(self.data_dff_inst[port], "din[{}]".format(bit), "DIN{0}[{1}]".format(port,bit))
|
self.copy_layout_pin(self.data_dff_inst[port], "din_{}".format(bit), "DIN{0}[{1}]".format(port,bit))
|
||||||
|
|
||||||
def route(self):
|
def route(self):
|
||||||
""" Route a single bank SRAM """
|
""" Route a single bank SRAM """
|
||||||
|
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
|
|
||||||
self.route_vdd_gnd()
|
|
||||||
|
|
||||||
self.route_clk()
|
self.route_clk()
|
||||||
|
|
||||||
self.route_control_logic()
|
self.route_control_logic()
|
||||||
|
|
@ -171,103 +169,6 @@ class sram_1bank(sram_base):
|
||||||
# the control logic to the bank
|
# the control logic to the bank
|
||||||
self.add_wire(("metal3","via2","metal2"),[row_addr_clk_pos, mid1_pos, mid2_pos, control_clk_buf_pos])
|
self.add_wire(("metal3","via2","metal2"),[row_addr_clk_pos, mid1_pos, mid2_pos, control_clk_buf_pos])
|
||||||
|
|
||||||
def route_vdd_gnd(self):
|
|
||||||
""" Propagate all vdd/gnd pins up to this level for all modules """
|
|
||||||
|
|
||||||
# These are the instances that every bank has
|
|
||||||
top_instances = [self.bank_inst]
|
|
||||||
for port in range(self.total_write):
|
|
||||||
top_instances.append(self.data_dff_inst[port])
|
|
||||||
for port in range(self.total_ports):
|
|
||||||
top_instances.append(self.row_addr_dff_inst[port])
|
|
||||||
top_instances.append(self.control_logic_inst[port])
|
|
||||||
if self.col_addr_dff:
|
|
||||||
top_instances.append(self.col_addr_dff_inst[port])
|
|
||||||
|
|
||||||
|
|
||||||
for inst in top_instances:
|
|
||||||
self.copy_layout_pin(inst, "vdd")
|
|
||||||
self.copy_layout_pin(inst, "gnd")
|
|
||||||
|
|
||||||
def new_route_vdd_gnd(self):
|
|
||||||
""" Propagate all vdd/gnd pins up to this level for all modules """
|
|
||||||
|
|
||||||
# These are the instances that every bank has
|
|
||||||
top_instances = [self.bank_inst]
|
|
||||||
for port in range(self.total_write):
|
|
||||||
top_instances.append(self.data_dff_inst[port])
|
|
||||||
for port in range(self.total_ports):
|
|
||||||
top_instances.append(self.row_addr_dff_inst[port])
|
|
||||||
top_instances.append(self.control_logic_inst[port])
|
|
||||||
if self.col_addr_dff:
|
|
||||||
top_instances.append(self.col_addr_dff_inst[port])
|
|
||||||
|
|
||||||
|
|
||||||
# for inst in top_instances:
|
|
||||||
# self.copy_layout_pin(inst, "vdd")
|
|
||||||
# self.copy_layout_pin(inst, "gnd")
|
|
||||||
|
|
||||||
blockages=self.get_blockages("metal3", top_level=True)
|
|
||||||
|
|
||||||
# Gather all of the vdd/gnd pins
|
|
||||||
vdd_pins=[]
|
|
||||||
gnd_pins=[]
|
|
||||||
for inst in top_instances:
|
|
||||||
vdd_pins.extend([x for x in inst.get_pins("vdd") if x.layer == "metal3"])
|
|
||||||
gnd_pins.extend([x for x in inst.get_pins("gnd") if x.layer == "metal3"])
|
|
||||||
|
|
||||||
# Create candidate stripes on M3/M4
|
|
||||||
lowest=self.find_lowest_coords()
|
|
||||||
highest=self.find_highest_coords()
|
|
||||||
m3_y_coords = np.arange(lowest[1],highest[1],self.m2_pitch)
|
|
||||||
|
|
||||||
# These are the rails that will be available for vdd/gnd
|
|
||||||
m3_rects = []
|
|
||||||
# These are the "inflated" shapes for DRC checks
|
|
||||||
m3_drc_rects = []
|
|
||||||
for y in m3_y_coords:
|
|
||||||
# This is just what metal will be drawn
|
|
||||||
ll = vector(lowest[0], y - 0.5*self.m3_width)
|
|
||||||
ur = vector(highest[0], y + 0.5*self.m3_width)
|
|
||||||
m3_rects.append([ll, ur])
|
|
||||||
# This is a full m3 pitch for DRC conflict checking
|
|
||||||
ll = vector(lowest[0], y - 0.5*self.m3_pitch )
|
|
||||||
ur = vector(highest[0], y + 0.5*self.m3_pitch)
|
|
||||||
m3_drc_rects.append([ll, ur])
|
|
||||||
|
|
||||||
vdd_rects = []
|
|
||||||
gnd_rects = []
|
|
||||||
|
|
||||||
# Now, figure how if the rails intersect a blockage, vdd, or gnd pin
|
|
||||||
# Divide the rails up alternately
|
|
||||||
# This should be done in less than n^2 using a kd-tree or something
|
|
||||||
# for drc_rect,rect in zip(m3_drc_rects,m3_rects):
|
|
||||||
# for b in blockages:
|
|
||||||
# if rect_overlaps(b,drc_rect):
|
|
||||||
# break
|
|
||||||
# else:
|
|
||||||
# gnd_rects.append(rect)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Create the vdd and gnd rails
|
|
||||||
for rect in m3_rects:
|
|
||||||
(ll,ur) = rect
|
|
||||||
|
|
||||||
for rect in gnd_rects:
|
|
||||||
(ll,ur) = rect
|
|
||||||
self.add_layout_pin(text="gnd",
|
|
||||||
layer="metal3",
|
|
||||||
offset=ll,
|
|
||||||
width=ur.x-ll.x,
|
|
||||||
height=ur.y-ll.y)
|
|
||||||
for rect in vdd_rects:
|
|
||||||
(ll,ur) = rect
|
|
||||||
self.add_layout_pin(text="vdd",
|
|
||||||
layer="metal3",
|
|
||||||
offset=ll,
|
|
||||||
width=ur.x-ll.x,
|
|
||||||
height=ur.y-ll.y)
|
|
||||||
|
|
||||||
def route_control_logic(self):
|
def route_control_logic(self):
|
||||||
""" Route the outputs from the control logic module """
|
""" Route the outputs from the control logic module """
|
||||||
|
|
@ -285,8 +186,8 @@ class sram_1bank(sram_base):
|
||||||
""" Connect the output of the row flops to the bank pins """
|
""" Connect the output of the row flops to the bank pins """
|
||||||
for port in range(self.total_ports):
|
for port in range(self.total_ports):
|
||||||
for bit in range(self.row_addr_size):
|
for bit in range(self.row_addr_size):
|
||||||
flop_name = "dout[{}]".format(bit)
|
flop_name = "dout_{}".format(bit)
|
||||||
bank_name = "addr{0}[{1}]".format(port,bit+self.col_addr_size)
|
bank_name = "addr{0}_{1}".format(port,bit+self.col_addr_size)
|
||||||
flop_pin = self.row_addr_dff_inst[port].get_pin(flop_name)
|
flop_pin = self.row_addr_dff_inst[port].get_pin(flop_name)
|
||||||
bank_pin = self.bank_inst.get_pin(bank_name)
|
bank_pin = self.bank_inst.get_pin(bank_name)
|
||||||
flop_pos = flop_pin.center()
|
flop_pos = flop_pin.center()
|
||||||
|
|
@ -300,18 +201,18 @@ class sram_1bank(sram_base):
|
||||||
def route_col_addr_dff(self):
|
def route_col_addr_dff(self):
|
||||||
""" Connect the output of the row flops to the bank pins """
|
""" Connect the output of the row flops to the bank pins """
|
||||||
for port in range(self.total_ports):
|
for port in range(self.total_ports):
|
||||||
bus_names = ["addr[{}]".format(x) for x in range(self.col_addr_size)]
|
bus_names = ["addr_{}".format(x) for x in range(self.col_addr_size)]
|
||||||
col_addr_bus_offsets = self.create_horizontal_bus(layer="metal1",
|
col_addr_bus_offsets = self.create_horizontal_bus(layer="metal1",
|
||||||
pitch=self.m1_pitch,
|
pitch=self.m1_pitch,
|
||||||
offset=self.col_addr_dff_inst[port].ul() + vector(0, self.m1_pitch),
|
offset=self.col_addr_dff_inst[port].ul() + vector(0, self.m1_pitch),
|
||||||
names=bus_names,
|
names=bus_names,
|
||||||
length=self.col_addr_dff_inst[port].width)
|
length=self.col_addr_dff_inst[port].width)
|
||||||
|
|
||||||
dff_names = ["dout[{}]".format(x) for x in range(self.col_addr_size)]
|
dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)]
|
||||||
data_dff_map = zip(dff_names, bus_names)
|
data_dff_map = zip(dff_names, bus_names)
|
||||||
self.connect_horizontal_bus(data_dff_map, self.col_addr_dff_inst[port], col_addr_bus_offsets)
|
self.connect_horizontal_bus(data_dff_map, self.col_addr_dff_inst[port], col_addr_bus_offsets)
|
||||||
|
|
||||||
bank_names = ["addr{0}[{1}]".format(port,x) for x in range(self.col_addr_size)]
|
bank_names = ["addr{0}_{1}".format(port,x) for x in range(self.col_addr_size)]
|
||||||
data_bank_map = zip(bank_names, bus_names)
|
data_bank_map = zip(bank_names, bus_names)
|
||||||
self.connect_horizontal_bus(data_bank_map, self.bank_inst, col_addr_bus_offsets)
|
self.connect_horizontal_bus(data_bank_map, self.bank_inst, col_addr_bus_offsets)
|
||||||
|
|
||||||
|
|
@ -322,8 +223,8 @@ class sram_1bank(sram_base):
|
||||||
for port in range(self.total_write):
|
for port in range(self.total_write):
|
||||||
offset = self.data_dff_inst[port].ul() + vector(0, self.m1_pitch)
|
offset = self.data_dff_inst[port].ul() + vector(0, self.m1_pitch)
|
||||||
|
|
||||||
dff_names = ["dout[{}]".format(x) for x in range(self.word_size)]
|
dff_names = ["dout_{}".format(x) for x in range(self.word_size)]
|
||||||
bank_names = ["din{0}[{1}]".format(port,x) for x in range(self.word_size)]
|
bank_names = ["din{0}_{1}".format(port,x) for x in range(self.word_size)]
|
||||||
|
|
||||||
route_map = list(zip(bank_names, dff_names))
|
route_map = list(zip(bank_names, dff_names))
|
||||||
dff_pins = {key: self.data_dff_inst[port].get_pin(key) for key in dff_names }
|
dff_pins = {key: self.data_dff_inst[port].get_pin(key) for key in dff_names }
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ class sram_4bank(sram_base):
|
||||||
|
|
||||||
# connect the MSB flops to the address input bus
|
# connect the MSB flops to the address input bus
|
||||||
for i in [0,1]:
|
for i in [0,1]:
|
||||||
msb_pins = self.msb_address_inst.get_pins("din[{}]".format(i))
|
msb_pins = self.msb_address_inst.get_pins("din_{}".format(i))
|
||||||
for msb_pin in msb_pins:
|
for msb_pin in msb_pins:
|
||||||
if msb_pin.layer == "metal3":
|
if msb_pin.layer == "metal3":
|
||||||
msb_pin_pos = msb_pin.lc()
|
msb_pin_pos = msb_pin.lc()
|
||||||
|
|
@ -141,7 +141,7 @@ class sram_4bank(sram_base):
|
||||||
|
|
||||||
# Connect bank decoder outputs to the bank select vertical bus wires
|
# Connect bank decoder outputs to the bank select vertical bus wires
|
||||||
for i in range(self.num_banks):
|
for i in range(self.num_banks):
|
||||||
msb_pin = self.msb_decoder_inst.get_pin("out[{}]".format(i))
|
msb_pin = self.msb_decoder_inst.get_pin("out_{}".format(i))
|
||||||
msb_pin_pos = msb_pin.lc()
|
msb_pin_pos = msb_pin.lc()
|
||||||
rail_pos = vector(self.vert_control_bus_positions["bank_sel[{}]".format(i)].x,msb_pin_pos.y)
|
rail_pos = vector(self.vert_control_bus_positions["bank_sel[{}]".format(i)].x,msb_pin_pos.y)
|
||||||
self.add_path("metal1",[msb_pin_pos,rail_pos])
|
self.add_path("metal1",[msb_pin_pos,rail_pos])
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from vector import vector
|
||||||
from globals import OPTS, print_time
|
from globals import OPTS, print_time
|
||||||
|
|
||||||
from design import design
|
from design import design
|
||||||
|
|
||||||
class sram_base(design):
|
class sram_base(design):
|
||||||
"""
|
"""
|
||||||
Dynamically generated SRAM by connecting banks to control logic. The
|
Dynamically generated SRAM by connecting banks to control logic. The
|
||||||
|
|
@ -77,17 +77,37 @@ class sram_base(design):
|
||||||
""" Layout creation """
|
""" Layout creation """
|
||||||
self.place_modules()
|
self.place_modules()
|
||||||
self.route()
|
self.route()
|
||||||
|
|
||||||
self.add_lvs_correspondence_points()
|
self.add_lvs_correspondence_points()
|
||||||
|
|
||||||
self.offset_all_coordinates()
|
self.offset_all_coordinates()
|
||||||
|
|
||||||
|
# Must be done after offsetting lower-left
|
||||||
|
self.route_supplies()
|
||||||
|
|
||||||
highest_coord = self.find_highest_coords()
|
highest_coord = self.find_highest_coords()
|
||||||
self.width = highest_coord[0]
|
self.width = highest_coord[0]
|
||||||
self.height = highest_coord[1]
|
self.height = highest_coord[1]
|
||||||
|
|
||||||
|
|
||||||
self.DRC_LVS(final_verification=True)
|
self.DRC_LVS(final_verification=True)
|
||||||
|
|
||||||
|
|
||||||
|
def route_supplies(self):
|
||||||
|
""" Route the supply grid and connect the pins to them. """
|
||||||
|
|
||||||
|
for inst in self.insts:
|
||||||
|
self.copy_power_pins(inst,"vdd")
|
||||||
|
self.copy_power_pins(inst,"gnd")
|
||||||
|
|
||||||
|
from supply_router import supply_router as router
|
||||||
|
layer_stack =("metal3","via3","metal4")
|
||||||
|
rtr=router(layer_stack, self)
|
||||||
|
rtr.route()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def compute_bus_sizes(self):
|
def compute_bus_sizes(self):
|
||||||
""" Compute the independent bus widths shared between two and four bank SRAMs """
|
""" Compute the independent bus widths shared between two and four bank SRAMs """
|
||||||
|
|
||||||
|
|
@ -138,7 +158,7 @@ class sram_base(design):
|
||||||
length=self.addr_bus_height))
|
length=self.addr_bus_height))
|
||||||
|
|
||||||
|
|
||||||
self.bank_sel_bus_names = ["bank_sel{0}[{1}]".format(port,i) for i in range(self.num_banks)]
|
self.bank_sel_bus_names = ["bank_sel{0}_{1}".format(port,i) for i in range(self.num_banks)]
|
||||||
self.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="metal2",
|
self.vert_control_bus_positions.update(self.create_vertical_pin_bus(layer="metal2",
|
||||||
pitch=self.m2_pitch,
|
pitch=self.m2_pitch,
|
||||||
offset=self.bank_sel_bus_offset,
|
offset=self.bank_sel_bus_offset,
|
||||||
|
|
@ -176,34 +196,6 @@ class sram_base(design):
|
||||||
length=self.control_bus_width))
|
length=self.control_bus_width))
|
||||||
|
|
||||||
|
|
||||||
def route_vdd_gnd(self):
|
|
||||||
""" Propagate all vdd/gnd pins up to this level for all modules """
|
|
||||||
|
|
||||||
# These are the instances that every bank has
|
|
||||||
top_instances = [self.bitcell_array_inst,
|
|
||||||
self.precharge_array_inst,
|
|
||||||
self.sense_amp_array_inst,
|
|
||||||
self.write_driver_array_inst,
|
|
||||||
self.tri_gate_array_inst,
|
|
||||||
self.row_decoder_inst,
|
|
||||||
self.wordline_driver_inst]
|
|
||||||
# Add these if we use the part...
|
|
||||||
if self.col_addr_size > 0:
|
|
||||||
top_instances.append(self.col_decoder_inst)
|
|
||||||
top_instances.append(self.col_mux_array_inst)
|
|
||||||
|
|
||||||
if self.num_banks > 1:
|
|
||||||
top_instances.append(self.bank_select_inst)
|
|
||||||
|
|
||||||
|
|
||||||
for inst in top_instances:
|
|
||||||
# Column mux has no vdd
|
|
||||||
if self.col_addr_size==0 or (self.col_addr_size>0 and inst != self.col_mux_array_inst):
|
|
||||||
self.copy_layout_pin(inst, "vdd")
|
|
||||||
# Precharge has no gnd
|
|
||||||
if inst != self.precharge_array_inst:
|
|
||||||
self.copy_layout_pin(inst, "gnd")
|
|
||||||
|
|
||||||
|
|
||||||
def add_multi_bank_modules(self):
|
def add_multi_bank_modules(self):
|
||||||
""" Create the multibank address flops and bank decoder """
|
""" Create the multibank address flops and bank decoder """
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,12 @@ class code_format_test(openram_test):
|
||||||
continue
|
continue
|
||||||
if re.search("testutils.py$", code):
|
if re.search("testutils.py$", code):
|
||||||
continue
|
continue
|
||||||
if re.search("grid.py$", code):
|
|
||||||
continue
|
|
||||||
if re.search("globals.py$", code):
|
if re.search("globals.py$", code):
|
||||||
continue
|
continue
|
||||||
if re.search("openram.py$", code):
|
if re.search("openram.py$", code):
|
||||||
continue
|
continue
|
||||||
|
if re.search("sram.py$", code):
|
||||||
|
continue
|
||||||
if re.search("gen_stimulus.py$", code):
|
if re.search("gen_stimulus.py$", code):
|
||||||
continue
|
continue
|
||||||
errors += check_print_output(code)
|
errors += check_print_output(code)
|
||||||
|
|
@ -52,7 +52,7 @@ def setup_files(path):
|
||||||
for f in current_files:
|
for f in current_files:
|
||||||
files.append(os.path.join(dir, f))
|
files.append(os.path.join(dir, f))
|
||||||
nametest = re.compile("\.py$", re.IGNORECASE)
|
nametest = re.compile("\.py$", re.IGNORECASE)
|
||||||
select_files = filter(nametest.search, files)
|
select_files = list(filter(nametest.search, files))
|
||||||
return select_files
|
return select_files
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -102,16 +102,17 @@ def check_print_output(file_name):
|
||||||
"""Check if any files (except debug.py) call the _print_ function. We should
|
"""Check if any files (except debug.py) call the _print_ function. We should
|
||||||
use the debug output with verbosity instead!"""
|
use the debug output with verbosity instead!"""
|
||||||
file = open(file_name, "r+b")
|
file = open(file_name, "r+b")
|
||||||
line = file.read()
|
line = file.read().decode('utf-8')
|
||||||
# skip comments with a hash
|
# skip comments with a hash
|
||||||
line = re.sub(r'#.*', '', line)
|
line = re.sub(r'#.*', '', line)
|
||||||
# skip doc string comments
|
# skip doc string comments
|
||||||
line=re.sub(r'\"\"\"[^\"]*\"\"\"', '', line, flags=re.S|re.M)
|
line=re.sub(r'\"\"\"[^\"]*\"\"\"', '', line, flags=re.S|re.M)
|
||||||
count = len(re.findall("\s*print\s+", line))
|
count = len(re.findall("[^p]+print\(", line))
|
||||||
if count > 0:
|
if count > 0:
|
||||||
debug.info(0, "\nFound " + str(count) +
|
debug.info(0, "\nFound " + str(count) +
|
||||||
" _print_ calls " + str(file_name))
|
" _print_ calls " + str(file_name))
|
||||||
|
|
||||||
|
file.close()
|
||||||
return(count)
|
return(count)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue