mirror of https://github.com/VLSIDA/OpenRAM.git
Merge branch 'dev' into pdriver
This commit is contained in:
commit
b366d88041
|
|
@ -1,6 +1,6 @@
|
||||||
freepdk45:
|
freepdk45:
|
||||||
script: "/home/gitlab-runner/regress_freepdk45.sh"
|
script: "/home/gitlab-runner/regress_freepdk45.sh"
|
||||||
|
|
||||||
scn3me_subm:
|
scn4m_subm:
|
||||||
script: "/home/gitlab-runner/regress_scn3me_subm.sh"
|
script: "/home/gitlab-runner/regress_scn4m_subm.sh"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,26 +20,33 @@ other OpenRAM features. Please see the README.md file on how to run
|
||||||
the unit tests. Unit tests should work in all technologies. We will run
|
the unit tests. Unit tests should work in all technologies. We will run
|
||||||
the tests on your contributions before they will be accepted.
|
the tests on your contributions before they will be accepted.
|
||||||
|
|
||||||
|
# Internal Development
|
||||||
|
|
||||||
|
For internal development, follow all of the following steps EXCEPT
|
||||||
|
do not fork your own copy. Instead, create a branch in our private repository
|
||||||
|
and consult with me when you want to merge it into the dev branch.
|
||||||
|
All unit tests should pass first.
|
||||||
|
|
||||||
# Pull Request Process
|
# Pull Request Process
|
||||||
|
|
||||||
1. One time, create a GitHub account at http://github.com
|
1. One time, create a GitHub account at http://github.com
|
||||||
|
|
||||||
2. Create a fork of the OpenRAM project on the github web page:
|
2. Create a fork of the OpenRAM project on the github web page:
|
||||||
https://github.com/mguthaus/OpenRAM
|
https://github.com/vlsida/openram
|
||||||
It is on the upper right and says "Fork": This will make your own
|
It is on the upper right and says "Fork": This will make your own
|
||||||
OpenRAM repository on GitHub in your account.
|
OpenRAM repository on GitHub in your account.
|
||||||
|
|
||||||
3. Clone your repository (or use an existing cloned copy if you've
|
3. Clone your repository (or use an existing cloned copy if you've
|
||||||
already done this once):
|
already done this once):
|
||||||
```
|
```
|
||||||
git clone https://github.com/<youruser>/OpenRAM.git
|
git clone https://github.com/<youruser>/oepnram.git
|
||||||
cd OpenRAM
|
cd openram
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Set up a new upstream that points to MY OpenRAM repository that you
|
4. Set up a new upstream that points to MY OpenRAM repository that you
|
||||||
forked (only first time):
|
forked (only first time):
|
||||||
```
|
```
|
||||||
git remote add upstream https://github.com/mguthaus/OpenRAM.git
|
git remote add upstream https://github.com/vlsida/openram.git
|
||||||
```
|
```
|
||||||
You now have two remotes for this project:
|
You now have two remotes for this project:
|
||||||
* origin which points to your GitHub fork of the project. You can read
|
* origin which points to your GitHub fork of the project. You can read
|
||||||
|
|
|
||||||
37
README.md
37
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,17 +92,9 @@ To increase the verbosity of the test, add one (or more) -v options:
|
||||||
python tests/00_code_format_check_test.py -v -t freepdk45
|
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.
|
||||||
|
|
||||||
A regression daemon script that can be used with cron is included in
|
|
||||||
a separate repository at https://github.com/mguthaus/openram-daemons
|
|
||||||
```
|
|
||||||
regress_daemon.py
|
|
||||||
regress_daemon.sh
|
|
||||||
```
|
|
||||||
This updates a git repository, checks out code, and sends an email
|
|
||||||
report with status information.
|
|
||||||
|
|
||||||
# CREATING CUSTOM TECHNOLOGIES
|
# CREATING CUSTOM TECHNOLOGIES
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class contact(hierarchy_design.hierarchy_design):
|
||||||
necessary to import layouts into Magic which requires the select to be in the same GDS
|
necessary to import layouts into Magic which requires the select to be in the same GDS
|
||||||
hierarchy as the contact.
|
hierarchy as the contact.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, layer_stack, dimensions=[1,1], implant_type=None, well_type=None):
|
def __init__(self, layer_stack, dimensions=[1,1], implant_type=None, well_type=None):
|
||||||
if implant_type or well_type:
|
if implant_type or well_type:
|
||||||
name = "{0}_{1}_{2}_{3}x{4}_{5}{6}".format(layer_stack[0],
|
name = "{0}_{1}_{2}_{3}x{4}_{5}{6}".format(layer_stack[0],
|
||||||
|
|
@ -24,13 +25,14 @@ class contact(hierarchy_design.hierarchy_design):
|
||||||
dimensions[1],
|
dimensions[1],
|
||||||
implant_type,
|
implant_type,
|
||||||
well_type)
|
well_type)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
name = "{0}_{1}_{2}_{3}x{4}".format(layer_stack[0],
|
name = "{0}_{1}_{2}_{3}x{4}".format(layer_stack[0],
|
||||||
layer_stack[1],
|
layer_stack[1],
|
||||||
layer_stack[2],
|
layer_stack[2],
|
||||||
dimensions[0],
|
dimensions[0],
|
||||||
dimensions[1])
|
dimensions[1])
|
||||||
|
|
||||||
hierarchy_design.hierarchy_design.__init__(self, name)
|
hierarchy_design.hierarchy_design.__init__(self, name)
|
||||||
debug.info(4, "create contact object {0}".format(name))
|
debug.info(4, "create contact object {0}".format(name))
|
||||||
|
|
||||||
|
|
@ -71,21 +73,21 @@ class contact(hierarchy_design.hierarchy_design):
|
||||||
self.second_layer_name = second_layer
|
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)
|
||||||
|
|
@ -143,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,
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ class design(hierarchy_design):
|
||||||
hierarchy_design.__init__(self,name)
|
hierarchy_design.__init__(self,name)
|
||||||
|
|
||||||
self.setup_drc_constants()
|
self.setup_drc_constants()
|
||||||
|
self.setup_multiport_constants()
|
||||||
|
|
||||||
self.m1_pitch = max(contact.m1m2.width,contact.m1m2.height) + max(self.m1_space, self.m2_space)
|
self.m1_pitch = max(contact.m1m2.width,contact.m1m2.height) + max(self.m1_space, self.m2_space)
|
||||||
self.m2_pitch = max(contact.m2m3.width,contact.m2m3.height) + max(self.m2_space, self.m3_space)
|
self.m2_pitch = max(contact.m2m3.width,contact.m2m3.height) + max(self.m2_space, self.m3_space)
|
||||||
|
|
@ -28,24 +29,59 @@ class design(hierarchy_design):
|
||||||
def setup_drc_constants(self):
|
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.active_space = drc("active_to_body_active")
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
These are contants and lists that aid multiport design.
|
||||||
|
Ports are always in the order RW, W, R.
|
||||||
|
Port indices start from 0 and increment.
|
||||||
|
A first RW port will have clk0, csb0, web0, addr0, data0
|
||||||
|
A first W port (with no RW ports) will be: clk0, csb0, addr0, data0
|
||||||
|
|
||||||
|
"""
|
||||||
|
total_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
|
||||||
|
|
||||||
|
# These are the read/write port indices.
|
||||||
|
self.readwrite_ports = []
|
||||||
|
# These are the read/write and write-only port indices
|
||||||
|
self.write_ports = []
|
||||||
|
# These are teh read/write and read-only port indice
|
||||||
|
self.read_ports = []
|
||||||
|
# These are all the ports
|
||||||
|
self.all_ports = list(range(total_ports))
|
||||||
|
|
||||||
|
port_number = 0
|
||||||
|
for port in range(OPTS.num_rw_ports):
|
||||||
|
self.readwrite_ports.append(port_number)
|
||||||
|
self.write_ports.append(port_number)
|
||||||
|
self.read_ports.append(port_number)
|
||||||
|
port_number += 1
|
||||||
|
for port in range(OPTS.num_w_ports):
|
||||||
|
self.write_ports.append(port_number)
|
||||||
|
port_number += 1
|
||||||
|
for port in range(OPTS.num_r_ports):
|
||||||
|
self.read_ports.append(port_number)
|
||||||
|
port_number += 1
|
||||||
|
|
||||||
def analytical_power(self, proc, vdd, temp, load):
|
def analytical_power(self, proc, vdd, temp, load):
|
||||||
""" Get total power of a module """
|
""" Get total power of a module """
|
||||||
|
|
@ -53,3 +89,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,14 +47,21 @@ 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):
|
||||||
|
""" Update the boundary with a new placement. """
|
||||||
|
self.compute_boundary(self.offset,self.mirror,self.rotate)
|
||||||
|
|
||||||
def compute_boundary(self,offset=vector(0,0),mirror="",rotate=0):
|
def compute_boundary(self,offset=vector(0,0),mirror="",rotate=0):
|
||||||
""" Transform with offset, mirror and rotation to get the absolute pin location.
|
""" Transform with offset, mirror and rotation to get the absolute pin location.
|
||||||
We must then re-find the ll and ur. The master is the cell instance. """
|
We must then re-find the ll and ur. The master is the cell instance. """
|
||||||
|
if OPTS.netlist_only:
|
||||||
|
return
|
||||||
(ll,ur) = [vector(0,0),vector(self.width,self.height)]
|
(ll,ur) = [vector(0,0),vector(self.width,self.height)]
|
||||||
|
|
||||||
if mirror=="MX":
|
if mirror=="MX":
|
||||||
ll=ll.scale(1,-1)
|
ll=ll.scale(1,-1)
|
||||||
ur=ur.scale(1,-1)
|
ur=ur.scale(1,-1)
|
||||||
|
|
@ -124,7 +132,7 @@ class instance(geometry):
|
||||||
An instance of an instance/module with a specified location and
|
An instance of an instance/module with a specified location and
|
||||||
rotation
|
rotation
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, mod, offset, mirror, rotate):
|
def __init__(self, name, mod, offset=[0,0], mirror="R0", rotate=0):
|
||||||
"""Initializes an instance to represent a module"""
|
"""Initializes an instance to represent a module"""
|
||||||
geometry.__init__(self)
|
geometry.__init__(self)
|
||||||
debug.check(mirror not in ["R90","R180","R270"], "Please use rotation and not mirroring during instantiation.")
|
debug.check(mirror not in ["R90","R180","R270"], "Please use rotation and not mirroring during instantiation.")
|
||||||
|
|
@ -135,8 +143,16 @@ class instance(geometry):
|
||||||
self.rotate = rotate
|
self.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
|
if OPTS.netlist_only:
|
||||||
self.height = mod.height
|
self.width = 0
|
||||||
|
self.height = 0
|
||||||
|
else:
|
||||||
|
if mirror in ["R90","R270"] or rotate in [90,270]:
|
||||||
|
self.width = round_to_grid(mod.height)
|
||||||
|
self.height = round_to_grid(mod.width)
|
||||||
|
else:
|
||||||
|
self.width = round_to_grid(mod.width)
|
||||||
|
self.height = round_to_grid(mod.height)
|
||||||
self.compute_boundary(offset,mirror,rotate)
|
self.compute_boundary(offset,mirror,rotate)
|
||||||
|
|
||||||
debug.info(4, "creating instance: " + self.name)
|
debug.info(4, "creating instance: " + self.name)
|
||||||
|
|
@ -184,10 +200,18 @@ 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):
|
||||||
|
""" This updates the placement of an instance. """
|
||||||
|
debug.info(3, "placing instance {}".format(self.name))
|
||||||
|
# Update the placement of an already added instance
|
||||||
|
self.offset = vector(offset).snap_to_grid()
|
||||||
|
self.mirror = mirror
|
||||||
|
self.rotate = rotate
|
||||||
|
self.update_boundary()
|
||||||
|
|
||||||
|
|
||||||
def get_pin(self,name,index=-1):
|
def get_pin(self,name,index=-1):
|
||||||
|
|
@ -223,7 +247,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 """
|
||||||
|
|
@ -245,13 +269,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. """
|
||||||
|
|
@ -286,15 +310,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. """
|
||||||
|
|
@ -306,7 +330,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"""
|
||||||
|
|
@ -318,8 +342,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) + "): "
|
||||||
|
|
@ -333,16 +357,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 """
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,14 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.gds_file = OPTS.openram_tech + "gds_lib/" + name + ".gds"
|
try:
|
||||||
self.sp_file = OPTS.openram_tech + "sp_lib/" + name + ".sp"
|
self.gds_file
|
||||||
|
except AttributeError:
|
||||||
|
self.gds_file = OPTS.openram_tech + "gds_lib/" + name + ".gds"
|
||||||
|
try:
|
||||||
|
self.sp_file
|
||||||
|
except AttributeError:
|
||||||
|
self.sp_file = OPTS.openram_tech + "sp_lib/" + name + ".sp"
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
hierarchy_layout.layout.__init__(self, name)
|
hierarchy_layout.layout.__init__(self, name)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import debug
|
||||||
from tech import drc, GDS
|
from tech import drc, GDS
|
||||||
from tech import layer as techlayer
|
from tech import layer as techlayer
|
||||||
import os
|
import os
|
||||||
|
from globals import OPTS
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from pin_layout import pin_layout
|
from pin_layout import pin_layout
|
||||||
import lef
|
import lef
|
||||||
|
|
@ -27,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()
|
||||||
|
|
||||||
|
|
@ -118,12 +119,12 @@ class layout(lef.lef):
|
||||||
for pin in pin_list:
|
for pin in pin_list:
|
||||||
pin.rect = [pin.ll() - offset, pin.ur() - offset]
|
pin.rect = [pin.ll() - offset, pin.ur() - offset]
|
||||||
|
|
||||||
|
|
||||||
def add_inst(self, name, mod, offset=[0,0], mirror="R0",rotate=0):
|
def add_inst(self, name, mod, offset=[0,0], mirror="R0",rotate=0):
|
||||||
"""Adds an instance of a mod to this module"""
|
"""Adds an instance of a mod to this module"""
|
||||||
self.insts.append(geometry.instance(name, mod, offset, mirror, rotate))
|
self.insts.append(geometry.instance(name, mod, offset, mirror, rotate))
|
||||||
debug.info(3, "adding instance {}".format(self.insts[-1]))
|
debug.info(3, "adding instance {}".format(self.insts[-1]))
|
||||||
debug.info(4, "instance list: " + ",".join(x.name for x in self.insts))
|
# This is commented out for runtime reasons
|
||||||
|
#debug.info(4, "instance list: " + ",".join(x.name for x in self.insts))
|
||||||
return self.insts[-1]
|
return self.insts[-1]
|
||||||
|
|
||||||
def get_inst(self, name):
|
def get_inst(self, name):
|
||||||
|
|
@ -133,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]
|
||||||
|
|
@ -146,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]
|
||||||
|
|
@ -162,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)
|
||||||
|
|
@ -176,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)
|
||||||
|
|
@ -191,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=""):
|
||||||
"""
|
"""
|
||||||
|
|
@ -206,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.")
|
||||||
|
|
||||||
|
|
@ -231,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)
|
||||||
|
|
@ -242,16 +258,20 @@ 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)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -269,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,
|
||||||
|
|
@ -312,7 +333,7 @@ class layout(lef.lef):
|
||||||
position_list=coordinates,
|
position_list=coordinates,
|
||||||
width=width)
|
width=width)
|
||||||
|
|
||||||
def add_route(self, design, layers, coordinates):
|
def add_route(self, layers, coordinates, layer_widths):
|
||||||
"""Connects a routing path on given layer,coordinates,width. The
|
"""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
|
||||||
|
|
@ -323,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):
|
||||||
|
|
@ -379,24 +401,23 @@ class layout(lef.lef):
|
||||||
dimensions=size,
|
dimensions=size,
|
||||||
implant_type=implant_type,
|
implant_type=implant_type,
|
||||||
well_type=well_type)
|
well_type=well_type)
|
||||||
|
|
||||||
debug.check(mirror=="R0","Use rotate to rotate vias instead of mirror.")
|
|
||||||
|
|
||||||
height = via.height
|
height = via.height
|
||||||
width = via.width
|
width = via.width
|
||||||
|
debug.check(mirror=="R0","Use rotate to rotate vias instead of mirror.")
|
||||||
|
|
||||||
if rotate==0:
|
if rotate==0:
|
||||||
corrected_offset = offset + vector(-0.5*width,-0.5*height)
|
corrected_offset = offset + vector(-0.5*width,-0.5*height)
|
||||||
elif rotate==90:
|
elif rotate==90:
|
||||||
corrected_offset = offset + vector(0.5*height,-0.5*width)
|
corrected_offset = offset + vector(0.5*height,-0.5*width)
|
||||||
elif rotate==180:
|
elif rotate==180:
|
||||||
corrected_offset = offset + vector(-0.5*width,0.5*height)
|
corrected_offset = offset + vector(0.5*width,0.5*height)
|
||||||
elif rotate==270:
|
elif rotate==270:
|
||||||
corrected_offset = offset + vector(-0.5*height,0.5*width)
|
corrected_offset = offset + vector(-0.5*height,0.5*width)
|
||||||
else:
|
else:
|
||||||
debug.error("Invalid rotation argument.",-1)
|
debug.error("Invalid rotation argument.",-1)
|
||||||
|
|
||||||
|
|
||||||
|
#print(rotate,offset,"->",corrected_offset)
|
||||||
self.add_mod(via)
|
self.add_mod(via)
|
||||||
inst=self.add_inst(name=via.name,
|
inst=self.add_inst(name=via.name,
|
||||||
mod=via,
|
mod=via,
|
||||||
|
|
@ -426,61 +447,72 @@ class layout(lef.lef):
|
||||||
def gds_read(self):
|
def gds_read(self):
|
||||||
"""Reads a GDSII file in the library and checks if it exists
|
"""Reads a GDSII file in the library and checks if it exists
|
||||||
Otherwise, start a new layout for dynamic generation."""
|
Otherwise, start a new layout for dynamic generation."""
|
||||||
|
if OPTS.netlist_only:
|
||||||
|
self.gds = None
|
||||||
|
return
|
||||||
|
|
||||||
# open the gds file if it exists or else create a blank layout
|
# 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 """
|
||||||
|
|
@ -599,6 +631,7 @@ class layout(lef.lef):
|
||||||
"""
|
"""
|
||||||
Connect a mapping of pin -> name for a bus. This could be
|
Connect a mapping of pin -> name for a bus. This could be
|
||||||
replaced with a channel router in the future.
|
replaced with a channel router in the future.
|
||||||
|
NOTE: This has only really been tested with point-to-point connections (not multiple pins on a net).
|
||||||
"""
|
"""
|
||||||
(horizontal_layer, via_layer, vertical_layer)=layer_stack
|
(horizontal_layer, via_layer, vertical_layer)=layer_stack
|
||||||
if horizontal:
|
if horizontal:
|
||||||
|
|
@ -708,31 +741,61 @@ class layout(lef.lef):
|
||||||
self.add_wire(layer_stack, [pin.center(), mid, trunk_mid])
|
self.add_wire(layer_stack, [pin.center(), mid, trunk_mid])
|
||||||
|
|
||||||
|
|
||||||
def create_channel_route(self, route_map, top_pins, bottom_pins, offset,
|
def create_channel_route(self, netlist, pins, offset,
|
||||||
layer_stack=("metal1", "via1", "metal2"), pitch=None,
|
layer_stack=("metal1", "via1", "metal2"), pitch=None,
|
||||||
vertical=False):
|
vertical=False):
|
||||||
"""
|
"""
|
||||||
This is a simple channel route for one-to-one connections that
|
The net list is a list of the nets. Each net is a list of pin
|
||||||
will jog the top route whenever there is a conflict. It does NOT
|
names to be connected. Pins is a dictionary of the pin names
|
||||||
try to minimize the number of tracks -- instead, it picks an order to avoid the vertical
|
to the pin structures. Offset is the lower-left of where the
|
||||||
conflicts between pins.
|
routing channel will start. This does NOT try to minimize the
|
||||||
"""
|
number of tracks -- instead, it picks an order to avoid the
|
||||||
|
vertical conflicts between pins.
|
||||||
|
|
||||||
def remove_pin_from_graph(pin, g):
|
"""
|
||||||
# Remove the pin from the keys
|
def remove_net_from_graph(pin, g):
|
||||||
|
"""
|
||||||
|
Remove the pin from the graph and all conflicts
|
||||||
|
"""
|
||||||
g.pop(pin,None)
|
g.pop(pin,None)
|
||||||
|
|
||||||
# Remove the pin from all conflicts
|
# Remove the pin from all conflicts
|
||||||
# This is O(n^2), so maybe optimize it.
|
# FIXME: This is O(n^2), so maybe optimize it.
|
||||||
for other_pin,conflicts in g.items():
|
for other_pin,conflicts in g.items():
|
||||||
if pin in conflicts:
|
if pin in conflicts:
|
||||||
conflicts.remove(pin)
|
conflicts.remove(pin)
|
||||||
vcg[other_pin]=conflicts
|
g[other_pin]=conflicts
|
||||||
|
return g
|
||||||
|
|
||||||
|
def vcg_nets_overlap(net1, net2, vertical):
|
||||||
|
"""
|
||||||
|
Check all the pin pairs on two nets and return a pin
|
||||||
|
overlap if any pin overlaps
|
||||||
|
"""
|
||||||
|
|
||||||
|
for pin1 in net1:
|
||||||
|
for pin2 in net2:
|
||||||
|
if vcg_pin_overlap(pin1, pin2, vertical):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def vcg_pin_overlap(pin1, pin2, vertical):
|
||||||
|
""" Check for vertical or horizontal overlap of the two pins """
|
||||||
|
|
||||||
|
# Pin 1 must be in the "BOTTOM" set
|
||||||
|
x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x-pin2.center().x)<pitch
|
||||||
|
|
||||||
|
# Pin 1 must be in the "LEFT" set
|
||||||
|
y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y-pin2.center().y)<pitch
|
||||||
|
overlaps = (not vertical and x_overlap) or (vertical and y_overlap)
|
||||||
|
return overlaps
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if not pitch:
|
if not pitch:
|
||||||
pitch = self.m2_pitch
|
pitch = self.m2_pitch
|
||||||
|
|
||||||
# merge the two dictionaries to easily access all pins
|
|
||||||
all_pins = {**top_pins, **bottom_pins}
|
|
||||||
|
|
||||||
# FIXME: Must extend this to a horizontal conflict graph too if we want to minimize the
|
# FIXME: Must extend this to a horizontal conflict graph too if we want to minimize the
|
||||||
# number of tracks!
|
# number of tracks!
|
||||||
|
|
@ -740,53 +803,59 @@ class layout(lef.lef):
|
||||||
|
|
||||||
# Initialize the vertical conflict graph (vcg) and make a list of all pins
|
# Initialize the vertical conflict graph (vcg) and make a list of all pins
|
||||||
vcg = {}
|
vcg = {}
|
||||||
for (top_name, bot_name) in route_map:
|
|
||||||
vcg[top_name] = []
|
# Create names for the nets for the graphs
|
||||||
vcg[bot_name] = []
|
nets = {}
|
||||||
|
index = 0
|
||||||
|
#print(netlist)
|
||||||
|
for pin_list in netlist:
|
||||||
|
net_name = "n{}".format(index)
|
||||||
|
index += 1
|
||||||
|
nets[net_name] = []
|
||||||
|
for pin_name in pin_list:
|
||||||
|
pin = pins[pin_name]
|
||||||
|
nets[net_name].append(pin)
|
||||||
|
|
||||||
# Find the vertical pin conflicts
|
# Find the vertical pin conflicts
|
||||||
# FIXME: O(n^2) but who cares for now
|
# FIXME: O(n^2) but who cares for now
|
||||||
for top_name,top_pin in top_pins.items():
|
for net_name1 in nets:
|
||||||
for bot_name,bot_pin in bottom_pins.items():
|
if net_name1 not in vcg.keys():
|
||||||
if not vertical and abs(top_pin.center().x-bot_pin.center().x) < pitch:
|
vcg[net_name1]=[]
|
||||||
vcg[top_name].append(bot_name)
|
for net_name2 in nets:
|
||||||
vcg[bot_name].append(top_name)
|
if net_name2 not in vcg.keys():
|
||||||
elif vertical and abs(top_pin.center().y-bot_pin.center().y) < pitch:
|
vcg[net_name2]=[]
|
||||||
vcg[top_name].append(bot_name)
|
# Skip yourself
|
||||||
vcg[bot_name].append(top_name)
|
if net_name1 == net_name2:
|
||||||
|
continue
|
||||||
# This is the starting offset of the first trunk
|
if vcg_nets_overlap(nets[net_name1], nets[net_name2], vertical):
|
||||||
if vertical:
|
vcg[net_name2].append(net_name1)
|
||||||
half_minwidth = 0.5*drc["minwidth_{}".format(layer_stack[2])]
|
|
||||||
offset = offset + vector(half_minwidth,0)
|
#FIXME: What if we have a cycle?
|
||||||
else:
|
|
||||||
half_minwidth = 0.5*drc["minwidth_{}".format(layer_stack[0])]
|
|
||||||
offset = offset + vector(0,half_minwidth)
|
|
||||||
|
|
||||||
# list of routes to do
|
# list of routes to do
|
||||||
while vcg:
|
while vcg:
|
||||||
#print(vcg)
|
#from pprint import pformat
|
||||||
|
#print("VCG:\n",pformat(vcg))
|
||||||
# get a route from conflict graph with empty fanout set
|
# get a route from conflict graph with empty fanout set
|
||||||
route_pin=None
|
net_name=None
|
||||||
for route_pin,conflicts in vcg.items():
|
for net_name,conflicts in vcg.items():
|
||||||
if len(conflicts)==0:
|
if len(conflicts)==0:
|
||||||
remove_pin_from_graph(route_pin,vcg)
|
vcg=remove_net_from_graph(net_name,vcg)
|
||||||
break
|
break
|
||||||
|
else:
|
||||||
|
# FIXME: We don't support cyclic VCGs right now.
|
||||||
|
debug.error("Cyclic VCG in channel router.",-1)
|
||||||
|
|
||||||
# Get the connected pins from the routing map
|
|
||||||
for pin_connections in route_map:
|
|
||||||
if route_pin in pin_connections:
|
|
||||||
break
|
|
||||||
#print("Routing:",route_pin,pin_connections)
|
|
||||||
|
|
||||||
# Remove the other pins from the conflict graph too
|
|
||||||
for other_pin in pin_connections:
|
|
||||||
remove_pin_from_graph(other_pin, vcg)
|
|
||||||
|
|
||||||
# Create a list of the pins rather than a list of the names
|
|
||||||
pin_list = [all_pins[pin_name] for pin_name in pin_connections]
|
|
||||||
|
|
||||||
# Add the trunk route and move up to next track
|
# These are the pins we'll have to connect
|
||||||
|
pin_list = nets[net_name]
|
||||||
|
#print("Routing:",net_name,[x.name for x in pin_list])
|
||||||
|
|
||||||
|
# Remove the net from other constriants in the VCG
|
||||||
|
vcg=remove_net_from_graph(net_name, vcg)
|
||||||
|
|
||||||
|
# Add the trunk routes from the bottom up for horizontal or the left to right for vertical
|
||||||
if vertical:
|
if vertical:
|
||||||
self.add_vertical_trunk_route(pin_list, offset, layer_stack, pitch)
|
self.add_vertical_trunk_route(pin_list, offset, layer_stack, pitch)
|
||||||
offset += vector(pitch,0)
|
offset += vector(pitch,0)
|
||||||
|
|
@ -795,22 +864,22 @@ class layout(lef.lef):
|
||||||
offset += vector(0,pitch)
|
offset += vector(0,pitch)
|
||||||
|
|
||||||
|
|
||||||
def create_vertical_channel_route(self, route_map, left_inst, right_inst, offset,
|
def create_vertical_channel_route(self, netlist, pins, offset,
|
||||||
layer_stack=("metal1", "via1", "metal2"),
|
layer_stack=("metal1", "via1", "metal2"),
|
||||||
pitch=None):
|
pitch=None):
|
||||||
"""
|
"""
|
||||||
Wrapper to create a vertical channel route
|
Wrapper to create a vertical channel route
|
||||||
"""
|
"""
|
||||||
self.create_channel_route(route_map, left_inst, right_inst, offset,
|
self.create_channel_route(netlist, pins, offset, layer_stack,
|
||||||
layer_stack, pitch, vertical=True)
|
pitch, vertical=True)
|
||||||
|
|
||||||
def create_horizontal_channel_route(self, route_map, top_pins, bottom_pins, offset,
|
def create_horizontal_channel_route(self, netlist, pins, offset,
|
||||||
layer_stack=("metal1", "via1", "metal2"),
|
layer_stack=("metal1", "via1", "metal2"),
|
||||||
pitch=None):
|
pitch=None):
|
||||||
"""
|
"""
|
||||||
Wrapper to create a horizontal channel route
|
Wrapper to create a horizontal channel route
|
||||||
"""
|
"""
|
||||||
self.create_channel_route(route_map, top_pins, bottom_pins, offset,
|
self.create_channel_route(netlist, pins, offset,
|
||||||
layer_stack, pitch, vertical=False)
|
layer_stack, pitch, vertical=False)
|
||||||
|
|
||||||
def add_enclosure(self, insts, layer="nwell"):
|
def add_enclosure(self, insts, layer="nwell"):
|
||||||
|
|
@ -833,19 +902,37 @@ class layout(lef.lef):
|
||||||
width=xmax-xmin,
|
width=xmax-xmin,
|
||||||
height=ymax-ymin)
|
height=ymax-ymin)
|
||||||
|
|
||||||
def add_power_pin(self, name, loc, rotate=True):
|
|
||||||
|
def copy_power_pins(self, inst, name):
|
||||||
|
"""
|
||||||
|
This will copy a power pin if it is on M3. If it is on M1, it will add a power via too.
|
||||||
|
"""
|
||||||
|
pins=inst.get_pins(name)
|
||||||
|
for pin in pins:
|
||||||
|
if pin.layer=="metal3":
|
||||||
|
self.add_layout_pin(name, pin.layer, pin.ll(), pin.width(), pin.height())
|
||||||
|
elif pin.layer=="metal1":
|
||||||
|
self.add_power_pin(name, pin.center())
|
||||||
|
else:
|
||||||
|
debug.warning("{0} pins of {1} should be on metal3 or metal1 for supply router.".format(name,inst.name))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def add_power_pin(self, name, loc, rotate=90):
|
||||||
"""
|
"""
|
||||||
Add a single power pin from M3 own to M1
|
Add a single power pin from M3 down to M1 at the given center location
|
||||||
"""
|
"""
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||||
offset=loc,
|
offset=loc,
|
||||||
rotate=90 if rotate else 0)
|
rotate=float(rotate))
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
via=self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||||
offset=loc,
|
offset=loc,
|
||||||
rotate=90 if rotate else 0)
|
rotate=float(rotate))
|
||||||
self.add_layout_pin_rect_center(text=name,
|
self.add_layout_pin_rect_center(text=name,
|
||||||
layer="metal3",
|
layer="metal3",
|
||||||
offset=loc)
|
offset=loc,
|
||||||
|
width=via.width,
|
||||||
|
height=via.height)
|
||||||
|
|
||||||
def add_power_ring(self, bbox):
|
def add_power_ring(self, bbox):
|
||||||
"""
|
"""
|
||||||
|
|
@ -964,7 +1051,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,26 +91,33 @@ 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))):
|
||||||
debug.error("Connections: {}".format(self.insts[-1].mod.pins))
|
from pprint import pformat
|
||||||
debug.error("Connections: {}".format(args))
|
modpins_string=pformat(self.insts[-1].mod.pins)
|
||||||
|
argpins_string=pformat(args)
|
||||||
|
debug.error("Connections: {}".format(modpins_string))
|
||||||
|
debug.error("Connections: {}".format(argpins_string))
|
||||||
debug.error("Number of net connections ({0}) does not match last instance ({1})".format(len(self.insts[-1].mod.pins),
|
debug.error("Number of net connections ({0}) does not match last instance ({1})".format(len(self.insts[-1].mod.pins),
|
||||||
len(args)), 1)
|
len(args)), 1)
|
||||||
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)):
|
||||||
|
from pprint import pformat
|
||||||
|
insts_string=pformat(self.insts)
|
||||||
|
conns_string=pformat(self.conns)
|
||||||
|
|
||||||
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
|
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
|
||||||
len(self.insts),
|
len(self.insts),
|
||||||
len(self.conns)))
|
len(self.conns)))
|
||||||
debug.error("Instances: \n"+str(self.insts))
|
debug.error("Instances: \n"+str(insts_string))
|
||||||
debug.error("-----")
|
debug.error("-----")
|
||||||
debug.error("Connections: \n"+str(self.conns),1)
|
debug.error("Connections: \n"+str(conns_string),1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def sp_read(self):
|
def sp_read(self):
|
||||||
"""Reads the sp file (and parse the pins) from the library
|
"""Reads the sp file (and parse the pins) from the library
|
||||||
Otherwise, initialize it to null for dynamic generation"""
|
Otherwise, initialize it to null for dynamic generation"""
|
||||||
if os.path.isfile(self.sp_file):
|
if self.sp_file and os.path.isfile(self.sp_file):
|
||||||
debug.info(3, "opening {0}".format(self.sp_file))
|
debug.info(3, "opening {0}".format(self.sp_file))
|
||||||
f = open(self.sp_file)
|
f = open(self.sp_file)
|
||||||
self.spice = f.readlines()
|
self.spice = f.readlines()
|
||||||
|
|
|
||||||
|
|
@ -1,115 +1,115 @@
|
||||||
import gdsMill
|
import gdsMill
|
||||||
import tech
|
import tech
|
||||||
import globals
|
import globals
|
||||||
import math
|
import math
|
||||||
import debug
|
import debug
|
||||||
import datetime
|
import datetime
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
class lef:
|
class lef:
|
||||||
"""
|
"""
|
||||||
SRAM LEF Class open GDS file, read pins information, obstruction
|
SRAM LEF Class open GDS file, read pins information, obstruction
|
||||||
and write them to LEF file
|
and write them to LEF file
|
||||||
"""
|
"""
|
||||||
def __init__(self,layers):
|
def __init__(self,layers):
|
||||||
# LEF db units per micron
|
# LEF db units per micron
|
||||||
self.lef_units = 1000
|
self.lef_units = 1000
|
||||||
# These are the layers of the obstructions
|
# These are the layers of the obstructions
|
||||||
self.lef_layers = layers
|
self.lef_layers = layers
|
||||||
|
|
||||||
def lef_write(self, lef_name):
|
def lef_write(self, lef_name):
|
||||||
"""Write the entire lef of the object to the file."""
|
"""Write the entire lef of the object to the file."""
|
||||||
debug.info(3, "Writing to {0}".format(lef_name))
|
debug.info(3, "Writing to {0}".format(lef_name))
|
||||||
|
|
||||||
self.indent = "" # To maintain the indent level easily
|
self.indent = "" # To maintain the indent level easily
|
||||||
|
|
||||||
self.lef = open(lef_name,"w")
|
self.lef = open(lef_name,"w")
|
||||||
self.lef_write_header()
|
self.lef_write_header()
|
||||||
for pin in self.pins:
|
for pin in self.pins:
|
||||||
self.lef_write_pin(pin)
|
self.lef_write_pin(pin)
|
||||||
self.lef_write_obstructions()
|
self.lef_write_obstructions()
|
||||||
self.lef_write_footer()
|
self.lef_write_footer()
|
||||||
self.lef.close()
|
self.lef.close()
|
||||||
|
|
||||||
def lef_write_header(self):
|
def lef_write_header(self):
|
||||||
""" Header of LEF file """
|
""" Header of LEF file """
|
||||||
self.lef.write("VERSION 5.4 ;\n")
|
self.lef.write("VERSION 5.4 ;\n")
|
||||||
self.lef.write("NAMESCASESENSITIVE ON ;\n")
|
self.lef.write("NAMESCASESENSITIVE ON ;\n")
|
||||||
self.lef.write("BUSBITCHARS \"[]\" ;\n")
|
self.lef.write("BUSBITCHARS \"[]\" ;\n")
|
||||||
self.lef.write("DIVIDERCHAR \"/\" ;\n")
|
self.lef.write("DIVIDERCHAR \"/\" ;\n")
|
||||||
self.lef.write("UNITS\n")
|
self.lef.write("UNITS\n")
|
||||||
self.lef.write(" DATABASE MICRONS {0} ;\n".format(self.lef_units))
|
self.lef.write(" DATABASE MICRONS {0} ;\n".format(self.lef_units))
|
||||||
self.lef.write("END UNITS\n")
|
self.lef.write("END UNITS\n")
|
||||||
|
|
||||||
self.lef.write("SITE MacroSite\n")
|
self.lef.write("SITE MacroSite\n")
|
||||||
self.indent += " "
|
self.indent += " "
|
||||||
self.lef.write("{0}CLASS Core ;\n".format(self.indent))
|
self.lef.write("{0}CLASS Core ;\n".format(self.indent))
|
||||||
self.lef.write("{0}SIZE {1} by {2} ;\n".format(self.indent,
|
self.lef.write("{0}SIZE {1} by {2} ;\n".format(self.indent,
|
||||||
self.lef_units*self.width,
|
self.lef_units*self.width,
|
||||||
self.lef_units*self.height))
|
self.lef_units*self.height))
|
||||||
self.indent = self.indent[:-3]
|
self.indent = self.indent[:-3]
|
||||||
self.lef.write("END MacroSite\n")
|
self.lef.write("END MacroSite\n")
|
||||||
|
|
||||||
self.lef.write("{0}MACRO {1}\n".format(self.indent,self.name))
|
self.lef.write("{0}MACRO {1}\n".format(self.indent,self.name))
|
||||||
self.indent += " "
|
self.indent += " "
|
||||||
self.lef.write("{0}CLASS BLOCK ;\n".format(self.indent))
|
self.lef.write("{0}CLASS BLOCK ;\n".format(self.indent))
|
||||||
self.lef.write("{0}SIZE {1} BY {2} ;\n" .format(self.indent,
|
self.lef.write("{0}SIZE {1} BY {2} ;\n" .format(self.indent,
|
||||||
self.lef_units*self.width,
|
self.lef_units*self.width,
|
||||||
self.lef_units*self.height))
|
self.lef_units*self.height))
|
||||||
self.lef.write("{0}SYMMETRY X Y R90 ;\n".format(self.indent))
|
self.lef.write("{0}SYMMETRY X Y R90 ;\n".format(self.indent))
|
||||||
self.lef.write("{0}SITE MacroSite ;\n".format(self.indent))
|
self.lef.write("{0}SITE MacroSite ;\n".format(self.indent))
|
||||||
|
|
||||||
|
|
||||||
def lef_write_footer(self):
|
def lef_write_footer(self):
|
||||||
self.lef.write("{0}END {1}\n".format(self.indent,self.name))
|
self.lef.write("{0}END {1}\n".format(self.indent,self.name))
|
||||||
self.indent = self.indent[:-3]
|
self.indent = self.indent[:-3]
|
||||||
self.lef.write("END LIBRARY\n")
|
self.lef.write("END LIBRARY\n")
|
||||||
|
|
||||||
|
|
||||||
def lef_write_pin(self, name):
|
def lef_write_pin(self, name):
|
||||||
pin_dir = self.get_pin_dir(name)
|
pin_dir = self.get_pin_dir(name)
|
||||||
pin_type = self.get_pin_type(name)
|
pin_type = self.get_pin_type(name)
|
||||||
self.lef.write("{0}PIN {1}\n".format(self.indent,name))
|
self.lef.write("{0}PIN {1}\n".format(self.indent,name))
|
||||||
self.indent += " "
|
self.indent += " "
|
||||||
|
|
||||||
self.lef.write("{0}DIRECTION {1} ;\n".format(self.indent,pin_dir))
|
self.lef.write("{0}DIRECTION {1} ;\n".format(self.indent,pin_dir))
|
||||||
|
|
||||||
if pin_type in ["POWER","GROUND"]:
|
if pin_type in ["POWER","GROUND"]:
|
||||||
self.lef.write("{0}USE {1} ; \n".format(self.indent,pin_type))
|
self.lef.write("{0}USE {1} ; \n".format(self.indent,pin_type))
|
||||||
self.lef.write("{0}SHAPE ABUTMENT ; \n".format(self.indent))
|
self.lef.write("{0}SHAPE ABUTMENT ; \n".format(self.indent))
|
||||||
|
|
||||||
self.lef.write("{0}PORT\n".format(self.indent))
|
self.lef.write("{0}PORT\n".format(self.indent))
|
||||||
self.indent += " "
|
self.indent += " "
|
||||||
|
|
||||||
# We could sort these together to minimize different layer sections, but meh.
|
# We could sort these together to minimize different layer sections, but meh.
|
||||||
pin_list = self.get_pins(name)
|
pin_list = self.get_pins(name)
|
||||||
for pin in pin_list:
|
for pin in pin_list:
|
||||||
self.lef.write("{0}LAYER {1} ;\n".format(self.indent,pin.layer))
|
self.lef.write("{0}LAYER {1} ;\n".format(self.indent,pin.layer))
|
||||||
self.lef_write_rect(pin.rect)
|
self.lef_write_rect(pin.rect)
|
||||||
|
|
||||||
# End the PORT
|
# End the PORT
|
||||||
self.indent = self.indent[:-3]
|
self.indent = self.indent[:-3]
|
||||||
self.lef.write("{0}END\n".format(self.indent))
|
self.lef.write("{0}END\n".format(self.indent))
|
||||||
|
|
||||||
# End the PIN
|
# End the PIN
|
||||||
self.indent = self.indent[:-3]
|
self.indent = self.indent[:-3]
|
||||||
self.lef.write("{0}END {1}\n".format(self.indent,name))
|
self.lef.write("{0}END {1}\n".format(self.indent,name))
|
||||||
|
|
||||||
def lef_write_obstructions(self):
|
def lef_write_obstructions(self):
|
||||||
""" Write all the obstructions on each layer """
|
""" Write all the obstructions on each layer """
|
||||||
self.lef.write("{0}OBS\n".format(self.indent))
|
self.lef.write("{0}OBS\n".format(self.indent))
|
||||||
for layer in self.lef_layers:
|
for layer in self.lef_layers:
|
||||||
self.lef.write("{0}LAYER {1} ;\n".format(self.indent,layer))
|
self.lef.write("{0}LAYER {1} ;\n".format(self.indent,layer))
|
||||||
self.indent += " "
|
self.indent += " "
|
||||||
blockages = self.get_blockages(layer,True)
|
blockages = self.get_blockages(layer,True)
|
||||||
for b in blockages:
|
for b in blockages:
|
||||||
self.lef_write_rect(b)
|
self.lef_write_rect(b)
|
||||||
self.indent = self.indent[:-3]
|
self.indent = self.indent[:-3]
|
||||||
self.lef.write("{0}END\n".format(self.indent))
|
self.lef.write("{0}END\n".format(self.indent))
|
||||||
|
|
||||||
def lef_write_rect(self, rect):
|
def lef_write_rect(self, rect):
|
||||||
""" Write a LEF rectangle """
|
""" Write a LEF rectangle """
|
||||||
self.lef.write("{0}RECT ".format(self.indent))
|
self.lef.write("{0}RECT ".format(self.indent))
|
||||||
for item in rect:
|
for item in rect:
|
||||||
self.lef.write(" {0} {1}".format(self.lef_units*item[0], self.lef_units*item[1]))
|
self.lef.write(" {0} {1}".format(self.lef_units*item[0], self.lef_units*item[1]))
|
||||||
self.lef.write(" ;\n")
|
self.lef.write(" ;\n")
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import debug
|
import debug
|
||||||
from tech import GDS
|
from tech import GDS, drc
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from tech import layer
|
from tech import layer
|
||||||
|
import math
|
||||||
|
|
||||||
class pin_layout:
|
class pin_layout:
|
||||||
"""
|
"""
|
||||||
|
|
@ -18,6 +19,10 @@ class pin_layout:
|
||||||
self.rect = [vector(rect[0]),vector(rect[1])]
|
self.rect = [vector(rect[0]),vector(rect[1])]
|
||||||
# snap the rect to the grid
|
# snap the rect to the grid
|
||||||
self.rect = [x.snap_to_grid() for x in self.rect]
|
self.rect = [x.snap_to_grid() for x in self.rect]
|
||||||
|
|
||||||
|
debug.check(self.width()>0,"Zero width pin.")
|
||||||
|
debug.check(self.height()>0,"Zero height pin.")
|
||||||
|
|
||||||
# if it's a layer number look up the layer name. this assumes a unique layer number.
|
# if it's a layer number look up the layer name. this assumes a unique layer number.
|
||||||
if type(layer_name_num)==int:
|
if type(layer_name_num)==int:
|
||||||
self.layer = list(layer.keys())[list(layer.values()).index(layer_name_num)]
|
self.layer = list(layer.keys())[list(layer.values()).index(layer_name_num)]
|
||||||
|
|
@ -30,32 +35,80 @@ class pin_layout:
|
||||||
return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1])
|
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__):
|
||||||
return (self.name==other.name and self.layer==other.layer and self.rect == other.rect)
|
return (self.layer==other.layer and self.rect == other.rect)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def overlaps(self, other):
|
def inflate(self, spacing=None):
|
||||||
|
"""
|
||||||
|
Inflate the rectangle by the spacing (or other rule)
|
||||||
|
and return the new rectangle.
|
||||||
|
"""
|
||||||
|
if not spacing:
|
||||||
|
spacing = 0.5*drc("{0}_to_{0}".format(self.layer))
|
||||||
|
|
||||||
|
(ll,ur) = self.rect
|
||||||
|
spacing = vector(spacing, spacing)
|
||||||
|
newll = ll - spacing
|
||||||
|
newur = ur + spacing
|
||||||
|
|
||||||
|
return (newll, newur)
|
||||||
|
|
||||||
|
def intersection(self, other):
|
||||||
""" Check if a shape overlaps with a rectangle """
|
""" Check if a shape overlaps with a rectangle """
|
||||||
ll = self.rect[0]
|
(ll,ur) = self.rect
|
||||||
ur = self.rect[1]
|
(oll,our) = other.rect
|
||||||
oll = other.rect[0]
|
|
||||||
our = other.rect[1]
|
min_x = max(ll.x, oll.x)
|
||||||
# Start assuming no overlaps
|
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
|
||||||
|
|
@ -63,7 +116,63 @@ class pin_layout:
|
||||||
if (oll.y >= ll.y and oll.y <= ur.y) or (our.y >= ll.y and our.y <= ur.y):
|
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 xcontains(self, other):
|
||||||
|
""" Check if shape contains the x overlap """
|
||||||
|
(ll,ur) = self.rect
|
||||||
|
(oll,our) = other.rect
|
||||||
|
|
||||||
|
return (oll.x >= ll.x and our.x <= ur.x)
|
||||||
|
|
||||||
|
def ycontains(self, other):
|
||||||
|
""" Check if shape contains the y overlap """
|
||||||
|
(ll,ur) = self.rect
|
||||||
|
(oll,our) = other.rect
|
||||||
|
|
||||||
|
return (oll.y >= ll.y and our.y <= ur.y)
|
||||||
|
|
||||||
|
def contains(self, other):
|
||||||
|
""" Check if a shape contains another rectangle """
|
||||||
|
# If it is the same shape entirely, it is contained!
|
||||||
|
if self == other:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Can only overlap on the same layer
|
||||||
|
if self.layer != other.layer:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.xcontains(other):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.ycontains(other):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def contained_by_any(self, shape_list):
|
||||||
|
""" Checks if shape is contained by any in the list """
|
||||||
|
for shape in shape_list:
|
||||||
|
if shape.contains(self):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def overlaps(self, other):
|
||||||
|
""" Check if a shape overlaps with a rectangle """
|
||||||
|
# Can only overlap on the same layer
|
||||||
|
if self.layer != other.layer:
|
||||||
|
return False
|
||||||
|
|
||||||
|
x_overlaps = self.xoverlaps(other)
|
||||||
|
y_overlaps = self.yoverlaps(other)
|
||||||
|
|
||||||
return x_overlaps and y_overlaps
|
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)
|
||||||
|
|
@ -191,3 +300,163 @@ class pin_layout:
|
||||||
magnification=GDS["zoom"],
|
magnification=GDS["zoom"],
|
||||||
rotate=None)
|
rotate=None)
|
||||||
|
|
||||||
|
|
||||||
|
def compute_overlap(self, other):
|
||||||
|
""" Calculate the rectangular overlap of two rectangles. """
|
||||||
|
(r1_ll,r1_ur) = self.rect
|
||||||
|
(r2_ll,r2_ur) = other.rect
|
||||||
|
|
||||||
|
#ov_ur = vector(min(r1_ur.x,r2_ur.x),min(r1_ur.y,r2_ur.y))
|
||||||
|
#ov_ll = vector(max(r1_ll.x,r2_ll.x),max(r1_ll.y,r2_ll.y))
|
||||||
|
|
||||||
|
dy = min(r1_ur.y,r2_ur.y)-max(r1_ll.y,r2_ll.y)
|
||||||
|
dx = min(r1_ur.x,r2_ur.x)-max(r1_ll.x,r2_ll.x)
|
||||||
|
|
||||||
|
if dx>=0 and dy>=0:
|
||||||
|
return [dx,dy]
|
||||||
|
else:
|
||||||
|
return [0,0]
|
||||||
|
|
||||||
|
def distance(self, other):
|
||||||
|
"""
|
||||||
|
Calculate the distance to another pin layout.
|
||||||
|
"""
|
||||||
|
(r1_ll,r1_ur) = self.rect
|
||||||
|
(r2_ll,r2_ur) = other.rect
|
||||||
|
|
||||||
|
def dist(x1, y1, x2, y2):
|
||||||
|
return sqrt((x2-x1)**2 + (y2-y1)**2)
|
||||||
|
|
||||||
|
left = r2_ur.x < r1_ll.x
|
||||||
|
right = r1_ur.x < r2_ll.x
|
||||||
|
bottom = r2_ur.y < r1_ll.y
|
||||||
|
top = r1_ur.y < r2_ll.y
|
||||||
|
|
||||||
|
if top and left:
|
||||||
|
return dist(r1_ll.x, r1_ur.y, r2_ur.x, r2_ll.y)
|
||||||
|
elif left and bottom:
|
||||||
|
return dist(r1_ll.x, r1_ll.y, r2_ur.x, r2_ur.y)
|
||||||
|
elif bottom and right:
|
||||||
|
return dist(r1_ur.x, r1_ll.y, r2_ll.x, r2_ur.y)
|
||||||
|
elif right and top:
|
||||||
|
return dist(r1_ur.x, r1_ur.y, r2_ll.x, r2_ll.y)
|
||||||
|
elif left:
|
||||||
|
return r1_ll.x - r2_ur.x
|
||||||
|
elif right:
|
||||||
|
return r2_ll.x - r1.ur.x
|
||||||
|
elif bottom:
|
||||||
|
return r1_ll.y - r2_ur.y
|
||||||
|
elif top:
|
||||||
|
return r2_ll.y - r1_ur.y
|
||||||
|
else:
|
||||||
|
# rectangles intersect
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def overlap_length(self, other):
|
||||||
|
"""
|
||||||
|
Calculate the intersection segment and determine its length
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.contains(other):
|
||||||
|
return math.inf
|
||||||
|
elif other.contains(self):
|
||||||
|
return math.inf
|
||||||
|
else:
|
||||||
|
intersections = self.compute_overlap_segment(other)
|
||||||
|
# This is the common case where two pairs of edges overlap
|
||||||
|
# at two points, so just find the distance between those two points
|
||||||
|
if len(intersections)==2:
|
||||||
|
(p1,p2) = intersections
|
||||||
|
return math.sqrt(pow(p1[0]-p2[0],2) + pow(p1[1]-p2[1],2))
|
||||||
|
else:
|
||||||
|
# This is where we had a corner intersection or none
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def compute_overlap_segment(self, other):
|
||||||
|
"""
|
||||||
|
Calculate the intersection segment of two rectangles
|
||||||
|
(if any)
|
||||||
|
"""
|
||||||
|
(r1_ll,r1_ur) = self.rect
|
||||||
|
(r2_ll,r2_ur) = other.rect
|
||||||
|
|
||||||
|
# The other corners besides ll and ur
|
||||||
|
r1_ul = vector(r1_ll.x, r1_ur.y)
|
||||||
|
r1_lr = vector(r1_ur.x, r1_ll.y)
|
||||||
|
r2_ul = vector(r2_ll.x, r2_ur.y)
|
||||||
|
r2_lr = vector(r2_ur.x, r2_ll.y)
|
||||||
|
|
||||||
|
from itertools import tee
|
||||||
|
def pairwise(iterable):
|
||||||
|
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||||
|
a, b = tee(iterable)
|
||||||
|
next(b, None)
|
||||||
|
return zip(a, b)
|
||||||
|
|
||||||
|
# R1 edges CW
|
||||||
|
r1_cw_points = [r1_ll, r1_ul, r1_ur, r1_lr, r1_ll]
|
||||||
|
r1_edges = []
|
||||||
|
for (p,q) in pairwise(r1_cw_points):
|
||||||
|
r1_edges.append([p,q])
|
||||||
|
|
||||||
|
# R2 edges CW
|
||||||
|
r2_cw_points = [r2_ll, r2_ul, r2_ur, r2_lr, r2_ll]
|
||||||
|
r2_edges = []
|
||||||
|
for (p,q) in pairwise(r2_cw_points):
|
||||||
|
r2_edges.append([p,q])
|
||||||
|
|
||||||
|
# There are 4 edges on each rectangle
|
||||||
|
# so just brute force check intersection of each
|
||||||
|
# Two pairs of them should intersect
|
||||||
|
intersections = []
|
||||||
|
for r1e in r1_edges:
|
||||||
|
for r2e in r2_edges:
|
||||||
|
i = self.segment_intersection(r1e, r2e)
|
||||||
|
if i:
|
||||||
|
intersections.append(i)
|
||||||
|
|
||||||
|
return intersections
|
||||||
|
|
||||||
|
def on_segment(self, p, q, r):
|
||||||
|
"""
|
||||||
|
Given three co-linear points, determine if q lies on segment pr
|
||||||
|
"""
|
||||||
|
if q[0] <= max(p[0], r[0]) and \
|
||||||
|
q[0] >= min(p[0], r[0]) and \
|
||||||
|
q[1] <= max(p[1], r[1]) and \
|
||||||
|
q[1] >= min(p[1], r[1]):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def segment_intersection(self, s1, s2):
|
||||||
|
"""
|
||||||
|
Determine the intersection point of two segments
|
||||||
|
Return the a segment if they overlap.
|
||||||
|
Return None if they don't.
|
||||||
|
"""
|
||||||
|
(a,b) = s1
|
||||||
|
(c,d) = s2
|
||||||
|
# Line AB represented as a1x + b1y = c1
|
||||||
|
a1 = b.y - a.y
|
||||||
|
b1 = a.x - b.x
|
||||||
|
c1 = a1*a.x + b1*a.y
|
||||||
|
|
||||||
|
# Line CD represented as a2x + b2y = c2
|
||||||
|
a2 = d.y - c.y
|
||||||
|
b2 = c.x - d.x
|
||||||
|
c2 = a2*c.x + b2*c.y
|
||||||
|
|
||||||
|
determinant = a1*b2 - a2*b1
|
||||||
|
|
||||||
|
if determinant!=0:
|
||||||
|
x = (b2*c1 - b1*c2)/determinant
|
||||||
|
y = (a1*c2 - a2*c1)/determinant
|
||||||
|
|
||||||
|
r = [x,y]
|
||||||
|
if self.on_segment(a, r, b) and self.on_segment(c, r, d):
|
||||||
|
return [x, y]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,34 @@
|
||||||
from tech import drc
|
from tech import drc
|
||||||
import debug
|
import debug
|
||||||
|
from design import design
|
||||||
from contact import contact
|
from contact import contact
|
||||||
from itertools import tee
|
from itertools import tee
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from vector3d import vector3d
|
from vector3d import vector3d
|
||||||
|
|
||||||
class route():
|
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.
|
||||||
"""
|
"""
|
||||||
def __init__(self, obj, layer_stack, path):
|
|
||||||
|
unique_route_id = 0
|
||||||
|
|
||||||
|
def __init__(self, obj, layer_stack, path, layer_widths=[None,1,None]):
|
||||||
name = "route_{0}".format(route.unique_route_id)
|
name = "route_{0}".format(route.unique_route_id)
|
||||||
route.unique_route_id += 1
|
route.unique_route_id += 1
|
||||||
design.design.__init__(self, name)
|
design.__init__(self, name)
|
||||||
debug.info(3, "create route obj {0}".format(name))
|
debug.info(3, "create route obj {0}".format(name))
|
||||||
|
|
||||||
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()
|
||||||
|
|
@ -29,16 +36,16 @@ class route():
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
|
|
@ -52,30 +59,56 @@ class route():
|
||||||
next(b, None)
|
next(b, None)
|
||||||
return zip(a, b)
|
return zip(a, b)
|
||||||
|
|
||||||
plist = pairwise(self.path)
|
plist = list(pairwise(self.path))
|
||||||
for p0,p1 in plist:
|
for p0,p1 in plist:
|
||||||
if p0.z != p1.z: # via
|
if p0.z != p1.z: # via
|
||||||
# offset if not rotated
|
via_size = [self.num_vias]*2
|
||||||
#via_offset = vector(p0.x+0.5*self.c.width,p0.y+0.5*self.c.height)
|
self.obj.add_via_center(self.layer_stack,vector(p0.x,p0.y),size=via_size,rotate=90)
|
||||||
# offset if rotated
|
|
||||||
via_offset = vector(p0.x+0.5*self.c.height,p0.y-0.5*self.c.width)
|
|
||||||
self.obj.add_via(self.layer_stack,via_offset,rotate=90)
|
|
||||||
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("Diagonal route! {}".format(self.path),-3)
|
||||||
else:
|
else:
|
||||||
# this will draw an extra corner at the end but that is ok
|
# this will draw an extra corner at the end but that is ok
|
||||||
self.draw_corner_wire(p1)
|
self.draw_corner_wire(p1)
|
||||||
# draw the point to point wire
|
# draw the point to point wire
|
||||||
self.draw_wire(p0,p1)
|
self.draw_wire(p0,p1)
|
||||||
|
|
||||||
|
|
||||||
|
# Draw the layers on the ends of the wires to ensure full width
|
||||||
|
# connections
|
||||||
|
self.draw_corner_wire(plist[0][0])
|
||||||
|
self.draw_corner_wire(plist[-1][1])
|
||||||
|
|
||||||
|
|
||||||
|
def get_layer_width(self, layer_zindex):
|
||||||
|
"""
|
||||||
|
Return the layer width
|
||||||
|
"""
|
||||||
|
if layer_zindex==0:
|
||||||
|
return self.horiz_layer_width
|
||||||
|
elif layer_zindex==1:
|
||||||
|
return self.vert_layer_width
|
||||||
|
else:
|
||||||
|
debug.error("Incorrect layer zindex.",-1)
|
||||||
|
|
||||||
|
def get_layer_name(self, layer_zindex):
|
||||||
|
"""
|
||||||
|
Return the layer name
|
||||||
|
"""
|
||||||
|
if layer_zindex==0:
|
||||||
|
return self.horiz_layer_name
|
||||||
|
elif layer_zindex==1:
|
||||||
|
return self.vert_layer_name
|
||||||
|
else:
|
||||||
|
debug.error("Incorrect layer zindex.",-1)
|
||||||
|
|
||||||
|
|
||||||
def draw_wire(self, p0, p1):
|
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:
|
||||||
|
|
@ -99,18 +132,18 @@ class route():
|
||||||
height = end.y - start.y
|
height = end.y - start.y
|
||||||
width = layer_width
|
width = layer_width
|
||||||
|
|
||||||
deisgn.add_rect(layer=layer_name,
|
self.obj.add_rect(layer=layer_name,
|
||||||
offset=offset,
|
offset=vector(offset.x,offset.y),
|
||||||
width=width,
|
width=width,
|
||||||
height=height)
|
height=height)
|
||||||
|
|
||||||
|
|
||||||
def draw_corner_wire(self, p0):
|
def draw_corner_wire(self, p0):
|
||||||
""" 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,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import gdsMill
|
||||||
import tech
|
import tech
|
||||||
import math
|
import math
|
||||||
import globals
|
import globals
|
||||||
|
import debug
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from pin_layout import pin_layout
|
from pin_layout import pin_layout
|
||||||
|
|
||||||
|
|
@ -37,12 +38,6 @@ def pin_center(boundary):
|
||||||
"""
|
"""
|
||||||
return [0.5 * (boundary[0] + boundary[2]), 0.5 * (boundary[1] + boundary[3])]
|
return [0.5 * (boundary[0] + boundary[2]), 0.5 * (boundary[1] + boundary[3])]
|
||||||
|
|
||||||
def pin_rect(boundary):
|
|
||||||
"""
|
|
||||||
This returns a LL,UR point pair.
|
|
||||||
"""
|
|
||||||
return [vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])]
|
|
||||||
|
|
||||||
def auto_measure_libcell(pin_list, name, units, layer):
|
def auto_measure_libcell(pin_list, name, units, layer):
|
||||||
"""
|
"""
|
||||||
Open a GDS file and find the pins in pin_list as text on a given layer.
|
Open a GDS file and find the pins in pin_list as text on a given layer.
|
||||||
|
|
@ -66,44 +61,60 @@ def auto_measure_libcell(pin_list, name, units, layer):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_gds_size(name, gds_filename, units, layer):
|
||||||
|
"""
|
||||||
|
Open a GDS file and return the size from either the
|
||||||
|
bounding box or a border layer.
|
||||||
|
"""
|
||||||
|
debug.info(4,"Creating VLSI layout for {}".format(name))
|
||||||
|
cell_vlsi = gdsMill.VlsiLayout(units=units)
|
||||||
|
reader = gdsMill.Gds2reader(cell_vlsi)
|
||||||
|
reader.loadFromFile(gds_filename)
|
||||||
|
|
||||||
|
cell = {}
|
||||||
|
measure_result = cell_vlsi.getLayoutBorder(layer)
|
||||||
|
if measure_result == None:
|
||||||
|
debug.info(2,"Layout border failed. Trying to measure size for {}".format(name))
|
||||||
|
measure_result = cell_vlsi.measureSize(name)
|
||||||
|
# returns width,height
|
||||||
|
return measure_result
|
||||||
|
|
||||||
def get_libcell_size(name, units, layer):
|
def get_libcell_size(name, units, layer):
|
||||||
"""
|
"""
|
||||||
Open a GDS file and return the library cell size from either the
|
Open a GDS file and return the library cell size from either the
|
||||||
bounding box or a border layer.
|
bounding box or a border layer.
|
||||||
"""
|
"""
|
||||||
cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds"
|
cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds"
|
||||||
|
return(get_gds_size(name, cell_gds, units, layer))
|
||||||
|
|
||||||
|
|
||||||
|
def get_gds_pins(pin_names, name, gds_filename, units):
|
||||||
|
"""
|
||||||
|
Open a GDS file and find the pins in pin_names as text on a given layer.
|
||||||
|
Return these as a rectangle layer pair for each pin.
|
||||||
|
"""
|
||||||
cell_vlsi = gdsMill.VlsiLayout(units=units)
|
cell_vlsi = gdsMill.VlsiLayout(units=units)
|
||||||
reader = gdsMill.Gds2reader(cell_vlsi)
|
reader = gdsMill.Gds2reader(cell_vlsi)
|
||||||
reader.loadFromFile(cell_gds)
|
reader.loadFromFile(gds_filename)
|
||||||
|
|
||||||
cell = {}
|
cell = {}
|
||||||
measure_result = cell_vlsi.getLayoutBorder(layer)
|
for pin_name in pin_names:
|
||||||
if measure_result == None:
|
cell[str(pin_name)]=[]
|
||||||
measure_result = cell_vlsi.measureSize(name)
|
pin_list=cell_vlsi.getPinShape(str(pin_name))
|
||||||
# returns width,height
|
for pin_shape in pin_list:
|
||||||
return measure_result
|
(layer,boundary)=pin_shape
|
||||||
|
rect=[vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])]
|
||||||
|
# this is a list because other cells/designs may have must-connect pins
|
||||||
|
cell[str(pin_name)].append(pin_layout(pin_name, rect, layer))
|
||||||
|
return cell
|
||||||
|
|
||||||
|
def get_libcell_pins(pin_list, name, units):
|
||||||
def get_libcell_pins(pin_list, name, units, layer):
|
|
||||||
"""
|
"""
|
||||||
Open a GDS file and find the pins in pin_list as text on a given layer.
|
Open a GDS file and find the pins in pin_list as text on a given layer.
|
||||||
Return these as a rectangle layer pair for each pin.
|
Return these as a rectangle layer pair for each pin.
|
||||||
"""
|
"""
|
||||||
cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds"
|
cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds"
|
||||||
cell_vlsi = gdsMill.VlsiLayout(units=units)
|
return(get_gds_pins(pin_list, name, cell_gds, units))
|
||||||
reader = gdsMill.Gds2reader(cell_vlsi)
|
|
||||||
reader.loadFromFile(cell_gds)
|
|
||||||
|
|
||||||
cell = {}
|
|
||||||
for pin in pin_list:
|
|
||||||
cell[str(pin)]=[]
|
|
||||||
label_list=cell_vlsi.getPinShapeByLabel(str(pin))
|
|
||||||
for label in label_list:
|
|
||||||
(name,layer,boundary)=label
|
|
||||||
rect = pin_rect(boundary)
|
|
||||||
# this is a list because other cells/designs may have must-connect pins
|
|
||||||
cell[str(pin)].append(pin_layout(pin, rect, layer))
|
|
||||||
return cell
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class bitcell(design.design):
|
||||||
|
|
||||||
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
|
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
|
||||||
(width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"])
|
(width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"])
|
||||||
pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"], layer["boundary"])
|
pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"])
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
design.design.__init__(self, "cell_6t")
|
design.design.__init__(self, "cell_6t")
|
||||||
|
|
@ -38,55 +38,33 @@ 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
|
||||||
|
|
||||||
|
|
||||||
def list_row_pins(self):
|
def list_all_wl_names(self):
|
||||||
""" Creates a list of all row pins (except for gnd and vdd) """
|
""" Creates a list of all wordline pin names """
|
||||||
row_pins = ["wl"]
|
row_pins = ["wl"]
|
||||||
return row_pins
|
return row_pins
|
||||||
|
|
||||||
def list_read_row_pins(self):
|
def list_all_bitline_names(self):
|
||||||
""" Creates a list of row pins associated with read ports """
|
""" Creates a list of all bitline pin names (both bl and br) """
|
||||||
row_pins = ["wl"]
|
|
||||||
return row_pins
|
|
||||||
|
|
||||||
def list_write_row_pins(self):
|
|
||||||
""" Creates a list of row pins associated with write ports """
|
|
||||||
row_pins = ["wl"]
|
|
||||||
return row_pins
|
|
||||||
|
|
||||||
|
|
||||||
def list_column_pins(self):
|
|
||||||
""" Creates a list of all column pins (except for gnd and vdd) """
|
|
||||||
column_pins = ["bl", "br"]
|
column_pins = ["bl", "br"]
|
||||||
return column_pins
|
return column_pins
|
||||||
|
|
||||||
def list_read_column_pins(self):
|
def list_all_bl_names(self):
|
||||||
""" Creates a list of column pins associated with read ports """
|
""" Creates a list of all bl pins names """
|
||||||
column_pins = ["bl"]
|
column_pins = ["bl"]
|
||||||
return column_pins
|
return column_pins
|
||||||
|
|
||||||
def list_read_bar_column_pins(self):
|
def list_all_br_names(self):
|
||||||
""" Creates a list of column pins associated with read_bar ports """
|
""" Creates a list of all br pins names """
|
||||||
column_pins = ["br"]
|
column_pins = ["br"]
|
||||||
return column_pins
|
return column_pins
|
||||||
|
|
||||||
def list_write_column_pins(self):
|
|
||||||
""" Creates a list of column pins associated with write ports """
|
|
||||||
column_pins = ["bl"]
|
|
||||||
return column_pins
|
|
||||||
|
|
||||||
def list_write_bar_column_pins(self):
|
|
||||||
""" Creates a list of column pins asscociated with write_bar ports"""
|
|
||||||
column_pins = ["br"]
|
|
||||||
return column_pins
|
|
||||||
|
|
||||||
def analytical_power(self, proc, vdd, temp, load):
|
def analytical_power(self, proc, vdd, temp, load):
|
||||||
"""Bitcell power in nW. Only characterizes leakage."""
|
"""Bitcell power in nW. Only characterizes leakage."""
|
||||||
from tech import spice
|
from tech import spice
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
import design
|
||||||
|
import debug
|
||||||
|
import utils
|
||||||
|
from tech import GDS,layer
|
||||||
|
|
||||||
|
class bitcell_1rw_1r(design.design):
|
||||||
|
"""
|
||||||
|
A single bit cell (6T, 8T, etc.) This module implements the
|
||||||
|
single memory cell used in the design. It is a hand-made cell, so
|
||||||
|
the layout and netlist should be available in the technology
|
||||||
|
library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||||
|
(width,height) = utils.get_libcell_size("cell_1rw_1r", GDS["unit"], layer["boundary"])
|
||||||
|
pin_map = utils.get_libcell_pins(pin_names, "cell_1rw_1r", GDS["unit"])
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
design.design.__init__(self, "cell_1rw_1r")
|
||||||
|
debug.info(2, "Create bitcell with 1RW and 1R Port")
|
||||||
|
|
||||||
|
self.width = bitcell_1rw_1r.width
|
||||||
|
self.height = bitcell_1rw_1r.height
|
||||||
|
self.pin_map = bitcell_1rw_1r.pin_map
|
||||||
|
|
||||||
|
def analytical_delay(self, slew, load=0, swing = 0.5):
|
||||||
|
# delay of bit cell is not like a driver(from WL)
|
||||||
|
# so the slew used should be 0
|
||||||
|
# it should not be slew dependent?
|
||||||
|
# because the value is there
|
||||||
|
# the delay is only over half transsmission gate
|
||||||
|
from tech import spice
|
||||||
|
r = spice["min_tx_r"]*3
|
||||||
|
c_para = spice["min_tx_drain_c"]
|
||||||
|
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = swing)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def list_bitcell_pins(self, col, row):
|
||||||
|
""" Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """
|
||||||
|
bitcell_pins = ["bl0_{0}".format(col),
|
||||||
|
"br0_{0}".format(col),
|
||||||
|
"bl1_{0}".format(col),
|
||||||
|
"br1_{0}".format(col),
|
||||||
|
"wl0_{0}".format(row),
|
||||||
|
"wl1_{0}".format(row),
|
||||||
|
"vdd",
|
||||||
|
"gnd"]
|
||||||
|
return bitcell_pins
|
||||||
|
|
||||||
|
def list_all_wl_names(self):
|
||||||
|
""" Creates a list of all wordline pin names """
|
||||||
|
row_pins = ["wl0", "wl1"]
|
||||||
|
return row_pins
|
||||||
|
|
||||||
|
def list_all_bitline_names(self):
|
||||||
|
""" Creates a list of all bitline pin names (both bl and br) """
|
||||||
|
column_pins = ["bl0", "br0", "bl1", "br1"]
|
||||||
|
return column_pins
|
||||||
|
|
||||||
|
def list_all_bl_names(self):
|
||||||
|
""" Creates a list of all bl pins names """
|
||||||
|
column_pins = ["bl0", "bl1"]
|
||||||
|
return column_pins
|
||||||
|
|
||||||
|
def list_all_br_names(self):
|
||||||
|
""" Creates a list of all br pins names """
|
||||||
|
column_pins = ["br0", "br1"]
|
||||||
|
return column_pins
|
||||||
|
|
||||||
|
def list_read_bl_names(self):
|
||||||
|
""" Creates a list of bl pin names associated with read ports """
|
||||||
|
column_pins = ["bl0", "bl1"]
|
||||||
|
return column_pins
|
||||||
|
|
||||||
|
def list_read_br_names(self):
|
||||||
|
""" Creates a list of br pin names associated with read ports """
|
||||||
|
column_pins = ["br0", "br1"]
|
||||||
|
return column_pins
|
||||||
|
|
||||||
|
def list_write_bl_names(self):
|
||||||
|
""" Creates a list of bl pin names associated with write ports """
|
||||||
|
column_pins = ["bl0"]
|
||||||
|
return column_pins
|
||||||
|
|
||||||
|
def list_write_br_names(self):
|
||||||
|
""" Creates a list of br pin names asscociated with write ports"""
|
||||||
|
column_pins = ["br0"]
|
||||||
|
return column_pins
|
||||||
|
|
||||||
|
def analytical_power(self, proc, vdd, temp, load):
|
||||||
|
"""Bitcell power in nW. Only characterizes leakage."""
|
||||||
|
from tech import spice
|
||||||
|
leakage = spice["bitcell_leakage"]
|
||||||
|
dynamic = 0 #temporary
|
||||||
|
total_power = self.return_power(dynamic, leakage)
|
||||||
|
return total_power
|
||||||
|
|
||||||
|
|
@ -0,0 +1,888 @@
|
||||||
|
import contact
|
||||||
|
import design
|
||||||
|
import debug
|
||||||
|
from tech import drc, parameter, spice
|
||||||
|
from vector import vector
|
||||||
|
from ptx import ptx
|
||||||
|
from globals import OPTS
|
||||||
|
|
||||||
|
class pbitcell(design.design):
|
||||||
|
"""
|
||||||
|
This module implements a parametrically sized multi-port bitcell,
|
||||||
|
with a variable number of read/write, write, and read ports
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, replica_bitcell=False):
|
||||||
|
|
||||||
|
self.num_rw_ports = OPTS.num_rw_ports
|
||||||
|
self.num_w_ports = OPTS.num_w_ports
|
||||||
|
self.num_r_ports = OPTS.num_r_ports
|
||||||
|
self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports
|
||||||
|
|
||||||
|
self.replica_bitcell = replica_bitcell
|
||||||
|
|
||||||
|
if self.replica_bitcell:
|
||||||
|
name = "replica_pbitcell_{0}RW_{1}W_{2}R".format(self.num_rw_ports, self.num_w_ports, self.num_r_ports)
|
||||||
|
else:
|
||||||
|
name = "pbitcell_{0}RW_{1}W_{2}R".format(self.num_rw_ports, self.num_w_ports, self.num_r_ports)
|
||||||
|
|
||||||
|
design.design.__init__(self, name)
|
||||||
|
debug.info(2, "create a multi-port bitcell with {0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports,
|
||||||
|
self.num_w_ports,
|
||||||
|
self.num_r_ports))
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
# We must always create the bitcell layout because some transistor sizes in the other netlists depend on it
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_pins()
|
||||||
|
self.add_modules()
|
||||||
|
self.create_storage()
|
||||||
|
|
||||||
|
if(self.num_rw_ports > 0):
|
||||||
|
self.create_readwrite_ports()
|
||||||
|
if(self.num_w_ports > 0):
|
||||||
|
self.create_write_ports()
|
||||||
|
if(self.num_r_ports > 0):
|
||||||
|
self.create_read_ports()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
self.calculate_spacing()
|
||||||
|
self.calculate_postions()
|
||||||
|
|
||||||
|
self.place_storage()
|
||||||
|
self.route_storage()
|
||||||
|
|
||||||
|
self.route_rails()
|
||||||
|
|
||||||
|
if(self.num_rw_ports > 0):
|
||||||
|
self.place_readwrite_ports()
|
||||||
|
self.route_readwrite_access()
|
||||||
|
if(self.num_w_ports > 0):
|
||||||
|
self.place_write_ports()
|
||||||
|
self.route_write_access()
|
||||||
|
if(self.num_r_ports > 0):
|
||||||
|
self.place_read_ports()
|
||||||
|
self.route_read_access()
|
||||||
|
self.extend_well()
|
||||||
|
|
||||||
|
self.route_wordlines()
|
||||||
|
self.route_bitlines()
|
||||||
|
self.route_supply()
|
||||||
|
|
||||||
|
if self.replica_bitcell:
|
||||||
|
self.route_rbc_short()
|
||||||
|
|
||||||
|
# in netlist_only mode, calling offset_all_coordinates or translate_all will not be possible
|
||||||
|
# this function is not needed to calculate the dimensions of pbitcell in netlist_only mode though
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.offset_all_coordinates()
|
||||||
|
gnd_overlap = vector(0, 0.5*contact.well.width)
|
||||||
|
self.translate_all(gnd_overlap)
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
""" add pins and set names for bitlines and wordlines """
|
||||||
|
self.rw_bl_names = []
|
||||||
|
self.rw_br_names = []
|
||||||
|
self.w_bl_names = []
|
||||||
|
self.w_br_names = []
|
||||||
|
self.r_bl_names = []
|
||||||
|
self.r_br_names = []
|
||||||
|
self.rw_wl_names = []
|
||||||
|
self.w_wl_names = []
|
||||||
|
self.r_wl_names = []
|
||||||
|
port = 0
|
||||||
|
|
||||||
|
for k in range(self.num_rw_ports):
|
||||||
|
self.add_pin("bl{}".format(port))
|
||||||
|
self.add_pin("br{}".format(port))
|
||||||
|
self.rw_bl_names.append("bl{}".format(port))
|
||||||
|
self.rw_br_names.append("br{}".format(port))
|
||||||
|
port += 1
|
||||||
|
for k in range(self.num_w_ports):
|
||||||
|
self.add_pin("bl{}".format(port))
|
||||||
|
self.add_pin("br{}".format(port))
|
||||||
|
self.w_bl_names.append("bl{}".format(port))
|
||||||
|
self.w_br_names.append("br{}".format(port))
|
||||||
|
port += 1
|
||||||
|
for k in range(self.num_r_ports):
|
||||||
|
self.add_pin("bl{}".format(port))
|
||||||
|
self.add_pin("br{}".format(port))
|
||||||
|
self.r_bl_names.append("bl{}".format(port))
|
||||||
|
self.r_br_names.append("br{}".format(port))
|
||||||
|
port += 1
|
||||||
|
|
||||||
|
port = 0
|
||||||
|
for k in range(self.num_rw_ports):
|
||||||
|
self.add_pin("wl{}".format(port))
|
||||||
|
self.rw_wl_names.append("wl{}".format(port))
|
||||||
|
port += 1
|
||||||
|
for k in range(self.num_w_ports):
|
||||||
|
self.add_pin("wl{}".format(port))
|
||||||
|
self.w_wl_names.append("wl{}".format(port))
|
||||||
|
port += 1
|
||||||
|
for k in range(self.num_r_ports):
|
||||||
|
self.add_pin("wl{}".format(port))
|
||||||
|
self.r_wl_names.append("wl{}".format(port))
|
||||||
|
port += 1
|
||||||
|
|
||||||
|
self.add_pin("vdd")
|
||||||
|
self.add_pin("gnd")
|
||||||
|
|
||||||
|
# if this is a replica bitcell, replace the instances of Q_bar with vdd
|
||||||
|
if self.replica_bitcell:
|
||||||
|
self.Q_bar = "vdd"
|
||||||
|
else:
|
||||||
|
self.Q_bar = "Q_bar"
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
|
""" Determine size of transistors and add ptx modules """
|
||||||
|
# if there are any read/write ports, then the inverter nmos is sized based the number of read/write ports
|
||||||
|
if(self.num_rw_ports > 0):
|
||||||
|
inverter_nmos_width = self.num_rw_ports*parameter["6T_inv_nmos_size"]
|
||||||
|
inverter_pmos_width = parameter["6T_inv_pmos_size"]
|
||||||
|
readwrite_nmos_width = parameter["6T_access_size"]
|
||||||
|
write_nmos_width = parameter["6T_access_size"]
|
||||||
|
read_nmos_width = 2*parameter["6T_inv_pmos_size"]
|
||||||
|
|
||||||
|
# if there are no read/write ports, then the inverter nmos is statically sized for the dual port case
|
||||||
|
else:
|
||||||
|
inverter_nmos_width = 2*parameter["6T_inv_pmos_size"]
|
||||||
|
inverter_pmos_width = parameter["6T_inv_pmos_size"]
|
||||||
|
readwrite_nmos_width = parameter["6T_access_size"]
|
||||||
|
write_nmos_width = parameter["6T_access_size"]
|
||||||
|
read_nmos_width = 2*parameter["6T_inv_pmos_size"]
|
||||||
|
|
||||||
|
# create ptx for inverter transistors
|
||||||
|
self.inverter_nmos = ptx(width=inverter_nmos_width,
|
||||||
|
tx_type="nmos")
|
||||||
|
self.add_mod(self.inverter_nmos)
|
||||||
|
|
||||||
|
self.inverter_pmos = ptx(width=inverter_pmos_width,
|
||||||
|
tx_type="pmos")
|
||||||
|
self.add_mod(self.inverter_pmos)
|
||||||
|
|
||||||
|
# create ptx for readwrite transitors
|
||||||
|
self.readwrite_nmos = ptx(width=readwrite_nmos_width,
|
||||||
|
tx_type="nmos")
|
||||||
|
self.add_mod(self.readwrite_nmos)
|
||||||
|
|
||||||
|
# create ptx for write transitors
|
||||||
|
self.write_nmos = ptx(width=write_nmos_width,
|
||||||
|
tx_type="nmos")
|
||||||
|
self.add_mod(self.write_nmos)
|
||||||
|
|
||||||
|
# create ptx for read transistors
|
||||||
|
self.read_nmos = ptx(width=read_nmos_width,
|
||||||
|
tx_type="nmos")
|
||||||
|
self.add_mod(self.read_nmos)
|
||||||
|
|
||||||
|
def calculate_spacing(self):
|
||||||
|
""" Calculate transistor spacings """
|
||||||
|
# calculate metal contact extensions over transistor active
|
||||||
|
readwrite_nmos_contact_extension = 0.5*(self.readwrite_nmos.active_contact.height - self.readwrite_nmos.active_height)
|
||||||
|
write_nmos_contact_extension = 0.5*(self.write_nmos.active_contact.height - self.write_nmos.active_height)
|
||||||
|
read_nmos_contact_extension = 0.5*(self.read_nmos.active_contact.height - self.read_nmos.active_height)
|
||||||
|
max_contact_extension = max(readwrite_nmos_contact_extension, write_nmos_contact_extension, read_nmos_contact_extension)
|
||||||
|
|
||||||
|
# y-offset for the access transistor's gate contact
|
||||||
|
self.gate_contact_yoffset = max_contact_extension + self.m2_space + 0.5*max(contact.poly.height, contact.m1m2.height)
|
||||||
|
|
||||||
|
# y-position of access transistors
|
||||||
|
self.port_ypos = self.m1_space + 0.5*contact.m1m2.height + self.gate_contact_yoffset
|
||||||
|
|
||||||
|
# y-position of inverter nmos
|
||||||
|
self.inverter_nmos_ypos = self.port_ypos
|
||||||
|
|
||||||
|
# spacing between ports (same for read/write and write ports)
|
||||||
|
self.bitline_offset = -0.5*self.readwrite_nmos.active_width + 0.5*contact.m1m2.height + self.m2_space + self.m2_width
|
||||||
|
m2_constraint = self.bitline_offset + self.m2_space + 0.5*contact.m1m2.height - 0.5*self.readwrite_nmos.active_width
|
||||||
|
self.write_port_spacing = max(self.active_space, self.m1_space, m2_constraint)
|
||||||
|
self.read_port_spacing = self.bitline_offset + self.m2_space
|
||||||
|
|
||||||
|
# spacing between cross coupled inverters
|
||||||
|
self.inverter_to_inverter_spacing = contact.poly.height + self.m1_space
|
||||||
|
|
||||||
|
# calculations related to inverter connections
|
||||||
|
inverter_pmos_contact_extension = 0.5*(self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height)
|
||||||
|
inverter_nmos_contact_extension = 0.5*(self.inverter_nmos.active_contact.height - self.inverter_nmos.active_height)
|
||||||
|
self.inverter_gap = max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \
|
||||||
|
+ self.poly_to_polycontact + 2*contact.poly.width \
|
||||||
|
+ self.m1_space + inverter_pmos_contact_extension
|
||||||
|
self.cross_couple_lower_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \
|
||||||
|
+ max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \
|
||||||
|
+ 0.5*contact.poly.width
|
||||||
|
self.cross_couple_upper_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \
|
||||||
|
+ max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \
|
||||||
|
+ self.poly_to_polycontact \
|
||||||
|
+ 1.5*contact.poly.width
|
||||||
|
|
||||||
|
# spacing between wordlines (and gnd)
|
||||||
|
self.rowline_spacing = self.m1_space + contact.m1m2.width
|
||||||
|
self.rowline_offset = -0.5*self.m1_width
|
||||||
|
|
||||||
|
# spacing for vdd
|
||||||
|
implant_constraint = max(inverter_pmos_contact_extension, 0) + 2*self.implant_enclose_active + 0.5*(contact.well.width - self.m1_width)
|
||||||
|
metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space
|
||||||
|
self.vdd_offset = max(implant_constraint, metal1_constraint) + 0.5*self.m1_width
|
||||||
|
|
||||||
|
# read port dimensions
|
||||||
|
width_reduction = self.read_nmos.active_width - self.read_nmos.get_pin("D").cx()
|
||||||
|
self.read_port_width = 2*self.read_nmos.active_width - 2*width_reduction
|
||||||
|
|
||||||
|
def calculate_postions(self):
|
||||||
|
""" Calculate positions that describe the edges and dimensions of the cell """
|
||||||
|
self.botmost_ypos = -0.5*self.m1_width - self.total_ports*self.rowline_spacing
|
||||||
|
self.topmost_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height + self.vdd_offset
|
||||||
|
|
||||||
|
self.leftmost_xpos = -0.5*self.inverter_to_inverter_spacing - self.inverter_nmos.active_width \
|
||||||
|
- self.num_rw_ports*(self.readwrite_nmos.active_width + self.write_port_spacing) \
|
||||||
|
- self.num_w_ports*(self.write_nmos.active_width + self.write_port_spacing) \
|
||||||
|
- self.num_r_ports*(self.read_port_width + self.read_port_spacing) \
|
||||||
|
- self.bitline_offset - 0.5*contact.m1m2.width
|
||||||
|
|
||||||
|
self.width = -2*self.leftmost_xpos
|
||||||
|
self.height = self.topmost_ypos - self.botmost_ypos
|
||||||
|
|
||||||
|
self.center_ypos = 0.5*(self.topmost_ypos + self.botmost_ypos)
|
||||||
|
|
||||||
|
def create_storage(self):
|
||||||
|
"""
|
||||||
|
Creates the crossed coupled inverters that act as storage for the bitcell.
|
||||||
|
The stored value of the cell is denoted as "Q", and the inverted value as "Q_bar".
|
||||||
|
"""
|
||||||
|
# create active for nmos
|
||||||
|
self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left",
|
||||||
|
mod=self.inverter_nmos)
|
||||||
|
self.connect_inst(["Q", self.Q_bar, "gnd", "gnd"])
|
||||||
|
|
||||||
|
self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right",
|
||||||
|
mod=self.inverter_nmos)
|
||||||
|
self.connect_inst(["gnd", "Q", self.Q_bar, "gnd"])
|
||||||
|
|
||||||
|
# create active for pmos
|
||||||
|
self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left",
|
||||||
|
mod=self.inverter_pmos)
|
||||||
|
self.connect_inst(["Q", self.Q_bar, "vdd", "vdd"])
|
||||||
|
|
||||||
|
self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right",
|
||||||
|
mod=self.inverter_pmos)
|
||||||
|
self.connect_inst(["vdd", "Q", self.Q_bar, "vdd"])
|
||||||
|
|
||||||
|
def place_storage(self):
|
||||||
|
""" Places the transistors for the crossed coupled inverters in the bitcell """
|
||||||
|
# calculate transistor offsets
|
||||||
|
left_inverter_xpos = -0.5*self.inverter_to_inverter_spacing - self.inverter_nmos.active_width
|
||||||
|
right_inverter_xpos = 0.5*self.inverter_to_inverter_spacing
|
||||||
|
inverter_pmos_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap
|
||||||
|
|
||||||
|
# create active for nmos
|
||||||
|
self.inverter_nmos_left.place([left_inverter_xpos, self.inverter_nmos_ypos])
|
||||||
|
self.inverter_nmos_right.place([right_inverter_xpos, self.inverter_nmos_ypos])
|
||||||
|
|
||||||
|
# create active for pmos
|
||||||
|
self.inverter_pmos_left.place([left_inverter_xpos, inverter_pmos_ypos])
|
||||||
|
self.inverter_pmos_right.place([right_inverter_xpos, inverter_pmos_ypos])
|
||||||
|
|
||||||
|
# update furthest left and right transistor edges (this will propagate to further transistor offset calculations)
|
||||||
|
self.left_building_edge = left_inverter_xpos
|
||||||
|
self.right_building_edge = right_inverter_xpos + self.inverter_nmos.active_width
|
||||||
|
|
||||||
|
def route_storage(self):
|
||||||
|
""" Routes inputs and outputs of inverters to cross couple them """
|
||||||
|
# connect input (gate) of inverters
|
||||||
|
self.add_path("poly", [self.inverter_nmos_left.get_pin("G").uc(), self.inverter_pmos_left.get_pin("G").bc()])
|
||||||
|
self.add_path("poly", [self.inverter_nmos_right.get_pin("G").uc(), self.inverter_pmos_right.get_pin("G").bc()])
|
||||||
|
|
||||||
|
# connect output (drain/source) of inverters
|
||||||
|
self.add_path("metal1", [self.inverter_nmos_left.get_pin("D").uc(), self.inverter_pmos_left.get_pin("D").bc()], width=contact.well.second_layer_width)
|
||||||
|
self.add_path("metal1", [self.inverter_nmos_right.get_pin("S").uc(), self.inverter_pmos_right.get_pin("S").bc()], width=contact.well.second_layer_width)
|
||||||
|
|
||||||
|
# add contacts to connect gate poly to drain/source metal1 (to connect Q to Q_bar)
|
||||||
|
contact_offset_left = vector(self.inverter_nmos_left.get_pin("D").rc().x + 0.5*contact.poly.height, self.cross_couple_upper_ypos)
|
||||||
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||||
|
offset=contact_offset_left,
|
||||||
|
rotate=90)
|
||||||
|
|
||||||
|
contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x - 0.5*contact.poly.height, self.cross_couple_lower_ypos)
|
||||||
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||||
|
offset=contact_offset_right,
|
||||||
|
rotate=90)
|
||||||
|
|
||||||
|
# connect contacts to gate poly (cross couple connections)
|
||||||
|
gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").lc().x, contact_offset_left.y)
|
||||||
|
self.add_path("poly", [contact_offset_left, gate_offset_right])
|
||||||
|
|
||||||
|
gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").rc().x, contact_offset_right.y)
|
||||||
|
self.add_path("poly", [contact_offset_right, gate_offset_left])
|
||||||
|
|
||||||
|
def route_rails(self):
|
||||||
|
""" Adds gnd and vdd rails and connects them to the inverters """
|
||||||
|
# Add rails for vdd and gnd
|
||||||
|
gnd_ypos = self.rowline_offset - self.total_ports*self.rowline_spacing
|
||||||
|
self.gnd_position = vector(0, gnd_ypos)
|
||||||
|
self.gnd = self.add_layout_pin_rect_center(text="gnd",
|
||||||
|
layer="metal1",
|
||||||
|
offset=self.gnd_position,
|
||||||
|
width=self.width,
|
||||||
|
height=self.m1_width)
|
||||||
|
|
||||||
|
vdd_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height + self.vdd_offset
|
||||||
|
self.vdd_position = vector(0, vdd_ypos)
|
||||||
|
self.vdd = self.add_layout_pin_rect_center(text="vdd",
|
||||||
|
layer="metal1",
|
||||||
|
offset=self.vdd_position,
|
||||||
|
width=self.width,
|
||||||
|
height=self.m1_width)
|
||||||
|
|
||||||
|
def create_readwrite_ports(self):
|
||||||
|
"""
|
||||||
|
Creates read/write ports to the bit cell. A differential pair of transistor can both read and write, like in a 6T cell.
|
||||||
|
A read or write is enabled by setting a Read-Write-Wordline (RWWL) high, subsequently turning on the transistor.
|
||||||
|
The transistor is connected between a Read-Write-Bitline (RWBL) and the storage component of the cell (Q).
|
||||||
|
In a write operation, driving RWBL high or low sets the value of the cell.
|
||||||
|
In a read operation, RWBL is precharged, then is either remains high or is discharged depending on the value of the cell.
|
||||||
|
This is a differential design, so each write port has a mirrored port that connects RWBR to Q_bar.
|
||||||
|
"""
|
||||||
|
# define read/write transistor variables as empty arrays based on the number of read/write ports
|
||||||
|
self.readwrite_nmos_left = [None] * self.num_rw_ports
|
||||||
|
self.readwrite_nmos_right = [None] * self.num_rw_ports
|
||||||
|
|
||||||
|
# iterate over the number of read/write ports
|
||||||
|
for k in range(0,self.num_rw_ports):
|
||||||
|
# add read/write transistors
|
||||||
|
self.readwrite_nmos_left[k] = self.add_inst(name="readwrite_nmos_left{}".format(k),
|
||||||
|
mod=self.readwrite_nmos)
|
||||||
|
self.connect_inst([self.rw_bl_names[k], self.rw_wl_names[k], "Q", "gnd"])
|
||||||
|
|
||||||
|
self.readwrite_nmos_right[k] = self.add_inst(name="readwrite_nmos_right{}".format(k),
|
||||||
|
mod=self.readwrite_nmos)
|
||||||
|
self.connect_inst([self.Q_bar, self.rw_wl_names[k], self.rw_br_names[k], "gnd"])
|
||||||
|
|
||||||
|
def place_readwrite_ports(self):
|
||||||
|
""" Places read/write ports in the bit cell """
|
||||||
|
# define read/write transistor variables as empty arrays based on the number of read/write ports
|
||||||
|
self.rwwl_positions = [None] * self.num_rw_ports
|
||||||
|
self.rwbl_positions = [None] * self.num_rw_ports
|
||||||
|
self.rwbr_positions = [None] * self.num_rw_ports
|
||||||
|
|
||||||
|
# iterate over the number of read/write ports
|
||||||
|
for k in range(0,self.num_rw_ports):
|
||||||
|
# calculate read/write transistor offsets
|
||||||
|
left_readwrite_transistor_xpos = self.left_building_edge \
|
||||||
|
- (k+1)*self.write_port_spacing \
|
||||||
|
- (k+1)*self.readwrite_nmos.active_width
|
||||||
|
|
||||||
|
right_readwrite_transistor_xpos = self.right_building_edge \
|
||||||
|
+ (k+1)*self.write_port_spacing \
|
||||||
|
+ k*self.readwrite_nmos.active_width
|
||||||
|
|
||||||
|
# place read/write transistors
|
||||||
|
self.readwrite_nmos_left[k].place(offset=[left_readwrite_transistor_xpos, self.port_ypos])
|
||||||
|
|
||||||
|
self.readwrite_nmos_right[k].place(offset=[right_readwrite_transistor_xpos, self.port_ypos])
|
||||||
|
|
||||||
|
# add pin for RWWL
|
||||||
|
rwwl_ypos = self.rowline_offset - k*self.rowline_spacing
|
||||||
|
self.rwwl_positions[k] = vector(0, rwwl_ypos)
|
||||||
|
self.add_layout_pin_rect_center(text=self.rw_wl_names[k],
|
||||||
|
layer="metal1",
|
||||||
|
offset=self.rwwl_positions[k],
|
||||||
|
width=self.width,
|
||||||
|
height=self.m1_width)
|
||||||
|
|
||||||
|
# add pins for RWBL and RWBR
|
||||||
|
rwbl_xpos = left_readwrite_transistor_xpos - self.bitline_offset + 0.5*self.m2_width
|
||||||
|
self.rwbl_positions[k] = vector(rwbl_xpos, self.center_ypos)
|
||||||
|
self.add_layout_pin_rect_center(text=self.rw_bl_names[k],
|
||||||
|
layer="metal2",
|
||||||
|
offset=self.rwbl_positions[k],
|
||||||
|
width=drc["minwidth_metal2"],
|
||||||
|
height=self.height)
|
||||||
|
|
||||||
|
rwbr_xpos = right_readwrite_transistor_xpos + self.readwrite_nmos.active_width + self.bitline_offset - 0.5*self.m2_width
|
||||||
|
self.rwbr_positions[k] = vector(rwbr_xpos, self.center_ypos)
|
||||||
|
self.add_layout_pin_rect_center(text=self.rw_br_names[k],
|
||||||
|
layer="metal2",
|
||||||
|
offset=self.rwbr_positions[k],
|
||||||
|
width=drc["minwidth_metal2"],
|
||||||
|
height=self.height)
|
||||||
|
|
||||||
|
# update furthest left and right transistor edges
|
||||||
|
self.left_building_edge = left_readwrite_transistor_xpos
|
||||||
|
self.right_building_edge = right_readwrite_transistor_xpos + self.readwrite_nmos.active_width
|
||||||
|
|
||||||
|
def create_write_ports(self):
|
||||||
|
"""
|
||||||
|
Creates write ports in the bit cell. A differential pair of transistors can write only.
|
||||||
|
A write is enabled by setting a Write-Rowline (WWL) high, subsequently turning on the transistor.
|
||||||
|
The transistor is connected between a Write-Bitline (WBL) and the storage component of the cell (Q).
|
||||||
|
In a write operation, driving WBL high or low sets the value of the cell.
|
||||||
|
This is a differential design, so each write port has a mirrored port that connects WBR to Q_bar.
|
||||||
|
"""
|
||||||
|
# define write transistor variables as empty arrays based on the number of write ports
|
||||||
|
self.write_nmos_left = [None] * self.num_w_ports
|
||||||
|
self.write_nmos_right = [None] * self.num_w_ports
|
||||||
|
|
||||||
|
# iterate over the number of write ports
|
||||||
|
for k in range(0,self.num_w_ports):
|
||||||
|
# add write transistors
|
||||||
|
self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k),
|
||||||
|
mod=self.write_nmos)
|
||||||
|
self.connect_inst([self.w_bl_names[k], self.w_wl_names[k], "Q", "gnd"])
|
||||||
|
|
||||||
|
self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k),
|
||||||
|
mod=self.write_nmos)
|
||||||
|
self.connect_inst([self.Q_bar, self.w_wl_names[k], self.w_br_names[k], "gnd"])
|
||||||
|
|
||||||
|
def place_write_ports(self):
|
||||||
|
""" Places write ports in the bit cell """
|
||||||
|
# define write transistor variables as empty arrays based on the number of write ports
|
||||||
|
self.wwl_positions = [None] * self.num_w_ports
|
||||||
|
self.wbl_positions = [None] * self.num_w_ports
|
||||||
|
self.wbr_positions = [None] * self.num_w_ports
|
||||||
|
|
||||||
|
# iterate over the number of write ports
|
||||||
|
for k in range(0,self.num_w_ports):
|
||||||
|
# Add transistors
|
||||||
|
# calculate write transistor offsets
|
||||||
|
left_write_transistor_xpos = self.left_building_edge \
|
||||||
|
- (k+1)*self.write_port_spacing \
|
||||||
|
- (k+1)*self.write_nmos.active_width
|
||||||
|
|
||||||
|
right_write_transistor_xpos = self.right_building_edge \
|
||||||
|
+ (k+1)*self.write_port_spacing \
|
||||||
|
+ k*self.write_nmos.active_width
|
||||||
|
|
||||||
|
# add write transistors
|
||||||
|
self.write_nmos_left[k].place(offset=[left_write_transistor_xpos, self.port_ypos])
|
||||||
|
|
||||||
|
self.write_nmos_right[k].place(offset=[right_write_transistor_xpos, self.port_ypos])
|
||||||
|
|
||||||
|
# add pin for WWL
|
||||||
|
wwl_ypos = rwwl_ypos = self.rowline_offset - self.num_rw_ports*self.rowline_spacing - k*self.rowline_spacing
|
||||||
|
self.wwl_positions[k] = vector(0, wwl_ypos)
|
||||||
|
self.add_layout_pin_rect_center(text=self.w_wl_names[k],
|
||||||
|
layer="metal1",
|
||||||
|
offset=self.wwl_positions[k],
|
||||||
|
width=self.width,
|
||||||
|
height=self.m1_width)
|
||||||
|
|
||||||
|
# add pins for WBL and WBR
|
||||||
|
wbl_xpos = left_write_transistor_xpos - self.bitline_offset + 0.5*self.m2_width
|
||||||
|
self.wbl_positions[k] = vector(wbl_xpos, self.center_ypos)
|
||||||
|
self.add_layout_pin_rect_center(text=self.w_bl_names[k],
|
||||||
|
layer="metal2",
|
||||||
|
offset=self.wbl_positions[k],
|
||||||
|
width=drc["minwidth_metal2"],
|
||||||
|
height=self.height)
|
||||||
|
|
||||||
|
wbr_xpos = right_write_transistor_xpos + self.write_nmos.active_width + self.bitline_offset - 0.5*self.m2_width
|
||||||
|
self.wbr_positions[k] = vector(wbr_xpos, self.center_ypos)
|
||||||
|
self.add_layout_pin_rect_center(text=self.w_br_names[k],
|
||||||
|
layer="metal2",
|
||||||
|
offset=self.wbr_positions[k],
|
||||||
|
width=drc["minwidth_metal2"],
|
||||||
|
height=self.height)
|
||||||
|
|
||||||
|
# update furthest left and right transistor edges
|
||||||
|
self.left_building_edge = left_write_transistor_xpos
|
||||||
|
self.right_building_edge = right_write_transistor_xpos + self.write_nmos.active_width
|
||||||
|
|
||||||
|
def create_read_ports(self):
|
||||||
|
"""
|
||||||
|
Creates read ports in the bit cell. A differential pair of ports can read only.
|
||||||
|
Two transistors function as a read port, denoted as the "read transistor" and the "read-access transistor".
|
||||||
|
The read transistor is connected to RWL (gate), RBL (drain), and the read-access transistor (source).
|
||||||
|
The read-access transistor is connected to Q_bar (gate), gnd (source), and the read transistor (drain).
|
||||||
|
A read is enabled by setting a Read-Rowline (RWL) high, subsequently turning on the read transistor.
|
||||||
|
The Read-Bitline (RBL) is precharged to high, and when the value of Q_bar is high, the read-access transistor
|
||||||
|
is turned on, creating a connection between RBL and gnd. RBL subsequently discharges allowing for a differential read
|
||||||
|
using sense amps. This is a differential design, so each read port has a mirrored port that connects RBL_bar to Q.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# define read transistor variables as empty arrays based on the number of read ports
|
||||||
|
self.read_nmos_left = [None] * self.num_r_ports
|
||||||
|
self.read_nmos_right = [None] * self.num_r_ports
|
||||||
|
self.read_access_nmos_left = [None] * self.num_r_ports
|
||||||
|
self.read_access_nmos_right = [None] * self.num_r_ports
|
||||||
|
|
||||||
|
# iterate over the number of read ports
|
||||||
|
for k in range(0,self.num_r_ports):
|
||||||
|
# add read-access transistors
|
||||||
|
self.read_access_nmos_left[k] = self.add_inst(name="read_access_nmos_left{}".format(k),
|
||||||
|
mod=self.read_nmos)
|
||||||
|
self.connect_inst(["RA_to_R_left{}".format(k), self.Q_bar, "gnd", "gnd"])
|
||||||
|
|
||||||
|
self.read_access_nmos_right[k] = self.add_inst(name="read_access_nmos_right{}".format(k),
|
||||||
|
mod=self.read_nmos)
|
||||||
|
self.connect_inst(["gnd", "Q", "RA_to_R_right{}".format(k), "gnd"])
|
||||||
|
|
||||||
|
# add read transistors
|
||||||
|
self.read_nmos_left[k] = self.add_inst(name="read_nmos_left{}".format(k),
|
||||||
|
mod=self.read_nmos)
|
||||||
|
self.connect_inst([self.r_bl_names[k], self.r_wl_names[k], "RA_to_R_left{}".format(k), "gnd"])
|
||||||
|
|
||||||
|
self.read_nmos_right[k] = self.add_inst(name="read_nmos_right{}".format(k),
|
||||||
|
mod=self.read_nmos)
|
||||||
|
self.connect_inst(["RA_to_R_right{}".format(k), self.r_wl_names[k], self.r_br_names[k], "gnd"])
|
||||||
|
|
||||||
|
def place_read_ports(self):
|
||||||
|
""" Places the read ports in the bit cell """
|
||||||
|
# define read transistor variables as empty arrays based on the number of read ports
|
||||||
|
self.rwl_positions = [None] * self.num_r_ports
|
||||||
|
self.rbl_positions = [None] * self.num_r_ports
|
||||||
|
self.rbr_positions = [None] * self.num_r_ports
|
||||||
|
|
||||||
|
# calculate offset to overlap the drain of the read-access transistor with the source of the read transistor
|
||||||
|
overlap_offset = self.read_nmos.get_pin("D").cx() - self.read_nmos.get_pin("S").cx()
|
||||||
|
|
||||||
|
# iterate over the number of read ports
|
||||||
|
for k in range(0,self.num_r_ports):
|
||||||
|
# calculate transistor offsets
|
||||||
|
left_read_transistor_xpos = self.left_building_edge \
|
||||||
|
- (k+1)*self.read_port_spacing \
|
||||||
|
- (k+1)*self.read_port_width
|
||||||
|
|
||||||
|
right_read_transistor_xpos = self.right_building_edge \
|
||||||
|
+ (k+1)*self.read_port_spacing \
|
||||||
|
+ k*self.read_port_width
|
||||||
|
|
||||||
|
# add read-access transistors
|
||||||
|
self.read_access_nmos_left[k].place(offset=[left_read_transistor_xpos+overlap_offset, self.port_ypos])
|
||||||
|
|
||||||
|
self.read_access_nmos_right[k].place(offset=[right_read_transistor_xpos, self.port_ypos])
|
||||||
|
|
||||||
|
# add read transistors
|
||||||
|
self.read_nmos_left[k].place(offset=[left_read_transistor_xpos, self.port_ypos])
|
||||||
|
|
||||||
|
self.read_nmos_right[k].place(offset=[right_read_transistor_xpos+overlap_offset, self.port_ypos])
|
||||||
|
|
||||||
|
# add pin for RWL
|
||||||
|
rwl_ypos = rwwl_ypos = self.rowline_offset - self.num_rw_ports*self.rowline_spacing - self.num_w_ports*self.rowline_spacing - k*self.rowline_spacing
|
||||||
|
self.rwl_positions[k] = vector(0, rwl_ypos)
|
||||||
|
self.add_layout_pin_rect_center(text=self.r_wl_names[k],
|
||||||
|
layer="metal1",
|
||||||
|
offset=self.rwl_positions[k],
|
||||||
|
width=self.width,
|
||||||
|
height=self.m1_width)
|
||||||
|
|
||||||
|
# add pins for RBL and RBR
|
||||||
|
rbl_xpos = left_read_transistor_xpos - self.bitline_offset + 0.5*self.m2_width
|
||||||
|
self.rbl_positions[k] = vector(rbl_xpos, self.center_ypos)
|
||||||
|
self.add_layout_pin_rect_center(text=self.r_bl_names[k],
|
||||||
|
layer="metal2",
|
||||||
|
offset=self.rbl_positions[k],
|
||||||
|
width=drc["minwidth_metal2"],
|
||||||
|
height=self.height)
|
||||||
|
|
||||||
|
rbr_xpos = right_read_transistor_xpos + self.read_port_width + self.bitline_offset - 0.5*self.m2_width
|
||||||
|
self.rbr_positions[k] = vector(rbr_xpos, self.center_ypos)
|
||||||
|
self.add_layout_pin_rect_center(text=self.r_br_names[k],
|
||||||
|
layer="metal2",
|
||||||
|
offset=self.rbr_positions[k],
|
||||||
|
width=drc["minwidth_metal2"],
|
||||||
|
height=self.height)
|
||||||
|
|
||||||
|
def route_wordlines(self):
|
||||||
|
""" Routes gate of transistors to their respective wordlines """
|
||||||
|
port_transistors = []
|
||||||
|
for k in range(self.num_rw_ports):
|
||||||
|
port_transistors.append(self.readwrite_nmos_left[k])
|
||||||
|
port_transistors.append(self.readwrite_nmos_right[k])
|
||||||
|
for k in range(self.num_w_ports):
|
||||||
|
port_transistors.append(self.write_nmos_left[k])
|
||||||
|
port_transistors.append(self.write_nmos_right[k])
|
||||||
|
for k in range(self.num_r_ports):
|
||||||
|
port_transistors.append(self.read_nmos_left[k])
|
||||||
|
port_transistors.append(self.read_nmos_right[k])
|
||||||
|
|
||||||
|
wl_positions = []
|
||||||
|
for k in range(self.num_rw_ports):
|
||||||
|
wl_positions.append(self.rwwl_positions[k])
|
||||||
|
wl_positions.append(self.rwwl_positions[k])
|
||||||
|
for k in range(self.num_w_ports):
|
||||||
|
wl_positions.append(self.wwl_positions[k])
|
||||||
|
wl_positions.append(self.wwl_positions[k])
|
||||||
|
for k in range(self.num_r_ports):
|
||||||
|
wl_positions.append(self.rwl_positions[k])
|
||||||
|
wl_positions.append(self.rwl_positions[k])
|
||||||
|
|
||||||
|
for k in range(2*self.total_ports):
|
||||||
|
gate_offset = port_transistors[k].get_pin("G").bc()
|
||||||
|
port_contact_offset = gate_offset + vector(0, -self.gate_contact_yoffset + self.poly_extend_active)
|
||||||
|
wl_contact_offset = vector(gate_offset.x, wl_positions[k].y)
|
||||||
|
|
||||||
|
# first transistor on either side of the cross coupled inverters does not need to route to wordline on metal2
|
||||||
|
if (k == 0) or (k == 1):
|
||||||
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||||
|
offset=port_contact_offset)
|
||||||
|
|
||||||
|
self.add_path("poly", [gate_offset, port_contact_offset])
|
||||||
|
self.add_path("metal1", [port_contact_offset, wl_contact_offset])
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||||
|
offset=port_contact_offset)
|
||||||
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||||
|
offset=port_contact_offset)
|
||||||
|
|
||||||
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||||
|
offset=wl_contact_offset,
|
||||||
|
rotate=90)
|
||||||
|
|
||||||
|
self.add_path("poly", [gate_offset, port_contact_offset])
|
||||||
|
self.add_path("metal2", [port_contact_offset, wl_contact_offset])
|
||||||
|
|
||||||
|
def route_bitlines(self):
|
||||||
|
""" Routes read/write transistors to their respective bitlines """
|
||||||
|
left_port_transistors = []
|
||||||
|
right_port_transistors = []
|
||||||
|
for k in range(self.num_rw_ports):
|
||||||
|
left_port_transistors.append(self.readwrite_nmos_left[k])
|
||||||
|
right_port_transistors.append(self.readwrite_nmos_right[k])
|
||||||
|
for k in range(self.num_w_ports):
|
||||||
|
left_port_transistors.append(self.write_nmos_left[k])
|
||||||
|
right_port_transistors.append(self.write_nmos_right[k])
|
||||||
|
for k in range(self.num_r_ports):
|
||||||
|
left_port_transistors.append(self.read_nmos_left[k])
|
||||||
|
right_port_transistors.append(self.read_nmos_right[k])
|
||||||
|
|
||||||
|
bl_positions = []
|
||||||
|
br_positions = []
|
||||||
|
for k in range(self.num_rw_ports):
|
||||||
|
bl_positions.append(self.rwbl_positions[k])
|
||||||
|
br_positions.append(self.rwbr_positions[k])
|
||||||
|
for k in range(self.num_w_ports):
|
||||||
|
bl_positions.append(self.wbl_positions[k])
|
||||||
|
br_positions.append(self.wbr_positions[k])
|
||||||
|
for k in range(self.num_r_ports):
|
||||||
|
bl_positions.append(self.rbl_positions[k])
|
||||||
|
br_positions.append(self.rbr_positions[k])
|
||||||
|
|
||||||
|
for k in range(self.total_ports):
|
||||||
|
port_contact_offest = left_port_transistors[k].get_pin("S").center()
|
||||||
|
bl_offset = vector(bl_positions[k].x, port_contact_offest.y)
|
||||||
|
|
||||||
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||||
|
offset=port_contact_offest)
|
||||||
|
|
||||||
|
self.add_path("metal2", [port_contact_offest, bl_offset], width=contact.m1m2.height)
|
||||||
|
|
||||||
|
for k in range(self.total_ports):
|
||||||
|
port_contact_offest = right_port_transistors[k].get_pin("D").center()
|
||||||
|
br_offset = vector(br_positions[k].x, port_contact_offest.y)
|
||||||
|
|
||||||
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||||
|
offset=port_contact_offest)
|
||||||
|
|
||||||
|
self.add_path("metal2", [port_contact_offest, br_offset], width=contact.m1m2.height)
|
||||||
|
|
||||||
|
def route_supply(self):
|
||||||
|
""" Route inverter nmos and read-access nmos to gnd. Route inverter pmos to vdd. """
|
||||||
|
# route inverter nmos and read-access nmos to gnd
|
||||||
|
nmos_contact_positions = []
|
||||||
|
nmos_contact_positions.append(self.inverter_nmos_left.get_pin("S").center())
|
||||||
|
nmos_contact_positions.append(self.inverter_nmos_right.get_pin("D").center())
|
||||||
|
for k in range(self.num_r_ports):
|
||||||
|
nmos_contact_positions.append(self.read_access_nmos_left[k].get_pin("D").center())
|
||||||
|
nmos_contact_positions.append(self.read_access_nmos_right[k].get_pin("S").center())
|
||||||
|
|
||||||
|
for position in nmos_contact_positions:
|
||||||
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||||
|
offset=position)
|
||||||
|
|
||||||
|
if position.x > 0:
|
||||||
|
contact_correct = 0.5*contact.m1m2.height
|
||||||
|
else:
|
||||||
|
contact_correct = -0.5*contact.m1m2.height
|
||||||
|
supply_offset = vector(position.x + contact_correct, self.gnd_position.y)
|
||||||
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||||
|
offset=supply_offset,
|
||||||
|
rotate=90)
|
||||||
|
|
||||||
|
self.add_path("metal2", [position, supply_offset])
|
||||||
|
|
||||||
|
# route inverter pmos to vdd
|
||||||
|
vdd_pos_left = vector(self.inverter_nmos_left.get_pin("S").uc().x, self.vdd_position.y)
|
||||||
|
self.add_path("metal1", [self.inverter_pmos_left.get_pin("S").uc(), vdd_pos_left])
|
||||||
|
|
||||||
|
vdd_pos_right = vector(self.inverter_nmos_right.get_pin("D").uc().x, self.vdd_position.y)
|
||||||
|
self.add_path("metal1", [self.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right])
|
||||||
|
|
||||||
|
def route_readwrite_access(self):
|
||||||
|
""" Routes read/write transistors to the storage component of the bitcell """
|
||||||
|
for k in range(self.num_rw_ports):
|
||||||
|
mid = vector(self.readwrite_nmos_left[k].get_pin("D").uc().x, self.cross_couple_lower_ypos)
|
||||||
|
Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos)
|
||||||
|
self.add_path("metal1", [self.readwrite_nmos_left[k].get_pin("D").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
|
||||||
|
self.add_path("metal1", [mid, Q_pos])
|
||||||
|
|
||||||
|
mid = vector(self.readwrite_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos)
|
||||||
|
Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos)
|
||||||
|
self.add_path("metal1", [self.readwrite_nmos_right[k].get_pin("S").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
|
||||||
|
self.add_path("metal1", [mid, Q_bar_pos])
|
||||||
|
|
||||||
|
def route_write_access(self):
|
||||||
|
""" Routes read/write transistors to the storage component of the bitcell """
|
||||||
|
for k in range(self.num_w_ports):
|
||||||
|
mid = vector(self.write_nmos_left[k].get_pin("D").uc().x, self.cross_couple_lower_ypos)
|
||||||
|
Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos)
|
||||||
|
self.add_path("metal1", [self.write_nmos_left[k].get_pin("D").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
|
||||||
|
self.add_path("metal1", [mid, Q_pos])
|
||||||
|
|
||||||
|
mid = vector(self.write_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos)
|
||||||
|
Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos)
|
||||||
|
self.add_path("metal1", [self.write_nmos_right[k].get_pin("S").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
|
||||||
|
self.add_path("metal1", [mid, Q_bar_pos])
|
||||||
|
|
||||||
|
def route_read_access(self):
|
||||||
|
""" Routes read access transistors to the storage component of the bitcell """
|
||||||
|
# add poly to metal1 contacts for gates of the inverters
|
||||||
|
left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x - drc["poly_to_polycontact"] - 0.5*contact.poly.width, self.cross_couple_upper_ypos)
|
||||||
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||||
|
offset=left_storage_contact,
|
||||||
|
rotate=90)
|
||||||
|
|
||||||
|
right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x + drc["poly_to_polycontact"] + 0.5*contact.poly.width, self.cross_couple_upper_ypos)
|
||||||
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||||
|
offset=right_storage_contact,
|
||||||
|
rotate=90)
|
||||||
|
|
||||||
|
inverter_gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_upper_ypos)
|
||||||
|
self.add_path("poly", [left_storage_contact, inverter_gate_offset_left])
|
||||||
|
|
||||||
|
inverter_gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").rc().x, self.cross_couple_upper_ypos)
|
||||||
|
self.add_path("poly", [right_storage_contact, inverter_gate_offset_right])
|
||||||
|
|
||||||
|
# add poly to metal1 contacts for gates of read-access transistors
|
||||||
|
# route from read-access contacts to inverter contacts on metal1
|
||||||
|
for k in range(self.num_r_ports):
|
||||||
|
port_contact_offset = self.read_access_nmos_left[k].get_pin("G").uc() + vector(0, self.gate_contact_yoffset - self.poly_extend_active)
|
||||||
|
|
||||||
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||||
|
offset=port_contact_offset)
|
||||||
|
|
||||||
|
self.add_path("poly", [self.read_access_nmos_left[k].get_pin("G").uc(), port_contact_offset])
|
||||||
|
|
||||||
|
mid = vector(self.read_access_nmos_left[k].get_pin("G").uc().x, self.cross_couple_upper_ypos)
|
||||||
|
self.add_path("metal1", [port_contact_offset, mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
|
||||||
|
self.add_path("metal1", [mid, left_storage_contact])
|
||||||
|
|
||||||
|
port_contact_offset = self.read_access_nmos_right[k].get_pin("G").uc() + vector(0, self.gate_contact_yoffset - self.poly_extend_active)
|
||||||
|
|
||||||
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||||
|
offset=port_contact_offset)
|
||||||
|
|
||||||
|
self.add_path("poly", [self.read_access_nmos_right[k].get_pin("G").uc(), port_contact_offset])
|
||||||
|
|
||||||
|
mid = vector(self.read_access_nmos_right[k].get_pin("G").uc().x, self.cross_couple_upper_ypos)
|
||||||
|
self.add_path("metal1", [port_contact_offset, mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
|
||||||
|
self.add_path("metal1", [mid, right_storage_contact])
|
||||||
|
|
||||||
|
def extend_well(self):
|
||||||
|
"""
|
||||||
|
Connects wells between ptx modules and places well contacts"""
|
||||||
|
# extend pwell to encompass entire nmos region of the cell up to the height of the tallest nmos transistor
|
||||||
|
max_nmos_well_height = max(self.inverter_nmos.cell_well_height,
|
||||||
|
self.readwrite_nmos.cell_well_height,
|
||||||
|
self.write_nmos.cell_well_height,
|
||||||
|
self.read_nmos.cell_well_height)
|
||||||
|
well_height = max_nmos_well_height + self.port_ypos - self.well_enclose_active - self.gnd_position.y
|
||||||
|
offset = vector(self.leftmost_xpos, self.botmost_ypos)
|
||||||
|
self.add_rect(layer="pwell",
|
||||||
|
offset=offset,
|
||||||
|
width=self.width,
|
||||||
|
height=well_height)
|
||||||
|
|
||||||
|
# extend nwell to encompass inverter_pmos
|
||||||
|
# calculate offset of the left pmos well
|
||||||
|
inverter_well_xpos = -(self.inverter_nmos.active_width + 0.5*self.inverter_to_inverter_spacing) - drc["well_enclosure_active"]
|
||||||
|
inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap - drc["well_enclosure_active"]
|
||||||
|
|
||||||
|
# calculate width of the two combined nwells
|
||||||
|
# calculate height to encompass nimplant connected to vdd
|
||||||
|
well_width = 2*(self.inverter_nmos.active_width + 0.5*self.inverter_to_inverter_spacing) + 2*drc["well_enclosure_active"]
|
||||||
|
well_height = self.vdd_position.y - inverter_well_ypos + drc["well_enclosure_active"] + drc["minwidth_tx"]
|
||||||
|
|
||||||
|
offset = [inverter_well_xpos,inverter_well_ypos]
|
||||||
|
self.add_rect(layer="nwell",
|
||||||
|
offset=offset,
|
||||||
|
width=well_width,
|
||||||
|
height=well_height)
|
||||||
|
|
||||||
|
# add well contacts
|
||||||
|
# connect pimplants to gnd
|
||||||
|
offset = vector(0, self.gnd_position.y)
|
||||||
|
self.add_contact_center(layers=("active", "contact", "metal1"),
|
||||||
|
offset=offset,
|
||||||
|
rotate=90,
|
||||||
|
implant_type="p",
|
||||||
|
well_type="p")
|
||||||
|
|
||||||
|
# connect nimplants to vdd
|
||||||
|
offset = vector(0, self.vdd_position.y)
|
||||||
|
self.add_contact_center(layers=("active", "contact", "metal1"),
|
||||||
|
offset=offset,
|
||||||
|
rotate=90,
|
||||||
|
implant_type="n",
|
||||||
|
well_type="n")
|
||||||
|
|
||||||
|
def list_bitcell_pins(self, col, row):
|
||||||
|
""" Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """
|
||||||
|
bitcell_pins = []
|
||||||
|
for port in range(self.total_ports):
|
||||||
|
bitcell_pins.append("bl{0}_{1}".format(port,col))
|
||||||
|
bitcell_pins.append("br{0}_{1}".format(port,col))
|
||||||
|
for port in range(self.total_ports):
|
||||||
|
bitcell_pins.append("wl{0}_{1}".format(port,row))
|
||||||
|
bitcell_pins.append("vdd")
|
||||||
|
bitcell_pins.append("gnd")
|
||||||
|
return bitcell_pins
|
||||||
|
|
||||||
|
def list_all_wl_names(self):
|
||||||
|
""" Creates a list of all wordline pin names """
|
||||||
|
wordline_names = self.rw_wl_names + self.w_wl_names + self.r_wl_names
|
||||||
|
return wordline_names
|
||||||
|
|
||||||
|
def list_all_bitline_names(self):
|
||||||
|
""" Creates a list of all bitline pin names (both bl and br) """
|
||||||
|
bitline_pins = []
|
||||||
|
for port in range(self.total_ports):
|
||||||
|
bitline_pins.append("bl{0}".format(port))
|
||||||
|
bitline_pins.append("br{0}".format(port))
|
||||||
|
return bitline_pins
|
||||||
|
|
||||||
|
def list_all_bl_names(self):
|
||||||
|
""" Creates a list of all bl pins names """
|
||||||
|
bl_pins = self.rw_bl_names + self.w_bl_names + self.r_bl_names
|
||||||
|
return bl_pins
|
||||||
|
|
||||||
|
def list_all_br_names(self):
|
||||||
|
""" Creates a list of all br pins names """
|
||||||
|
br_pins = self.rw_br_names + self.w_br_names + self.r_br_names
|
||||||
|
return br_pins
|
||||||
|
|
||||||
|
def route_rbc_short(self):
|
||||||
|
""" route the short from Q_bar to gnd necessary for the replica bitcell """
|
||||||
|
Q_bar_pos = self.inverter_pmos_right.get_pin("S").center()
|
||||||
|
vdd_pos = self.inverter_pmos_right.get_pin("D").center()
|
||||||
|
self.add_path("metal1", [Q_bar_pos, vdd_pos])
|
||||||
|
|
||||||
|
def analytical_delay(self, slew, load=0, swing = 0.5):
|
||||||
|
#FIXME: Delay copied exactly over from bitcell
|
||||||
|
from tech import spice
|
||||||
|
r = spice["min_tx_r"]*3
|
||||||
|
c_para = spice["min_tx_drain_c"]
|
||||||
|
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = swing)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def analytical_power(self, proc, vdd, temp, load):
|
||||||
|
"""Bitcell power in nW. Only characterizes leakage."""
|
||||||
|
from tech import spice
|
||||||
|
leakage = spice["bitcell_leakage"]
|
||||||
|
dynamic = 0 #temporary
|
||||||
|
total_power = self.return_power(dynamic, leakage)
|
||||||
|
return total_power
|
||||||
|
|
@ -10,9 +10,9 @@ class replica_bitcell(design.design):
|
||||||
is a hand-made cell, so the layout and netlist should be available in
|
is a hand-made cell, so the layout and netlist should be available in
|
||||||
the technology library. """
|
the technology library. """
|
||||||
|
|
||||||
pin_names = ["BL", "BR", "WL", "vdd", "gnd"]
|
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
|
||||||
(width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"])
|
(width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"])
|
||||||
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"], layer["boundary"])
|
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"])
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
design.design.__init__(self, "replica_cell_6t")
|
design.design.__init__(self, "replica_cell_6t")
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import design
|
||||||
|
import debug
|
||||||
|
import utils
|
||||||
|
from tech import GDS,layer
|
||||||
|
|
||||||
|
class replica_bitcell_1rw_1r(design.design):
|
||||||
|
"""
|
||||||
|
A single bit cell which is forced to store a 0.
|
||||||
|
This module implements the single memory cell used in the design. It
|
||||||
|
is a hand-made cell, so the layout and netlist should be available in
|
||||||
|
the technology library. """
|
||||||
|
|
||||||
|
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||||
|
(width,height) = utils.get_libcell_size("replica_cell_1rw_1r", GDS["unit"], layer["boundary"])
|
||||||
|
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1rw_1r", GDS["unit"])
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
design.design.__init__(self, "replica_cell_1rw_1r")
|
||||||
|
debug.info(2, "Create replica bitcell 1rw+1r object")
|
||||||
|
|
||||||
|
self.width = replica_bitcell_1rw_1r.width
|
||||||
|
self.height = replica_bitcell_1rw_1r.height
|
||||||
|
self.pin_map = replica_bitcell_1rw_1r.pin_map
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import debug
|
||||||
|
import design
|
||||||
|
from tech import drc, spice
|
||||||
|
from vector import vector
|
||||||
|
from globals import OPTS
|
||||||
|
from pbitcell import pbitcell
|
||||||
|
|
||||||
|
class replica_pbitcell(design.design):
|
||||||
|
"""
|
||||||
|
Creates a replica bitcell using pbitcell
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.num_rw_ports = OPTS.num_rw_ports
|
||||||
|
self.num_w_ports = OPTS.num_w_ports
|
||||||
|
self.num_r_ports = OPTS.num_r_ports
|
||||||
|
self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports
|
||||||
|
|
||||||
|
design.design.__init__(self, "replica_pbitcell")
|
||||||
|
debug.info(1, "create a replica bitcell using pbitcell with {0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports,
|
||||||
|
self.num_w_ports,
|
||||||
|
self.num_r_ports))
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_pins()
|
||||||
|
self.add_modules()
|
||||||
|
self.create_modules()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
self.place_pbitcell()
|
||||||
|
self.route_rbc_connections()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
for port in range(self.total_ports):
|
||||||
|
self.add_pin("bl{}".format(port))
|
||||||
|
self.add_pin("br{}".format(port))
|
||||||
|
|
||||||
|
for port in range(self.total_ports):
|
||||||
|
self.add_pin("wl{}".format(port))
|
||||||
|
|
||||||
|
self.add_pin("vdd")
|
||||||
|
self.add_pin("gnd")
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
|
self.prbc = pbitcell(replica_bitcell=True)
|
||||||
|
self.add_mod(self.prbc)
|
||||||
|
|
||||||
|
self.height = self.prbc.height
|
||||||
|
self.width = self.prbc.width
|
||||||
|
|
||||||
|
def create_modules(self):
|
||||||
|
self.prbc_inst = self.add_inst(name="pbitcell",
|
||||||
|
mod=self.prbc)
|
||||||
|
|
||||||
|
temp = []
|
||||||
|
for port in range(self.total_ports):
|
||||||
|
temp.append("bl{}".format(port))
|
||||||
|
temp.append("br{}".format(port))
|
||||||
|
for port in range(self.total_ports):
|
||||||
|
temp.append("wl{}".format(port))
|
||||||
|
temp.append("vdd")
|
||||||
|
temp.append("gnd")
|
||||||
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
def place_pbitcell(self):
|
||||||
|
offset = [0,0]
|
||||||
|
self.prbc_inst.place(offset=offset)
|
||||||
|
|
||||||
|
def route_rbc_connections(self):
|
||||||
|
for port in range(self.total_ports):
|
||||||
|
self.copy_layout_pin(self.prbc_inst, "bl{}".format(port))
|
||||||
|
self.copy_layout_pin(self.prbc_inst, "br{}".format(port))
|
||||||
|
for port in range(self.total_ports):
|
||||||
|
self.copy_layout_pin(self.prbc_inst, "wl{}".format(port))
|
||||||
|
self.copy_layout_pin(self.prbc_inst, "vdd")
|
||||||
|
self.copy_layout_pin(self.prbc_inst, "gnd")
|
||||||
|
|
||||||
|
|
@ -5,6 +5,9 @@ from globals import OPTS,find_exe,get_tool
|
||||||
from .lib import *
|
from .lib import *
|
||||||
from .delay import *
|
from .delay import *
|
||||||
from .setup_hold import *
|
from .setup_hold import *
|
||||||
|
from .functional import *
|
||||||
|
from .worst_case import *
|
||||||
|
from .simulation import *
|
||||||
|
|
||||||
|
|
||||||
debug.info(1,"Initializing characterizer...")
|
debug.info(1,"Initializing characterizer...")
|
||||||
|
|
@ -15,7 +18,7 @@ if not OPTS.analytical_delay:
|
||||||
|
|
||||||
if OPTS.spice_name != "":
|
if OPTS.spice_name != "":
|
||||||
OPTS.spice_exe=find_exe(OPTS.spice_name)
|
OPTS.spice_exe=find_exe(OPTS.spice_name)
|
||||||
if OPTS.spice_exe=="":
|
if OPTS.spice_exe=="" or OPTS.spice_exe==None:
|
||||||
debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name),1)
|
debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name),1)
|
||||||
else:
|
else:
|
||||||
(OPTS.spice_name,OPTS.spice_exe) = get_tool("spice",["xa", "hspice", "ngspice", "ngspice.exe"])
|
(OPTS.spice_name,OPTS.spice_exe) = get_tool("spice",["xa", "hspice", "ngspice", "ngspice.exe"])
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,290 @@
|
||||||
|
import sys,re,shutil
|
||||||
|
from design import design
|
||||||
|
import debug
|
||||||
|
import math
|
||||||
|
import tech
|
||||||
|
import random
|
||||||
|
from .stimuli import *
|
||||||
|
from .charutils import *
|
||||||
|
import utils
|
||||||
|
from globals import OPTS
|
||||||
|
|
||||||
|
from .simulation import simulation
|
||||||
|
|
||||||
|
|
||||||
|
class functional(simulation):
|
||||||
|
"""
|
||||||
|
Functions to write random data values to a random address then read them back and check
|
||||||
|
for successful SRAM operation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, sram, spfile, corner):
|
||||||
|
simulation.__init__(self, sram, spfile, corner)
|
||||||
|
|
||||||
|
# Seed the characterizer with a constant seed for unit tests
|
||||||
|
if OPTS.is_unit_test:
|
||||||
|
random.seed(12345)
|
||||||
|
|
||||||
|
self.set_corner(corner)
|
||||||
|
self.set_spice_constants()
|
||||||
|
self.set_stimulus_variables()
|
||||||
|
self.create_signal_names()
|
||||||
|
|
||||||
|
# Number of checks can be changed
|
||||||
|
self.num_cycles = 2
|
||||||
|
self.stored_words = {}
|
||||||
|
self.write_check = []
|
||||||
|
self.read_check = []
|
||||||
|
|
||||||
|
def set_spice_constants(self):
|
||||||
|
"""Spice constants for functional test"""
|
||||||
|
simulation.set_spice_constants(self)
|
||||||
|
#Heuristic increase for functional period. Base feasible period typically does not pass the functional test
|
||||||
|
#for column mux of this size. Increase the feasible period by 20% for this case.
|
||||||
|
if self.sram.words_per_row >= 4:
|
||||||
|
self.period = self.period*1.2
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# Generate a random sequence of reads and writes
|
||||||
|
self.write_random_memory_sequence()
|
||||||
|
|
||||||
|
# Run SPICE simulation
|
||||||
|
self.write_functional_stimulus()
|
||||||
|
self.stim.run_sim()
|
||||||
|
|
||||||
|
# read DOUT values from SPICE simulation. If the values do not fall within the noise margins, return the error.
|
||||||
|
(success, error) = self.read_stim_results()
|
||||||
|
if not success:
|
||||||
|
return (0, error)
|
||||||
|
|
||||||
|
# Check read values with written values. If the values do not match, return an error.
|
||||||
|
return self.check_stim_results()
|
||||||
|
|
||||||
|
def write_random_memory_sequence(self):
|
||||||
|
rw_ops = ["noop", "write", "read"]
|
||||||
|
w_ops = ["noop", "write"]
|
||||||
|
r_ops = ["noop", "read"]
|
||||||
|
rw_read_din_data = "0"*self.word_size
|
||||||
|
check = 0
|
||||||
|
|
||||||
|
# First cycle idle
|
||||||
|
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, 0, self.t_current)
|
||||||
|
self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size)
|
||||||
|
|
||||||
|
# Write at least once
|
||||||
|
addr = self.gen_addr()
|
||||||
|
word = self.gen_data()
|
||||||
|
comment = self.gen_cycle_comment("write", word, addr, 0, self.t_current)
|
||||||
|
self.add_write(comment, addr, word, 0)
|
||||||
|
self.stored_words[addr] = word
|
||||||
|
|
||||||
|
# Read at least once. For multiport, it is important that one read cycle uses all RW and R port to read from the same address simultaniously.
|
||||||
|
# This will test the viablilty of the transistor sizing in the bitcell.
|
||||||
|
for port in self.all_ports:
|
||||||
|
if port in self.write_ports:
|
||||||
|
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
|
||||||
|
else:
|
||||||
|
comment = self.gen_cycle_comment("read", word, addr, port, self.t_current)
|
||||||
|
self.add_read_one_port(comment, addr, rw_read_din_data, port)
|
||||||
|
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
|
||||||
|
check += 1
|
||||||
|
self.cycle_times.append(self.t_current)
|
||||||
|
self.t_current += self.period
|
||||||
|
|
||||||
|
# Perform a random sequence of writes and reads on random ports, using random addresses and random words
|
||||||
|
for i in range(self.num_cycles):
|
||||||
|
w_addrs = []
|
||||||
|
for port in self.all_ports:
|
||||||
|
if port in self.readwrite_ports:
|
||||||
|
op = random.choice(rw_ops)
|
||||||
|
elif port in self.write_ports:
|
||||||
|
op = random.choice(w_ops)
|
||||||
|
else:
|
||||||
|
op = random.choice(r_ops)
|
||||||
|
|
||||||
|
if op == "noop":
|
||||||
|
addr = "0"*self.addr_size
|
||||||
|
word = "0"*self.word_size
|
||||||
|
self.add_noop_one_port(addr, word, port)
|
||||||
|
elif op == "write":
|
||||||
|
addr = self.gen_addr()
|
||||||
|
word = self.gen_data()
|
||||||
|
# two ports cannot write to the same address
|
||||||
|
if addr in w_addrs:
|
||||||
|
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
|
||||||
|
else:
|
||||||
|
comment = self.gen_cycle_comment("write", word, addr, port, self.t_current)
|
||||||
|
self.add_write_one_port(comment, addr, word, port)
|
||||||
|
self.stored_words[addr] = word
|
||||||
|
w_addrs.append(addr)
|
||||||
|
else:
|
||||||
|
(addr,word) = random.choice(list(self.stored_words.items()))
|
||||||
|
# cannot read from an address that is currently being written to
|
||||||
|
if addr in w_addrs:
|
||||||
|
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
|
||||||
|
else:
|
||||||
|
comment = self.gen_cycle_comment("read", word, addr, port, self.t_current)
|
||||||
|
self.add_read_one_port(comment, addr, rw_read_din_data, port)
|
||||||
|
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
|
||||||
|
check += 1
|
||||||
|
|
||||||
|
self.cycle_times.append(self.t_current)
|
||||||
|
self.t_current += self.period
|
||||||
|
|
||||||
|
# Last cycle idle needed to correctly measure the value on the second to last clock edge
|
||||||
|
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, 0, self.t_current)
|
||||||
|
self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size)
|
||||||
|
|
||||||
|
def read_stim_results(self):
|
||||||
|
# Extrat DOUT values from spice timing.lis
|
||||||
|
for (word, dout_port, eo_period, check) in self.write_check:
|
||||||
|
sp_read_value = ""
|
||||||
|
for bit in range(self.word_size):
|
||||||
|
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(),bit,check))
|
||||||
|
if value > 0.88 * self.vdd_voltage:
|
||||||
|
sp_read_value = "1" + sp_read_value
|
||||||
|
elif value < 0.12 * self.vdd_voltage:
|
||||||
|
sp_read_value = "0" + sp_read_value
|
||||||
|
else:
|
||||||
|
error ="FAILED: {0}_{1} value {2} at time {3}n does not fall within noise margins <{4} or >{5}.".format(dout_port,
|
||||||
|
bit,
|
||||||
|
value,
|
||||||
|
eo_period,
|
||||||
|
0.12*self.vdd_voltage,
|
||||||
|
0.88*self.vdd_voltage)
|
||||||
|
return (0, error)
|
||||||
|
|
||||||
|
self.read_check.append([sp_read_value, dout_port, eo_period, check])
|
||||||
|
return (1, "SUCCESS")
|
||||||
|
|
||||||
|
def check_stim_results(self):
|
||||||
|
for i in range(len(self.write_check)):
|
||||||
|
if self.write_check[i][0] != self.read_check[i][0]:
|
||||||
|
error = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n".format(self.read_check[i][1],
|
||||||
|
self.read_check[i][0],
|
||||||
|
self.write_check[i][0],
|
||||||
|
int((self.read_check[i][2]-self.period)/self.period),
|
||||||
|
self.read_check[i][2])
|
||||||
|
return(0, error)
|
||||||
|
return(1, "SUCCESS")
|
||||||
|
|
||||||
|
def gen_data(self):
|
||||||
|
""" Generates a random word to write. """
|
||||||
|
rand = random.randint(0,(2**self.word_size)-1)
|
||||||
|
data_bits = self.convert_to_bin(rand,False)
|
||||||
|
return data_bits
|
||||||
|
|
||||||
|
def gen_addr(self):
|
||||||
|
""" Generates a random address value to write to. """
|
||||||
|
rand = random.randint(0,(2**self.addr_size)-1)
|
||||||
|
addr_bits = self.convert_to_bin(rand,True)
|
||||||
|
return addr_bits
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
""" Gets an available address and corresponding word. """
|
||||||
|
# Currently unused but may need later depending on how the functional test develops
|
||||||
|
addr = random.choice(self.stored_words.keys())
|
||||||
|
word = self.stored_words[addr]
|
||||||
|
return (addr,word)
|
||||||
|
|
||||||
|
def convert_to_bin(self,value,is_addr):
|
||||||
|
""" Converts addr & word to usable binary values. """
|
||||||
|
new_value = str.replace(bin(value),"0b","")
|
||||||
|
if(is_addr):
|
||||||
|
expected_value = self.addr_size
|
||||||
|
else:
|
||||||
|
expected_value = self.word_size
|
||||||
|
for i in range (expected_value - len(new_value)):
|
||||||
|
new_value = "0" + new_value
|
||||||
|
|
||||||
|
#print("Binary Conversion: {} to {}".format(value, new_value))
|
||||||
|
return new_value
|
||||||
|
|
||||||
|
def create_signal_names(self):
|
||||||
|
self.addr_name = "A"
|
||||||
|
self.din_name = "DIN"
|
||||||
|
self.dout_name = "DOUT"
|
||||||
|
|
||||||
|
def write_functional_stimulus(self):
|
||||||
|
""" Writes SPICE stimulus. """
|
||||||
|
temp_stim = "{0}/stim.sp".format(OPTS.openram_temp)
|
||||||
|
self.sf = open(temp_stim,"w")
|
||||||
|
self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period))
|
||||||
|
self.stim = stimuli(self.sf,self.corner)
|
||||||
|
|
||||||
|
#Write include statements
|
||||||
|
self.sram_sp_file = "{}sram.sp".format(OPTS.openram_temp)
|
||||||
|
shutil.copy(self.sp_file, self.sram_sp_file)
|
||||||
|
self.stim.write_include(self.sram_sp_file)
|
||||||
|
|
||||||
|
#Write Vdd/Gnd statements
|
||||||
|
self.sf.write("\n* Global Power Supplies\n")
|
||||||
|
self.stim.write_supply()
|
||||||
|
|
||||||
|
#Instantiate the SRAM
|
||||||
|
self.sf.write("\n* Instantiation of the SRAM\n")
|
||||||
|
self.stim.inst_sram(sram=self.sram,
|
||||||
|
port_signal_names=(self.addr_name,self.din_name,self.dout_name),
|
||||||
|
port_info=(len(self.all_ports), self.write_ports, self.read_ports),
|
||||||
|
abits=self.addr_size,
|
||||||
|
dbits=self.word_size,
|
||||||
|
sram_name=self.name)
|
||||||
|
|
||||||
|
# Add load capacitance to each of the read ports
|
||||||
|
self.sf.write("\n* SRAM output loads\n")
|
||||||
|
for port in self.read_ports:
|
||||||
|
for bit in range(self.word_size):
|
||||||
|
sig_name="{0}{1}_{2} ".format(self.dout_name, port, bit)
|
||||||
|
self.sf.write("CD{0}{1} {2} 0 {3}f\n".format(port, bit, sig_name, self.load))
|
||||||
|
|
||||||
|
# Write debug comments to stim file
|
||||||
|
self.sf.write("\n\n * Sequence of operations\n")
|
||||||
|
for comment in self.fn_cycle_comments:
|
||||||
|
self.sf.write("*{}\n".format(comment))
|
||||||
|
|
||||||
|
# Generate data input bits
|
||||||
|
self.sf.write("\n* Generation of data and address signals\n")
|
||||||
|
for port in self.write_ports:
|
||||||
|
for bit in range(self.word_size):
|
||||||
|
sig_name="{0}{1}_{2} ".format(self.din_name, port, bit)
|
||||||
|
self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[port][bit], self.period, self.slew, 0.05)
|
||||||
|
|
||||||
|
# Generate address bits
|
||||||
|
for port in self.all_ports:
|
||||||
|
for bit in range(self.addr_size):
|
||||||
|
sig_name="{0}{1}_{2} ".format(self.addr_name, port, bit)
|
||||||
|
self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][bit], self.period, self.slew, 0.05)
|
||||||
|
|
||||||
|
# Generate control signals
|
||||||
|
self.sf.write("\n * Generation of control signals\n")
|
||||||
|
for port in self.all_ports:
|
||||||
|
self.stim.gen_pwl("CSB{}".format(port), self.cycle_times , self.csb_values[port], self.period, self.slew, 0.05)
|
||||||
|
|
||||||
|
for port in self.readwrite_ports:
|
||||||
|
self.stim.gen_pwl("WEB{}".format(port), self.cycle_times , self.web_values[port], self.period, self.slew, 0.05)
|
||||||
|
|
||||||
|
# Generate CLK signals
|
||||||
|
for port in self.all_ports:
|
||||||
|
self.stim.gen_pulse(sig_name="{0}{1}".format(tech.spice["clk"], port),
|
||||||
|
v1=self.gnd_voltage,
|
||||||
|
v2=self.vdd_voltage,
|
||||||
|
offset=self.period,
|
||||||
|
period=self.period,
|
||||||
|
t_rise=self.slew,
|
||||||
|
t_fall=self.slew)
|
||||||
|
|
||||||
|
# Generate DOUT value measurements
|
||||||
|
self.sf.write("\n * Generation of dout measurements\n")
|
||||||
|
for (word, dout_port, eo_period, check) in self.write_check:
|
||||||
|
t_intital = eo_period - 0.01*self.period
|
||||||
|
t_final = eo_period + 0.01*self.period
|
||||||
|
for bit in range(self.word_size):
|
||||||
|
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port,bit,check),
|
||||||
|
dout="{0}_{1}".format(dout_port,bit),
|
||||||
|
t_intital=t_intital,
|
||||||
|
t_final=t_final)
|
||||||
|
|
||||||
|
self.stim.write_control(self.cycle_times[-1] + self.period)
|
||||||
|
self.sf.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -12,18 +12,27 @@ class lib:
|
||||||
""" lib file generation."""
|
""" lib file generation."""
|
||||||
|
|
||||||
def __init__(self, out_dir, sram, sp_file, use_model=OPTS.analytical_delay):
|
def __init__(self, out_dir, sram, sp_file, use_model=OPTS.analytical_delay):
|
||||||
|
|
||||||
self.out_dir = out_dir
|
self.out_dir = out_dir
|
||||||
self.sram = sram
|
self.sram = sram
|
||||||
self.sp_file = sp_file
|
self.sp_file = sp_file
|
||||||
self.use_model = use_model
|
self.use_model = use_model
|
||||||
|
self.set_port_indices()
|
||||||
|
|
||||||
self.prepare_tables()
|
self.prepare_tables()
|
||||||
|
|
||||||
self.create_corners()
|
self.create_corners()
|
||||||
|
|
||||||
self.characterize_corners()
|
self.characterize_corners()
|
||||||
|
|
||||||
|
def set_port_indices(self):
|
||||||
|
"""Copies port information set in the SRAM instance"""
|
||||||
|
self.total_port_num = len(self.sram.all_ports)
|
||||||
|
self.all_ports = self.sram.all_ports
|
||||||
|
self.readwrite_ports = self.sram.readwrite_ports
|
||||||
|
self.read_ports = self.sram.read_ports
|
||||||
|
self.write_ports = self.sram.write_ports
|
||||||
|
|
||||||
def prepare_tables(self):
|
def prepare_tables(self):
|
||||||
""" Determine the load/slews if they aren't specified in the config file. """
|
""" Determine the load/slews if they aren't specified in the config file. """
|
||||||
# These are the parameters to determine the table sizes
|
# These are the parameters to determine the table sizes
|
||||||
|
|
@ -75,7 +84,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. """
|
||||||
|
|
||||||
|
|
@ -85,20 +94,21 @@ class lib:
|
||||||
|
|
||||||
self.write_header()
|
self.write_header()
|
||||||
|
|
||||||
self.write_data_bus()
|
#Loop over all ports.
|
||||||
|
for port in self.all_ports:
|
||||||
self.write_addr_bus()
|
#set the read and write port as inputs.
|
||||||
|
self.write_data_bus(port)
|
||||||
self.write_control_pins()
|
self.write_addr_bus(port)
|
||||||
|
self.write_control_pins(port) #need to split this into sram and port control signals
|
||||||
self.write_clk()
|
self.write_clk_timing_power(port)
|
||||||
|
|
||||||
self.write_footer()
|
self.write_footer()
|
||||||
|
|
||||||
|
|
||||||
def write_footer(self):
|
def write_footer(self):
|
||||||
""" Write the footer """
|
""" Write the footer """
|
||||||
self.lib.write("}\n")
|
self.lib.write(" }\n") #Closing brace for the cell
|
||||||
|
self.lib.write("}\n") #Closing brace for the library
|
||||||
|
|
||||||
def write_header(self):
|
def write_header(self):
|
||||||
""" Write the header information """
|
""" Write the header information """
|
||||||
|
|
@ -127,10 +137,15 @@ class lib:
|
||||||
self.lib.write(" dont_touch : true;\n")
|
self.lib.write(" dont_touch : true;\n")
|
||||||
self.lib.write(" area : {};\n\n".format(self.sram.width * self.sram.height))
|
self.lib.write(" area : {};\n\n".format(self.sram.width * self.sram.height))
|
||||||
|
|
||||||
|
#Build string of all control signals.
|
||||||
|
control_str = 'CSb0' #assume at least 1 port
|
||||||
|
for i in range(1, self.total_port_num):
|
||||||
|
control_str += ' & CSb{0}'.format(i)
|
||||||
|
|
||||||
# Leakage is included in dynamic when macro is enabled
|
# Leakage is included in dynamic when macro is enabled
|
||||||
self.lib.write(" leakage_power () {\n")
|
self.lib.write(" leakage_power () {\n")
|
||||||
self.lib.write(" when : \"CSb\";\n")
|
self.lib.write(" when : \"{0}\";\n".format(control_str))
|
||||||
self.lib.write(" value : {};\n".format(self.char_results["leakage_power"]))
|
self.lib.write(" value : {};\n".format(self.char_sram_results["leakage_power"]))
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
self.lib.write(" cell_leakage_power : {};\n".format(0))
|
self.lib.write(" cell_leakage_power : {};\n".format(0))
|
||||||
|
|
||||||
|
|
@ -267,12 +282,12 @@ class lib:
|
||||||
self.lib.write(" }\n\n")
|
self.lib.write(" }\n\n")
|
||||||
|
|
||||||
|
|
||||||
def write_FF_setuphold(self):
|
def write_FF_setuphold(self, port):
|
||||||
""" Adds Setup and Hold timing results"""
|
""" Adds Setup and Hold timing results"""
|
||||||
|
|
||||||
self.lib.write(" timing(){ \n")
|
self.lib.write(" timing(){ \n")
|
||||||
self.lib.write(" timing_type : setup_rising; \n")
|
self.lib.write(" timing_type : setup_rising; \n")
|
||||||
self.lib.write(" related_pin : \"clk\"; \n")
|
self.lib.write(" related_pin : \"clk{0}\"; \n".format(port))
|
||||||
self.lib.write(" rise_constraint(CONSTRAINT_TABLE) {\n")
|
self.lib.write(" rise_constraint(CONSTRAINT_TABLE) {\n")
|
||||||
rounded_values = list(map(round_time,self.times["setup_times_LH"]))
|
rounded_values = list(map(round_time,self.times["setup_times_LH"]))
|
||||||
self.write_values(rounded_values,len(self.slews)," ")
|
self.write_values(rounded_values,len(self.slews)," ")
|
||||||
|
|
@ -284,7 +299,7 @@ class lib:
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
self.lib.write(" timing(){ \n")
|
self.lib.write(" timing(){ \n")
|
||||||
self.lib.write(" timing_type : hold_rising; \n")
|
self.lib.write(" timing_type : hold_rising; \n")
|
||||||
self.lib.write(" related_pin : \"clk\"; \n")
|
self.lib.write(" related_pin : \"clk{0}\"; \n".format(port))
|
||||||
self.lib.write(" rise_constraint(CONSTRAINT_TABLE) {\n")
|
self.lib.write(" rise_constraint(CONSTRAINT_TABLE) {\n")
|
||||||
rounded_values = list(map(round_time,self.times["hold_times_LH"]))
|
rounded_values = list(map(round_time,self.times["hold_times_LH"]))
|
||||||
self.write_values(rounded_values,len(self.slews)," ")
|
self.write_values(rounded_values,len(self.slews)," ")
|
||||||
|
|
@ -295,135 +310,112 @@ class lib:
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
|
|
||||||
|
def write_data_bus_output(self, read_port):
|
||||||
|
|
||||||
def write_data_bus(self):
|
|
||||||
""" Adds data bus timing results."""
|
""" Adds data bus timing results."""
|
||||||
|
|
||||||
self.lib.write(" bus(DIN){\n")
|
self.lib.write(" bus(DOUT{0}){{\n".format(read_port))
|
||||||
self.lib.write(" bus_type : DATA; \n")
|
self.lib.write(" bus_type : DATA; \n")
|
||||||
self.lib.write(" direction : in; \n")
|
self.lib.write(" direction : output; \n")
|
||||||
# This is conservative, but limit to range that we characterized.
|
|
||||||
self.lib.write(" max_capacitance : {0}; \n".format(max(self.loads)))
|
|
||||||
self.lib.write(" min_capacitance : {0}; \n".format(min(self.loads)))
|
|
||||||
self.lib.write(" memory_write(){ \n")
|
|
||||||
self.lib.write(" address : ADDR; \n")
|
|
||||||
self.lib.write(" clocked_on : clk; \n")
|
|
||||||
self.lib.write(" }\n")
|
|
||||||
|
|
||||||
self.lib.write(" bus(DOUT){\n")
|
|
||||||
self.lib.write(" bus_type : DATA; \n")
|
|
||||||
self.lib.write(" direction : out; \n")
|
|
||||||
# This is conservative, but limit to range that we characterized.
|
# This is conservative, but limit to range that we characterized.
|
||||||
self.lib.write(" max_capacitance : {0}; \n".format(max(self.loads)))
|
self.lib.write(" max_capacitance : {0}; \n".format(max(self.loads)))
|
||||||
self.lib.write(" min_capacitance : {0}; \n".format(min(self.loads)))
|
self.lib.write(" min_capacitance : {0}; \n".format(min(self.loads)))
|
||||||
self.lib.write(" memory_read(){ \n")
|
self.lib.write(" memory_read(){ \n")
|
||||||
self.lib.write(" address : ADDR; \n")
|
self.lib.write(" address : ADDR{0}; \n".format(read_port))
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
|
|
||||||
|
|
||||||
self.lib.write(" pin(DOUT[{0}:0]){{\n".format(self.sram.word_size - 1))
|
self.lib.write(" pin(DOUT{1}[{0}:0]){{\n".format(self.sram.word_size - 1, read_port))
|
||||||
self.write_FF_setuphold()
|
|
||||||
self.lib.write(" timing(){ \n")
|
self.lib.write(" timing(){ \n")
|
||||||
self.lib.write(" timing_sense : non_unate; \n")
|
self.lib.write(" timing_sense : non_unate; \n")
|
||||||
self.lib.write(" related_pin : \"clk\"; \n")
|
self.lib.write(" related_pin : \"clk{0}\"; \n".format(read_port))
|
||||||
self.lib.write(" timing_type : rising_edge; \n")
|
self.lib.write(" timing_type : rising_edge; \n")
|
||||||
self.lib.write(" cell_rise(CELL_TABLE) {\n")
|
self.lib.write(" cell_rise(CELL_TABLE) {\n")
|
||||||
self.write_values(self.char_results["delay_lh"],len(self.loads)," ")
|
self.write_values(self.char_port_results[read_port]["delay_lh"],len(self.loads)," ")
|
||||||
self.lib.write(" }\n") # rise delay
|
self.lib.write(" }\n") # rise delay
|
||||||
self.lib.write(" cell_fall(CELL_TABLE) {\n")
|
self.lib.write(" cell_fall(CELL_TABLE) {\n")
|
||||||
self.write_values(self.char_results["delay_hl"],len(self.loads)," ")
|
self.write_values(self.char_port_results[read_port]["delay_hl"],len(self.loads)," ")
|
||||||
self.lib.write(" }\n") # fall delay
|
self.lib.write(" }\n") # fall delay
|
||||||
self.lib.write(" rise_transition(CELL_TABLE) {\n")
|
self.lib.write(" rise_transition(CELL_TABLE) {\n")
|
||||||
self.write_values(self.char_results["slew_lh"],len(self.loads)," ")
|
self.write_values(self.char_port_results[read_port]["slew_lh"],len(self.loads)," ")
|
||||||
self.lib.write(" }\n") # rise trans
|
self.lib.write(" }\n") # rise trans
|
||||||
self.lib.write(" fall_transition(CELL_TABLE) {\n")
|
self.lib.write(" fall_transition(CELL_TABLE) {\n")
|
||||||
self.write_values(self.char_results["slew_hl"],len(self.loads)," ")
|
self.write_values(self.char_port_results[read_port]["slew_hl"],len(self.loads)," ")
|
||||||
self.lib.write(" }\n") # fall trans
|
self.lib.write(" }\n") # fall trans
|
||||||
self.lib.write(" }\n") # timing
|
self.lib.write(" }\n") # timing
|
||||||
self.lib.write(" }\n") # pin
|
self.lib.write(" }\n") # pin
|
||||||
self.lib.write(" }\n\n") # bus
|
self.lib.write(" }\n\n") # bus
|
||||||
|
|
||||||
|
def write_data_bus_input(self, write_port):
|
||||||
|
""" Adds DIN data bus timing results."""
|
||||||
|
|
||||||
def write_addr_bus(self):
|
self.lib.write(" bus(DIN{0}){{\n".format(write_port))
|
||||||
|
self.lib.write(" bus_type : DATA; \n")
|
||||||
|
self.lib.write(" direction : input; \n")
|
||||||
|
# This is conservative, but limit to range that we characterized.
|
||||||
|
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
||||||
|
self.lib.write(" memory_write(){ \n")
|
||||||
|
self.lib.write(" address : ADDR{0}; \n".format(write_port))
|
||||||
|
self.lib.write(" clocked_on : clk{0}; \n".format(write_port))
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
self.lib.write(" pin(DIN{1}[{0}:0]){{\n".format(self.sram.word_size - 1, write_port))
|
||||||
|
self.write_FF_setuphold(write_port)
|
||||||
|
self.lib.write(" }\n") # pin
|
||||||
|
self.lib.write(" }\n") #bus
|
||||||
|
|
||||||
|
def write_data_bus(self, port):
|
||||||
|
""" Adds data bus timing results."""
|
||||||
|
if port in self.write_ports:
|
||||||
|
self.write_data_bus_input(port)
|
||||||
|
if port in self.read_ports:
|
||||||
|
self.write_data_bus_output(port)
|
||||||
|
|
||||||
|
def write_addr_bus(self, port):
|
||||||
""" Adds addr bus timing results."""
|
""" Adds addr bus timing results."""
|
||||||
|
|
||||||
self.lib.write(" bus(ADDR){\n")
|
self.lib.write(" bus(ADDR{0}){{\n".format(port))
|
||||||
self.lib.write(" bus_type : ADDR; \n")
|
self.lib.write(" bus_type : ADDR; \n")
|
||||||
self.lib.write(" direction : input; \n")
|
self.lib.write(" direction : input; \n")
|
||||||
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
||||||
self.lib.write(" max_transition : {0};\n".format(self.slews[-1]))
|
self.lib.write(" max_transition : {0};\n".format(self.slews[-1]))
|
||||||
self.lib.write(" pin(ADDR[{0}:0])".format(self.sram.addr_size - 1))
|
self.lib.write(" pin(ADDR{1}[{0}:0])".format(self.sram.addr_size - 1, port))
|
||||||
self.lib.write("{\n")
|
self.lib.write("{\n")
|
||||||
|
|
||||||
self.write_FF_setuphold()
|
self.write_FF_setuphold(port)
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
self.lib.write(" }\n\n")
|
self.lib.write(" }\n\n")
|
||||||
|
|
||||||
|
|
||||||
def write_control_pins(self):
|
def write_control_pins(self, port):
|
||||||
""" Adds control pins timing results."""
|
""" Adds control pins timing results."""
|
||||||
|
#The control pins are still to be determined. This is a placeholder for what could be.
|
||||||
ctrl_pin_names = ["CSb", "OEb", "WEb"]
|
ctrl_pin_names = ["CSb{0}".format(port)]
|
||||||
|
if port in self.readwrite_ports:
|
||||||
|
ctrl_pin_names.append("WEb{0}".format(port))
|
||||||
|
|
||||||
for i in ctrl_pin_names:
|
for i in ctrl_pin_names:
|
||||||
self.lib.write(" pin({0})".format(i))
|
self.lib.write(" pin({0})".format(i))
|
||||||
self.lib.write("{\n")
|
self.lib.write("{\n")
|
||||||
self.lib.write(" direction : input; \n")
|
self.lib.write(" direction : input; \n")
|
||||||
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
||||||
self.write_FF_setuphold()
|
self.write_FF_setuphold(port)
|
||||||
self.lib.write(" }\n\n")
|
self.lib.write(" }\n\n")
|
||||||
|
|
||||||
|
def write_clk_timing_power(self, port):
|
||||||
def write_clk(self):
|
|
||||||
""" Adds clk pin timing results."""
|
""" Adds clk pin timing results."""
|
||||||
|
|
||||||
self.lib.write(" pin(clk){\n")
|
self.lib.write(" pin(clk{0}){{\n".format(port))
|
||||||
self.lib.write(" clock : true;\n")
|
self.lib.write(" clock : true;\n")
|
||||||
self.lib.write(" direction : input; \n")
|
self.lib.write(" direction : input; \n")
|
||||||
# FIXME: This depends on the clock buffer size in the control logic
|
# FIXME: This depends on the clock buffer size in the control logic
|
||||||
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
||||||
|
|
||||||
# Find the average power of 1 and 0 bits for writes and reads over all loads/slews
|
self.add_clk_control_power(port)
|
||||||
# Could make it a table, but this is fine for now.
|
|
||||||
avg_write_power = np.mean(self.char_results["write1_power"] + self.char_results["write0_power"])
|
|
||||||
avg_read_power = np.mean(self.char_results["read1_power"] + self.char_results["read0_power"])
|
|
||||||
|
|
||||||
# Equally divide read/write power between first and second half of clock period
|
min_pulse_width = round_time(self.char_sram_results["min_period"])/2.0
|
||||||
self.lib.write(" internal_power(){\n")
|
min_period = round_time(self.char_sram_results["min_period"])
|
||||||
self.lib.write(" when : \"!CSb & clk & !WEb\"; \n")
|
|
||||||
self.lib.write(" rise_power(scalar){\n")
|
|
||||||
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
|
|
||||||
self.lib.write(" }\n")
|
|
||||||
self.lib.write(" fall_power(scalar){\n")
|
|
||||||
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
|
|
||||||
self.lib.write(" }\n")
|
|
||||||
self.lib.write(" }\n")
|
|
||||||
|
|
||||||
self.lib.write(" internal_power(){\n")
|
|
||||||
self.lib.write(" when : \"!CSb & !clk & WEb\"; \n")
|
|
||||||
self.lib.write(" rise_power(scalar){\n")
|
|
||||||
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
|
|
||||||
self.lib.write(" }\n")
|
|
||||||
self.lib.write(" fall_power(scalar){\n")
|
|
||||||
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
|
|
||||||
self.lib.write(" }\n")
|
|
||||||
self.lib.write(" }\n")
|
|
||||||
# Have 0 internal power when disabled, this will be represented as leakage power.
|
|
||||||
self.lib.write(" internal_power(){\n")
|
|
||||||
self.lib.write(" when : \"CSb\"; \n")
|
|
||||||
self.lib.write(" rise_power(scalar){\n")
|
|
||||||
self.lib.write(" values(\"0\");\n")
|
|
||||||
self.lib.write(" }\n")
|
|
||||||
self.lib.write(" fall_power(scalar){\n")
|
|
||||||
self.lib.write(" values(\"0\");\n")
|
|
||||||
self.lib.write(" }\n")
|
|
||||||
self.lib.write(" }\n")
|
|
||||||
|
|
||||||
min_pulse_width = round_time(self.char_results["min_period"])/2.0
|
|
||||||
min_period = round_time(self.char_results["min_period"])
|
|
||||||
self.lib.write(" timing(){ \n")
|
self.lib.write(" timing(){ \n")
|
||||||
self.lib.write(" timing_type :\"min_pulse_width\"; \n")
|
self.lib.write(" timing_type :\"min_pulse_width\"; \n")
|
||||||
self.lib.write(" related_pin : clk; \n")
|
self.lib.write(" related_pin : clk{0}; \n".format(port))
|
||||||
self.lib.write(" rise_constraint(scalar) {\n")
|
self.lib.write(" rise_constraint(scalar) {\n")
|
||||||
self.lib.write(" values(\"{0}\"); \n".format(min_pulse_width))
|
self.lib.write(" values(\"{0}\"); \n".format(min_pulse_width))
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
|
|
@ -433,7 +425,7 @@ class lib:
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
self.lib.write(" timing(){ \n")
|
self.lib.write(" timing(){ \n")
|
||||||
self.lib.write(" timing_type :\"minimum_period\"; \n")
|
self.lib.write(" timing_type :\"minimum_period\"; \n")
|
||||||
self.lib.write(" related_pin : clk; \n")
|
self.lib.write(" related_pin : clk{0}; \n".format(port))
|
||||||
self.lib.write(" rise_constraint(scalar) {\n")
|
self.lib.write(" rise_constraint(scalar) {\n")
|
||||||
self.lib.write(" values(\"{0}\"); \n".format(min_period))
|
self.lib.write(" values(\"{0}\"); \n".format(min_period))
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
|
|
@ -441,21 +433,65 @@ class lib:
|
||||||
self.lib.write(" values(\"{0}\"); \n".format(min_period))
|
self.lib.write(" values(\"{0}\"); \n".format(min_period))
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n\n")
|
||||||
self.lib.write(" }\n")
|
|
||||||
|
def add_clk_control_power(self, port):
|
||||||
|
"""Writes powers under the clock pin group for a specified port"""
|
||||||
|
#Web added to read/write ports. Likely to change when control logic finished.
|
||||||
|
web_name = ""
|
||||||
|
|
||||||
|
if port in self.write_ports:
|
||||||
|
if port in self.read_ports:
|
||||||
|
web_name = " & !WEb{0}".format(port)
|
||||||
|
avg_write_power = np.mean(self.char_port_results[port]["write1_power"] + self.char_port_results[port]["write0_power"])
|
||||||
|
self.lib.write(" internal_power(){\n")
|
||||||
|
self.lib.write(" when : \"!CSb{0} & clk{0}{1}\"; \n".format(port, web_name))
|
||||||
|
self.lib.write(" rise_power(scalar){\n")
|
||||||
|
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
self.lib.write(" fall_power(scalar){\n")
|
||||||
|
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
|
||||||
|
if port in self.read_ports:
|
||||||
|
if port in self.write_ports:
|
||||||
|
web_name = " & WEb{0}".format(port)
|
||||||
|
avg_read_power = np.mean(self.char_port_results[port]["read1_power"] + self.char_port_results[port]["read0_power"])
|
||||||
|
self.lib.write(" internal_power(){\n")
|
||||||
|
self.lib.write(" when : \"!CSb{0} & !clk{0}{1}\"; \n".format(port, web_name))
|
||||||
|
self.lib.write(" rise_power(scalar){\n")
|
||||||
|
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
self.lib.write(" fall_power(scalar){\n")
|
||||||
|
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
|
||||||
|
# Have 0 internal power when disabled, this will be represented as leakage power.
|
||||||
|
self.lib.write(" internal_power(){\n")
|
||||||
|
self.lib.write(" when : \"CSb{0}\"; \n".format(port))
|
||||||
|
self.lib.write(" rise_power(scalar){\n")
|
||||||
|
self.lib.write(" values(\"0\");\n")
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
self.lib.write(" fall_power(scalar){\n")
|
||||||
|
self.lib.write(" values(\"0\");\n")
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
|
||||||
def compute_delay(self):
|
def compute_delay(self):
|
||||||
""" Do the analysis if we haven't characterized the SRAM yet """
|
""" Do the analysis if we haven't characterized the SRAM yet """
|
||||||
if not hasattr(self,"d"):
|
if not hasattr(self,"d"):
|
||||||
self.d = delay(self.sram, self.sp_file, self.corner)
|
self.d = delay(self.sram, self.sp_file, self.corner)
|
||||||
if self.use_model:
|
if self.use_model:
|
||||||
self.char_results = self.d.analytical_delay(self.sram,self.slews,self.loads)
|
char_results = self.d.analytical_delay(self.slews,self.loads)
|
||||||
|
self.char_sram_results, self.char_port_results = char_results
|
||||||
else:
|
else:
|
||||||
probe_address = "1" * self.sram.addr_size
|
probe_address = "1" * self.sram.addr_size
|
||||||
probe_data = self.sram.word_size - 1
|
probe_data = self.sram.word_size - 1
|
||||||
self.char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads)
|
char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads)
|
||||||
|
self.char_sram_results, self.char_port_results = char_results
|
||||||
|
|
||||||
def compute_setup_hold(self):
|
def compute_setup_hold(self):
|
||||||
""" Do the analysis if we haven't characterized a FF yet """
|
""" Do the analysis if we haven't characterized a FF yet """
|
||||||
# Do the analysis if we haven't characterized a FF yet
|
# Do the analysis if we haven't characterized a FF yet
|
||||||
|
|
@ -466,3 +502,37 @@ class lib:
|
||||||
else:
|
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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import tech
|
||||||
from .stimuli import *
|
from .stimuli import *
|
||||||
import debug
|
import debug
|
||||||
from .charutils import *
|
from .charutils import *
|
||||||
import ms_flop
|
import dff
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -15,9 +15,9 @@ class setup_hold():
|
||||||
|
|
||||||
def __init__(self, corner):
|
def __init__(self, corner):
|
||||||
# This must match the spice model order
|
# This must match the spice model order
|
||||||
self.pins = ["data", "dout", "dout_bar", "clk", "vdd", "gnd"]
|
self.pins = ["data", "dout", "clk", "vdd", "gnd"]
|
||||||
self.model_name = "ms_flop"
|
self.model_name = "dff"
|
||||||
self.model_location = OPTS.openram_tech + "sp_lib/ms_flop.sp"
|
self.model_location = OPTS.openram_tech + "sp_lib/dff.sp"
|
||||||
self.period = tech.spice["feasible_period"]
|
self.period = tech.spice["feasible_period"]
|
||||||
|
|
||||||
debug.info(2,"Feasible period from technology file: {0} ".format(self.period))
|
debug.info(2,"Feasible period from technology file: {0} ".format(self.period))
|
||||||
|
|
@ -276,17 +276,36 @@ class setup_hold():
|
||||||
HL_setup = []
|
HL_setup = []
|
||||||
LH_hold = []
|
LH_hold = []
|
||||||
HL_hold = []
|
HL_hold = []
|
||||||
|
|
||||||
|
#For debugging, skips characterization and returns dummy values.
|
||||||
|
# i = 1.0
|
||||||
|
# for self.related_input_slew in related_slews:
|
||||||
|
# for self.constrained_input_slew in constrained_slews:
|
||||||
|
# LH_setup.append(i)
|
||||||
|
# HL_setup.append(i+1.0)
|
||||||
|
# LH_hold.append(i+2.0)
|
||||||
|
# HL_hold.append(i+3.0)
|
||||||
|
# i+=4.0
|
||||||
|
|
||||||
|
# times = {"setup_times_LH": LH_setup,
|
||||||
|
# "setup_times_HL": HL_setup,
|
||||||
|
# "hold_times_LH": LH_hold,
|
||||||
|
# "hold_times_HL": HL_hold
|
||||||
|
# }
|
||||||
|
# return times
|
||||||
|
|
||||||
|
|
||||||
for self.related_input_slew in related_slews:
|
for self.related_input_slew in related_slews:
|
||||||
for self.constrained_input_slew in constrained_slews:
|
for self.constrained_input_slew in constrained_slews:
|
||||||
debug.info(1, "Clock slew: {0} Data slew: {1}".format(self.related_input_slew,self.constrained_input_slew))
|
debug.info(1, "Clock slew: {0} Data slew: {1}".format(self.related_input_slew,self.constrained_input_slew))
|
||||||
LH_setup_time = self.setup_LH_time()
|
LH_setup_time = self.setup_LH_time()
|
||||||
debug.info(1, " Setup Time for low_to_high transistion: {0}".format(LH_setup_time))
|
debug.info(1, " Setup Time for low_to_high transition: {0}".format(LH_setup_time))
|
||||||
HL_setup_time = self.setup_HL_time()
|
HL_setup_time = self.setup_HL_time()
|
||||||
debug.info(1, " Setup Time for high_to_low transistion: {0}".format(HL_setup_time))
|
debug.info(1, " Setup Time for high_to_low transition: {0}".format(HL_setup_time))
|
||||||
LH_hold_time = self.hold_LH_time()
|
LH_hold_time = self.hold_LH_time()
|
||||||
debug.info(1, " Hold Time for low_to_high transistion: {0}".format(LH_hold_time))
|
debug.info(1, " Hold Time for low_to_high transition: {0}".format(LH_hold_time))
|
||||||
HL_hold_time = self.hold_HL_time()
|
HL_hold_time = self.hold_HL_time()
|
||||||
debug.info(1, " Hold Time for high_to_low transistion: {0}".format(HL_hold_time))
|
debug.info(1, " Hold Time for high_to_low transition: {0}".format(HL_hold_time))
|
||||||
LH_setup.append(LH_setup_time)
|
LH_setup.append(LH_setup_time)
|
||||||
HL_setup.append(HL_setup_time)
|
HL_setup.append(HL_setup_time)
|
||||||
LH_hold.append(LH_hold_time)
|
LH_hold.append(LH_hold_time)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,222 @@
|
||||||
|
import sys,re,shutil
|
||||||
|
from design import design
|
||||||
|
import debug
|
||||||
|
import math
|
||||||
|
import tech
|
||||||
|
from .stimuli import *
|
||||||
|
from .trim_spice import *
|
||||||
|
from .charutils import *
|
||||||
|
import utils
|
||||||
|
from globals import OPTS
|
||||||
|
|
||||||
|
class simulation():
|
||||||
|
|
||||||
|
def __init__(self, sram, spfile, corner):
|
||||||
|
self.sram = sram
|
||||||
|
|
||||||
|
self.name = self.sram.name
|
||||||
|
self.word_size = self.sram.word_size
|
||||||
|
self.addr_size = self.sram.addr_size
|
||||||
|
self.num_cols = self.sram.num_cols
|
||||||
|
self.num_rows = self.sram.num_rows
|
||||||
|
self.num_banks = self.sram.num_banks
|
||||||
|
self.sp_file = spfile
|
||||||
|
|
||||||
|
self.all_ports = self.sram.all_ports
|
||||||
|
self.readwrite_ports = self.sram.readwrite_ports
|
||||||
|
self.read_ports = self.sram.read_ports
|
||||||
|
self.write_ports = self.sram.write_ports
|
||||||
|
|
||||||
|
def set_corner(self,corner):
|
||||||
|
""" Set the corner values """
|
||||||
|
self.corner = corner
|
||||||
|
(self.process, self.vdd_voltage, self.temperature) = corner
|
||||||
|
|
||||||
|
def set_spice_constants(self):
|
||||||
|
""" sets feasible timing parameters """
|
||||||
|
self.period = tech.spice["feasible_period"]
|
||||||
|
self.slew = tech.spice["rise_time"]*2
|
||||||
|
self.load = tech.spice["msflop_in_cap"]*4
|
||||||
|
self.gnd_voltage = 0
|
||||||
|
|
||||||
|
def set_stimulus_variables(self):
|
||||||
|
# Clock signals
|
||||||
|
self.cycle_times = []
|
||||||
|
self.t_current = 0
|
||||||
|
|
||||||
|
# control signals: only one cs_b for entire multiported sram, one we_b for each write port
|
||||||
|
self.csb_values = [[] for port in self.all_ports]
|
||||||
|
self.web_values = [[] for port in self.readwrite_ports]
|
||||||
|
|
||||||
|
# Three dimensional list to handle each addr and data bits for wach port over the number of checks
|
||||||
|
self.addr_values = [[[] for bit in range(self.addr_size)] for port in self.all_ports]
|
||||||
|
self.data_values = [[[] for bit in range(self.word_size)] for port in self.write_ports]
|
||||||
|
|
||||||
|
# For generating comments in SPICE stimulus
|
||||||
|
self.cycle_comments = []
|
||||||
|
self.fn_cycle_comments = []
|
||||||
|
|
||||||
|
def add_control_one_port(self, port, op):
|
||||||
|
"""Appends control signals for operation to a given port"""
|
||||||
|
#Determine values to write to port
|
||||||
|
web_val = 1
|
||||||
|
csb_val = 1
|
||||||
|
if op == "read":
|
||||||
|
csb_val = 0
|
||||||
|
elif op == "write":
|
||||||
|
csb_val = 0
|
||||||
|
web_val = 0
|
||||||
|
elif op != "noop":
|
||||||
|
debug.error("Could not add control signals for port {0}. Command {1} not recognized".format(port,op),1)
|
||||||
|
|
||||||
|
# Append the values depending on the type of port
|
||||||
|
self.csb_values[port].append(csb_val)
|
||||||
|
# If port is in both lists, add rw control signal. Condition indicates its a RW port.
|
||||||
|
if port in self.readwrite_ports:
|
||||||
|
self.web_values[port].append(web_val)
|
||||||
|
|
||||||
|
def add_data(self, data, port):
|
||||||
|
""" Add the array of data values """
|
||||||
|
debug.check(len(data)==self.word_size, "Invalid data word size.")
|
||||||
|
|
||||||
|
bit = self.word_size - 1
|
||||||
|
for c in data:
|
||||||
|
if c=="0":
|
||||||
|
self.data_values[port][bit].append(0)
|
||||||
|
elif c=="1":
|
||||||
|
self.data_values[port][bit].append(1)
|
||||||
|
else:
|
||||||
|
debug.error("Non-binary data string",1)
|
||||||
|
bit -= 1
|
||||||
|
|
||||||
|
def add_address(self, address, port):
|
||||||
|
""" Add the array of address values """
|
||||||
|
debug.check(len(address)==self.addr_size, "Invalid address size.")
|
||||||
|
|
||||||
|
bit = self.addr_size - 1
|
||||||
|
for c in address:
|
||||||
|
if c=="0":
|
||||||
|
self.addr_values[port][bit].append(0)
|
||||||
|
elif c=="1":
|
||||||
|
self.addr_values[port][bit].append(1)
|
||||||
|
else:
|
||||||
|
debug.error("Non-binary address string",1)
|
||||||
|
bit -= 1
|
||||||
|
|
||||||
|
def add_write(self, comment, address, data, port):
|
||||||
|
""" Add the control values for a write cycle. """
|
||||||
|
debug.check(port in self.write_ports, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_ports))
|
||||||
|
debug.info(2, comment)
|
||||||
|
self.fn_cycle_comments.append(comment)
|
||||||
|
self.append_cycle_comment(port, comment)
|
||||||
|
|
||||||
|
self.cycle_times.append(self.t_current)
|
||||||
|
self.t_current += self.period
|
||||||
|
|
||||||
|
self.add_control_one_port(port, "write")
|
||||||
|
self.add_data(data,port)
|
||||||
|
self.add_address(address,port)
|
||||||
|
|
||||||
|
#This value is hard coded here. Possibly change to member variable or set in add_noop_one_port
|
||||||
|
noop_data = "0"*self.word_size
|
||||||
|
#Add noops to all other ports.
|
||||||
|
for unselected_port in self.all_ports:
|
||||||
|
if unselected_port != port:
|
||||||
|
self.add_noop_one_port(address, noop_data, unselected_port)
|
||||||
|
|
||||||
|
def add_read(self, comment, address, din_data, port):
|
||||||
|
""" Add the control values for a read cycle. """
|
||||||
|
debug.check(port in self.read_ports, "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, self.read_ports))
|
||||||
|
debug.info(2, comment)
|
||||||
|
self.fn_cycle_comments.append(comment)
|
||||||
|
self.append_cycle_comment(port, comment)
|
||||||
|
|
||||||
|
self.cycle_times.append(self.t_current)
|
||||||
|
self.t_current += self.period
|
||||||
|
self.add_control_one_port(port, "read")
|
||||||
|
|
||||||
|
#If the port is also a readwrite then add data.
|
||||||
|
if port in self.write_ports:
|
||||||
|
self.add_data(din_data,port)
|
||||||
|
self.add_address(address, port)
|
||||||
|
|
||||||
|
#This value is hard coded here. Possibly change to member variable or set in add_noop_one_port
|
||||||
|
noop_data = "0"*self.word_size
|
||||||
|
#Add noops to all other ports.
|
||||||
|
for unselected_port in self.all_ports:
|
||||||
|
if unselected_port != port:
|
||||||
|
self.add_noop_one_port(address, noop_data, unselected_port)
|
||||||
|
|
||||||
|
def add_noop_all_ports(self, comment, address, data):
|
||||||
|
""" Add the control values for a noop to all ports. """
|
||||||
|
debug.info(2, comment)
|
||||||
|
self.fn_cycle_comments.append(comment)
|
||||||
|
self.append_cycle_comment("All", comment)
|
||||||
|
|
||||||
|
self.cycle_times.append(self.t_current)
|
||||||
|
self.t_current += self.period
|
||||||
|
|
||||||
|
for port in self.all_ports:
|
||||||
|
self.add_noop_one_port(address, data, port)
|
||||||
|
|
||||||
|
def add_write_one_port(self, comment, address, data, port):
|
||||||
|
""" Add the control values for a write cycle. Does not increment the period. """
|
||||||
|
debug.check(port in self.write_ports, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_ports))
|
||||||
|
debug.info(2, comment)
|
||||||
|
self.fn_cycle_comments.append(comment)
|
||||||
|
|
||||||
|
self.add_control_one_port(port, "write")
|
||||||
|
self.add_data(data,port)
|
||||||
|
self.add_address(address,port)
|
||||||
|
|
||||||
|
def add_read_one_port(self, comment, address, din_data, port):
|
||||||
|
""" Add the control values for a read cycle. Does not increment the period. """
|
||||||
|
debug.check(port in self.read_ports, "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, self.read_ports))
|
||||||
|
debug.info(2, comment)
|
||||||
|
self.fn_cycle_comments.append(comment)
|
||||||
|
|
||||||
|
self.add_control_one_port(port, "read")
|
||||||
|
#If the port is also a readwrite then add data.
|
||||||
|
if port in self.write_ports:
|
||||||
|
self.add_data(din_data,port)
|
||||||
|
self.add_address(address, port)
|
||||||
|
|
||||||
|
def add_noop_one_port(self, address, data, port):
|
||||||
|
""" Add the control values for a noop to a single port. Does not increment the period. """
|
||||||
|
self.add_control_one_port(port, "noop")
|
||||||
|
if port in self.write_ports:
|
||||||
|
self.add_data(data,port)
|
||||||
|
self.add_address(address, port)
|
||||||
|
|
||||||
|
def append_cycle_comment(self, port, comment):
|
||||||
|
"""Add comment to list to be printed in stimulus file"""
|
||||||
|
#Clean up time before appending. Make spacing dynamic as well.
|
||||||
|
time = "{0:.2f} ns:".format(self.t_current)
|
||||||
|
time_spacing = len(time)+6
|
||||||
|
self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times),
|
||||||
|
port,
|
||||||
|
time,
|
||||||
|
time_spacing,
|
||||||
|
comment))
|
||||||
|
|
||||||
|
def gen_cycle_comment(self, op, word, addr, port, t_current):
|
||||||
|
if op == "noop":
|
||||||
|
comment = "\tIdle during cycle {0} ({1}ns - {2}ns)".format(int(t_current/self.period),
|
||||||
|
t_current,
|
||||||
|
t_current+self.period)
|
||||||
|
elif op == "write":
|
||||||
|
comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word,
|
||||||
|
addr,
|
||||||
|
port,
|
||||||
|
int(t_current/self.period),
|
||||||
|
t_current,
|
||||||
|
t_current+self.period)
|
||||||
|
else:
|
||||||
|
comment = "\tReading {0} from address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word,
|
||||||
|
addr,
|
||||||
|
port,
|
||||||
|
int(t_current/self.period),
|
||||||
|
t_current,
|
||||||
|
t_current+self.period)
|
||||||
|
return comment
|
||||||
|
|
||||||
|
|
@ -28,24 +28,53 @@ class stimuli():
|
||||||
|
|
||||||
(self.process, self.voltage, self.temperature) = corner
|
(self.process, self.voltage, self.temperature) = corner
|
||||||
self.device_models = tech.spice["fet_models"][self.process]
|
self.device_models = tech.spice["fet_models"][self.process]
|
||||||
|
|
||||||
|
|
||||||
def inst_sram(self, abits, dbits, sram_name):
|
def inst_sram(self, sram, port_signal_names, port_info, abits, dbits, sram_name):
|
||||||
""" Function to instatiate an SRAM subckt. """
|
""" Function to instatiate an SRAM subckt. """
|
||||||
|
pin_names = self.gen_pin_names(port_signal_names, port_info, abits, dbits)
|
||||||
|
#Only checking length. This should check functionality as well (TODO) and/or import that information from the SRAM
|
||||||
|
debug.check(len(sram.pins) == len(pin_names), "Number of pins generated for characterization do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(sram.pins,pin_names))
|
||||||
|
|
||||||
self.sf.write("Xsram ")
|
self.sf.write("Xsram ")
|
||||||
for i in range(dbits):
|
for pin in pin_names:
|
||||||
self.sf.write("DIN[{0}] ".format(i))
|
self.sf.write("{0} ".format(pin))
|
||||||
for i in range(abits):
|
|
||||||
self.sf.write("A[{0}] ".format(i))
|
|
||||||
for i in tech.spice["control_signals"]:
|
|
||||||
self.sf.write("{0} ".format(i))
|
|
||||||
self.sf.write("{0} ".format(tech.spice["clk"]))
|
|
||||||
for i in range(dbits):
|
|
||||||
self.sf.write("DOUT[{0}] ".format(i))
|
|
||||||
self.sf.write("{0} {1} ".format(self.vdd_name, self.gnd_name))
|
|
||||||
self.sf.write("{0}\n".format(sram_name))
|
self.sf.write("{0}\n".format(sram_name))
|
||||||
|
|
||||||
|
def gen_pin_names(self, port_signal_names, port_info, abits, dbits):
|
||||||
|
"""Creates the pins names of the SRAM based on the no. of ports."""
|
||||||
|
#This may seem redundant as the pin names are already defined in the sram. However, it is difficult to extract the
|
||||||
|
#functionality from the names, so they are recreated. As the order is static, changing the order of the pin names
|
||||||
|
#will cause issues here.
|
||||||
|
pin_names = []
|
||||||
|
(addr_name, din_name, dout_name) = port_signal_names
|
||||||
|
(total_ports, write_index, read_index) = port_info
|
||||||
|
|
||||||
|
for write_input in write_index:
|
||||||
|
for i in range(dbits):
|
||||||
|
pin_names.append("{0}{1}_{2}".format(din_name,write_input, i))
|
||||||
|
|
||||||
|
for port in range(total_ports):
|
||||||
|
for i in range(abits):
|
||||||
|
pin_names.append("{0}{1}_{2}".format(addr_name,port,i))
|
||||||
|
|
||||||
|
#Control signals not finalized.
|
||||||
|
for port in range(total_ports):
|
||||||
|
pin_names.append("CSB{0}".format(port))
|
||||||
|
for port in range(total_ports):
|
||||||
|
if (port in read_index) and (port in write_index):
|
||||||
|
pin_names.append("WEB{0}".format(port))
|
||||||
|
|
||||||
|
for port in range(total_ports):
|
||||||
|
pin_names.append("{0}{1}".format(tech.spice["clk"], port))
|
||||||
|
|
||||||
|
for read_output in read_index:
|
||||||
|
for i in range(dbits):
|
||||||
|
pin_names.append("{0}{1}_{2}".format(dout_name,read_output, i))
|
||||||
|
|
||||||
|
pin_names.append("{0}".format(self.vdd_name))
|
||||||
|
pin_names.append("{0}".format(self.gnd_name))
|
||||||
|
return pin_names
|
||||||
|
|
||||||
def inst_model(self, pins, model_name):
|
def inst_model(self, pins, model_name):
|
||||||
""" Function to instantiate a generic model with a set of pins """
|
""" Function to instantiate a generic model with a set of pins """
|
||||||
self.sf.write("X{0} ".format(model_name))
|
self.sf.write("X{0} ".format(model_name))
|
||||||
|
|
@ -138,7 +167,7 @@ class stimuli():
|
||||||
to the initial value.
|
to the initial value.
|
||||||
"""
|
"""
|
||||||
# the initial value is not a clock time
|
# the initial value is not a clock time
|
||||||
debug.check(len(clk_times)==len(data_values),"Clock and data value lengths don't match.")
|
debug.check(len(clk_times)==len(data_values),"Clock and data value lengths don't match. {0} clock values, {1} data values for {2}".format(len(clk_times), len(data_values), sig_name))
|
||||||
|
|
||||||
# shift signal times earlier for setup time
|
# shift signal times earlier for setup time
|
||||||
times = np.array(clk_times) - setup*period
|
times = np.array(clk_times) - setup*period
|
||||||
|
|
@ -198,9 +227,24 @@ class stimuli():
|
||||||
power_exp,
|
power_exp,
|
||||||
t_initial,
|
t_initial,
|
||||||
t_final))
|
t_final))
|
||||||
|
|
||||||
|
def gen_meas_value(self, meas_name, dout, t_intital, t_final):
|
||||||
|
measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_intital, t_final)
|
||||||
|
self.sf.write(measure_string)
|
||||||
|
|
||||||
def write_control(self, end_time):
|
def write_control(self, end_time, runlvl=4):
|
||||||
""" Write the control cards to run and end the simulation """
|
""" Write the control cards to run and end the simulation """
|
||||||
|
|
||||||
|
# These are guesses...
|
||||||
|
if runlvl==1:
|
||||||
|
reltol = 0.02 # 2%
|
||||||
|
elif runlvl==2:
|
||||||
|
reltol = 0.01 # 1%
|
||||||
|
elif runlvl==3:
|
||||||
|
reltol = 0.005 # 0.5%
|
||||||
|
else:
|
||||||
|
reltol = 0.001 # 0.1%
|
||||||
|
|
||||||
# UIC is needed for ngspice to converge
|
# UIC is needed for ngspice to converge
|
||||||
self.sf.write(".TRAN 5p {0}n UIC\n".format(end_time))
|
self.sf.write(".TRAN 5p {0}n UIC\n".format(end_time))
|
||||||
if OPTS.spice_name == "ngspice":
|
if OPTS.spice_name == "ngspice":
|
||||||
|
|
@ -208,9 +252,9 @@ class stimuli():
|
||||||
# which is more accurate, but slower than the default trapezoid method
|
# which is more accurate, but slower than the default trapezoid method
|
||||||
# Do not remove this or it may not converge due to some "pa_00" nodes
|
# Do not remove this or it may not converge due to some "pa_00" nodes
|
||||||
# unless you figure out what these are.
|
# unless you figure out what these are.
|
||||||
self.sf.write(".OPTIONS POST=1 RUNLVL=4 PROBE method=gear TEMP={}\n".format(self.temperature))
|
self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear TEMP={1}\n".format(reltol,self.temperature))
|
||||||
else:
|
else:
|
||||||
self.sf.write(".OPTIONS POST=1 RUNLVL=4 PROBE TEMP={}\n".format(self.temperature))
|
self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE TEMP={1}\n".format(runlvl,self.temperature))
|
||||||
|
|
||||||
# create plots for all signals
|
# create plots for all signals
|
||||||
self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n")
|
self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n")
|
||||||
|
|
@ -240,12 +284,15 @@ class stimuli():
|
||||||
|
|
||||||
def write_supply(self):
|
def write_supply(self):
|
||||||
""" Writes supply voltage statements """
|
""" Writes supply voltage statements """
|
||||||
self.sf.write("V{0} {0} 0.0 {1}\n".format(self.vdd_name, self.voltage))
|
gnd_node_name = "0"
|
||||||
self.sf.write("V{0} {0} 0.0 {1}\n".format(self.gnd_name, 0))
|
self.sf.write("V{0} {0} {1} {2}\n".format(self.vdd_name, gnd_node_name, self.voltage))
|
||||||
# This is for the test power supply
|
# This is for the test power supply
|
||||||
self.sf.write("V{0} {0} 0.0 {1}\n".format("test"+self.vdd_name, self.voltage))
|
self.sf.write("V{0} {0} {1} {2}\n".format("test"+self.vdd_name, gnd_node_name, self.voltage))
|
||||||
self.sf.write("V{0} {0} 0.0 {1}\n".format("test"+self.gnd_name, 0))
|
self.sf.write("V{0} {0} {1} {2}\n".format("test"+self.gnd_name, gnd_node_name, 0.0))
|
||||||
|
|
||||||
|
#Adding a commented out supply for simulators where gnd and 0 are not global grounds.
|
||||||
|
self.sf.write("\n*Nodes gnd and 0 are the same global ground node in ngspice/hspice/xa. Otherwise, this source may be needed.\n")
|
||||||
|
self.sf.write("*V{0} {0} {1} {2}\n".format(self.gnd_name, gnd_node_name, 0.0))
|
||||||
|
|
||||||
def run_sim(self):
|
def run_sim(self):
|
||||||
""" Run hspice in batch mode and output rawfile to parse. """
|
""" Run hspice in batch mode and output rawfile to parse. """
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import debug
|
import debug
|
||||||
from math import log
|
from math import log
|
||||||
|
import re
|
||||||
|
|
||||||
class trim_spice():
|
class trim_spice():
|
||||||
"""
|
"""
|
||||||
|
|
@ -54,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)
|
||||||
|
|
@ -73,25 +74,28 @@ class trim_spice():
|
||||||
self.sp_buffer.insert(0, "* It should NOT be used for LVS!!")
|
self.sp_buffer.insert(0, "* It should NOT be used for LVS!!")
|
||||||
self.sp_buffer.insert(0, "* WARNING: This is a TRIMMED NETLIST.")
|
self.sp_buffer.insert(0, "* WARNING: This is a TRIMMED NETLIST.")
|
||||||
|
|
||||||
self.remove_insts("bitcell_array",[wl_name,bl_name])
|
|
||||||
|
wl_regex = r"wl\d*_{}".format(wl_address)
|
||||||
|
bl_regex = r"bl\d*_{}".format(int(self.words_per_row*data_bit + col_address))
|
||||||
|
self.remove_insts("bitcell_array",[wl_regex,bl_regex])
|
||||||
|
|
||||||
# 2. Keep sense amps basd on BL
|
# 2. Keep sense amps basd on BL
|
||||||
# FIXME: The bit lines are not indexed the same in sense_amp_array
|
# FIXME: The bit lines are not indexed the same in sense_amp_array
|
||||||
#self.remove_insts("sense_amp_array",[bl_name])
|
#self.remove_insts("sense_amp_array",[bl_regex])
|
||||||
|
|
||||||
# 3. Keep column muxes basd on BL
|
# 3. Keep column muxes basd on BL
|
||||||
self.remove_insts("column_mux_array",[bl_name])
|
self.remove_insts("column_mux_array",[bl_regex])
|
||||||
|
|
||||||
# 4. Keep write driver based on DATA
|
# 4. Keep write driver based on DATA
|
||||||
data_name = "data[{}]".format(data_bit)
|
data_regex = r"data_{}".format(data_bit)
|
||||||
self.remove_insts("write_driver_array",[data_name])
|
self.remove_insts("write_driver_array",[data_regex])
|
||||||
|
|
||||||
# 5. Keep wordline driver based on WL
|
# 5. Keep wordline driver based on WL
|
||||||
# Need to keep the gater too
|
# Need to keep the gater too
|
||||||
#self.remove_insts("wordline_driver",wl_name)
|
#self.remove_insts("wordline_driver",wl_regex)
|
||||||
|
|
||||||
# 6. Keep precharges based on BL
|
# 6. Keep precharges based on BL
|
||||||
self.remove_insts("precharge_array",[bl_name])
|
self.remove_insts("precharge_array",[bl_regex])
|
||||||
|
|
||||||
# Everything else isn't worth removing. :)
|
# Everything else isn't worth removing. :)
|
||||||
|
|
||||||
|
|
@ -107,6 +111,10 @@ class trim_spice():
|
||||||
match of the line with a term so you can search for a single
|
match of the line with a term so you can search for a single
|
||||||
net connection, the instance name, anything..
|
net connection, the instance name, anything..
|
||||||
"""
|
"""
|
||||||
|
removed_insts = 0
|
||||||
|
#Expects keep_inst_list are regex patterns. Compile them here.
|
||||||
|
compiled_patterns = [re.compile(pattern) for pattern in keep_inst_list]
|
||||||
|
|
||||||
start_name = ".SUBCKT {}".format(subckt_name)
|
start_name = ".SUBCKT {}".format(subckt_name)
|
||||||
end_name = ".ENDS {}".format(subckt_name)
|
end_name = ".ENDS {}".format(subckt_name)
|
||||||
|
|
||||||
|
|
@ -120,11 +128,14 @@ class trim_spice():
|
||||||
new_buffer.append(line)
|
new_buffer.append(line)
|
||||||
in_subckt=False
|
in_subckt=False
|
||||||
elif in_subckt:
|
elif in_subckt:
|
||||||
for k in keep_inst_list:
|
removed_insts += 1
|
||||||
if k in line:
|
for pattern in compiled_patterns:
|
||||||
|
if pattern.search(line) != None:
|
||||||
new_buffer.append(line)
|
new_buffer.append(line)
|
||||||
|
removed_insts -= 1
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
new_buffer.append(line)
|
new_buffer.append(line)
|
||||||
|
|
||||||
self.sp_buffer = new_buffer
|
self.sp_buffer = new_buffer
|
||||||
|
debug.info(2, "Removed {} instances from {} subcircuit.".format(removed_insts, subckt_name))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import sys,re,shutil
|
||||||
|
import debug
|
||||||
|
import tech
|
||||||
|
import math
|
||||||
|
from .stimuli import *
|
||||||
|
from .trim_spice import *
|
||||||
|
from .charutils import *
|
||||||
|
import utils
|
||||||
|
from globals import OPTS
|
||||||
|
from .delay import delay
|
||||||
|
|
||||||
|
class worst_case(delay):
|
||||||
|
"""Functions to test for the worst case delay in a target SRAM
|
||||||
|
|
||||||
|
The current worst case determines a feasible period for the SRAM then tests
|
||||||
|
several bits and record the delay and differences between the bits.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, sram, spfile, corner):
|
||||||
|
delay.__init__(self,sram,spfile,corner)
|
||||||
|
|
||||||
|
|
||||||
|
def analyze(self,probe_address, probe_data, slews, loads):
|
||||||
|
"""
|
||||||
|
Main function to test the delays of different bits.
|
||||||
|
"""
|
||||||
|
debug.check(OPTS.num_rw_ports < 2 and OPTS.num_w_ports < 1 and OPTS.num_r_ports < 1 ,
|
||||||
|
"Bit testing does not currently support multiport.")
|
||||||
|
#Dict to hold all characterization values
|
||||||
|
char_sram_data = {}
|
||||||
|
|
||||||
|
self.set_probe(probe_address, probe_data)
|
||||||
|
#self.prepare_netlist()
|
||||||
|
|
||||||
|
self.load=max(loads)
|
||||||
|
self.slew=max(slews)
|
||||||
|
|
||||||
|
# 1) Find a feasible period and it's corresponding delays using the trimmed array.
|
||||||
|
feasible_delays = self.find_feasible_period()
|
||||||
|
|
||||||
|
# 2) Find the delays of several bits
|
||||||
|
test_bits = self.get_test_bits()
|
||||||
|
bit_delays = self.simulate_for_bit_delays(test_bits)
|
||||||
|
|
||||||
|
for i in range(len(test_bits)):
|
||||||
|
debug.info(1, "Bit tested: addr {0[0]} data_pos {0[1]}\n Values {1}".format(test_bits[i], bit_delays[i]))
|
||||||
|
|
||||||
|
def simulate_for_bit_delays(self, test_bits):
|
||||||
|
"""Simulates the delay of the sram of over several bits."""
|
||||||
|
bit_delays = [{} for i in range(len(test_bits))]
|
||||||
|
|
||||||
|
#Assumes a bitcell with only 1 rw port. (6t, port 0)
|
||||||
|
port = 0
|
||||||
|
self.targ_read_ports = [self.read_ports[port]]
|
||||||
|
self.targ_write_ports = [self.write_ports[port]]
|
||||||
|
|
||||||
|
for i in range(len(test_bits)):
|
||||||
|
(bit_addr, bit_data) = test_bits[i]
|
||||||
|
self.set_probe(bit_addr, bit_data)
|
||||||
|
debug.info(1,"Delay bit test: period {}, addr {}, data_pos {}".format(self.period, bit_addr, bit_data))
|
||||||
|
(success, results)=self.run_delay_simulation()
|
||||||
|
debug.check(success, "Bit Test Failed: period {}, addr {}, data_pos {}".format(self.period, bit_addr, bit_data))
|
||||||
|
bit_delays[i] = results[port]
|
||||||
|
|
||||||
|
return bit_delays
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_bits(self):
|
||||||
|
"""Statically determines address and bit values to test"""
|
||||||
|
#First and last address, first middle, and last bit. Last bit is repeated twice with different data position.
|
||||||
|
bit_addrs = ["0"*self.addr_size, "0"+"1"*(self.addr_size-1), "1"*self.addr_size, "1"*self.addr_size]
|
||||||
|
data_positions = [0, (self.word_size-1)//2, 0, self.word_size-1]
|
||||||
|
#Return them in a tuple form
|
||||||
|
return [(bit_addrs[i], data_positions[i]) for i in range(len(bit_addrs))]
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
word_size = 2
|
word_size = 2
|
||||||
num_words = 16
|
num_words = 16
|
||||||
num_banks = 1
|
|
||||||
|
|
||||||
tech_name = "freepdk45"
|
tech_name = "freepdk45"
|
||||||
process_corners = ["TT"]
|
process_corners = ["TT"]
|
||||||
|
|
@ -8,5 +7,5 @@ supply_voltages = [1.0]
|
||||||
temperatures = [25]
|
temperatures = [25]
|
||||||
|
|
||||||
output_path = "temp"
|
output_path = "temp"
|
||||||
output_name = "sram_2_16_1_freepdk45"
|
output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
word_size = 2
|
|
||||||
num_words = 16
|
|
||||||
num_banks = 1
|
|
||||||
|
|
||||||
tech_name = "scn3me_subm"
|
|
||||||
process_corners = ["TT"]
|
|
||||||
supply_voltages = [ 5.0 ]
|
|
||||||
temperatures = [ 25 ]
|
|
||||||
|
|
||||||
|
|
||||||
output_path = "temp"
|
|
||||||
output_name = "sram_2_16_1_scn3me_subm"
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
word_size = 2
|
||||||
|
num_words = 16
|
||||||
|
|
||||||
|
tech_name = "scn4m_subm"
|
||||||
|
process_corners = ["TT"]
|
||||||
|
supply_voltages = [ 3.3 ]
|
||||||
|
temperatures = [ 25 ]
|
||||||
|
|
||||||
|
output_path = "temp"
|
||||||
|
output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name)
|
||||||
|
|
||||||
|
drc_name = "magic"
|
||||||
|
lvs_name = "netgen"
|
||||||
|
pex_name = "magic"
|
||||||
|
|
@ -294,7 +294,7 @@ class Gds2reader:
|
||||||
mirrorFlag = bool(transFlags&0x8000) ##these flags are a bit sketchy
|
mirrorFlag = bool(transFlags&0x8000) ##these flags are a bit sketchy
|
||||||
rotateFlag = bool(transFlags&0x0002)
|
rotateFlag = bool(transFlags&0x0002)
|
||||||
magnifyFlag = bool(transFlags&0x0004)
|
magnifyFlag = bool(transFlags&0x0004)
|
||||||
thisSref.transFlags=(mirrorFlag,rotateFlag,magnifyFlag)
|
thisSref.transFlags=[mirrorFlag,magnifyFlag,rotateFlag]
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tMirror X:"+str(mirrorFlag))
|
print("\t\t\tMirror X:"+str(mirrorFlag))
|
||||||
print( "\t\t\tRotate:"+str(rotateFlag))
|
print( "\t\t\tRotate:"+str(rotateFlag))
|
||||||
|
|
@ -345,7 +345,7 @@ class Gds2reader:
|
||||||
mirrorFlag = bool(transFlags&0x8000) ##these flags are a bit sketchy
|
mirrorFlag = bool(transFlags&0x8000) ##these flags are a bit sketchy
|
||||||
rotateFlag = bool(transFlags&0x0002)
|
rotateFlag = bool(transFlags&0x0002)
|
||||||
magnifyFlag = bool(transFlags&0x0004)
|
magnifyFlag = bool(transFlags&0x0004)
|
||||||
thisAref.transFlags=(mirrorFlag,rotateFlag,magnifyFlag)
|
thisAref.transFlags=[mirrorFlag,magnifyFlag,rotateFlag]
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tMirror X:"+str(mirrorFlag))
|
print("\t\t\tMirror X:"+str(mirrorFlag))
|
||||||
print("\t\t\tRotate:"+str(rotateFlag))
|
print("\t\t\tRotate:"+str(rotateFlag))
|
||||||
|
|
@ -408,7 +408,7 @@ class Gds2reader:
|
||||||
mirrorFlag = bool(transFlags&0x8000) ##these flags are a bit sketchy
|
mirrorFlag = bool(transFlags&0x8000) ##these flags are a bit sketchy
|
||||||
rotateFlag = bool(transFlags&0x0002)
|
rotateFlag = bool(transFlags&0x0002)
|
||||||
magnifyFlag = bool(transFlags&0x0004)
|
magnifyFlag = bool(transFlags&0x0004)
|
||||||
thisText.transFlags=(mirrorFlag,rotateFlag,magnifyFlag)
|
thisText.transFlags=[mirrorFlag,magnifyFlag,rotateFlag]
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tMirror X:"+str(mirrorFlag))
|
print("\t\t\tMirror X:"+str(mirrorFlag))
|
||||||
print("\t\t\tRotate:"+str(rotateFlag))
|
print("\t\t\tRotate:"+str(rotateFlag))
|
||||||
|
|
|
||||||
|
|
@ -280,8 +280,13 @@ class Gds2writer:
|
||||||
if(thisSref.transFlags!=""):
|
if(thisSref.transFlags!=""):
|
||||||
idBits=b'\x1A\x01'
|
idBits=b'\x1A\x01'
|
||||||
mirrorFlag = int(thisSref.transFlags[0])<<15
|
mirrorFlag = int(thisSref.transFlags[0])<<15
|
||||||
rotateFlag = int(thisSref.transFlags[1])<<1
|
# The rotate and magnify flags specify "absolute" rotate and magnify.
|
||||||
magnifyFlag = int(thisSref.transFlags[2])<<3
|
# It is unclear what that is (ignore all further rotates/mags in the
|
||||||
|
# hierarchy? But anyway, calibre doesn't support it.
|
||||||
|
rotateFlag=0
|
||||||
|
magnifyFlag = 0
|
||||||
|
#rotateFlag = int(thisSref.transFlags[2])<<1
|
||||||
|
#magnifyFlag = int(thisSref.transFlags[1])<<2
|
||||||
transFlags = struct.pack(">H",mirrorFlag|rotateFlag|magnifyFlag)
|
transFlags = struct.pack(">H",mirrorFlag|rotateFlag|magnifyFlag)
|
||||||
self.writeRecord(idBits+transFlags)
|
self.writeRecord(idBits+transFlags)
|
||||||
if(thisSref.magFactor!=""):
|
if(thisSref.magFactor!=""):
|
||||||
|
|
@ -327,15 +332,20 @@ class Gds2writer:
|
||||||
if(thisAref.transFlags):
|
if(thisAref.transFlags):
|
||||||
idBits=b'\x1A\x01'
|
idBits=b'\x1A\x01'
|
||||||
mirrorFlag = int(thisAref.transFlags[0])<<15
|
mirrorFlag = int(thisAref.transFlags[0])<<15
|
||||||
rotateFlag = int(thisAref.transFlags[1])<<1
|
# The rotate and magnify flags specify "absolute" rotate and magnify.
|
||||||
magnifyFlag = int(thisAref.transFlags[0])<<3
|
# It is unclear what that is (ignore all further rotates/mags in the
|
||||||
|
# hierarchy? But anyway, calibre doesn't support it.
|
||||||
|
rotateFlag=0
|
||||||
|
magnifyFlag = 0
|
||||||
|
#rotateFlag = int(thisAref.transFlags[2])<<1
|
||||||
|
#magnifyFlag = int(thisAref.transFlags[1])<<2
|
||||||
transFlags = struct.pack(">H",mirrorFlag|rotateFlag|magnifyFlag)
|
transFlags = struct.pack(">H",mirrorFlag|rotateFlag|magnifyFlag)
|
||||||
self.writeRecord(idBits+transFlags)
|
self.writeRecord(idBits+transFlags)
|
||||||
if(thisAref.magFactor):
|
if(thisAref.magFactor!=""):
|
||||||
idBits=b'\x1B\x05'
|
idBits=b'\x1B\x05'
|
||||||
magFactor=self.ibmDataFromIeeeDouble(thisAref.magFactor)
|
magFactor=self.ibmDataFromIeeeDouble(thisAref.magFactor)
|
||||||
self.writeRecord(idBits+magFactor)
|
self.writeRecord(idBits+magFactor)
|
||||||
if(thisAref.rotateAngle):
|
if(thisAref.rotateAngle!=""):
|
||||||
idBits=b'\x1C\x05'
|
idBits=b'\x1C\x05'
|
||||||
rotateAngle=self.ibmDataFromIeeeDouble(thisAref.rotateAngle)
|
rotateAngle=self.ibmDataFromIeeeDouble(thisAref.rotateAngle)
|
||||||
self.writeRecord(idBits+rotateAngle)
|
self.writeRecord(idBits+rotateAngle)
|
||||||
|
|
@ -374,15 +384,20 @@ class Gds2writer:
|
||||||
if(thisText.transFlags != ""):
|
if(thisText.transFlags != ""):
|
||||||
idBits=b'\x1A\x01'
|
idBits=b'\x1A\x01'
|
||||||
mirrorFlag = int(thisText.transFlags[0])<<15
|
mirrorFlag = int(thisText.transFlags[0])<<15
|
||||||
rotateFlag = int(thisText.transFlags[1])<<1
|
# The rotate and magnify flags specify "absolute" rotate and magnify.
|
||||||
magnifyFlag = int(thisText.transFlags[0])<<3
|
# It is unclear what that is (ignore all further rotates/mags in the
|
||||||
|
# hierarchy? But anyway, calibre doesn't support it.
|
||||||
|
rotateFlag=0
|
||||||
|
magnifyFlag = 0
|
||||||
|
#rotateFlag = int(thisText.transFlags[2])<<1
|
||||||
|
#magnifyFlag = int(thisText.transFlags[1])<<2
|
||||||
transFlags = struct.pack(">H",mirrorFlag|rotateFlag|magnifyFlag)
|
transFlags = struct.pack(">H",mirrorFlag|rotateFlag|magnifyFlag)
|
||||||
self.writeRecord(idBits+transFlags)
|
self.writeRecord(idBits+transFlags)
|
||||||
if(thisText.magFactor != ""):
|
if(thisText.magFactor!=""):
|
||||||
idBits=b'\x1B\x05'
|
idBits=b'\x1B\x05'
|
||||||
magFactor=self.ibmDataFromIeeeDouble(thisText.magFactor)
|
magFactor=self.ibmDataFromIeeeDouble(thisText.magFactor)
|
||||||
self.writeRecord(idBits+magFactor)
|
self.writeRecord(idBits+magFactor)
|
||||||
if(thisText.rotateAngle != ""):
|
if(thisText.rotateAngle!=""):
|
||||||
idBits=b'\x1C\x05'
|
idBits=b'\x1C\x05'
|
||||||
rotateAngle=self.ibmDataFromIeeeDouble(thisText.rotateAngle)
|
rotateAngle=self.ibmDataFromIeeeDouble(thisText.rotateAngle)
|
||||||
self.writeRecord(idBits+rotateAngle)
|
self.writeRecord(idBits+rotateAngle)
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ class GdsSref:
|
||||||
self.elementFlags=""
|
self.elementFlags=""
|
||||||
self.plex=""
|
self.plex=""
|
||||||
self.sName=""
|
self.sName=""
|
||||||
self.transFlags=(False,False,False)
|
self.transFlags=[0,0,0]
|
||||||
self.magFactor=""
|
self.magFactor=""
|
||||||
self.rotateAngle=""
|
self.rotateAngle=""
|
||||||
self.coordinates=""
|
self.coordinates=""
|
||||||
|
|
@ -129,7 +129,7 @@ class GdsAref:
|
||||||
self.elementFlags=""
|
self.elementFlags=""
|
||||||
self.plex=""
|
self.plex=""
|
||||||
self.aName=""
|
self.aName=""
|
||||||
self.transFlags=(False,False,False)
|
self.transFlags=[0,0,0]
|
||||||
self.magFactor=""
|
self.magFactor=""
|
||||||
self.rotateAngle=""
|
self.rotateAngle=""
|
||||||
self.coordinates=""
|
self.coordinates=""
|
||||||
|
|
@ -141,7 +141,7 @@ class GdsText:
|
||||||
self.plex=""
|
self.plex=""
|
||||||
self.drawingLayer=""
|
self.drawingLayer=""
|
||||||
self.purposeLayer = None
|
self.purposeLayer = None
|
||||||
self.transFlags=(False,False,False)
|
self.transFlags=[0,0,0]
|
||||||
self.magFactor=""
|
self.magFactor=""
|
||||||
self.rotateAngle=""
|
self.rotateAngle=""
|
||||||
self.pathType=""
|
self.pathType=""
|
||||||
|
|
@ -167,4 +167,4 @@ class GdsBox:
|
||||||
self.drawingLayer=""
|
self.drawingLayer=""
|
||||||
self.purposeLayer = None
|
self.purposeLayer = None
|
||||||
self.boxValue=""
|
self.boxValue=""
|
||||||
self.coordinates=""
|
self.coordinates=""
|
||||||
|
|
|
||||||
|
|
@ -60,17 +60,23 @@ class VlsiLayout:
|
||||||
self.tempCoordinates=None
|
self.tempCoordinates=None
|
||||||
self.tempPassFail = True
|
self.tempPassFail = True
|
||||||
|
|
||||||
|
# This is a dict indexed by the pin labels.
|
||||||
|
# It contains a list of list of shapes, one for each occurance of the label.
|
||||||
|
# Multiple labels may be disconnected.
|
||||||
|
self.pins = {}
|
||||||
|
|
||||||
def rotatedCoordinates(self,coordinatesToRotate,rotateAngle):
|
def rotatedCoordinates(self,coordinatesToRotate,rotateAngle):
|
||||||
#helper method to rotate a list of coordinates
|
#helper method to rotate a list of coordinates
|
||||||
angle=math.radians(float(0))
|
angle=math.radians(float(0))
|
||||||
if(rotateAngle):
|
if(rotateAngle):
|
||||||
angle = math.radians(float(repr(rotateAngle)))
|
angle = math.radians(float(rotateAngle))
|
||||||
|
|
||||||
coordinatesRotate = [] #this will hold the rotated values
|
coordinatesRotate = [] #this will hold the rotated values
|
||||||
for coordinate in coordinatesToRotate:
|
for coordinate in coordinatesToRotate:
|
||||||
|
# This is the CCW rotation matrix
|
||||||
newX = coordinate[0]*math.cos(angle) - coordinate[1]*math.sin(angle)
|
newX = coordinate[0]*math.cos(angle) - coordinate[1]*math.sin(angle)
|
||||||
newY = coordinate[0]*math.sin(angle) + coordinate[1]*math.cos(angle)
|
newY = coordinate[0]*math.sin(angle) + coordinate[1]*math.cos(angle)
|
||||||
coordinatesRotate += [(newX,newY)]
|
coordinatesRotate.extend((newX,newY))
|
||||||
return coordinatesRotate
|
return coordinatesRotate
|
||||||
|
|
||||||
def rename(self,newName):
|
def rename(self,newName):
|
||||||
|
|
@ -141,7 +147,7 @@ class VlsiLayout:
|
||||||
contained by any other structure. this is the root."""
|
contained by any other structure. this is the root."""
|
||||||
structureNames=[]
|
structureNames=[]
|
||||||
for name in self.structures:
|
for name in self.structures:
|
||||||
structureNames+=[name]
|
structureNames.append(name)
|
||||||
|
|
||||||
for name in self.structures:
|
for name in self.structures:
|
||||||
if(len(self.structures[name].srefs)>0): #does this structure reference any others?
|
if(len(self.structures[name].srefs)>0): #does this structure reference any others?
|
||||||
|
|
@ -152,19 +158,20 @@ class VlsiLayout:
|
||||||
self.rootStructureName = structureNames[0]
|
self.rootStructureName = structureNames[0]
|
||||||
|
|
||||||
def traverseTheHierarchy(self, startingStructureName=None, delegateFunction = None,
|
def traverseTheHierarchy(self, startingStructureName=None, delegateFunction = None,
|
||||||
transformPath = [], rotateAngle = 0, transFlags = (0,0,0), coordinates = (0,0)):
|
transformPath = [], rotateAngle = 0, transFlags = [0,0,0], coordinates = (0,0)):
|
||||||
#since this is a recursive function, must deal with the default
|
#since this is a recursive function, must deal with the default
|
||||||
#parameters explicitly
|
#parameters explicitly
|
||||||
if startingStructureName == None:
|
if startingStructureName == None:
|
||||||
startingStructureName = self.rootStructureName
|
startingStructureName = self.rootStructureName
|
||||||
|
|
||||||
#set up the rotation matrix
|
#set up the rotation matrix
|
||||||
if(rotateAngle == None or rotateAngle == ""):
|
if(rotateAngle == None or rotateAngle == ""):
|
||||||
rotateAngle = 0
|
angle = 0
|
||||||
else:
|
else:
|
||||||
rotateAngle = math.radians(float(rotateAngle))
|
# MRG: Added negative to make CCW rotate 8/29/18
|
||||||
mRotate = matrix([[math.cos(rotateAngle),-math.sin(rotateAngle),0.0],
|
angle = math.radians(float(rotateAngle))
|
||||||
[math.sin(rotateAngle),math.cos(rotateAngle),0.0],
|
mRotate = matrix([[math.cos(angle),-math.sin(angle),0.0],
|
||||||
|
[math.sin(angle),math.cos(angle),0.0],
|
||||||
[0.0,0.0,1.0]])
|
[0.0,0.0,1.0]])
|
||||||
#set up the translation matrix
|
#set up the translation matrix
|
||||||
translateX = float(coordinates[0])
|
translateX = float(coordinates[0])
|
||||||
|
|
@ -180,7 +187,7 @@ class VlsiLayout:
|
||||||
|
|
||||||
#we need to keep track of all transforms in the hierarchy
|
#we need to keep track of all transforms in the hierarchy
|
||||||
#when we add an element to the xy tree, we apply all transforms from the bottom up
|
#when we add an element to the xy tree, we apply all transforms from the bottom up
|
||||||
transformPath += [(mRotate,mScale,mTranslate)]
|
transformPath.append((mRotate,mScale,mTranslate))
|
||||||
if delegateFunction != None:
|
if delegateFunction != None:
|
||||||
delegateFunction(startingStructureName, transformPath)
|
delegateFunction(startingStructureName, transformPath)
|
||||||
#starting with a particular structure, we will recursively traverse the tree
|
#starting with a particular structure, we will recursively traverse the tree
|
||||||
|
|
@ -190,15 +197,12 @@ class VlsiLayout:
|
||||||
#if not, return back to the caller (caller can be this function)
|
#if not, return back to the caller (caller can be this function)
|
||||||
for sref in self.structures[startingStructureName].srefs:
|
for sref in self.structures[startingStructureName].srefs:
|
||||||
#here, we are going to modify the sref coordinates based on the parent objects rotation
|
#here, we are going to modify the sref coordinates based on the parent objects rotation
|
||||||
# if (sref.sName.count("via") == 0):
|
|
||||||
self.traverseTheHierarchy(startingStructureName = sref.sName,
|
self.traverseTheHierarchy(startingStructureName = sref.sName,
|
||||||
delegateFunction = delegateFunction,
|
delegateFunction = delegateFunction,
|
||||||
transformPath = transformPath,
|
transformPath = transformPath,
|
||||||
rotateAngle = sref.rotateAngle,
|
rotateAngle = sref.rotateAngle,
|
||||||
transFlags = sref.transFlags,
|
transFlags = sref.transFlags,
|
||||||
coordinates = sref.coordinates)
|
coordinates = sref.coordinates)
|
||||||
# else:
|
|
||||||
# print("WARNING: via encountered, ignoring:", sref.sName)
|
|
||||||
#MUST HANDLE AREFs HERE AS WELL
|
#MUST HANDLE AREFs HERE AS WELL
|
||||||
#when we return, drop the last transform from the transformPath
|
#when we return, drop the last transform from the transformPath
|
||||||
del transformPath[-1]
|
del transformPath[-1]
|
||||||
|
|
@ -207,7 +211,11 @@ class VlsiLayout:
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
self.deduceHierarchy()
|
self.deduceHierarchy()
|
||||||
#self.traverseTheHierarchy()
|
#self.traverseTheHierarchy()
|
||||||
self.populateCoordinateMap()
|
self.populateCoordinateMap()
|
||||||
|
|
||||||
|
for layerNumber in self.layerNumbersInUse:
|
||||||
|
self.processLabelPins(layerNumber)
|
||||||
|
|
||||||
|
|
||||||
def populateCoordinateMap(self):
|
def populateCoordinateMap(self):
|
||||||
def addToXyTree(startingStructureName = None,transformPath = None):
|
def addToXyTree(startingStructureName = None,transformPath = None):
|
||||||
|
|
@ -225,12 +233,14 @@ class VlsiLayout:
|
||||||
uVector = transform[0] * uVector #rotate
|
uVector = transform[0] * uVector #rotate
|
||||||
vVector = transform[0] * vVector #rotate
|
vVector = transform[0] * vVector #rotate
|
||||||
origin = transform[1] * origin #scale
|
origin = transform[1] * origin #scale
|
||||||
uVector = transform[1] * uVector #rotate
|
uVector = transform[1] * uVector #scale
|
||||||
vVector = transform[1] * vVector #rotate
|
vVector = transform[1] * vVector #scale
|
||||||
origin = transform[2] * origin #translate
|
origin = transform[2] * origin #translate
|
||||||
#we don't need to do a translation on the basis vectors
|
#we don't need to do a translation on the basis vectors
|
||||||
self.xyTree+=[(startingStructureName,origin,uVector,vVector)] #populate the xyTree with each
|
#uVector = transform[2] * uVector #translate
|
||||||
#structureName and coordinate space
|
#vVector = transform[2] * vVector #translate
|
||||||
|
#populate the xyTree with each structureName and coordinate space
|
||||||
|
self.xyTree.append((startingStructureName,origin,uVector,vVector))
|
||||||
self.traverseTheHierarchy(delegateFunction = addToXyTree)
|
self.traverseTheHierarchy(delegateFunction = addToXyTree)
|
||||||
|
|
||||||
def microns(self,userUnits):
|
def microns(self,userUnits):
|
||||||
|
|
@ -274,8 +284,9 @@ class VlsiLayout:
|
||||||
Method to insert one layout into another at a particular offset.
|
Method to insert one layout into another at a particular offset.
|
||||||
"""
|
"""
|
||||||
offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1]))
|
offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1]))
|
||||||
if self.debug==1:
|
if self.debug:
|
||||||
debug.info(0,"DEBUG: GdsMill vlsiLayout: addInstance: type %s, nameOfLayout "%type(layoutToAdd),nameOfLayout)
|
debug.info(0,"DEBUG: GdsMill vlsiLayout: addInstance: type {0}, nameOfLayout {1}".format(type(layoutToAdd),nameOfLayout))
|
||||||
|
debug.info(0,"DEBUG: name={0} offset={1} mirror={2} rotate={3}".format(layoutToAdd.rootStructureName,offsetInMicrons, mirror, rotate))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -295,7 +306,7 @@ class VlsiLayout:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# If layoutToAdd is a unique object (not this), then copy heirarchy,
|
# If layoutToAdd is a unique object (not this), then copy hierarchy,
|
||||||
# otherwise, if it is a text name of an internal structure, use it.
|
# otherwise, if it is a text name of an internal structure, use it.
|
||||||
|
|
||||||
if layoutToAdd != self:
|
if layoutToAdd != self:
|
||||||
|
|
@ -306,19 +317,7 @@ class VlsiLayout:
|
||||||
#also combine the "layers in use" list
|
#also combine the "layers in use" list
|
||||||
for layerNumber in layoutToAdd.layerNumbersInUse:
|
for layerNumber in layoutToAdd.layerNumbersInUse:
|
||||||
if layerNumber not in self.layerNumbersInUse:
|
if layerNumber not in self.layerNumbersInUse:
|
||||||
self.layerNumbersInUse += [layerNumber]
|
self.layerNumbersInUse.append(layerNumber)
|
||||||
#Also, check if the user units / microns is the same as this Layout
|
|
||||||
#if (layoutToAdd.units != self.units):
|
|
||||||
#print("WARNING: VlsiLayout: Units from design to be added do not match target Layout")
|
|
||||||
|
|
||||||
# if debug: print("DEBUG: vlsilayout: Using %d layers")
|
|
||||||
|
|
||||||
# If we can't find the structure, error
|
|
||||||
#if StructureFound == False:
|
|
||||||
#print("ERROR: vlsiLayout.addInstance: [%s] Name not found in local structures, "%(nameOfLayout))
|
|
||||||
#return #FIXME: remove!
|
|
||||||
#exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
#add a reference to the new layout structure in this layout's root
|
#add a reference to the new layout structure in this layout's root
|
||||||
layoutToAddSref = GdsSref()
|
layoutToAddSref = GdsSref()
|
||||||
|
|
@ -326,9 +325,11 @@ class VlsiLayout:
|
||||||
layoutToAddSref.coordinates = offsetInLayoutUnits
|
layoutToAddSref.coordinates = offsetInLayoutUnits
|
||||||
|
|
||||||
if mirror or rotate:
|
if mirror or rotate:
|
||||||
########flags = (mirror around x-axis, absolute rotation, absolute magnification)
|
|
||||||
layoutToAddSref.transFlags = (False,False,False)
|
layoutToAddSref.transFlags = [0,0,0]
|
||||||
#Below angles are angular angles(relative), not absolute
|
# transFlags = (mirror around x-axis, magnification, rotation)
|
||||||
|
# If magnification or rotation is true, it is the flags are then
|
||||||
|
# followed by an amount in the record
|
||||||
if mirror=="R90":
|
if mirror=="R90":
|
||||||
rotate = 90.0
|
rotate = 90.0
|
||||||
if mirror=="R180":
|
if mirror=="R180":
|
||||||
|
|
@ -336,18 +337,20 @@ class VlsiLayout:
|
||||||
if mirror=="R270":
|
if mirror=="R270":
|
||||||
rotate = 270.0
|
rotate = 270.0
|
||||||
if rotate:
|
if rotate:
|
||||||
|
#layoutToAddSref.transFlags[2] = 1
|
||||||
layoutToAddSref.rotateAngle = rotate
|
layoutToAddSref.rotateAngle = rotate
|
||||||
if mirror == "x" or mirror == "MX":
|
if mirror == "x" or mirror == "MX":
|
||||||
layoutToAddSref.transFlags = (True,False,False)
|
layoutToAddSref.transFlags[0] = 1
|
||||||
if mirror == "y" or mirror == "MY": #NOTE: "MY" option will override specified rotate angle
|
if mirror == "y" or mirror == "MY": #NOTE: "MY" option will override specified rotate angle
|
||||||
layoutToAddSref.transFlags = (True,False,False)
|
layoutToAddSref.transFlags[0] = 1
|
||||||
|
#layoutToAddSref.transFlags[2] = 1
|
||||||
layoutToAddSref.rotateAngle = 180.0
|
layoutToAddSref.rotateAngle = 180.0
|
||||||
if mirror == "xy" or mirror == "XY": #NOTE: "XY" option will override specified rotate angle
|
if mirror == "xy" or mirror == "XY": #NOTE: "XY" option will override specified rotate angle
|
||||||
layoutToAddSref.transFlags = (False,False,False)
|
#layoutToAddSref.transFlags[2] = 1
|
||||||
layoutToAddSref.rotateAngle = 180.0
|
layoutToAddSref.rotateAngle = 180.0
|
||||||
|
|
||||||
#add the sref to the root structure
|
#add the sref to the root structure
|
||||||
self.structures[self.rootStructureName].srefs+=[layoutToAddSref]
|
self.structures[self.rootStructureName].srefs.append(layoutToAddSref)
|
||||||
|
|
||||||
def addBox(self,layerNumber=0, purposeNumber=None, offsetInMicrons=(0,0), width=1.0, height=1.0,center=False):
|
def addBox(self,layerNumber=0, purposeNumber=None, offsetInMicrons=(0,0), width=1.0, height=1.0,center=False):
|
||||||
"""
|
"""
|
||||||
|
|
@ -365,11 +368,7 @@ class VlsiLayout:
|
||||||
(offsetInLayoutUnits[0],offsetInLayoutUnits[1]+heightInLayoutUnits),
|
(offsetInLayoutUnits[0],offsetInLayoutUnits[1]+heightInLayoutUnits),
|
||||||
offsetInLayoutUnits]
|
offsetInLayoutUnits]
|
||||||
else:
|
else:
|
||||||
|
startPoint = (offsetInLayoutUnits[0]-widthInLayoutUnits/2.0, offsetInLayoutUnits[1]-heightInLayoutUnits/2.0)
|
||||||
#is there where gdsmill is halving the coordinates???
|
|
||||||
#if you printGDS of temp.gds, the header says 1 user unit = .0005 database units. By default user units = .001.
|
|
||||||
#something to do with the ieeedouble in gdswriter.py????
|
|
||||||
startPoint = (offsetInLayoutUnits[0]-widthInLayoutUnits/2, offsetInLayoutUnits[1]-heightInLayoutUnits/2) #width/2 height/2
|
|
||||||
coordinates=[startPoint,
|
coordinates=[startPoint,
|
||||||
(startPoint[0]+widthInLayoutUnits,startPoint[1]),
|
(startPoint[0]+widthInLayoutUnits,startPoint[1]),
|
||||||
(startPoint[0]+widthInLayoutUnits,startPoint[1]+heightInLayoutUnits),
|
(startPoint[0]+widthInLayoutUnits,startPoint[1]+heightInLayoutUnits),
|
||||||
|
|
@ -382,7 +381,7 @@ class VlsiLayout:
|
||||||
boundaryToAdd.coordinates = coordinates
|
boundaryToAdd.coordinates = coordinates
|
||||||
boundaryToAdd.purposeLayer = purposeNumber
|
boundaryToAdd.purposeLayer = purposeNumber
|
||||||
#add the sref to the root structure
|
#add the sref to the root structure
|
||||||
self.structures[self.rootStructureName].boundaries+=[boundaryToAdd]
|
self.structures[self.rootStructureName].boundaries.append(boundaryToAdd)
|
||||||
|
|
||||||
def addPath(self, layerNumber=0, purposeNumber = None, coordinates=[(0,0)], width=1.0):
|
def addPath(self, layerNumber=0, purposeNumber = None, coordinates=[(0,0)], width=1.0):
|
||||||
"""
|
"""
|
||||||
|
|
@ -394,14 +393,14 @@ class VlsiLayout:
|
||||||
for coordinate in coordinates:
|
for coordinate in coordinates:
|
||||||
cX = self.userUnits(coordinate[0])
|
cX = self.userUnits(coordinate[0])
|
||||||
cY = self.userUnits(coordinate[1])
|
cY = self.userUnits(coordinate[1])
|
||||||
layoutUnitCoordinates += [(cX,cY)]
|
layoutUnitCoordinates.append((cX,cY))
|
||||||
pathToAdd = GdsPath()
|
pathToAdd = GdsPath()
|
||||||
pathToAdd.drawingLayer=layerNumber
|
pathToAdd.drawingLayer=layerNumber
|
||||||
pathToAdd.purposeLayer = purposeNumber
|
pathToAdd.purposeLayer = purposeNumber
|
||||||
pathToAdd.pathWidth=widthInLayoutUnits
|
pathToAdd.pathWidth=widthInLayoutUnits
|
||||||
pathToAdd.coordinates=layoutUnitCoordinates
|
pathToAdd.coordinates=layoutUnitCoordinates
|
||||||
#add the sref to the root structure
|
#add the sref to the root structure
|
||||||
self.structures[self.rootStructureName].paths+=[pathToAdd]
|
self.structures[self.rootStructureName].paths.append(pathToAdd)
|
||||||
|
|
||||||
def addText(self, text, layerNumber=0, purposeNumber = None, offsetInMicrons=(0,0), magnification=0.1, rotate = None):
|
def addText(self, text, layerNumber=0, purposeNumber = None, offsetInMicrons=(0,0), magnification=0.1, rotate = None):
|
||||||
offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1]))
|
offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1]))
|
||||||
|
|
@ -410,17 +409,17 @@ class VlsiLayout:
|
||||||
textToAdd.purposeLayer = purposeNumber
|
textToAdd.purposeLayer = purposeNumber
|
||||||
textToAdd.dataType = 0
|
textToAdd.dataType = 0
|
||||||
textToAdd.coordinates = [offsetInLayoutUnits]
|
textToAdd.coordinates = [offsetInLayoutUnits]
|
||||||
|
textToAdd.transFlags = [0,0,0]
|
||||||
if(len(text)%2 == 1):
|
if(len(text)%2 == 1):
|
||||||
#pad with a zero
|
|
||||||
text = text + '\x00'
|
text = text + '\x00'
|
||||||
textToAdd.textString = text
|
textToAdd.textString = text
|
||||||
textToAdd.transFlags = (False,False,True)
|
#textToAdd.transFlags[1] = 1
|
||||||
textToAdd.magFactor = magnification
|
textToAdd.magFactor = magnification
|
||||||
if rotate:
|
if rotate:
|
||||||
textToAdd.transFlags = (False,True,True)
|
#textToAdd.transFlags[2] = 1
|
||||||
textToAdd.rotateAngle = rotate
|
textToAdd.rotateAngle = rotate
|
||||||
#add the sref to the root structure
|
#add the sref to the root structure
|
||||||
self.structures[self.rootStructureName].texts+=[textToAdd]
|
self.structures[self.rootStructureName].texts.append(textToAdd)
|
||||||
|
|
||||||
def isBounded(self,testPoint,startPoint,endPoint):
|
def isBounded(self,testPoint,startPoint,endPoint):
|
||||||
#these arguments are touples of (x,y) coordinates
|
#these arguments are touples of (x,y) coordinates
|
||||||
|
|
@ -488,6 +487,10 @@ class VlsiLayout:
|
||||||
return False #these shapes are ok
|
return False #these shapes are ok
|
||||||
|
|
||||||
def isPointInsideOfBox(self,pointCoordinates,boxCoordinates):
|
def isPointInsideOfBox(self,pointCoordinates,boxCoordinates):
|
||||||
|
"""
|
||||||
|
Check if a point is contained in the shape
|
||||||
|
"""
|
||||||
|
debug.check(len(boxCoordinates)==4,"Invalid number of coordinates for box.")
|
||||||
leftBound = boxCoordinates[0][0]
|
leftBound = boxCoordinates[0][0]
|
||||||
rightBound = boxCoordinates[0][0]
|
rightBound = boxCoordinates[0][0]
|
||||||
topBound = boxCoordinates[0][1]
|
topBound = boxCoordinates[0][1]
|
||||||
|
|
@ -509,7 +512,9 @@ class VlsiLayout:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def isShapeInsideOfBox(self,shapeCoordinates, boxCoordinates):
|
def isShapeInsideOfBox(self,shapeCoordinates, boxCoordinates):
|
||||||
#go through every point in the shape to test if they are all inside the box
|
"""
|
||||||
|
Go through every point in the shape to test if they are all inside the box.
|
||||||
|
"""
|
||||||
for point in shapeCoordinates:
|
for point in shapeCoordinates:
|
||||||
if not self.isPointInsideOfBox(point,boxCoordinates):
|
if not self.isPointInsideOfBox(point,boxCoordinates):
|
||||||
return False
|
return False
|
||||||
|
|
@ -532,7 +537,7 @@ class VlsiLayout:
|
||||||
#remap coordinates
|
#remap coordinates
|
||||||
shiftedBoundaryCoordinates = []
|
shiftedBoundaryCoordinates = []
|
||||||
for shapeCoordinate in boundary.rotatedCoordinates(rotateAngle):
|
for shapeCoordinate in boundary.rotatedCoordinates(rotateAngle):
|
||||||
shiftedBoundaryCoordinates+=[(shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1])]
|
shiftedBoundaryCoordinates.append((shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1]))
|
||||||
joint = self.doShapesIntersect(self.tempCoordinates, shiftedBoundaryCoordinates)
|
joint = self.doShapesIntersect(self.tempCoordinates, shiftedBoundaryCoordinates)
|
||||||
if joint:
|
if joint:
|
||||||
self.tempPassFail = False
|
self.tempPassFail = False
|
||||||
|
|
@ -545,7 +550,7 @@ class VlsiLayout:
|
||||||
#remap coordinates
|
#remap coordinates
|
||||||
shiftedBoundaryCoordinates = []
|
shiftedBoundaryCoordinates = []
|
||||||
for shapeCoordinate in path.equivalentBoundaryCoordinates(rotateAngle):
|
for shapeCoordinate in path.equivalentBoundaryCoordinates(rotateAngle):
|
||||||
shiftedBoundaryCoordinates+=[(shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1])]
|
shiftedBoundaryCoordinates.append((shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1]))
|
||||||
joint = self.doShapesIntersect(self.tempCoordinates, shiftedBoundaryCoordinates)
|
joint = self.doShapesIntersect(self.tempCoordinates, shiftedBoundaryCoordinates)
|
||||||
if joint:
|
if joint:
|
||||||
self.tempPassFail = False
|
self.tempPassFail = False
|
||||||
|
|
@ -568,7 +573,7 @@ class VlsiLayout:
|
||||||
self.traverseTheHierarchy(delegateFunction = isThisBlockOk)
|
self.traverseTheHierarchy(delegateFunction = isThisBlockOk)
|
||||||
#if its bad, this global tempPassFail will be false
|
#if its bad, this global tempPassFail will be false
|
||||||
#if true, we can add the block
|
#if true, we can add the block
|
||||||
passFailRecord+=[self.tempPassFail]
|
passFailRecord.append(self.tempPassFail)
|
||||||
print("Percent Complete:"+str(percentDone))
|
print("Percent Complete:"+str(percentDone))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -579,10 +584,11 @@ class VlsiLayout:
|
||||||
blockY = (yIndex*effectiveBlock)+offsetInMicrons[1]
|
blockY = (yIndex*effectiveBlock)+offsetInMicrons[1]
|
||||||
if passFailRecord[passFailIndex]:
|
if passFailRecord[passFailIndex]:
|
||||||
self.addBox(layerToFill, (blockX,blockY), width=blockSize, height=blockSize)
|
self.addBox(layerToFill, (blockX,blockY), width=blockSize, height=blockSize)
|
||||||
passFailIndex+=1
|
passFailIndex += 1
|
||||||
print("Done\n\n")
|
print("Done\n\n")
|
||||||
|
|
||||||
def getLayoutBorder(self,borderlayer):
|
def getLayoutBorder(self,borderlayer):
|
||||||
|
cellSizeMicron=None
|
||||||
for boundary in self.structures[self.rootStructureName].boundaries:
|
for boundary in self.structures[self.rootStructureName].boundaries:
|
||||||
if boundary.drawingLayer==borderlayer:
|
if boundary.drawingLayer==borderlayer:
|
||||||
if self.debug:
|
if self.debug:
|
||||||
|
|
@ -614,29 +620,22 @@ class VlsiLayout:
|
||||||
return [[self.units[0]*cellBoundary[0],self.units[0]*cellBoundary[1]],
|
return [[self.units[0]*cellBoundary[0],self.units[0]*cellBoundary[1]],
|
||||||
[self.units[0]*cellBoundary[2],self.units[0]*cellBoundary[3]]]
|
[self.units[0]*cellBoundary[2],self.units[0]*cellBoundary[3]]]
|
||||||
|
|
||||||
def measureSizeInStructure(self,Structure,cellBoundary):
|
def measureSizeInStructure(self,structure,cellBoundary):
|
||||||
StructureName=Structure[0]
|
(structureName,structureOrigin,structureuVector,structurevVector)=structure
|
||||||
StructureOrigin=[Structure[1][0],Structure[1][1]]
|
for boundary in self.structures[str(structureName)].boundaries:
|
||||||
StructureuVector=[Structure[2][0],Structure[2][1],Structure[2][2]]
|
|
||||||
StructurevVector=[Structure[3][0],Structure[3][1],Structure[3][2]]
|
|
||||||
#debug.info(debug_level,"Checking Structure: "+str(StructureName))
|
|
||||||
#debug.info(debug_level,"-Structure Structure Origin:"+str(StructureOrigin))
|
|
||||||
#debug.info(debug_level,"-Structure direction: uVector["+str(StructureuVector)+"]")
|
|
||||||
#debug.info(debug_level,"-Structure direction: vVector["+str(StructurevVector)+"]")
|
|
||||||
|
|
||||||
for boundary in self.structures[str(StructureName)].boundaries:
|
|
||||||
left_bottom=boundary.coordinates[0]
|
left_bottom=boundary.coordinates[0]
|
||||||
right_top=boundary.coordinates[2]
|
right_top=boundary.coordinates[2]
|
||||||
thisBoundary=[left_bottom[0],left_bottom[1],right_top[0],right_top[1]]
|
thisBoundary=[left_bottom[0],left_bottom[1],right_top[0],right_top[1]]
|
||||||
thisBoundary=self.transformRectangle(thisBoundary,StructureuVector,StructurevVector)
|
thisBoundary=self.transformRectangle(thisBoundary,structureuVector,structurevVector)
|
||||||
thisBoundary=[thisBoundary[0]+StructureOrigin[0],thisBoundary[1]+StructureOrigin[1],
|
thisBoundary=[thisBoundary[0]+structureOrigin[0],thisBoundary[1]+structureOrigin[1],
|
||||||
thisBoundary[2]+StructureOrigin[0],thisBoundary[3]+StructureOrigin[1]]
|
thisBoundary[2]+structureOrigin[0],thisBoundary[3]+structureOrigin[1]]
|
||||||
cellBoundary=self.updateBoundary(thisBoundary,cellBoundary)
|
cellBoundary=self.updateBoundary(thisBoundary,cellBoundary)
|
||||||
return cellBoundary
|
return cellBoundary
|
||||||
|
|
||||||
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:
|
||||||
|
|
@ -650,147 +649,123 @@ class VlsiLayout:
|
||||||
return cellBoundary
|
return cellBoundary
|
||||||
|
|
||||||
|
|
||||||
def getLabelDBInfo(self,label_name):
|
def getTexts(self, layer):
|
||||||
"""
|
"""
|
||||||
Return the coordinates in DB units and layer of all matching labels
|
Get all of the labels on a given layer only at the root level.
|
||||||
"""
|
"""
|
||||||
label_list = []
|
text_list = []
|
||||||
label_layer = None
|
|
||||||
label_coordinate = [None, None]
|
|
||||||
|
|
||||||
# Why must this be the last one found? It breaks if we return the first.
|
|
||||||
for Text in self.structures[self.rootStructureName].texts:
|
for Text in self.structures[self.rootStructureName].texts:
|
||||||
if Text.textString == label_name or Text.textString == label_name+"\x00":
|
if Text.drawingLayer == layer:
|
||||||
label_layer = Text.drawingLayer
|
text_list.append(Text)
|
||||||
label_coordinate = Text.coordinates[0]
|
return text_list
|
||||||
if label_layer!=None:
|
|
||||||
label_list.append((label_coordinate,label_layer))
|
|
||||||
|
|
||||||
debug.check(len(label_list)>0,"Did not find labels {0}.".format(label_name))
|
|
||||||
return label_list
|
|
||||||
|
|
||||||
|
|
||||||
def getLabelInfo(self,label_name):
|
|
||||||
"""
|
|
||||||
Return the coordinates in USER units and layer of a label
|
|
||||||
"""
|
|
||||||
label_list=self.getLabelDBInfo(label_name)
|
|
||||||
new_list=[]
|
|
||||||
for label in label_list:
|
|
||||||
(label_coordinate,label_layer)=label
|
|
||||||
user_coordinates = [x*self.units[0] for x in label_coordinate]
|
|
||||||
new_list.append(user_coordinates,label_layer)
|
|
||||||
return new_list
|
|
||||||
|
|
||||||
def getPinShapeByLocLayer(self, coordinate, layer):
|
def getPinShape(self, pin_name):
|
||||||
"""
|
|
||||||
Return the largest enclosing rectangle on a layer and at a location.
|
|
||||||
Coordinates should be in USER units.
|
|
||||||
"""
|
|
||||||
db_coordinate = [x/self.units[0] for x in coordinate]
|
|
||||||
return self.getPinShapeByDBLocLayer(db_coordinate, layer)
|
|
||||||
|
|
||||||
def getPinShapeByDBLocLayer(self, coordinate, layer):
|
|
||||||
"""
|
|
||||||
Return the largest enclosing rectangle on a layer and at a location.
|
|
||||||
Coordinates should be in DB units.
|
|
||||||
"""
|
|
||||||
pin_boundaries=self.getAllPinShapesInStructureList(coordinate, layer)
|
|
||||||
|
|
||||||
if len(pin_boundaries) == 0:
|
|
||||||
debug.warning("Did not find pin on layer {0} at coordinate {1}".format(layer, coordinate))
|
|
||||||
|
|
||||||
# sort the boundaries, return the max area pin boundary
|
|
||||||
pin_boundaries.sort(key=boundaryArea,reverse=True)
|
|
||||||
pin_boundary=pin_boundaries[0]
|
|
||||||
|
|
||||||
# Convert to USER units
|
|
||||||
pin_boundary=[pin_boundary[0]*self.units[0],pin_boundary[1]*self.units[0],
|
|
||||||
pin_boundary[2]*self.units[0],pin_boundary[3]*self.units[0]]
|
|
||||||
|
|
||||||
# Make a name if we don't have the pin name
|
|
||||||
return ["p"+str(coordinate)+"_"+str(layer), layer, pin_boundary]
|
|
||||||
|
|
||||||
def getAllPinShapesByLocLayer(self, coordinate, layer):
|
|
||||||
"""
|
|
||||||
Return ALL the enclosing rectangles on the same layer
|
|
||||||
at the given coordinate. Coordinates should be in USER units.
|
|
||||||
"""
|
|
||||||
db_coordinate = [int(x/self.units[0]) for x in coordinate]
|
|
||||||
return self.getAllPinShapesByDBLocLayer(db_coordinate, layer)
|
|
||||||
|
|
||||||
def getAllPinShapesByDBLocLayer(self, coordinate, layer):
|
|
||||||
"""
|
|
||||||
Return ALL the enclosing rectangles on the same layer
|
|
||||||
at the given coordinate. Coordinates should be in DB units.
|
|
||||||
"""
|
|
||||||
pin_boundaries=self.getAllPinShapesInStructureList(coordinate, layer)
|
|
||||||
|
|
||||||
# Convert to user units
|
|
||||||
new_boundaries = []
|
|
||||||
for pin_boundary in pin_boundaries:
|
|
||||||
new_boundaries.append([pin_boundary[0]*self.units[0],pin_boundary[1]*self.units[0],
|
|
||||||
pin_boundary[2]*self.units[0],pin_boundary[3]*self.units[0]])
|
|
||||||
|
|
||||||
# Make a name if we don't have the pin name
|
|
||||||
return ["p"+str(coordinate)+"_"+str(layer), layer, new_boundaries]
|
|
||||||
|
|
||||||
def getPinShapeByLabel(self,label_name):
|
|
||||||
"""
|
"""
|
||||||
Search for a pin label and return the largest enclosing rectangle
|
Search for a pin label and return the largest enclosing rectangle
|
||||||
on the same layer as the pin label.
|
on the same layer as the pin label.
|
||||||
|
If there are multiple pin lists, return the max of each.
|
||||||
"""
|
"""
|
||||||
label_list=self.getLabelDBInfo(label_name)
|
pin_map = self.pins[pin_name]
|
||||||
shape_list=[]
|
max_pins = []
|
||||||
for label in label_list:
|
for pin_list in pin_map:
|
||||||
(label_coordinate,label_layer)=label
|
max_pin = None
|
||||||
shape = self.getPinShapeByDBLocLayer(label_coordinate, label_layer)
|
max_area = 0
|
||||||
shape_list.append(shape)
|
for pin in pin_list:
|
||||||
return shape_list
|
(layer,boundary) = pin
|
||||||
|
new_area = boundaryArea(boundary)
|
||||||
|
if max_pin == None or new_area>max_area:
|
||||||
|
max_pin = pin
|
||||||
|
max_area = new_area
|
||||||
|
max_pins.append(max_pin)
|
||||||
|
|
||||||
def getAllPinShapesByLabel(self,label_name):
|
return max_pins
|
||||||
|
|
||||||
|
|
||||||
|
def getAllPinShapes(self, pin_name):
|
||||||
"""
|
"""
|
||||||
Search for a pin label and return ALL the enclosing rectangles on the same layer
|
Search for a pin label and return ALL the enclosing rectangles on the same layer
|
||||||
as the pin label.
|
as the pin label.
|
||||||
"""
|
"""
|
||||||
|
shape_list = []
|
||||||
label_list=self.getLabelDBInfo(label_name)
|
pin_map = self.pins[pin_name]
|
||||||
shape_list=[]
|
for pin_list in pin_map:
|
||||||
for label in label_list:
|
for pin in pin_list:
|
||||||
(label_coordinate,label_layer)=label
|
(pin_layer, boundary) = pin
|
||||||
shape_list.append(self.getAllPinShapesByDBLocLayer(label_coordinate, label_layer))
|
shape_list.append(pin)
|
||||||
|
|
||||||
return shape_list
|
return shape_list
|
||||||
|
|
||||||
def getAllPinShapesInStructureList(self,coordinates,layer):
|
|
||||||
|
def processLabelPins(self, layer):
|
||||||
"""
|
"""
|
||||||
Given a coordinate, search for enclosing structures on the given layer.
|
Find all text labels and create a map to a list of shapes that
|
||||||
Return all pin shapes.
|
they enclose on the given layer.
|
||||||
|
"""
|
||||||
|
# Get the labels on a layer in the root level
|
||||||
|
labels = self.getTexts(layer)
|
||||||
|
# Get all of the shapes on the layer at all levels
|
||||||
|
# and transform them to the current level
|
||||||
|
shapes = self.getAllShapes(layer)
|
||||||
|
|
||||||
|
for label in labels:
|
||||||
|
label_coordinate = label.coordinates[0]
|
||||||
|
user_coordinate = [x*self.units[0] for x in label_coordinate]
|
||||||
|
pin_shapes = []
|
||||||
|
for boundary in shapes:
|
||||||
|
if self.labelInRectangle(user_coordinate,boundary):
|
||||||
|
pin_shapes.append((layer, boundary))
|
||||||
|
|
||||||
|
label_text = label.textString
|
||||||
|
# Remove the padding if it exists
|
||||||
|
if label_text[-1] == "\x00":
|
||||||
|
label_text = label_text[0:-1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.pins[label_text]
|
||||||
|
except KeyError:
|
||||||
|
self.pins[label_text] = []
|
||||||
|
self.pins[label_text].append(pin_shapes)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def getAllShapes(self,layer):
|
||||||
|
"""
|
||||||
|
Return all gshapes on a given layer in [llx, lly, urx, ury] format and
|
||||||
|
user units.
|
||||||
"""
|
"""
|
||||||
boundaries = []
|
boundaries = []
|
||||||
|
|
||||||
for TreeUnit in self.xyTree:
|
for TreeUnit in self.xyTree:
|
||||||
boundaries += self.getPinInStructure(coordinates,layer,TreeUnit)
|
#print(TreeUnit[0])
|
||||||
|
boundaries.extend(self.getShapesInStructure(layer,TreeUnit))
|
||||||
|
|
||||||
|
# Remove duplicates without defining a hash
|
||||||
|
# (could be sped up by creating hash and using list(set())
|
||||||
|
new_boundaries = []
|
||||||
|
for boundary in boundaries:
|
||||||
|
if boundary not in new_boundaries:
|
||||||
|
new_boundaries.append(boundary)
|
||||||
|
|
||||||
|
# Convert to user units
|
||||||
|
boundaries = []
|
||||||
|
for boundary in new_boundaries:
|
||||||
|
boundaries.append([boundary[0]*self.units[0],boundary[1]*self.units[0],
|
||||||
|
boundary[2]*self.units[0],boundary[3]*self.units[0]])
|
||||||
|
|
||||||
return boundaries
|
return boundaries
|
||||||
|
|
||||||
|
|
||||||
def getPinInStructure(self,coordinates,layer,structure):
|
def getShapesInStructure(self,layer,structure):
|
||||||
"""
|
"""
|
||||||
Go through all the shapes in a structure and return the list of shapes
|
Go through all the shapes in a structure and return the list of shapes in
|
||||||
that the label coordinates are inside.
|
the form [llx, lly, urx, ury]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# check if this is a rectangle
|
(structureName,structureOrigin,structureuVector,structurevVector)=structure
|
||||||
structureName=structure[0]
|
#print(structureName,"u",structureuVector.transpose(),"v",structurevVector.transpose(),"o",structureOrigin.transpose())
|
||||||
structureOrigin=[structure[1][0],structure[1][1]]
|
|
||||||
structureuVector=[structure[2][0],structure[2][1],structure[2][2]]
|
|
||||||
structurevVector=[structure[3][0],structure[3][1],structure[3][2]]
|
|
||||||
|
|
||||||
boundaries = []
|
boundaries = []
|
||||||
|
|
||||||
for boundary in self.structures[str(structureName)].boundaries:
|
for boundary in self.structures[str(structureName)].boundaries:
|
||||||
# Pin enclosures only work on rectangular pins so ignore any non rectangle
|
# FIXME: Right now, this only supports rectangular shapes!
|
||||||
# This may report not finding pins, but the user should fix this by adding a rectangle.
|
#debug.check(len(boundary.coordinates)==5,"Non-rectangular shape.")
|
||||||
if len(boundary.coordinates)!=5:
|
if len(boundary.coordinates)!=5:
|
||||||
continue
|
continue
|
||||||
if layer==boundary.drawingLayer:
|
if layer==boundary.drawingLayer:
|
||||||
|
|
@ -798,15 +773,15 @@ class VlsiLayout:
|
||||||
right_top=boundary.coordinates[2]
|
right_top=boundary.coordinates[2]
|
||||||
# Rectangle is [leftx, bottomy, rightx, topy].
|
# Rectangle is [leftx, bottomy, rightx, topy].
|
||||||
boundaryRect=[left_bottom[0],left_bottom[1],right_top[0],right_top[1]]
|
boundaryRect=[left_bottom[0],left_bottom[1],right_top[0],right_top[1]]
|
||||||
|
# perform the rotation
|
||||||
boundaryRect=self.transformRectangle(boundaryRect,structureuVector,structurevVector)
|
boundaryRect=self.transformRectangle(boundaryRect,structureuVector,structurevVector)
|
||||||
|
# add the offset
|
||||||
boundaryRect=[boundaryRect[0]+structureOrigin[0].item(),boundaryRect[1]+structureOrigin[1].item(),
|
boundaryRect=[boundaryRect[0]+structureOrigin[0].item(),boundaryRect[1]+structureOrigin[1].item(),
|
||||||
boundaryRect[2]+structureOrigin[0].item(),boundaryRect[3]+structureOrigin[1].item()]
|
boundaryRect[2]+structureOrigin[0].item(),boundaryRect[3]+structureOrigin[1].item()]
|
||||||
|
boundaries.append(boundaryRect)
|
||||||
if self.labelInRectangle(coordinates,boundaryRect):
|
|
||||||
boundaries.append(boundaryRect)
|
|
||||||
|
|
||||||
return boundaries
|
return boundaries
|
||||||
|
|
||||||
def transformRectangle(self,originalRectangle,uVector,vVector):
|
def transformRectangle(self,originalRectangle,uVector,vVector):
|
||||||
"""
|
"""
|
||||||
Transforms the four coordinates of a rectangle in space
|
Transforms the four coordinates of a rectangle in space
|
||||||
|
|
@ -830,8 +805,12 @@ class VlsiLayout:
|
||||||
"""
|
"""
|
||||||
Rotate a coordinate in space.
|
Rotate a coordinate in space.
|
||||||
"""
|
"""
|
||||||
x=coordinate[0]*uVector[0].item()+coordinate[1]*uVector[1].item()
|
# MRG: 9/3/18 Incorrect matrixi multiplication!
|
||||||
y=coordinate[1]*vVector[1].item()+coordinate[0]*vVector[0].item()
|
# This is fixed to be:
|
||||||
|
# |u[0] v[0]| |x| |x'|
|
||||||
|
# |u[1] v[1]|x|y|=|y'|
|
||||||
|
x=coordinate[0]*uVector[0].item()+coordinate[1]*vVector[0].item()
|
||||||
|
y=coordinate[0]*uVector[1].item()+coordinate[1]*vVector[1].item()
|
||||||
transformCoordinate=[x,y]
|
transformCoordinate=[x,y]
|
||||||
|
|
||||||
return transformCoordinate
|
return transformCoordinate
|
||||||
|
|
@ -841,13 +820,14 @@ class VlsiLayout:
|
||||||
"""
|
"""
|
||||||
Checks if a coordinate is within a given rectangle. Rectangle is [leftx, bottomy, rightx, topy].
|
Checks if a coordinate is within a given rectangle. Rectangle is [leftx, bottomy, rightx, topy].
|
||||||
"""
|
"""
|
||||||
coordinate_In_Rectangle_x_range=(coordinate[0]>=int(rectangle[0]))&(coordinate[0]<=int(rectangle[2]))
|
coordinate_In_Rectangle_x_range=(coordinate[0]>=rectangle[0])&(coordinate[0]<=rectangle[2])
|
||||||
coordinate_In_Rectangle_y_range=(coordinate[1]>=int(rectangle[1]))&(coordinate[1]<=int(rectangle[3]))
|
coordinate_In_Rectangle_y_range=(coordinate[1]>=rectangle[1])&(coordinate[1]<=rectangle[3])
|
||||||
if coordinate_In_Rectangle_x_range & coordinate_In_Rectangle_y_range:
|
if coordinate_In_Rectangle_x_range & coordinate_In_Rectangle_y_range:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def boundaryArea(A):
|
def boundaryArea(A):
|
||||||
"""
|
"""
|
||||||
Returns boundary area for sorting.
|
Returns boundary area for sorting.
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ def parse_args():
|
||||||
global OPTS
|
global OPTS
|
||||||
|
|
||||||
option_list = {
|
option_list = {
|
||||||
optparse.make_option("-b", "--backannotated", action="store_true", dest="run_pex",
|
optparse.make_option("-b", "--backannotated", action="store_true", dest="use_pex",
|
||||||
help="Back annotate simulation"),
|
help="Back annotate simulation"),
|
||||||
optparse.make_option("-o", "--output", dest="output_name",
|
optparse.make_option("-o", "--output", dest="output_name",
|
||||||
help="Base output file name(s) prefix", metavar="FILE"),
|
help="Base output file name(s) prefix", metavar="FILE"),
|
||||||
|
|
@ -59,7 +59,7 @@ def parse_args():
|
||||||
OPTS.tech_name = "scmos"
|
OPTS.tech_name = "scmos"
|
||||||
# Alias SCMOS to AMI 0.5um
|
# Alias SCMOS to AMI 0.5um
|
||||||
if OPTS.tech_name == "scmos":
|
if OPTS.tech_name == "scmos":
|
||||||
OPTS.tech_name = "scn3me_subm"
|
OPTS.tech_name = "scn4m_subm"
|
||||||
|
|
||||||
return (options, args)
|
return (options, args)
|
||||||
|
|
||||||
|
|
@ -74,10 +74,12 @@ def print_banner():
|
||||||
print("|=========" + name.center(60) + "=========|")
|
print("|=========" + name.center(60) + "=========|")
|
||||||
print("|=========" + " ".center(60) + "=========|")
|
print("|=========" + " ".center(60) + "=========|")
|
||||||
print("|=========" + "VLSI Design and Automation Lab".center(60) + "=========|")
|
print("|=========" + "VLSI Design and Automation Lab".center(60) + "=========|")
|
||||||
print("|=========" + "University of California Santa Cruz CE Department".center(60) + "=========|")
|
print("|=========" + "Computer Science and Engineering Department".center(60) + "=========|")
|
||||||
|
print("|=========" + "University of California Santa Cruz".center(60) + "=========|")
|
||||||
print("|=========" + " ".center(60) + "=========|")
|
print("|=========" + " ".center(60) + "=========|")
|
||||||
print("|=========" + "VLSI Computer Architecture Research Group".center(60) + "=========|")
|
print("|=========" + "VLSI Computer Architecture Research Group".center(60) + "=========|")
|
||||||
print("|=========" + "Oklahoma State University ECE Department".center(60) + "=========|")
|
print("|=========" + "Electrical and Computer Engineering Department".center(60) + "=========|")
|
||||||
|
print("|=========" + "Oklahoma State University".center(60) + "=========|")
|
||||||
print("|=========" + " ".center(60) + "=========|")
|
print("|=========" + " ".center(60) + "=========|")
|
||||||
user_info = "Usage help: openram-user-group@ucsc.edu"
|
user_info = "Usage help: openram-user-group@ucsc.edu"
|
||||||
print("|=========" + user_info.center(60) + "=========|")
|
print("|=========" + user_info.center(60) + "=========|")
|
||||||
|
|
@ -98,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."""
|
||||||
|
|
@ -116,6 +126,8 @@ def init_openram(config_file, is_unit_test=True):
|
||||||
|
|
||||||
import_tech()
|
import_tech()
|
||||||
|
|
||||||
|
init_paths()
|
||||||
|
|
||||||
# Reset the static duplicate name checker for unit tests.
|
# Reset the static duplicate name checker for unit tests.
|
||||||
import hierarchy_design
|
import hierarchy_design
|
||||||
hierarchy_design.hierarchy_design.name_map=[]
|
hierarchy_design.hierarchy_design.name_map=[]
|
||||||
|
|
@ -142,22 +154,31 @@ def init_openram(config_file, is_unit_test=True):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_tool(tool_type, preferences):
|
def get_tool(tool_type, preferences, default_name=None):
|
||||||
"""
|
"""
|
||||||
Find which tool we have from a list of preferences and return the
|
Find which tool we have from a list of preferences and return the
|
||||||
one selected and its full path.
|
one selected and its full path. If default is specified,
|
||||||
|
find that one only and error otherwise.
|
||||||
"""
|
"""
|
||||||
debug.info(2,"Finding {} tool...".format(tool_type))
|
debug.info(2,"Finding {} tool...".format(tool_type))
|
||||||
|
|
||||||
for name in preferences:
|
if default_name:
|
||||||
exe_name = find_exe(name)
|
exe_name=find_exe(default_name)
|
||||||
if exe_name != None:
|
if exe_name == None:
|
||||||
debug.info(1, "Using {0}: {1}".format(tool_type,exe_name))
|
debug.error("{0} not found. Cannot find {1} tool.".format(default_name,tool_type),2)
|
||||||
return(name,exe_name)
|
|
||||||
else:
|
else:
|
||||||
debug.info(1, "Could not find {0}, trying next {1} tool.".format(name,tool_type))
|
debug.info(1, "Using {0}: {1}".format(tool_type,exe_name))
|
||||||
|
return(default_name,exe_name)
|
||||||
else:
|
else:
|
||||||
return(None,"")
|
for name in preferences:
|
||||||
|
exe_name = find_exe(name)
|
||||||
|
if exe_name != None:
|
||||||
|
debug.info(1, "Using {0}: {1}".format(tool_type,exe_name))
|
||||||
|
return(name,exe_name)
|
||||||
|
else:
|
||||||
|
debug.info(1, "Could not find {0}, trying next {1} tool.".format(name,tool_type))
|
||||||
|
else:
|
||||||
|
return(None,"")
|
||||||
|
|
||||||
|
|
||||||
def read_config(config_file, is_unit_test=True):
|
def read_config(config_file, is_unit_test=True):
|
||||||
|
|
@ -195,32 +216,35 @@ def read_config(config_file, is_unit_test=True):
|
||||||
# Note that if we re-read a config file, nothing will get read again!
|
# Note that if we re-read a config file, nothing will get read again!
|
||||||
if not k in OPTS.__dict__ or k=="tech_name":
|
if not k in OPTS.__dict__ or k=="tech_name":
|
||||||
OPTS.__dict__[k]=v
|
OPTS.__dict__[k]=v
|
||||||
|
|
||||||
|
# Massage the output path to be an absolute one
|
||||||
if not OPTS.output_path.endswith('/'):
|
if not OPTS.output_path.endswith('/'):
|
||||||
OPTS.output_path += "/"
|
OPTS.output_path += "/"
|
||||||
if not OPTS.output_path.startswith('/'):
|
if not OPTS.output_path.startswith('/'):
|
||||||
OPTS.output_path = os.getcwd() + "/" + OPTS.output_path
|
OPTS.output_path = os.getcwd() + "/" + OPTS.output_path
|
||||||
debug.info(1, "Output saved in " + OPTS.output_path)
|
debug.info(1, "Output saved in " + OPTS.output_path)
|
||||||
|
|
||||||
|
# Remember if we are running unit tests to reduce output
|
||||||
OPTS.is_unit_test=is_unit_test
|
OPTS.is_unit_test=is_unit_test
|
||||||
|
|
||||||
|
# If we are only generating a netlist, we can't do DRC/LVS
|
||||||
|
if OPTS.netlist_only:
|
||||||
|
OPTS.check_lvsdrc=False
|
||||||
|
|
||||||
# If config didn't set output name, make a reasonable default.
|
# If config didn't set output name, make a reasonable default.
|
||||||
if (OPTS.output_name == ""):
|
if (OPTS.output_name == ""):
|
||||||
OPTS.output_name = "sram_{0}rw_{1}b_{2}w_{3}bank_{4}".format(OPTS.rw_ports,
|
ports = ""
|
||||||
OPTS.word_size,
|
if OPTS.num_rw_ports>0:
|
||||||
OPTS.num_words,
|
ports += "{}rw_".format(OPTS.num_rw_ports)
|
||||||
OPTS.num_banks,
|
if OPTS.num_w_ports>0:
|
||||||
OPTS.tech_name)
|
ports += "{}w_".format(OPTS.num_w_ports)
|
||||||
|
if OPTS.num_r_ports>0:
|
||||||
|
ports += "{}r_".format(OPTS.num_r_ports)
|
||||||
|
OPTS.output_name = "sram_{0}b_{1}_{2}{3}".format(OPTS.word_size,
|
||||||
|
OPTS.num_words,
|
||||||
|
ports,
|
||||||
|
OPTS.tech_name)
|
||||||
|
|
||||||
# Don't delete the output dir, it may have other files!
|
|
||||||
# make the directory if it doesn't exist
|
|
||||||
try:
|
|
||||||
os.makedirs(OPTS.output_path, 0o750)
|
|
||||||
except OSError as e:
|
|
||||||
if e.errno == 17: # errno.EEXIST
|
|
||||||
os.chmod(OPTS.output_path, 0o750)
|
|
||||||
except:
|
|
||||||
debug.error("Unable to make output directory.",-1)
|
|
||||||
|
|
||||||
|
|
||||||
def end_openram():
|
def end_openram():
|
||||||
|
|
@ -244,7 +268,8 @@ def cleanup_paths():
|
||||||
if not OPTS.purge_temp:
|
if not OPTS.purge_temp:
|
||||||
debug.info(0,"Preserving temp directory: {}".format(OPTS.openram_temp))
|
debug.info(0,"Preserving temp directory: {}".format(OPTS.openram_temp))
|
||||||
return
|
return
|
||||||
if os.path.exists(OPTS.openram_temp):
|
elif os.path.exists(OPTS.openram_temp):
|
||||||
|
debug.info(1,"Purging temp directory: {}".format(OPTS.openram_temp))
|
||||||
# This annoyingly means you have to re-cd into the directory each debug iteration
|
# This annoyingly means you have to re-cd into the directory each debug iteration
|
||||||
#shutil.rmtree(OPTS.openram_temp, ignore_errors=True)
|
#shutil.rmtree(OPTS.openram_temp, ignore_errors=True)
|
||||||
contents = [os.path.join(OPTS.openram_temp, i) for i in os.listdir(OPTS.openram_temp)]
|
contents = [os.path.join(OPTS.openram_temp, i) for i in os.listdir(OPTS.openram_temp)]
|
||||||
|
|
@ -270,7 +295,8 @@ def setup_paths():
|
||||||
|
|
||||||
# Add all of the subdirs to the python path
|
# 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", "router", "modules", "base", "pgates"]:
|
subdirlist = [ item for item in os.listdir(OPENRAM_HOME) if os.path.isdir(os.path.join(OPENRAM_HOME, item)) ]
|
||||||
|
for subdir in subdirlist:
|
||||||
full_path = "{0}/{1}".format(OPENRAM_HOME,subdir)
|
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))
|
||||||
|
|
@ -280,14 +306,6 @@ def setup_paths():
|
||||||
OPTS.openram_temp += "/"
|
OPTS.openram_temp += "/"
|
||||||
debug.info(1, "Temporary files saved in " + OPTS.openram_temp)
|
debug.info(1, "Temporary files saved in " + OPTS.openram_temp)
|
||||||
|
|
||||||
cleanup_paths()
|
|
||||||
|
|
||||||
# make the directory if it doesn't exist
|
|
||||||
try:
|
|
||||||
os.makedirs(OPTS.openram_temp, 0o750)
|
|
||||||
except OSError as e:
|
|
||||||
if e.errno == 17: # errno.EEXIST
|
|
||||||
os.chmod(OPTS.openram_temp, 0o750)
|
|
||||||
|
|
||||||
|
|
||||||
def is_exe(fpath):
|
def is_exe(fpath):
|
||||||
|
|
@ -303,7 +321,29 @@ def find_exe(check_exe):
|
||||||
if is_exe(exe):
|
if is_exe(exe):
|
||||||
return exe
|
return exe
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def init_paths():
|
||||||
|
""" Create the temp and output directory if it doesn't exist """
|
||||||
|
|
||||||
|
# make the directory if it doesn't exist
|
||||||
|
try:
|
||||||
|
debug.info(1,"Creating temp directory: {}".format(OPTS.openram_temp))
|
||||||
|
os.makedirs(OPTS.openram_temp, 0o750)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == 17: # errno.EEXIST
|
||||||
|
os.chmod(OPTS.openram_temp, 0o750)
|
||||||
|
|
||||||
|
# Don't delete the output dir, it may have other files!
|
||||||
|
# make the directory if it doesn't exist
|
||||||
|
try:
|
||||||
|
os.makedirs(OPTS.output_path, 0o750)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == 17: # errno.EEXIST
|
||||||
|
os.chmod(OPTS.output_path, 0o750)
|
||||||
|
except:
|
||||||
|
debug.error("Unable to make output directory.",-1)
|
||||||
|
|
||||||
|
|
||||||
# imports correct technology directories for testing
|
# imports correct technology directories for testing
|
||||||
def import_tech():
|
def import_tech():
|
||||||
global OPTS
|
global OPTS
|
||||||
|
|
@ -362,8 +402,6 @@ def report_status():
|
||||||
debug.error("{0} is not an integer in config file.".format(OPTS.word_size))
|
debug.error("{0} is not an integer in config file.".format(OPTS.word_size))
|
||||||
if type(OPTS.num_words)!=int:
|
if type(OPTS.num_words)!=int:
|
||||||
debug.error("{0} is not an integer in config file.".format(OPTS.sram_size))
|
debug.error("{0} is not an integer in config file.".format(OPTS.sram_size))
|
||||||
if type(OPTS.num_banks)!=int:
|
|
||||||
debug.error("{0} is not an integer in config file.".format(OPTS.num_banks))
|
|
||||||
|
|
||||||
if not OPTS.tech_name:
|
if not OPTS.tech_name:
|
||||||
debug.error("Tech name must be specified in config file.")
|
debug.error("Tech name must be specified in config file.")
|
||||||
|
|
@ -372,6 +410,9 @@ def report_status():
|
||||||
print("Word size: {0}\nWords: {1}\nBanks: {2}".format(OPTS.word_size,
|
print("Word size: {0}\nWords: {1}\nBanks: {2}".format(OPTS.word_size,
|
||||||
OPTS.num_words,
|
OPTS.num_words,
|
||||||
OPTS.num_banks))
|
OPTS.num_banks))
|
||||||
|
if OPTS.netlist_only:
|
||||||
|
print("Netlist only mode (no physical design is being done).")
|
||||||
|
|
||||||
if not OPTS.check_lvsdrc:
|
if not OPTS.check_lvsdrc:
|
||||||
print("DRC/LVS/PEX checking is disabled.")
|
print("DRC/LVS/PEX checking is disabled.")
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -15,13 +15,42 @@ class bank_select(design.design):
|
||||||
banks are created in upper level SRAM module
|
banks are created in upper level SRAM module
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name="bank_select"):
|
def __init__(self, name="bank_select", port="rw"):
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
|
self.port = port
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_pins()
|
||||||
|
self.add_modules()
|
||||||
|
self.create_modules()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
self.calculate_module_offsets()
|
||||||
|
self.place_modules()
|
||||||
|
self.route_modules()
|
||||||
|
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
|
||||||
# Number of control lines in the bus
|
# Number of control lines in the bus
|
||||||
self.num_control_lines = 6
|
if self.port == "rw":
|
||||||
|
self.num_control_lines = 4
|
||||||
|
else:
|
||||||
|
self.num_control_lines = 3
|
||||||
# The order of the control signals on the control bus:
|
# The order of the control signals on the control bus:
|
||||||
self.input_control_signals = ["clk_buf", "tri_en_bar", "tri_en", "clk_buf_bar", "w_en", "s_en"]
|
# FIXME: Update for multiport (these names are not right)
|
||||||
|
self.input_control_signals = ["clk_buf", "clk_buf_bar"]
|
||||||
|
if (self.port == "rw") or (self.port == "w"):
|
||||||
|
self.input_control_signals.append("w_en")
|
||||||
|
if (self.port == "rw") or (self.port == "r"):
|
||||||
|
self.input_control_signals.append("s_en")
|
||||||
# These will be outputs of the gaters if this is multibank
|
# These will be outputs of the gaters if this is multibank
|
||||||
self.control_signals = ["gated_"+str for str in self.input_control_signals]
|
self.control_signals = ["gated_"+str for str in self.input_control_signals]
|
||||||
|
|
||||||
|
|
@ -31,33 +60,36 @@ class bank_select(design.design):
|
||||||
self.add_pin("vdd","POWER")
|
self.add_pin("vdd","POWER")
|
||||||
self.add_pin("gnd","GROUND")
|
self.add_pin("gnd","GROUND")
|
||||||
|
|
||||||
self.create_modules()
|
def add_modules(self):
|
||||||
self.calculate_module_offsets()
|
|
||||||
self.add_modules()
|
|
||||||
self.route_modules()
|
|
||||||
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
def create_modules(self):
|
|
||||||
""" Create modules for later instantiation """
|
""" Create modules for later instantiation """
|
||||||
|
from importlib import reload
|
||||||
|
c = reload(__import__(OPTS.bitcell))
|
||||||
|
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
||||||
|
self.bitcell = self.mod_bitcell()
|
||||||
|
|
||||||
|
height = self.bitcell.height + drc("poly_to_active")
|
||||||
|
|
||||||
# 1x Inverter
|
# 1x Inverter
|
||||||
self.inv = pinv()
|
self.inv_sel = pinv(height=height)
|
||||||
self.add_mod(self.inv)
|
self.add_mod(self.inv_sel)
|
||||||
|
|
||||||
# 4x Inverter
|
# 4x Inverter
|
||||||
self.inv4x = pinv(4)
|
self.inv = self.inv4x = pinv(4)
|
||||||
self.add_mod(self.inv4x)
|
self.add_mod(self.inv4x)
|
||||||
|
|
||||||
self.nor2 = pnor2()
|
self.nor2 = pnor2(height=height)
|
||||||
self.add_mod(self.nor2)
|
self.add_mod(self.nor2)
|
||||||
|
|
||||||
|
self.inv4x_nor = pinv(size=4, height=height)
|
||||||
|
self.add_mod(self.inv4x_nor)
|
||||||
|
|
||||||
self.nand2 = pnand2()
|
self.nand2 = pnand2()
|
||||||
self.add_mod(self.nand2)
|
self.add_mod(self.nand2)
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -67,15 +99,10 @@ class bank_select(design.design):
|
||||||
self.height = self.yoffset_maxpoint + 2*self.m1_pitch
|
self.height = self.yoffset_maxpoint + 2*self.m1_pitch
|
||||||
self.width = self.xoffset_inv + self.inv4x.width
|
self.width = self.xoffset_inv + self.inv4x.width
|
||||||
|
|
||||||
def add_modules(self):
|
def create_modules(self):
|
||||||
|
|
||||||
# bank select inverter
|
|
||||||
self.bank_select_inv_position = vector(self.xoffset_bank_sel_inv, 0)
|
|
||||||
|
|
||||||
# bank select inverter (must be made unique if more than one OR)
|
|
||||||
self.bank_sel_inv=self.add_inst(name="bank_sel_inv",
|
self.bank_sel_inv=self.add_inst(name="bank_sel_inv",
|
||||||
mod=self.inv,
|
mod=self.inv_sel)
|
||||||
offset=[self.xoffset_bank_sel_inv, 0])
|
|
||||||
self.connect_inst(["bank_sel", "bank_sel_bar", "vdd", "gnd"])
|
self.connect_inst(["bank_sel", "bank_sel_bar", "vdd", "gnd"])
|
||||||
|
|
||||||
self.logic_inst = []
|
self.logic_inst = []
|
||||||
|
|
@ -87,7 +114,64 @@ class bank_select(design.design):
|
||||||
name_nor = "nor_{}".format(input_name)
|
name_nor = "nor_{}".format(input_name)
|
||||||
name_inv = "inv_{}".format(input_name)
|
name_inv = "inv_{}".format(input_name)
|
||||||
|
|
||||||
y_offset = self.inv.height * i
|
# These require OR (nor2+inv) gates since they are active low.
|
||||||
|
# (writes occur on clk low)
|
||||||
|
if input_name in ("clk_buf"):
|
||||||
|
|
||||||
|
self.logic_inst.append(self.add_inst(name=name_nor,
|
||||||
|
mod=self.nor2))
|
||||||
|
self.connect_inst([input_name,
|
||||||
|
"bank_sel_bar",
|
||||||
|
gated_name+"_temp_bar",
|
||||||
|
"vdd",
|
||||||
|
"gnd"])
|
||||||
|
|
||||||
|
# They all get inverters on the output
|
||||||
|
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||||
|
mod=self.inv4x_nor))
|
||||||
|
self.connect_inst([gated_name+"_temp_bar",
|
||||||
|
gated_name,
|
||||||
|
"vdd",
|
||||||
|
"gnd"])
|
||||||
|
|
||||||
|
# the rest are AND (nand2+inv) gates
|
||||||
|
else:
|
||||||
|
self.logic_inst.append(self.add_inst(name=name_nand,
|
||||||
|
mod=self.nand2))
|
||||||
|
self.connect_inst([input_name,
|
||||||
|
"bank_sel",
|
||||||
|
gated_name+"_temp_bar",
|
||||||
|
"vdd",
|
||||||
|
"gnd"])
|
||||||
|
|
||||||
|
# They all get inverters on the output
|
||||||
|
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||||
|
mod=self.inv4x))
|
||||||
|
self.connect_inst([gated_name+"_temp_bar",
|
||||||
|
gated_name,
|
||||||
|
"vdd",
|
||||||
|
"gnd"])
|
||||||
|
|
||||||
|
def place_modules(self):
|
||||||
|
|
||||||
|
# bank select inverter
|
||||||
|
self.bank_select_inv_position = vector(self.xoffset_bank_sel_inv, 0)
|
||||||
|
|
||||||
|
# bank select inverter (must be made unique if more than one OR)
|
||||||
|
self.bank_sel_inv.place(vector(self.xoffset_bank_sel_inv, 0))
|
||||||
|
|
||||||
|
for i in range(self.num_control_lines):
|
||||||
|
|
||||||
|
logic_inst = self.logic_inst[i]
|
||||||
|
inv_inst = self.inv_inst[i]
|
||||||
|
|
||||||
|
input_name = self.input_control_signals[i]
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
y_offset = 0
|
||||||
|
else:
|
||||||
|
y_offset = self.inv4x_nor.height + self.inv.height * (i-1)
|
||||||
|
|
||||||
if i%2:
|
if i%2:
|
||||||
y_offset += self.inv.height
|
y_offset += self.inv.height
|
||||||
mirror = "MX"
|
mirror = "MX"
|
||||||
|
|
@ -96,42 +180,20 @@ class bank_select(design.design):
|
||||||
|
|
||||||
# These require OR (nor2+inv) gates since they are active low.
|
# These require OR (nor2+inv) gates since they are active low.
|
||||||
# (writes occur on clk low)
|
# (writes occur on clk low)
|
||||||
if input_name in ("clk_buf", "tri_en_bar"):
|
if input_name in ("clk_buf"):
|
||||||
|
|
||||||
self.logic_inst.append(self.add_inst(name=name_nor,
|
logic_inst.place(offset=[self.xoffset_nor, y_offset],
|
||||||
mod=self.nor2,
|
mirror=mirror)
|
||||||
offset=[self.xoffset_nor, y_offset],
|
|
||||||
mirror=mirror))
|
|
||||||
self.connect_inst([input_name,
|
|
||||||
"bank_sel_bar",
|
|
||||||
gated_name+"_temp_bar",
|
|
||||||
"vdd",
|
|
||||||
"gnd"])
|
|
||||||
|
|
||||||
|
|
||||||
# the rest are AND (nand2+inv) gates
|
# the rest are AND (nand2+inv) gates
|
||||||
else:
|
else:
|
||||||
self.logic_inst.append(self.add_inst(name=name_nand,
|
logic_inst.place(offset=[self.xoffset_nand, y_offset],
|
||||||
mod=self.nand2,
|
mirror=mirror)
|
||||||
offset=[self.xoffset_nand, y_offset],
|
|
||||||
mirror=mirror))
|
|
||||||
bank_sel_signal = "bank_sel"
|
|
||||||
self.connect_inst([input_name,
|
|
||||||
"bank_sel",
|
|
||||||
gated_name+"_temp_bar",
|
|
||||||
"vdd",
|
|
||||||
"gnd"])
|
|
||||||
|
|
||||||
# They all get inverters on the output
|
# They all get inverters on the output
|
||||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
inv_inst.place(offset=[self.xoffset_inv, y_offset],
|
||||||
mod=self.inv4x,
|
mirror=mirror)
|
||||||
offset=[self.xoffset_inv, y_offset],
|
|
||||||
mirror=mirror))
|
|
||||||
self.connect_inst([gated_name+"_temp_bar",
|
|
||||||
gated_name,
|
|
||||||
"vdd",
|
|
||||||
"gnd"])
|
|
||||||
|
|
||||||
|
|
||||||
def route_modules(self):
|
def route_modules(self):
|
||||||
|
|
||||||
|
|
@ -161,7 +223,7 @@ class bank_select(design.design):
|
||||||
self.add_label_pin(text="bank_sel_bar",
|
self.add_label_pin(text="bank_sel_bar",
|
||||||
layer="metal2",
|
layer="metal2",
|
||||||
offset=vector(xoffset_bank_sel_bar, 0),
|
offset=vector(xoffset_bank_sel_bar, 0),
|
||||||
height=2*self.inv.height)
|
height=self.inv.height)
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||||
offset=bank_sel_bar_pin.rc())
|
offset=bank_sel_bar_pin.rc())
|
||||||
|
|
||||||
|
|
@ -173,7 +235,7 @@ class bank_select(design.design):
|
||||||
|
|
||||||
input_name = self.input_control_signals[i]
|
input_name = self.input_control_signals[i]
|
||||||
gated_name = self.control_signals[i]
|
gated_name = self.control_signals[i]
|
||||||
if input_name in ("clk_buf", "tri_en_bar"):
|
if input_name in ("clk_buf"):
|
||||||
xoffset_bank_signal = xoffset_bank_sel_bar
|
xoffset_bank_signal = xoffset_bank_sel_bar
|
||||||
else:
|
else:
|
||||||
xoffset_bank_signal = xoffset_bank_sel
|
xoffset_bank_signal = xoffset_bank_sel
|
||||||
|
|
|
||||||
|
|
@ -1,201 +1,227 @@
|
||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
from tech import drc, spice
|
from tech import drc, spice
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class bitcell_array(design.design):
|
class bitcell_array(design.design):
|
||||||
"""
|
"""
|
||||||
Creates a rows x cols array of memory cells. Assumes bit-lines
|
Creates a rows x cols array of memory cells. Assumes bit-lines
|
||||||
and word line is connected by abutment.
|
and word line is connected by abutment.
|
||||||
Connects the word lines and bit lines.
|
Connects the word lines and bit lines.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cols, rows, name="bitcell_array"):
|
def __init__(self, cols, rows, name="bitcell_array"):
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
|
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
|
||||||
|
|
||||||
|
|
||||||
self.column_size = cols
|
self.column_size = cols
|
||||||
self.row_size = rows
|
self.row_size = rows
|
||||||
|
|
||||||
from importlib import reload
|
self.create_netlist()
|
||||||
c = reload(__import__(OPTS.bitcell))
|
if not OPTS.netlist_only:
|
||||||
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
self.create_layout()
|
||||||
self.cell = self.mod_bitcell()
|
|
||||||
self.add_mod(self.cell)
|
# We don't offset this because we need to align
|
||||||
|
# the replica bitcell in the control logic
|
||||||
# We increase it by a well enclosure so the precharges don't overlap our wells
|
#self.offset_all_coordinates()
|
||||||
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
|
|
||||||
|
def create_netlist(self):
|
||||||
self.add_pins()
|
""" Create and connect the netlist """
|
||||||
self.create_layout()
|
self.add_modules()
|
||||||
self.add_layout_pins()
|
self.add_pins()
|
||||||
|
self.create_modules()
|
||||||
# We don't offset this because we need to align
|
|
||||||
# the replica bitcell in the control logic
|
def create_layout(self):
|
||||||
#self.offset_all_coordinates()
|
|
||||||
|
# We increase it by a well enclosure so the precharges don't overlap our wells
|
||||||
self.DRC_LVS()
|
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
|
||||||
def add_pins(self):
|
|
||||||
row_list = self.cell.list_row_pins()
|
xoffset = 0.0
|
||||||
column_list = self.cell.list_column_pins()
|
for col in range(self.column_size):
|
||||||
for col in range(self.column_size):
|
yoffset = 0.0
|
||||||
for cell_column in column_list:
|
for row in range(self.row_size):
|
||||||
self.add_pin(cell_column+"[{0}]".format(col))
|
name = "bit_r{0}_c{1}".format(row, col)
|
||||||
for row in range(self.row_size):
|
|
||||||
for cell_row in row_list:
|
if row % 2:
|
||||||
self.add_pin(cell_row+"[{0}]".format(row))
|
tempy = yoffset + self.cell.height
|
||||||
self.add_pin("vdd")
|
dir_key = "MX"
|
||||||
self.add_pin("gnd")
|
else:
|
||||||
|
tempy = yoffset
|
||||||
def create_layout(self):
|
dir_key = ""
|
||||||
xoffset = 0.0
|
|
||||||
self.cell_inst = {}
|
self.cell_inst[row,col].place(offset=[xoffset, tempy],
|
||||||
for col in range(self.column_size):
|
mirror=dir_key)
|
||||||
yoffset = 0.0
|
yoffset += self.cell.height
|
||||||
for row in range(self.row_size):
|
xoffset += self.cell.width
|
||||||
name = "bit_r{0}_c{1}".format(row, col)
|
|
||||||
|
self.add_layout_pins()
|
||||||
if row % 2:
|
|
||||||
tempy = yoffset + self.cell.height
|
self.DRC_LVS()
|
||||||
dir_key = "MX"
|
|
||||||
else:
|
def add_pins(self):
|
||||||
tempy = yoffset
|
row_list = self.cell.list_all_wl_names()
|
||||||
dir_key = ""
|
column_list = self.cell.list_all_bitline_names()
|
||||||
|
for col in range(self.column_size):
|
||||||
self.cell_inst[row,col]=self.add_inst(name=name,
|
for cell_column in column_list:
|
||||||
mod=self.cell,
|
self.add_pin(cell_column+"_{0}".format(col))
|
||||||
offset=[xoffset, tempy],
|
for row in range(self.row_size):
|
||||||
mirror=dir_key)
|
for cell_row in row_list:
|
||||||
self.connect_inst(self.cell.list_bitcell_pins(col, row))
|
self.add_pin(cell_row+"_{0}".format(row))
|
||||||
|
self.add_pin("vdd")
|
||||||
yoffset += self.cell.height
|
self.add_pin("gnd")
|
||||||
xoffset += self.cell.width
|
|
||||||
|
def add_modules(self):
|
||||||
|
""" Add the modules used in this design """
|
||||||
def add_layout_pins(self):
|
|
||||||
""" Add the layout pins """
|
from importlib import reload
|
||||||
|
c = reload(__import__(OPTS.bitcell))
|
||||||
column_list = self.cell.list_column_pins()
|
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
||||||
offset = vector(0.0, 0.0)
|
self.cell = self.mod_bitcell()
|
||||||
for col in range(self.column_size):
|
self.add_mod(self.cell)
|
||||||
for cell_column in column_list:
|
|
||||||
bl_pin = self.cell_inst[0,col].get_pin(cell_column)
|
def create_modules(self):
|
||||||
self.add_layout_pin(text=cell_column+"[{0}]".format(col),
|
""" Create the module instances used in this design """
|
||||||
layer="metal2",
|
self.cell_inst = {}
|
||||||
offset=bl_pin.ll(),
|
for col in range(self.column_size):
|
||||||
width=bl_pin.width(),
|
for row in range(self.row_size):
|
||||||
height=self.height)
|
name = "bit_r{0}_c{1}".format(row, col)
|
||||||
|
self.cell_inst[row,col]=self.add_inst(name=name,
|
||||||
# increments to the next column width
|
mod=self.cell)
|
||||||
offset.x += self.cell.width
|
self.connect_inst(self.cell.list_bitcell_pins(col, row))
|
||||||
|
|
||||||
row_list = self.cell.list_row_pins()
|
def add_layout_pins(self):
|
||||||
offset.x = 0.0
|
""" Add the layout pins """
|
||||||
for row in range(self.row_size):
|
|
||||||
for cell_row in row_list:
|
row_list = self.cell.list_all_wl_names()
|
||||||
wl_pin = self.cell_inst[row,0].get_pin(cell_row)
|
column_list = self.cell.list_all_bitline_names()
|
||||||
self.add_layout_pin(text=cell_row+"[{0}]".format(row),
|
|
||||||
layer="metal1",
|
offset = vector(0.0, 0.0)
|
||||||
offset=wl_pin.ll(),
|
for col in range(self.column_size):
|
||||||
width=self.width,
|
for cell_column in column_list:
|
||||||
height=wl_pin.height())
|
bl_pin = self.cell_inst[0,col].get_pin(cell_column)
|
||||||
|
self.add_layout_pin(text=cell_column+"_{0}".format(col),
|
||||||
# increments to the next row height
|
layer="metal2",
|
||||||
offset.y += self.cell.height
|
offset=bl_pin.ll(),
|
||||||
|
width=bl_pin.width(),
|
||||||
# For every second row and column, add a via for vdd
|
height=self.height)
|
||||||
for row in range(self.row_size):
|
|
||||||
for col in range(self.column_size):
|
# increments to the next column width
|
||||||
inst = self.cell_inst[row,col]
|
offset.x += self.cell.width
|
||||||
for vdd_pin in inst.get_pins("vdd"):
|
|
||||||
# Drop to M1 if needed
|
offset.x = 0.0
|
||||||
if vdd_pin.layer == "metal1":
|
for row in range(self.row_size):
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
for cell_row in row_list:
|
||||||
offset=vdd_pin.center(),
|
wl_pin = self.cell_inst[row,0].get_pin(cell_row)
|
||||||
rotate=90)
|
self.add_layout_pin(text=cell_row+"_{0}".format(row),
|
||||||
# Always drop to M2
|
layer="metal1",
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
offset=wl_pin.ll(),
|
||||||
offset=vdd_pin.center())
|
width=self.width,
|
||||||
self.add_layout_pin_rect_center(text="vdd",
|
height=wl_pin.height())
|
||||||
layer="metal3",
|
|
||||||
offset=vdd_pin.center())
|
# increments to the next row height
|
||||||
|
offset.y += self.cell.height
|
||||||
|
|
||||||
# For every second row and column (+1), add a via for gnd
|
# For every second row and column, add a via for vdd
|
||||||
for row in range(self.row_size):
|
for row in range(self.row_size):
|
||||||
for col in range(self.column_size):
|
for col in range(self.column_size):
|
||||||
inst = self.cell_inst[row,col]
|
inst = self.cell_inst[row,col]
|
||||||
for gnd_pin in inst.get_pins("gnd"):
|
for vdd_pin in inst.get_pins("vdd"):
|
||||||
# Drop to M1 if needed
|
# Drop to M1 if needed
|
||||||
if gnd_pin.layer == "metal1":
|
if vdd_pin.layer == "metal1":
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||||
offset=gnd_pin.center(),
|
offset=vdd_pin.center(),
|
||||||
rotate=90)
|
rotate=90)
|
||||||
# Always drop to M2
|
# Always drop to M2
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||||
offset=gnd_pin.center())
|
offset=vdd_pin.center())
|
||||||
self.add_layout_pin_rect_center(text="gnd",
|
self.add_layout_pin_rect_center(text="vdd",
|
||||||
layer="metal3",
|
layer="metal3",
|
||||||
offset=gnd_pin.center())
|
offset=vdd_pin.center())
|
||||||
|
|
||||||
def analytical_delay(self, slew, load=0):
|
|
||||||
from tech import drc
|
# For every second row and column (+1), add a via for gnd
|
||||||
wl_wire = self.gen_wl_wire()
|
for row in range(self.row_size):
|
||||||
wl_wire.return_delay_over_wire(slew)
|
for col in range(self.column_size):
|
||||||
|
inst = self.cell_inst[row,col]
|
||||||
wl_to_cell_delay = wl_wire.return_delay_over_wire(slew)
|
for gnd_pin in inst.get_pins("gnd"):
|
||||||
# hypothetical delay from cell to bl end without sense amp
|
# Drop to M1 if needed
|
||||||
bl_wire = self.gen_bl_wire()
|
if gnd_pin.layer == "metal1":
|
||||||
cell_load = 2 * bl_wire.return_input_cap() # we ingore the wire r
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||||
# hence just use the whole c
|
offset=gnd_pin.center(),
|
||||||
bl_swing = 0.1
|
rotate=90)
|
||||||
cell_delay = self.cell.analytical_delay(wl_to_cell_delay.slew, cell_load, swing = bl_swing)
|
# Always drop to M2
|
||||||
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||||
#we do not consider the delay over the wire for now
|
offset=gnd_pin.center())
|
||||||
return self.return_delay(cell_delay.delay+wl_to_cell_delay.delay,
|
self.add_layout_pin_rect_center(text="gnd",
|
||||||
wl_to_cell_delay.slew)
|
layer="metal3",
|
||||||
|
offset=gnd_pin.center())
|
||||||
def analytical_power(self, proc, vdd, temp, load):
|
|
||||||
"""Power of Bitcell array and bitline in nW."""
|
def analytical_delay(self, slew, load=0):
|
||||||
from tech import drc
|
from tech import drc
|
||||||
|
wl_wire = self.gen_wl_wire()
|
||||||
# Dynamic Power from Bitline
|
wl_wire.return_delay_over_wire(slew)
|
||||||
bl_wire = self.gen_bl_wire()
|
|
||||||
cell_load = 2 * bl_wire.return_input_cap()
|
wl_to_cell_delay = wl_wire.return_delay_over_wire(slew)
|
||||||
bl_swing = 0.1 #This should probably be defined in the tech file or input
|
# hypothetical delay from cell to bl end without sense amp
|
||||||
freq = spice["default_event_rate"]
|
bl_wire = self.gen_bl_wire()
|
||||||
bitline_dynamic = bl_swing*cell_load*vdd*vdd*freq #not sure if calculation is correct
|
cell_load = 2 * bl_wire.return_input_cap() # we ingore the wire r
|
||||||
|
# hence just use the whole c
|
||||||
#Calculate the bitcell power which currently only includes leakage
|
bl_swing = 0.1
|
||||||
cell_power = self.cell.analytical_power(proc, vdd, temp, load)
|
cell_delay = self.cell.analytical_delay(wl_to_cell_delay.slew, cell_load, swing = bl_swing)
|
||||||
|
|
||||||
#Leakage power grows with entire array and bitlines.
|
#we do not consider the delay over the wire for now
|
||||||
total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size,
|
return self.return_delay(cell_delay.delay+wl_to_cell_delay.delay,
|
||||||
cell_power.leakage * self.column_size * self.row_size)
|
wl_to_cell_delay.slew)
|
||||||
return total_power
|
|
||||||
|
def analytical_power(self, proc, vdd, temp, load):
|
||||||
def gen_wl_wire(self):
|
"""Power of Bitcell array and bitline in nW."""
|
||||||
wl_wire = self.generate_rc_net(int(self.column_size), self.width, drc["minwidth_metal1"])
|
from tech import drc
|
||||||
wl_wire.wire_c = 2*spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell
|
|
||||||
return wl_wire
|
# Dynamic Power from Bitline
|
||||||
|
bl_wire = self.gen_bl_wire()
|
||||||
def gen_bl_wire(self):
|
cell_load = 2 * bl_wire.return_input_cap()
|
||||||
bl_pos = 0
|
bl_swing = 0.1 #This should probably be defined in the tech file or input
|
||||||
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), self.height, drc["minwidth_metal1"])
|
freq = spice["default_event_rate"]
|
||||||
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
|
bitline_dynamic = bl_swing*cell_load*vdd*vdd*freq #not sure if calculation is correct
|
||||||
return bl_wire
|
|
||||||
|
#Calculate the bitcell power which currently only includes leakage
|
||||||
def output_load(self, bl_pos=0):
|
cell_power = self.cell.analytical_power(proc, vdd, temp, load)
|
||||||
bl_wire = self.gen_bl_wire()
|
|
||||||
return bl_wire.wire_c # sense amp only need to charge small portion of the bl
|
#Leakage power grows with entire array and bitlines.
|
||||||
# set as one segment for now
|
total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size,
|
||||||
|
cell_power.leakage * self.column_size * self.row_size)
|
||||||
def input_load(self):
|
return total_power
|
||||||
wl_wire = self.gen_wl_wire()
|
|
||||||
return wl_wire.return_input_cap()
|
def gen_wl_wire(self):
|
||||||
|
if OPTS.netlist_only:
|
||||||
|
width = 0
|
||||||
|
else:
|
||||||
|
width = self.width
|
||||||
|
wl_wire = self.generate_rc_net(int(self.column_size), width, drc("minwidth_metal1"))
|
||||||
|
wl_wire.wire_c = 2*spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell
|
||||||
|
return wl_wire
|
||||||
|
|
||||||
|
def gen_bl_wire(self):
|
||||||
|
if OPTS.netlist_only:
|
||||||
|
height = 0
|
||||||
|
else:
|
||||||
|
height = self.height
|
||||||
|
bl_pos = 0
|
||||||
|
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), height, drc("minwidth_metal1"))
|
||||||
|
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
|
||||||
|
return bl_wire
|
||||||
|
|
||||||
|
def output_load(self, bl_pos=0):
|
||||||
|
bl_wire = self.gen_bl_wire()
|
||||||
|
return bl_wire.wire_c # sense amp only need to charge small portion of the bl
|
||||||
|
# set as one segment for now
|
||||||
|
|
||||||
|
def input_load(self):
|
||||||
|
wl_wire = self.gen_wl_wire()
|
||||||
|
return wl_wire.return_input_cap()
|
||||||
|
|
|
||||||
|
|
@ -18,23 +18,40 @@ class control_logic(design.design):
|
||||||
Dynamically generated Control logic for the total SRAM circuit.
|
Dynamically generated Control logic for the total SRAM circuit.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, num_rows):
|
def __init__(self, num_rows, words_per_row, port_type="rw"):
|
||||||
""" Constructor """
|
""" Constructor """
|
||||||
design.design.__init__(self, "control_logic")
|
name = "control_logic_" + port_type
|
||||||
debug.info(1, "Creating {}".format(self.name))
|
design.design.__init__(self, name)
|
||||||
|
debug.info(1, "Creating {}".format(name))
|
||||||
|
|
||||||
self.num_rows = num_rows
|
self.num_rows = num_rows
|
||||||
self.create_layout()
|
self.words_per_row = words_per_row
|
||||||
self.DRC_LVS()
|
self.port_type = port_type
|
||||||
|
|
||||||
|
if self.port_type == "rw":
|
||||||
|
self.num_control_signals = 2
|
||||||
|
else:
|
||||||
|
self.num_control_signals = 1
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.setup_signal_busses()
|
||||||
|
self.add_pins()
|
||||||
|
self.add_modules()
|
||||||
|
self.create_modules()
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
""" Create layout and route between modules """
|
""" Create layout and route between modules """
|
||||||
self.setup_layout_offsets()
|
self.route_rails()
|
||||||
self.add_pins()
|
self.place_modules()
|
||||||
self.create_modules()
|
self.route_all()
|
||||||
self.add_rails()
|
|
||||||
self.add_modules()
|
#self.add_lvs_correspondence_points()
|
||||||
self.add_routing()
|
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
|
|
@ -46,13 +63,13 @@ class control_logic(design.design):
|
||||||
self.add_pin("vdd","POWER")
|
self.add_pin("vdd","POWER")
|
||||||
self.add_pin("gnd","GROUND")
|
self.add_pin("gnd","GROUND")
|
||||||
|
|
||||||
def create_modules(self):
|
def add_modules(self):
|
||||||
""" add all the required modules """
|
""" Add all the required modules """
|
||||||
|
|
||||||
dff = dff_inv()
|
dff = dff_inv()
|
||||||
dff_height = dff.height
|
dff_height = dff.height
|
||||||
|
|
||||||
self.ctrl_dff_array = dff_inv_array(rows=3,columns=1)
|
self.ctrl_dff_array = dff_inv_array(rows=self.num_control_signals,columns=1)
|
||||||
self.add_mod(self.ctrl_dff_array)
|
self.add_mod(self.ctrl_dff_array)
|
||||||
|
|
||||||
self.nand2 = pnand2(height=dff_height)
|
self.nand2 = pnand2(height=dff_height)
|
||||||
|
|
@ -72,41 +89,65 @@ class control_logic(design.design):
|
||||||
self.inv8 = pinv(size=16, height=dff_height)
|
self.inv8 = pinv(size=16, height=dff_height)
|
||||||
self.add_mod(self.inv8)
|
self.add_mod(self.inv8)
|
||||||
|
|
||||||
from importlib import reload
|
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||||
c = reload(__import__(OPTS.replica_bitline))
|
from importlib import reload
|
||||||
replica_bitline = getattr(c, OPTS.replica_bitline)
|
c = reload(__import__(OPTS.replica_bitline))
|
||||||
# FIXME: These should be tuned according to the size!
|
replica_bitline = getattr(c, OPTS.replica_bitline)
|
||||||
delay_stages = 4 # Must be non-inverting
|
|
||||||
|
delay_stages, delay_fanout = self.get_delay_chain_size()
|
||||||
|
bitcell_loads = int(math.ceil(self.num_rows / 2.0))
|
||||||
|
self.replica_bitline = replica_bitline(delay_stages, delay_fanout, bitcell_loads, name="replica_bitline_"+self.port_type)
|
||||||
|
self.add_mod(self.replica_bitline)
|
||||||
|
|
||||||
|
def get_delay_chain_size(self):
|
||||||
|
"""Determine the size of the delay chain used for the Sense Amp Enable """
|
||||||
|
# FIXME: These should be tuned according to the additional size parameters
|
||||||
delay_fanout = 3 # This can be anything >=2
|
delay_fanout = 3 # This can be anything >=2
|
||||||
bitcell_loads = int(math.ceil(self.num_rows / 5.0))
|
# Delay stages Must be non-inverting
|
||||||
self.replica_bitline = replica_bitline(delay_stages, delay_fanout, bitcell_loads)
|
if self.words_per_row >= 8:
|
||||||
self.add_mod(self.replica_bitline)
|
delay_stages = 8
|
||||||
|
elif self.words_per_row == 4:
|
||||||
|
delay_stages = 6
|
||||||
def setup_layout_offsets(self):
|
else:
|
||||||
""" Setup layout offsets, determine the size of the busses etc """
|
delay_stages = 4
|
||||||
# These aren't for instantiating, but we use them to get the dimensions
|
return (delay_stages, delay_fanout)
|
||||||
#self.poly_contact_offset = vector(0.5*contact.poly.width,0.5*contact.poly.height)
|
|
||||||
|
def setup_signal_busses(self):
|
||||||
# Have the cell gap leave enough room to route an M2 wire.
|
""" Setup bus names, determine the size of the busses etc """
|
||||||
# Some cells may have pwell/nwell spacing problems too when the wells are different heights.
|
|
||||||
#self.cell_gap = max(self.m2_pitch,drc["pwell_to_nwell"])
|
|
||||||
|
|
||||||
# List of input control signals
|
# List of input control signals
|
||||||
self.input_list =["csb","web","oeb"]
|
if self.port_type == "rw":
|
||||||
self.dff_output_list =["cs_bar", "cs", "we_bar", "we", "oe_bar", "oe"]
|
self.input_list = ["csb", "web"]
|
||||||
|
else:
|
||||||
|
self.input_list = ["csb"]
|
||||||
|
|
||||||
|
if self.port_type == "rw":
|
||||||
|
self.dff_output_list = ["cs_bar", "cs", "we_bar", "we"]
|
||||||
|
else:
|
||||||
|
self.dff_output_list = ["cs_bar", "cs"]
|
||||||
|
|
||||||
# list of output control signals (for making a vertical bus)
|
# list of output control signals (for making a vertical bus)
|
||||||
self.internal_bus_list = ["clk_buf", "clk_buf_bar", "we", "cs", "oe"]
|
if self.port_type == "rw":
|
||||||
|
self.internal_bus_list = ["clk_buf", "clk_buf_bar", "we", "cs"]
|
||||||
|
else:
|
||||||
|
self.internal_bus_list = ["clk_buf", "clk_buf_bar", "cs"]
|
||||||
# leave space for the bus plus one extra space
|
# leave space for the bus plus one extra space
|
||||||
self.internal_bus_width = (len(self.internal_bus_list)+1)*self.m2_pitch
|
self.internal_bus_width = (len(self.internal_bus_list)+1)*self.m2_pitch
|
||||||
# Ooutputs to the bank
|
|
||||||
self.output_list = ["s_en", "w_en", "clk_buf_bar", "clk_buf"]
|
# Outputs to the bank
|
||||||
# # with tri/tri_en
|
if self.port_type == "r":
|
||||||
# self.output_list = ["s_en", "w_en", "tri_en", "tri_en_bar", "clk_buf_bar", "clk_buf"]
|
self.output_list = ["s_en"]
|
||||||
|
elif self.port_type == "w":
|
||||||
|
self.output_list = ["w_en"]
|
||||||
|
else:
|
||||||
|
self.output_list = ["s_en", "w_en"]
|
||||||
|
self.output_list.append("clk_buf_bar")
|
||||||
|
self.output_list.append("clk_buf")
|
||||||
|
|
||||||
self.supply_list = ["vdd", "gnd"]
|
self.supply_list = ["vdd", "gnd"]
|
||||||
|
|
||||||
|
|
||||||
def add_rails(self):
|
def route_rails(self):
|
||||||
""" Add the input signal inverted tracks """
|
""" Add the input signal inverted tracks """
|
||||||
height = 4*self.inv1.height - self.m2_pitch
|
height = 4*self.inv1.height - self.m2_pitch
|
||||||
offset = vector(self.ctrl_dff_array.width,0)
|
offset = vector(self.ctrl_dff_array.width,0)
|
||||||
|
|
@ -114,223 +155,189 @@ class control_logic(design.design):
|
||||||
self.rail_offsets = self.create_vertical_bus("metal2", self.m2_pitch, offset, self.internal_bus_list, height)
|
self.rail_offsets = self.create_vertical_bus("metal2", self.m2_pitch, offset, self.internal_bus_list, height)
|
||||||
|
|
||||||
|
|
||||||
def add_modules(self):
|
def create_modules(self):
|
||||||
|
""" Create all the modules """
|
||||||
|
self.create_dffs()
|
||||||
|
self.create_clk_row()
|
||||||
|
if (self.port_type == "rw") or (self.port_type == "w"):
|
||||||
|
self.create_we_row()
|
||||||
|
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||||
|
self.create_rbl_in_row()
|
||||||
|
self.create_sen_row()
|
||||||
|
self.create_rbl()
|
||||||
|
|
||||||
|
|
||||||
|
def place_modules(self):
|
||||||
""" Place all the modules """
|
""" Place all the modules """
|
||||||
# Keep track of all right-most instances to determine row boundary
|
# Keep track of all right-most instances to determine row boundary
|
||||||
# and add the vdd/gnd pins
|
# and add the vdd/gnd pins
|
||||||
self.row_end_inst = []
|
self.row_end_inst = []
|
||||||
|
|
||||||
|
|
||||||
# Add the control flops on the left of the bus
|
# Add the control flops on the left of the bus
|
||||||
self.add_dffs()
|
self.place_dffs()
|
||||||
|
|
||||||
|
row = 0
|
||||||
# Add the logic on the right of the bus
|
# Add the logic on the right of the bus
|
||||||
self.add_clk_row(row=0) # clk is a double-high cell
|
self.place_clk_row(row=row) # clk is a double-high cell
|
||||||
self.add_we_row(row=2)
|
row += 2
|
||||||
# self.add_trien_row(row=3)
|
if (self.port_type == "rw") or (self.port_type == "w"):
|
||||||
# self.add_trien_bar_row(row=4)
|
self.place_we_row(row=row)
|
||||||
self.add_rbl_in_row(row=3)
|
pre_height = self.w_en_inst.uy()
|
||||||
self.add_sen_row(row=4)
|
control_center_y = self.w_en_inst.by()
|
||||||
self.add_rbl(row=5)
|
row += 1
|
||||||
|
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||||
|
self.place_rbl_in_row(row=row)
|
||||||
|
self.place_sen_row(row=row+1)
|
||||||
|
self.place_rbl(row=row+2)
|
||||||
|
pre_height = self.rbl_inst.uy()
|
||||||
|
control_center_y = self.rbl_inst.by()
|
||||||
|
|
||||||
self.add_lvs_correspondence_points()
|
# This offset is used for placement of the control logic in the SRAM level.
|
||||||
|
self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y)
|
||||||
|
|
||||||
# This offset is used for placement of the control logic in
|
# Extra pitch on top and right
|
||||||
# the SRAM level.
|
self.height = pre_height + self.m3_pitch
|
||||||
self.control_logic_center = vector(self.ctrl_dff_inst.rx(), self.rbl_inst.by())
|
|
||||||
|
|
||||||
self.height = self.rbl_inst.uy()
|
|
||||||
# Max of modules or logic rows
|
# Max of modules or logic rows
|
||||||
self.width = max(self.rbl_inst.rx(), max([inst.rx() for inst in self.row_end_inst]))
|
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||||
|
self.width = max(self.rbl_inst.rx(), max([inst.rx() for inst in self.row_end_inst])) + self.m2_pitch
|
||||||
|
else:
|
||||||
|
self.width = max([inst.rx() for inst in self.row_end_inst]) + self.m2_pitch
|
||||||
|
|
||||||
|
def route_all(self):
|
||||||
def add_routing(self):
|
|
||||||
""" Routing between modules """
|
""" Routing between modules """
|
||||||
self.route_dffs()
|
self.route_dffs()
|
||||||
#self.route_trien()
|
if (self.port_type == "rw") or (self.port_type == "w"):
|
||||||
#self.route_trien_bar()
|
self.route_wen()
|
||||||
self.route_rbl_in()
|
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||||
self.route_wen()
|
self.route_rbl_in()
|
||||||
self.route_sen()
|
self.route_sen()
|
||||||
self.route_clk()
|
self.route_clk()
|
||||||
self.route_supply()
|
self.route_supply()
|
||||||
|
|
||||||
|
|
||||||
def add_rbl(self,row):
|
def create_rbl(self):
|
||||||
""" Add the replica bitline """
|
""" Create the replica bitline """
|
||||||
|
self.rbl_inst=self.add_inst(name="replica_bitline",
|
||||||
|
mod=self.replica_bitline)
|
||||||
|
self.connect_inst(["rbl_in", "pre_s_en", "vdd", "gnd"])
|
||||||
|
|
||||||
|
def place_rbl(self,row):
|
||||||
|
""" Place the replica bitline """
|
||||||
y_off = row * self.inv1.height + 2*self.m1_pitch
|
y_off = row * self.inv1.height + 2*self.m1_pitch
|
||||||
|
|
||||||
# Add the RBL above the rows
|
# Add the RBL above the rows
|
||||||
# Add to the right of the control rows and routing channel
|
# Add to the right of the control rows and routing channel
|
||||||
self.replica_bitline_offset = vector(0, y_off)
|
self.replica_bitline_offset = vector(0, y_off)
|
||||||
self.rbl_inst=self.add_inst(name="replica_bitline",
|
self.rbl_inst.place(self.replica_bitline_offset)
|
||||||
mod=self.replica_bitline,
|
|
||||||
offset=self.replica_bitline_offset)
|
|
||||||
self.connect_inst(["rbl_in", "pre_s_en", "vdd", "gnd"])
|
|
||||||
|
|
||||||
|
|
||||||
def add_clk_row(self,row):
|
def create_clk_row(self):
|
||||||
""" Add the multistage clock buffer below the control flops """
|
""" Create the multistage clock buffer """
|
||||||
x_off = self.ctrl_dff_array.width + self.internal_bus_width
|
|
||||||
(y_off,mirror)=self.get_offset(row)
|
|
||||||
|
|
||||||
clkbuf_offset = vector(x_off,y_off)
|
|
||||||
self.clkbuf_inst = self.add_inst(name="clkbuf",
|
self.clkbuf_inst = self.add_inst(name="clkbuf",
|
||||||
mod=self.clkbuf,
|
mod=self.clkbuf)
|
||||||
offset=clkbuf_offset)
|
|
||||||
|
|
||||||
self.connect_inst(["clk","clk_buf_bar","clk_buf","vdd","gnd"])
|
self.connect_inst(["clk","clk_buf_bar","clk_buf","vdd","gnd"])
|
||||||
|
|
||||||
|
def place_clk_row(self,row):
|
||||||
|
""" Place the multistage clock buffer below the control flops """
|
||||||
|
x_off = self.ctrl_dff_array.width + self.internal_bus_width
|
||||||
|
(y_off,mirror)=self.get_offset(row)
|
||||||
|
clkbuf_offset = vector(x_off,y_off)
|
||||||
|
self.clkbuf_inst.place(clkbuf_offset)
|
||||||
self.row_end_inst.append(self.clkbuf_inst)
|
self.row_end_inst.append(self.clkbuf_inst)
|
||||||
|
|
||||||
|
|
||||||
def add_rbl_in_row(self,row):
|
def create_rbl_in_row(self):
|
||||||
x_off = self.ctrl_dff_array.width + self.internal_bus_width
|
self.rbl_in_bar_inst=self.add_inst(name="nand2_rbl_in_bar",
|
||||||
(y_off,mirror)=self.get_offset(row)
|
mod=self.nand2)
|
||||||
|
self.connect_inst(["clk_buf_bar", "cs", "rbl_in_bar", "vdd", "gnd"])
|
||||||
|
|
||||||
# input: OE, clk_buf_bar,CS output: rbl_in_bar
|
|
||||||
self.rbl_in_bar_offset = vector(x_off, y_off)
|
|
||||||
self.rbl_in_bar_inst=self.add_inst(name="nand3_rbl_in_bar",
|
|
||||||
mod=self.nand3,
|
|
||||||
offset=self.rbl_in_bar_offset,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst(["clk_buf_bar", "oe", "cs", "rbl_in_bar", "vdd", "gnd"])
|
|
||||||
x_off += self.nand3.width
|
|
||||||
|
|
||||||
# input: rbl_in_bar, output: rbl_in
|
# input: rbl_in_bar, output: rbl_in
|
||||||
self.rbl_in_offset = vector(x_off, y_off)
|
|
||||||
self.rbl_in_inst=self.add_inst(name="inv_rbl_in",
|
self.rbl_in_inst=self.add_inst(name="inv_rbl_in",
|
||||||
mod=self.inv1,
|
mod=self.inv1)
|
||||||
offset=self.rbl_in_offset,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst(["rbl_in_bar", "rbl_in", "vdd", "gnd"])
|
self.connect_inst(["rbl_in_bar", "rbl_in", "vdd", "gnd"])
|
||||||
|
|
||||||
self.row_end_inst.append(self.rbl_in_inst)
|
|
||||||
|
def place_rbl_in_row(self,row):
|
||||||
def add_sen_row(self,row):
|
|
||||||
""" The sense enable buffer gets placed to the far right of the
|
|
||||||
row. """
|
|
||||||
x_off = self.ctrl_dff_array.width + self.internal_bus_width
|
x_off = self.ctrl_dff_array.width + self.internal_bus_width
|
||||||
(y_off,mirror)=self.get_offset(row)
|
(y_off,mirror)=self.get_offset(row)
|
||||||
|
|
||||||
|
|
||||||
|
self.rbl_in_bar_offset = vector(x_off, y_off)
|
||||||
|
self.rbl_in_bar_inst.place(offset=self.rbl_in_bar_offset,
|
||||||
|
mirror=mirror)
|
||||||
|
x_off += self.nand2.width
|
||||||
|
|
||||||
|
self.rbl_in_offset = vector(x_off, y_off)
|
||||||
|
self.rbl_in_inst.place(offset=self.rbl_in_offset,
|
||||||
|
mirror=mirror)
|
||||||
|
self.row_end_inst.append(self.rbl_in_inst)
|
||||||
|
|
||||||
|
def create_sen_row(self):
|
||||||
|
""" Create the sense enable buffer. """
|
||||||
# input: pre_s_en, output: pre_s_en_bar
|
# input: pre_s_en, output: pre_s_en_bar
|
||||||
self.pre_s_en_bar_offset = vector(x_off, y_off)
|
|
||||||
self.pre_s_en_bar_inst=self.add_inst(name="inv_pre_s_en_bar",
|
self.pre_s_en_bar_inst=self.add_inst(name="inv_pre_s_en_bar",
|
||||||
mod=self.inv2,
|
mod=self.inv2)
|
||||||
offset=self.pre_s_en_bar_offset,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst(["pre_s_en", "pre_s_en_bar", "vdd", "gnd"])
|
self.connect_inst(["pre_s_en", "pre_s_en_bar", "vdd", "gnd"])
|
||||||
|
|
||||||
x_off += self.inv2.width
|
|
||||||
|
|
||||||
# BUFFER INVERTERS FOR S_EN
|
# BUFFER INVERTERS FOR S_EN
|
||||||
# input: input: pre_s_en_bar, output: s_en
|
# input: input: pre_s_en_bar, output: s_en
|
||||||
self.s_en_offset = vector(x_off, y_off)
|
|
||||||
self.s_en_inst=self.add_inst(name="inv_s_en",
|
self.s_en_inst=self.add_inst(name="inv_s_en",
|
||||||
mod=self.inv8,
|
mod=self.inv8)
|
||||||
offset=self.s_en_offset,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst(["pre_s_en_bar", "s_en", "vdd", "gnd"])
|
self.connect_inst(["pre_s_en_bar", "s_en", "vdd", "gnd"])
|
||||||
|
|
||||||
|
def place_sen_row(self,row):
|
||||||
|
"""
|
||||||
|
The sense enable buffer gets placed to the far right of the
|
||||||
|
row.
|
||||||
|
"""
|
||||||
|
x_off = self.ctrl_dff_array.width + self.internal_bus_width
|
||||||
|
(y_off,mirror)=self.get_offset(row)
|
||||||
|
|
||||||
|
self.pre_s_en_bar_offset = vector(x_off, y_off)
|
||||||
|
self.pre_s_en_bar_inst.place(offset=self.pre_s_en_bar_offset,
|
||||||
|
mirror=mirror)
|
||||||
|
x_off += self.inv2.width
|
||||||
|
|
||||||
|
self.s_en_offset = vector(x_off, y_off)
|
||||||
|
self.s_en_inst.place(offset=self.s_en_offset,
|
||||||
|
mirror=mirror)
|
||||||
self.row_end_inst.append(self.s_en_inst)
|
self.row_end_inst.append(self.s_en_inst)
|
||||||
|
|
||||||
def add_trien_row(self, row):
|
|
||||||
x_off = self.ctrl_dff_array.width + self.internal_bus_width
|
|
||||||
(y_off,mirror)=self.get_offset(row)
|
|
||||||
|
|
||||||
|
|
||||||
x_off += self.nand2.width
|
|
||||||
|
|
||||||
# BUFFER INVERTERS FOR TRI_EN
|
|
||||||
tri_en_offset = vector(x_off, y_off)
|
|
||||||
self.tri_en_inst=self.add_inst(name="inv_tri_en1",
|
|
||||||
mod=self.inv2,
|
|
||||||
offset=tri_en_offset,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst(["pre_tri_en_bar", "pre_tri_en1", "vdd", "gnd"])
|
|
||||||
x_off += self.inv2.width
|
|
||||||
|
|
||||||
tri_en_buf1_offset = vector(x_off, y_off)
|
|
||||||
self.tri_en_buf1_inst=self.add_inst(name="tri_en_buf1",
|
|
||||||
mod=self.inv2,
|
|
||||||
offset=tri_en_buf1_offset,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst(["pre_tri_en1", "pre_tri_en_bar1", "vdd", "gnd"])
|
|
||||||
x_off += self.inv2.width
|
|
||||||
|
|
||||||
tri_en_buf2_offset = vector(x_off, y_off)
|
|
||||||
self.tri_en_buf2_inst=self.add_inst(name="tri_en_buf2",
|
|
||||||
mod=self.inv8,
|
|
||||||
offset=tri_en_buf2_offset,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst(["pre_tri_en_bar1", "tri_en", "vdd", "gnd"])
|
|
||||||
|
|
||||||
self.row_end_inst.append(self.tri_en_inst)
|
|
||||||
|
|
||||||
def add_trien_bar_row(self, row):
|
|
||||||
x_off = self.ctrl_dff_array.width + self.internal_bus_width
|
|
||||||
(y_off,mirror)=self.get_offset(row)
|
|
||||||
|
|
||||||
|
|
||||||
# input: OE, clk_buf_bar output: tri_en_bar
|
|
||||||
tri_en_bar_offset = vector(x_off,y_off)
|
|
||||||
self.tri_en_bar_inst=self.add_inst(name="nand2_tri_en",
|
|
||||||
mod=self.nand2,
|
|
||||||
offset=tri_en_bar_offset,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst(["clk_buf_bar", "oe", "pre_tri_en_bar", "vdd", "gnd"])
|
|
||||||
x_off += self.nand2.width
|
|
||||||
|
|
||||||
# BUFFER INVERTERS FOR TRI_EN
|
|
||||||
tri_en_bar_buf1_offset = vector(x_off, y_off)
|
|
||||||
self.tri_en_bar_buf1_inst=self.add_inst(name="tri_en_bar_buf1",
|
|
||||||
mod=self.inv2,
|
|
||||||
offset=tri_en_bar_buf1_offset,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst(["pre_tri_en_bar", "pre_tri_en2", "vdd", "gnd"])
|
|
||||||
x_off += self.inv2.width
|
|
||||||
|
|
||||||
tri_en_bar_buf2_offset = vector(x_off, y_off)
|
|
||||||
self.tri_en_bar_buf2_inst=self.add_inst(name="tri_en_bar_buf2",
|
|
||||||
mod=self.inv8,
|
|
||||||
offset=tri_en_bar_buf2_offset,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst(["pre_tri_en2", "tri_en_bar", "vdd", "gnd"])
|
|
||||||
x_off += self.inv8.width
|
|
||||||
|
|
||||||
self.row_end_inst.append(self.tri_en_bar_buf2_inst)
|
|
||||||
|
|
||||||
def route_dffs(self):
|
def route_dffs(self):
|
||||||
""" Route the input inverters """
|
""" Route the input inverters """
|
||||||
|
|
||||||
dff_out_map = zip(["dout_bar[{}]".format(i) for i in range(3)], ["cs", "we", "oe"])
|
if self.port_type == "r":
|
||||||
|
control_inputs = ["cs"]
|
||||||
|
else:
|
||||||
|
control_inputs = ["cs", "we"]
|
||||||
|
dff_out_map = zip(["dout_bar_{}".format(i) for i in range(2*self.num_control_signals - 1)], control_inputs)
|
||||||
self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets)
|
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
|
||||||
in_pos = self.ctrl_dff_inst.get_pin("clk").uc()
|
in_pos = self.ctrl_dff_inst.get_pin("clk").uc()
|
||||||
mid_pos = in_pos + vector(0,self.m2_pitch)
|
mid_pos = in_pos + vector(0,2*self.m2_pitch)
|
||||||
rail_pos = vector(self.rail_offsets["clk_buf"].x, mid_pos.y)
|
rail_pos = vector(self.rail_offsets["clk_buf"].x, mid_pos.y)
|
||||||
self.add_wire(("metal1","via1","metal2"),[in_pos, mid_pos, rail_pos])
|
self.add_wire(("metal1","via1","metal2"),[in_pos, mid_pos, rail_pos])
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||||
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")
|
||||||
self.copy_layout_pin(self.ctrl_dff_inst, "din[1]", "web")
|
if (self.port_type == "rw"):
|
||||||
self.copy_layout_pin(self.ctrl_dff_inst, "din[2]", "oeb")
|
self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web")
|
||||||
|
|
||||||
|
|
||||||
def add_dffs(self):
|
def create_dffs(self):
|
||||||
""" Add the three input DFFs (with inverters) """
|
""" Add the three input DFFs (with inverters) """
|
||||||
self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs",
|
self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs",
|
||||||
mod=self.ctrl_dff_array,
|
mod=self.ctrl_dff_array)
|
||||||
offset=vector(0,0))
|
|
||||||
|
|
||||||
self.connect_inst(self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list)
|
self.connect_inst(self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list)
|
||||||
|
|
||||||
|
def place_dffs(self):
|
||||||
|
""" Place the input DFFs (with inverters) """
|
||||||
|
self.ctrl_dff_inst.place(vector(0,0))
|
||||||
|
|
||||||
|
|
||||||
def get_offset(self,row):
|
def get_offset(self,row):
|
||||||
""" Compute the y-offset and mirroring """
|
""" Compute the y-offset and mirroring """
|
||||||
|
|
@ -343,52 +350,67 @@ class control_logic(design.design):
|
||||||
|
|
||||||
return (y_off,mirror)
|
return (y_off,mirror)
|
||||||
|
|
||||||
def add_we_row(self,row):
|
def create_we_row(self):
|
||||||
|
# input: WE, CS output: w_en_bar
|
||||||
|
if self.port_type == "rw":
|
||||||
|
nand_mod = self.nand3
|
||||||
|
temp = ["clk_buf_bar", "cs", "we", "w_en_bar", "vdd", "gnd"]
|
||||||
|
else:
|
||||||
|
nand_mod = self.nand2
|
||||||
|
temp = ["clk_buf_bar", "cs", "w_en_bar", "vdd", "gnd"]
|
||||||
|
|
||||||
|
self.w_en_bar_inst = self.add_inst(name="nand3_w_en_bar",
|
||||||
|
mod=nand_mod)
|
||||||
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
# input: w_en_bar, output: pre_w_en
|
||||||
|
self.pre_w_en_inst = self.add_inst(name="inv_pre_w_en",
|
||||||
|
mod=self.inv1)
|
||||||
|
self.connect_inst(["w_en_bar", "pre_w_en", "vdd", "gnd"])
|
||||||
|
|
||||||
|
# BUFFER INVERTERS FOR W_EN
|
||||||
|
self.pre_w_en_bar_inst = self.add_inst(name="inv_pre_w_en_bar",
|
||||||
|
mod=self.inv2)
|
||||||
|
self.connect_inst(["pre_w_en", "pre_w_en_bar", "vdd", "gnd"])
|
||||||
|
|
||||||
|
self.w_en_inst = self.add_inst(name="inv_w_en2",
|
||||||
|
mod=self.inv8)
|
||||||
|
self.connect_inst(["pre_w_en_bar", "w_en", "vdd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
|
def place_we_row(self,row):
|
||||||
x_off = self.ctrl_dff_inst.width + self.internal_bus_width
|
x_off = self.ctrl_dff_inst.width + self.internal_bus_width
|
||||||
(y_off,mirror)=self.get_offset(row)
|
(y_off,mirror)=self.get_offset(row)
|
||||||
|
|
||||||
# input: WE, CS output: w_en_bar
|
|
||||||
w_en_bar_offset = vector(x_off, y_off)
|
w_en_bar_offset = vector(x_off, y_off)
|
||||||
self.w_en_bar_inst=self.add_inst(name="nand3_w_en_bar",
|
self.w_en_bar_inst.place(offset=w_en_bar_offset,
|
||||||
mod=self.nand3,
|
mirror=mirror)
|
||||||
offset=w_en_bar_offset,
|
if self.port_type == "rw":
|
||||||
mirror=mirror)
|
x_off += self.nand3.width
|
||||||
self.connect_inst(["clk_buf_bar", "cs", "we", "w_en_bar", "vdd", "gnd"])
|
else:
|
||||||
x_off += self.nand3.width
|
x_off += self.nand2.width
|
||||||
|
|
||||||
# input: w_en_bar, output: pre_w_en
|
|
||||||
pre_w_en_offset = vector(x_off, y_off)
|
pre_w_en_offset = vector(x_off, y_off)
|
||||||
self.pre_w_en_inst=self.add_inst(name="inv_pre_w_en",
|
self.pre_w_en_inst.place(offset=pre_w_en_offset,
|
||||||
mod=self.inv1,
|
mirror=mirror)
|
||||||
offset=pre_w_en_offset,
|
|
||||||
mirror=mirror)
|
|
||||||
|
|
||||||
self.connect_inst(["w_en_bar", "pre_w_en", "vdd", "gnd"])
|
|
||||||
x_off += self.inv1.width
|
x_off += self.inv1.width
|
||||||
|
|
||||||
# BUFFER INVERTERS FOR W_EN
|
|
||||||
pre_w_en_bar_offset = vector(x_off, y_off)
|
pre_w_en_bar_offset = vector(x_off, y_off)
|
||||||
self.pre_w_en_bar_inst=self.add_inst(name="inv_pre_w_en_bar",
|
self.pre_w_en_bar_inst.place(offset=pre_w_en_bar_offset,
|
||||||
mod=self.inv2,
|
mirror=mirror)
|
||||||
offset=pre_w_en_bar_offset,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst(["pre_w_en", "pre_w_en_bar", "vdd", "gnd"])
|
|
||||||
x_off += self.inv2.width
|
x_off += self.inv2.width
|
||||||
|
|
||||||
w_en_offset = vector(x_off, y_off)
|
w_en_offset = vector(x_off, y_off)
|
||||||
self.w_en_inst=self.add_inst(name="inv_w_en2",
|
self.w_en_inst.place(offset=w_en_offset,
|
||||||
mod=self.inv8,
|
mirror=mirror)
|
||||||
offset=w_en_offset,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst(["pre_w_en_bar", "w_en", "vdd", "gnd"])
|
|
||||||
x_off += self.inv8.width
|
x_off += self.inv8.width
|
||||||
|
|
||||||
self.row_end_inst.append(self.w_en_inst)
|
self.row_end_inst.append(self.w_en_inst)
|
||||||
|
|
||||||
|
|
||||||
def route_rbl_in(self):
|
def route_rbl_in(self):
|
||||||
""" Connect the logic for the rbl_in generation """
|
""" Connect the logic for the rbl_in generation """
|
||||||
rbl_in_map = zip(["A", "B", "C"], ["clk_buf_bar", "oe", "cs"])
|
rbl_in_map = zip(["A", "B"], ["clk_buf_bar", "cs"])
|
||||||
self.connect_vertical_bus(rbl_in_map, self.rbl_in_bar_inst, self.rail_offsets)
|
self.connect_vertical_bus(rbl_in_map, self.rbl_in_bar_inst, self.rail_offsets)
|
||||||
|
|
||||||
# Connect the NAND3 output to the inverter
|
# Connect the NAND3 output to the inverter
|
||||||
|
|
@ -460,7 +482,10 @@ class control_logic(design.design):
|
||||||
|
|
||||||
|
|
||||||
def route_wen(self):
|
def route_wen(self):
|
||||||
wen_map = zip(["A", "B", "C"], ["clk_buf_bar", "cs", "we"])
|
if self.port_type == "rw":
|
||||||
|
wen_map = zip(["A", "B", "C"], ["clk_buf_bar", "cs", "we"])
|
||||||
|
else:
|
||||||
|
wen_map = zip(["A", "B"], ["clk_buf_bar", "cs"])
|
||||||
self.connect_vertical_bus(wen_map, self.w_en_bar_inst, self.rail_offsets)
|
self.connect_vertical_bus(wen_map, self.w_en_bar_inst, self.rail_offsets)
|
||||||
|
|
||||||
# Connect the NAND3 output to the inverter
|
# Connect the NAND3 output to the inverter
|
||||||
|
|
@ -475,44 +500,6 @@ class control_logic(design.design):
|
||||||
|
|
||||||
self.connect_output(self.w_en_inst, "Z", "w_en")
|
self.connect_output(self.w_en_inst, "Z", "w_en")
|
||||||
|
|
||||||
def route_trien(self):
|
|
||||||
|
|
||||||
# Connect the NAND2 output to the buffer
|
|
||||||
tri_en_bar_pos = self.tri_en_bar_inst.get_pin("Z").center()
|
|
||||||
inv_in_pos = self.tri_en_inst.get_pin("A").center()
|
|
||||||
mid1 = vector(tri_en_bar_pos.x,inv_in_pos.y)
|
|
||||||
self.add_wire(("metal1","via1","metal2"),[tri_en_bar_pos,mid1,inv_in_pos])
|
|
||||||
|
|
||||||
# Connect the INV output to the buffer
|
|
||||||
tri_en_pos = self.tri_en_inst.get_pin("Z").center()
|
|
||||||
inv_in_pos = self.tri_en_buf1_inst.get_pin("A").center()
|
|
||||||
mid_xoffset = 0.5*(tri_en_pos.x + inv_in_pos.x)
|
|
||||||
mid1 = vector(mid_xoffset,tri_en_pos.y)
|
|
||||||
mid2 = vector(mid_xoffset,inv_in_pos.y)
|
|
||||||
self.add_path("metal1",[tri_en_pos,mid1,mid2,inv_in_pos])
|
|
||||||
|
|
||||||
self.add_path("metal1",[self.tri_en_buf1_ist.get_pin("Z").center(), self.tri_en_buf2_inst.get_pin("A").center()])
|
|
||||||
|
|
||||||
self.connect_output(self.tri_en_buf2_inst, "Z", "tri_en")
|
|
||||||
|
|
||||||
def route_trien_bar(self):
|
|
||||||
|
|
||||||
trien_map = zip(["A", "B"], ["clk_buf_bar", "oe"])
|
|
||||||
self.connect_vertical_bus(trien_map, self.tri_en_bar_inst, self.rail_offsets)
|
|
||||||
|
|
||||||
# Connect the NAND2 output to the buffer
|
|
||||||
tri_en_bar_pos = self.tri_en_bar_inst.get_pin("Z").center()
|
|
||||||
inv_in_pos = self.tri_en_bar_buf1_inst.get_pin("A").center()
|
|
||||||
mid_xoffset = 0.5*(tri_en_bar_pos.x + inv_in_pos.x)
|
|
||||||
mid1 = vector(mid_xoffset,tri_en_bar_pos.y)
|
|
||||||
mid2 = vector(mid_xoffset,inv_in_pos.y)
|
|
||||||
self.add_path("metal1",[tri_en_bar_pos,mid1,mid2,inv_in_pos])
|
|
||||||
|
|
||||||
self.add_path("metal1",[self.tri_en_bar_buf1_inst.get_pin("Z").center(), self.tri_en_bar_buf2_inst.get_pin("A").center()])
|
|
||||||
|
|
||||||
self.connect_output(self.tri_en_bar_buf2_inst, "Z", "tri_en_bar")
|
|
||||||
|
|
||||||
|
|
||||||
def route_sen(self):
|
def route_sen(self):
|
||||||
rbl_out_pos = self.rbl_inst.get_pin("out").bc()
|
rbl_out_pos = self.rbl_inst.get_pin("out").bc()
|
||||||
in_pos = self.pre_s_en_bar_inst.get_pin("A").lc()
|
in_pos = self.pre_s_en_bar_inst.get_pin("A").lc()
|
||||||
|
|
@ -574,9 +561,9 @@ class control_logic(design.design):
|
||||||
self.add_power_pin("gnd", pin_loc)
|
self.add_power_pin("gnd", pin_loc)
|
||||||
self.add_path("metal1", [row_loc, pin_loc])
|
self.add_path("metal1", [row_loc, pin_loc])
|
||||||
|
|
||||||
|
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||||
self.copy_layout_pin(self.rbl_inst,"gnd")
|
self.copy_layout_pin(self.rbl_inst,"gnd")
|
||||||
self.copy_layout_pin(self.rbl_inst,"vdd")
|
self.copy_layout_pin(self.rbl_inst,"vdd")
|
||||||
|
|
||||||
self.copy_layout_pin(self.ctrl_dff_inst,"gnd")
|
self.copy_layout_pin(self.ctrl_dff_inst,"gnd")
|
||||||
self.copy_layout_pin(self.ctrl_dff_inst,"vdd")
|
self.copy_layout_pin(self.ctrl_dff_inst,"vdd")
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,12 @@ class delay_chain(design.design):
|
||||||
Usually, this will be constant, but it could have varied fanout.
|
Usually, this will be constant, but it could have varied fanout.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
unique_id = 1
|
||||||
|
|
||||||
def __init__(self, fanout_list, name="delay_chain"):
|
def __init__(self, fanout_list, name="delay_chain"):
|
||||||
"""init function"""
|
"""init function"""
|
||||||
|
name = name+"_{}".format(delay_chain.unique_id)
|
||||||
|
delay_chain.unique_id += 1
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
# Two fanouts are needed so that we can route the vdd/gnd connections
|
# Two fanouts are needed so that we can route the vdd/gnd connections
|
||||||
|
|
@ -24,14 +28,23 @@ class delay_chain(design.design):
|
||||||
# number of inverters including any fanout loads.
|
# number of inverters including any fanout loads.
|
||||||
self.fanout_list = fanout_list
|
self.fanout_list = fanout_list
|
||||||
|
|
||||||
from importlib import reload
|
self.create_netlist()
|
||||||
c = reload(__import__(OPTS.bitcell))
|
if not OPTS.netlist_only:
|
||||||
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
self.create_layout()
|
||||||
self.bitcell = self.mod_bitcell()
|
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_modules()
|
||||||
self.add_pins()
|
self.add_pins()
|
||||||
self.create_module()
|
self.create_inverters()
|
||||||
self.add_inverters()
|
|
||||||
|
def create_layout(self):
|
||||||
|
# Each stage is a a row
|
||||||
|
self.height = len(self.fanout_list)*self.inv.height
|
||||||
|
# The width is determined by the largest fanout plus the driver
|
||||||
|
self.width = (max(self.fanout_list)+1) * self.inv.width
|
||||||
|
|
||||||
|
self.place_inverters()
|
||||||
self.route_inverters()
|
self.route_inverters()
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
@ -43,40 +56,22 @@ class delay_chain(design.design):
|
||||||
self.add_pin("vdd")
|
self.add_pin("vdd")
|
||||||
self.add_pin("gnd")
|
self.add_pin("gnd")
|
||||||
|
|
||||||
def create_module(self):
|
def add_modules(self):
|
||||||
""" Add the inverter logical module """
|
|
||||||
|
|
||||||
self.inv = pinv(route_output=False)
|
self.inv = pinv(route_output=False)
|
||||||
self.add_mod(self.inv)
|
self.add_mod(self.inv)
|
||||||
|
|
||||||
# Each stage is a a row
|
def create_inverters(self):
|
||||||
self.height = len(self.fanout_list)*self.inv.height
|
""" Create the inverters and connect them based on the stage list """
|
||||||
# The width is determined by the largest fanout plus the driver
|
|
||||||
self.width = (max(self.fanout_list)+1) * self.inv.width
|
|
||||||
|
|
||||||
|
|
||||||
def add_inverters(self):
|
|
||||||
""" Add the inverters and connect them based on the stage list """
|
|
||||||
self.driver_inst_list = []
|
self.driver_inst_list = []
|
||||||
self.rightest_load_inst = {}
|
self.rightest_load_inst = {}
|
||||||
self.load_inst_map = {}
|
self.load_inst_map = {}
|
||||||
for stage_num,fanout_size in zip(range(len(self.fanout_list)),self.fanout_list):
|
for stage_num,fanout_size in zip(range(len(self.fanout_list)),self.fanout_list):
|
||||||
if stage_num % 2:
|
|
||||||
inv_mirror = "MX"
|
|
||||||
inv_offset = vector(0, (stage_num+1)* self.inv.height)
|
|
||||||
else:
|
|
||||||
inv_mirror = "R0"
|
|
||||||
inv_offset = vector(0, stage_num * self.inv.height)
|
|
||||||
|
|
||||||
# Add the inverter
|
# Add the inverter
|
||||||
cur_driver=self.add_inst(name="dinv{}".format(stage_num),
|
cur_driver=self.add_inst(name="dinv{}".format(stage_num),
|
||||||
mod=self.inv,
|
mod=self.inv)
|
||||||
offset=inv_offset,
|
|
||||||
mirror=inv_mirror)
|
|
||||||
# keep track of the inverter instances so we can use them to get the pins
|
# keep track of the inverter instances so we can use them to get the pins
|
||||||
self.driver_inst_list.append(cur_driver)
|
self.driver_inst_list.append(cur_driver)
|
||||||
|
|
||||||
|
|
||||||
# Hook up the driver
|
# Hook up the driver
|
||||||
if stage_num+1==len(self.fanout_list):
|
if stage_num+1==len(self.fanout_list):
|
||||||
stageout_name = "out"
|
stageout_name = "out"
|
||||||
|
|
@ -91,11 +86,8 @@ class delay_chain(design.design):
|
||||||
# Now add the dummy loads to the right
|
# Now add the dummy loads to the right
|
||||||
self.load_inst_map[cur_driver]=[]
|
self.load_inst_map[cur_driver]=[]
|
||||||
for i in range(fanout_size):
|
for i in range(fanout_size):
|
||||||
inv_offset += vector(self.inv.width,0)
|
|
||||||
cur_load=self.add_inst(name="dload_{0}_{1}".format(stage_num,i),
|
cur_load=self.add_inst(name="dload_{0}_{1}".format(stage_num,i),
|
||||||
mod=self.inv,
|
mod=self.inv)
|
||||||
offset=inv_offset,
|
|
||||||
mirror=inv_mirror)
|
|
||||||
# Fanout stage is always driven by driver and output is disconnected
|
# Fanout stage is always driven by driver and output is disconnected
|
||||||
disconnect_name = "n_{0}_{1}".format(stage_num,i)
|
disconnect_name = "n_{0}_{1}".format(stage_num,i)
|
||||||
self.connect_inst([stageout_name, disconnect_name, "vdd", "gnd"])
|
self.connect_inst([stageout_name, disconnect_name, "vdd", "gnd"])
|
||||||
|
|
@ -105,6 +97,29 @@ class delay_chain(design.design):
|
||||||
else:
|
else:
|
||||||
# Keep track of the last one so we can add the the wire later
|
# Keep track of the last one so we can add the the wire later
|
||||||
self.rightest_load_inst[cur_driver]=cur_load
|
self.rightest_load_inst[cur_driver]=cur_load
|
||||||
|
|
||||||
|
def place_inverters(self):
|
||||||
|
""" Place the inverters and connect them based on the stage list """
|
||||||
|
for stage_num,fanout_size in zip(range(len(self.fanout_list)),self.fanout_list):
|
||||||
|
if stage_num % 2:
|
||||||
|
inv_mirror = "MX"
|
||||||
|
inv_offset = vector(0, (stage_num+1)* self.inv.height)
|
||||||
|
else:
|
||||||
|
inv_mirror = "R0"
|
||||||
|
inv_offset = vector(0, stage_num * self.inv.height)
|
||||||
|
|
||||||
|
# Add the inverter
|
||||||
|
cur_driver=self.driver_inst_list[stage_num]
|
||||||
|
cur_driver.place(offset=inv_offset,
|
||||||
|
mirror=inv_mirror)
|
||||||
|
|
||||||
|
# Now add the dummy loads to the right
|
||||||
|
load_list = self.load_inst_map[cur_driver]
|
||||||
|
for i in range(fanout_size):
|
||||||
|
inv_offset += vector(self.inv.width,0)
|
||||||
|
load_list[i].place(offset=inv_offset,
|
||||||
|
mirror=inv_mirror)
|
||||||
|
|
||||||
|
|
||||||
def add_route(self, pin1, pin2):
|
def add_route(self, pin1, pin2):
|
||||||
""" This guarantees that we route from the top to bottom row correctly. """
|
""" This guarantees that we route from the top to bottom row correctly. """
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class dff(design.design):
|
||||||
|
|
||||||
pin_names = ["D", "Q", "clk", "vdd", "gnd"]
|
pin_names = ["D", "Q", "clk", "vdd", "gnd"]
|
||||||
(width,height) = utils.get_libcell_size("dff", GDS["unit"], layer["boundary"])
|
(width,height) = utils.get_libcell_size("dff", GDS["unit"], layer["boundary"])
|
||||||
pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"], layer["boundary"])
|
pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"])
|
||||||
|
|
||||||
def __init__(self, name="dff"):
|
def __init__(self, name="dff"):
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
|
|
@ -21,6 +21,25 @@ class dff(design.design):
|
||||||
self.height = dff.height
|
self.height = dff.height
|
||||||
self.pin_map = dff.pin_map
|
self.pin_map = dff.pin_map
|
||||||
|
|
||||||
|
def analytical_power(self, proc, vdd, temp, load):
|
||||||
|
"""Returns dynamic and leakage power. Results in nW"""
|
||||||
|
from tech import spice
|
||||||
|
c_eff = self.calculate_effective_capacitance(load)
|
||||||
|
f = spice["default_event_rate"]
|
||||||
|
power_dyn = c_eff*vdd*vdd*f
|
||||||
|
power_leak = spice["msflop_leakage"]
|
||||||
|
|
||||||
|
total_power = self.return_power(power_dyn, power_leak)
|
||||||
|
return total_power
|
||||||
|
|
||||||
|
def calculate_effective_capacitance(self, load):
|
||||||
|
"""Computes effective capacitance. Results in fF"""
|
||||||
|
from tech import spice, parameter
|
||||||
|
c_load = load
|
||||||
|
c_para = spice["flop_para_cap"]#ff
|
||||||
|
transition_prob = spice["flop_transition_prob"]
|
||||||
|
return transition_prob*(c_load + c_para)
|
||||||
|
|
||||||
def analytical_delay(self, slew, load = 0.0):
|
def analytical_delay(self, slew, load = 0.0):
|
||||||
# dont know how to calculate this now, use constant in tech file
|
# dont know how to calculate this now, use constant in tech file
|
||||||
from tech import spice
|
from tech import spice
|
||||||
|
|
|
||||||
|
|
@ -20,23 +20,30 @@ class dff_array(design.design):
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
debug.info(1, "Creating {0} rows={1} cols={2}".format(self.name, self.rows, self.columns))
|
debug.info(1, "Creating {0} rows={1} cols={2}".format(self.name, self.rows, self.columns))
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_modules()
|
||||||
|
self.add_pins()
|
||||||
|
self.create_dff_array()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
self.width = self.columns * self.dff.width
|
||||||
|
self.height = self.rows * self.dff.height
|
||||||
|
|
||||||
|
self.place_dff_array()
|
||||||
|
self.add_layout_pins()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
from importlib import reload
|
from importlib import reload
|
||||||
c = reload(__import__(OPTS.dff))
|
c = reload(__import__(OPTS.dff))
|
||||||
self.mod_dff = getattr(c, OPTS.dff)
|
self.mod_dff = getattr(c, OPTS.dff)
|
||||||
self.dff = self.mod_dff("dff")
|
self.dff = self.mod_dff("dff")
|
||||||
self.add_mod(self.dff)
|
self.add_mod(self.dff)
|
||||||
|
|
||||||
self.width = self.columns * self.dff.width
|
|
||||||
self.height = self.rows * self.dff.height
|
|
||||||
|
|
||||||
self.create_layout()
|
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
self.add_pins()
|
|
||||||
self.create_dff_array()
|
|
||||||
self.add_layout_pins()
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
for col in range(self.columns):
|
for col in range(self.columns):
|
||||||
|
|
@ -53,39 +60,44 @@ class dff_array(design.design):
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
for col in range(self.columns):
|
for col in range(self.columns):
|
||||||
name = "Xdff_r{0}_c{1}".format(row,col)
|
name = "Xdff_r{0}_c{1}".format(row,col)
|
||||||
if (row % 2 == 0):
|
|
||||||
base = vector(col*self.dff.width,row*self.dff.height)
|
|
||||||
mirror = "R0"
|
|
||||||
else:
|
|
||||||
base = vector(col*self.dff.width,(row+1)*self.dff.height)
|
|
||||||
mirror = "MX"
|
|
||||||
self.dff_insts[row,col]=self.add_inst(name=name,
|
self.dff_insts[row,col]=self.add_inst(name=name,
|
||||||
mod=self.dff,
|
mod=self.dff)
|
||||||
offset=base,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst([self.get_din_name(row,col),
|
self.connect_inst([self.get_din_name(row,col),
|
||||||
self.get_dout_name(row,col),
|
self.get_dout_name(row,col),
|
||||||
"clk",
|
"clk",
|
||||||
"vdd",
|
"vdd",
|
||||||
"gnd"])
|
"gnd"])
|
||||||
|
|
||||||
|
def place_dff_array(self):
|
||||||
|
for row in range(self.rows):
|
||||||
|
for col in range(self.columns):
|
||||||
|
name = "Xdff_r{0}_c{1}".format(row,col)
|
||||||
|
if (row % 2 == 0):
|
||||||
|
base = vector(col*self.dff.width,row*self.dff.height)
|
||||||
|
mirror = "R0"
|
||||||
|
else:
|
||||||
|
base = vector(col*self.dff.width,(row+1)*self.dff.height)
|
||||||
|
mirror = "MX"
|
||||||
|
self.dff_insts[row,col].place(offset=base,
|
||||||
|
mirror=mirror)
|
||||||
|
|
||||||
def get_din_name(self, row, col):
|
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
|
||||||
|
|
||||||
|
|
@ -124,29 +136,23 @@ 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")
|
||||||
if self.columns==1:
|
self.add_layout_pin_segment_center(text="clk",
|
||||||
self.add_layout_pin(text="clk",
|
layer="metal3",
|
||||||
layer="metal2",
|
start=vector(0,clk_ypos),
|
||||||
offset=clk_pin.ll().scale(1,0),
|
end=vector(self.width,clk_ypos))
|
||||||
width=self.m2_width,
|
for col in range(self.columns):
|
||||||
height=self.height)
|
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
||||||
else:
|
# Make a vertical strip for each column
|
||||||
self.add_layout_pin_segment_center(text="clk",
|
self.add_rect(layer="metal2",
|
||||||
layer="metal3",
|
offset=clk_pin.ll().scale(1,0),
|
||||||
start=vector(0,self.m3_pitch+self.m3_width),
|
width=self.m2_width,
|
||||||
end=vector(self.width,self.m3_pitch+self.m3_width))
|
height=self.height)
|
||||||
for col in range(self.columns):
|
# Drop a via to the M3 pin
|
||||||
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||||
# Make a vertical strip for each column
|
offset=vector(clk_pin.cx(),clk_ypos))
|
||||||
self.add_rect(layer="metal2",
|
|
||||||
offset=clk_pin.ll().scale(1,0),
|
|
||||||
width=self.m2_width,
|
|
||||||
height=self.height)
|
|
||||||
# Drop a via to the M3 pin
|
|
||||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
|
||||||
offset=vector(clk_pin.cx(),self.m3_pitch+self.m3_width))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def analytical_delay(self, slew, load=0.0):
|
def analytical_delay(self, slew, load=0.0):
|
||||||
|
|
|
||||||
|
|
@ -26,29 +26,41 @@ class dff_buf(design.design):
|
||||||
debug.check(inv1_size>=2, "Inverter must be greater than two for rail spacing DRC rules.")
|
debug.check(inv1_size>=2, "Inverter must be greater than two for rail spacing DRC rules.")
|
||||||
debug.check(inv2_size>=2, "Inverter must be greater than two for rail spacing DRC rules.")
|
debug.check(inv2_size>=2, "Inverter must be greater than two for rail spacing DRC rules.")
|
||||||
|
|
||||||
|
self.inv1_size=inv1_size
|
||||||
|
self.inv2_size=inv2_size
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_modules()
|
||||||
|
self.add_pins()
|
||||||
|
self.create_modules()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
self.width = self.dff.width + self.inv1.width + self.inv2.width
|
||||||
|
self.height = self.dff.height
|
||||||
|
|
||||||
|
self.place_modules()
|
||||||
|
self.route_wires()
|
||||||
|
self.add_layout_pins()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
from importlib import reload
|
from importlib import reload
|
||||||
c = reload(__import__(OPTS.dff))
|
c = reload(__import__(OPTS.dff))
|
||||||
self.mod_dff = getattr(c, OPTS.dff)
|
self.mod_dff = getattr(c, OPTS.dff)
|
||||||
self.dff = self.mod_dff("dff")
|
self.dff = self.mod_dff("dff")
|
||||||
self.add_mod(self.dff)
|
self.add_mod(self.dff)
|
||||||
|
|
||||||
self.inv1 = pinv(size=inv1_size,height=self.dff.height)
|
self.inv1 = pinv(size=self.inv1_size,height=self.dff.height)
|
||||||
self.add_mod(self.inv1)
|
self.add_mod(self.inv1)
|
||||||
|
|
||||||
self.inv2 = pinv(size=inv2_size,height=self.dff.height)
|
self.inv2 = pinv(size=self.inv2_size,height=self.dff.height)
|
||||||
self.add_mod(self.inv2)
|
self.add_mod(self.inv2)
|
||||||
|
|
||||||
self.width = self.dff.width + self.inv1.width + self.inv2.width
|
|
||||||
self.height = self.dff.height
|
|
||||||
|
|
||||||
self.create_layout()
|
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
self.add_pins()
|
|
||||||
self.add_insts()
|
|
||||||
self.add_wires()
|
|
||||||
self.add_layout_pins()
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
self.add_pin("D")
|
self.add_pin("D")
|
||||||
|
|
@ -58,26 +70,30 @@ class dff_buf(design.design):
|
||||||
self.add_pin("vdd")
|
self.add_pin("vdd")
|
||||||
self.add_pin("gnd")
|
self.add_pin("gnd")
|
||||||
|
|
||||||
def add_insts(self):
|
def create_modules(self):
|
||||||
# Add the DFF
|
|
||||||
self.dff_inst=self.add_inst(name="dff_buf_dff",
|
self.dff_inst=self.add_inst(name="dff_buf_dff",
|
||||||
mod=self.dff,
|
mod=self.dff)
|
||||||
offset=vector(0,0))
|
|
||||||
self.connect_inst(["D", "qint", "clk", "vdd", "gnd"])
|
self.connect_inst(["D", "qint", "clk", "vdd", "gnd"])
|
||||||
|
|
||||||
# Add INV1 to the right
|
|
||||||
self.inv1_inst=self.add_inst(name="dff_buf_inv1",
|
self.inv1_inst=self.add_inst(name="dff_buf_inv1",
|
||||||
mod=self.inv1,
|
mod=self.inv1)
|
||||||
offset=vector(self.dff_inst.rx(),0))
|
|
||||||
self.connect_inst(["qint", "Qb", "vdd", "gnd"])
|
self.connect_inst(["qint", "Qb", "vdd", "gnd"])
|
||||||
|
|
||||||
# Add INV2 to the right
|
|
||||||
self.inv2_inst=self.add_inst(name="dff_buf_inv2",
|
self.inv2_inst=self.add_inst(name="dff_buf_inv2",
|
||||||
mod=self.inv2,
|
mod=self.inv2)
|
||||||
offset=vector(self.inv1_inst.rx(),0))
|
|
||||||
self.connect_inst(["Qb", "Q", "vdd", "gnd"])
|
self.connect_inst(["Qb", "Q", "vdd", "gnd"])
|
||||||
|
|
||||||
|
def place_modules(self):
|
||||||
|
# Add the DFF
|
||||||
|
self.dff_inst.place(vector(0,0))
|
||||||
|
|
||||||
|
# Add INV1 to the right
|
||||||
|
self.inv1_inst.place(vector(self.dff_inst.rx(),0))
|
||||||
|
|
||||||
def add_wires(self):
|
# Add INV2 to the right
|
||||||
|
self.inv2_inst.place(vector(self.inv1_inst.rx(),0))
|
||||||
|
|
||||||
|
def route_wires(self):
|
||||||
# Route dff q to inv1 a
|
# Route dff q to inv1 a
|
||||||
q_pin = self.dff_inst.get_pin("Q")
|
q_pin = self.dff_inst.get_pin("Q")
|
||||||
a1_pin = self.inv1_inst.get_pin("A")
|
a1_pin = self.inv1_inst.get_pin("A")
|
||||||
|
|
|
||||||
|
|
@ -20,18 +20,22 @@ class dff_buf_array(design.design):
|
||||||
name = "dff_buf_array_{0}x{1}".format(rows, columns)
|
name = "dff_buf_array_{0}x{1}".format(rows, columns)
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
debug.info(1, "Creating {}".format(self.name))
|
debug.info(1, "Creating {}".format(self.name))
|
||||||
|
self.inv1_size = inv1_size
|
||||||
|
self.inv2_size = inv2_size
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
self.dff = dff_buf.dff_buf(inv1_size, inv2_size)
|
def create_netlist(self):
|
||||||
self.add_mod(self.dff)
|
self.add_pins()
|
||||||
|
self.add_modules()
|
||||||
self.width = self.columns * self.dff.width
|
self.create_dff_array()
|
||||||
self.height = self.rows * self.dff.height
|
|
||||||
|
|
||||||
self.create_layout()
|
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
self.add_pins()
|
self.width = self.columns * self.dff.width
|
||||||
self.create_dff_array()
|
self.height = self.rows * self.dff.height
|
||||||
|
self.place_dff_array()
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
|
@ -47,8 +51,25 @@ class dff_buf_array(design.design):
|
||||||
self.add_pin("vdd")
|
self.add_pin("vdd")
|
||||||
self.add_pin("gnd")
|
self.add_pin("gnd")
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
|
self.dff = dff_buf.dff_buf(self.inv1_size, self.inv2_size)
|
||||||
|
self.add_mod(self.dff)
|
||||||
|
|
||||||
def create_dff_array(self):
|
def create_dff_array(self):
|
||||||
self.dff_insts={}
|
self.dff_insts={}
|
||||||
|
for row in range(self.rows):
|
||||||
|
for col in range(self.columns):
|
||||||
|
name = "Xdff_r{0}_c{1}".format(row,col)
|
||||||
|
self.dff_insts[row,col]=self.add_inst(name=name,
|
||||||
|
mod=self.dff)
|
||||||
|
self.connect_inst([self.get_din_name(row,col),
|
||||||
|
self.get_dout_name(row,col),
|
||||||
|
self.get_dout_bar_name(row,col),
|
||||||
|
"clk",
|
||||||
|
"vdd",
|
||||||
|
"gnd"])
|
||||||
|
|
||||||
|
def place_dff_array(self):
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
for col in range(self.columns):
|
for col in range(self.columns):
|
||||||
name = "Xdff_r{0}_c{1}".format(row,col)
|
name = "Xdff_r{0}_c{1}".format(row,col)
|
||||||
|
|
@ -58,44 +79,36 @@ class dff_buf_array(design.design):
|
||||||
else:
|
else:
|
||||||
base = vector(col*self.dff.width,(row+1)*self.dff.height)
|
base = vector(col*self.dff.width,(row+1)*self.dff.height)
|
||||||
mirror = "MX"
|
mirror = "MX"
|
||||||
self.dff_insts[row,col]=self.add_inst(name=name,
|
self.dff_insts[row,col].place(offset=base,
|
||||||
mod=self.dff,
|
mirror=mirror)
|
||||||
offset=base,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst([self.get_din_name(row,col),
|
|
||||||
self.get_dout_name(row,col),
|
|
||||||
self.get_dout_bar_name(row,col),
|
|
||||||
"clk",
|
|
||||||
"vdd",
|
|
||||||
"gnd"])
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -140,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",
|
||||||
|
|
@ -150,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")
|
||||||
|
|
||||||
|
|
@ -162,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))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,31 +18,30 @@ class dff_inv(design.design):
|
||||||
name = "dff_inv_{0}".format(inv_size)
|
name = "dff_inv_{0}".format(inv_size)
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
debug.info(1, "Creating {}".format(self.name))
|
debug.info(1, "Creating {}".format(self.name))
|
||||||
|
self.inv_size = inv_size
|
||||||
|
|
||||||
# This is specifically for SCMOS where the DFF vdd/gnd rails are more than min width.
|
# This is specifically for SCMOS where the DFF vdd/gnd rails are more than min width.
|
||||||
# This causes a DRC in the pinv which assumes min width rails. This ensures the output
|
# This causes a DRC in the pinv which assumes min width rails. This ensures the output
|
||||||
# contact does not violate spacing to the rail in the NMOS.
|
# contact does not violate spacing to the rail in the NMOS.
|
||||||
debug.check(inv_size>=2, "Inverter must be greater than two for rail spacing DRC rules.")
|
debug.check(inv_size>=2, "Inverter must be greater than two for rail spacing DRC rules.")
|
||||||
|
|
||||||
from importlib import reload
|
self.create_netlist()
|
||||||
c = reload(__import__(OPTS.dff))
|
if not OPTS.netlist_only:
|
||||||
self.mod_dff = getattr(c, OPTS.dff)
|
self.create_layout()
|
||||||
self.dff = self.mod_dff("dff")
|
|
||||||
self.add_mod(self.dff)
|
|
||||||
|
|
||||||
self.inv1 = pinv(size=inv_size,height=self.dff.height)
|
|
||||||
self.add_mod(self.inv1)
|
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_pins()
|
||||||
|
self.add_modules()
|
||||||
|
self.create_modules()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
self.width = self.dff.width + self.inv1.width
|
self.width = self.dff.width + self.inv1.width
|
||||||
self.height = self.dff.height
|
self.height = self.dff.height
|
||||||
|
|
||||||
self.create_layout()
|
self.place_modules()
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
self.add_pins()
|
|
||||||
self.add_insts()
|
|
||||||
self.add_wires()
|
self.add_wires()
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
|
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
|
|
@ -53,18 +52,31 @@ class dff_inv(design.design):
|
||||||
self.add_pin("vdd")
|
self.add_pin("vdd")
|
||||||
self.add_pin("gnd")
|
self.add_pin("gnd")
|
||||||
|
|
||||||
def add_insts(self):
|
def add_modules(self):
|
||||||
# Add the DFF
|
from importlib import reload
|
||||||
|
c = reload(__import__(OPTS.dff))
|
||||||
|
self.mod_dff = getattr(c, OPTS.dff)
|
||||||
|
self.dff = self.mod_dff("dff")
|
||||||
|
self.add_mod(self.dff)
|
||||||
|
|
||||||
|
self.inv1 = pinv(size=self.inv_size,height=self.dff.height)
|
||||||
|
self.add_mod(self.inv1)
|
||||||
|
|
||||||
|
def create_modules(self):
|
||||||
self.dff_inst=self.add_inst(name="dff_inv_dff",
|
self.dff_inst=self.add_inst(name="dff_inv_dff",
|
||||||
mod=self.dff,
|
mod=self.dff)
|
||||||
offset=vector(0,0))
|
|
||||||
self.connect_inst(["D", "Q", "clk", "vdd", "gnd"])
|
self.connect_inst(["D", "Q", "clk", "vdd", "gnd"])
|
||||||
|
|
||||||
# Add INV1 to the right
|
|
||||||
self.inv1_inst=self.add_inst(name="dff_inv_inv1",
|
self.inv1_inst=self.add_inst(name="dff_inv_inv1",
|
||||||
mod=self.inv1,
|
mod=self.inv1)
|
||||||
offset=vector(self.dff_inst.rx(),0))
|
|
||||||
self.connect_inst(["Q", "Qb", "vdd", "gnd"])
|
self.connect_inst(["Q", "Qb", "vdd", "gnd"])
|
||||||
|
|
||||||
|
def place_modules(self):
|
||||||
|
# Place the DFF
|
||||||
|
self.dff_inst.place(vector(0,0))
|
||||||
|
|
||||||
|
# Place the INV1 to the right
|
||||||
|
self.inv1_inst.place(vector(self.dff_inst.rx(),0))
|
||||||
|
|
||||||
|
|
||||||
def add_wires(self):
|
def add_wires(self):
|
||||||
|
|
|
||||||
|
|
@ -20,21 +20,29 @@ class dff_inv_array(design.design):
|
||||||
name = "dff_inv_array_{0}x{1}".format(rows, columns)
|
name = "dff_inv_array_{0}x{1}".format(rows, columns)
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
debug.info(1, "Creating {}".format(self.name))
|
debug.info(1, "Creating {}".format(self.name))
|
||||||
|
self.inv_size = inv_size
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
self.dff = dff_inv.dff_inv(inv_size)
|
def create_netlist(self):
|
||||||
self.add_mod(self.dff)
|
self.add_pins()
|
||||||
|
self.add_modules()
|
||||||
|
self.create_dff_array()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
self.width = self.columns * self.dff.width
|
self.width = self.columns * self.dff.width
|
||||||
self.height = self.rows * self.dff.height
|
self.height = self.rows * self.dff.height
|
||||||
|
|
||||||
self.create_layout()
|
self.place_dff_array()
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
self.add_pins()
|
|
||||||
self.create_dff_array()
|
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
|
self.dff = dff_inv.dff_inv(self.inv_size)
|
||||||
|
self.add_mod(self.dff)
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
for col in range(self.columns):
|
for col in range(self.columns):
|
||||||
|
|
@ -52,16 +60,8 @@ class dff_inv_array(design.design):
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
for col in range(self.columns):
|
for col in range(self.columns):
|
||||||
name = "Xdff_r{0}_c{1}".format(row,col)
|
name = "Xdff_r{0}_c{1}".format(row,col)
|
||||||
if (row % 2 == 0):
|
|
||||||
base = vector(col*self.dff.width,row*self.dff.height)
|
|
||||||
mirror = "R0"
|
|
||||||
else:
|
|
||||||
base = vector(col*self.dff.width,(row+1)*self.dff.height)
|
|
||||||
mirror = "MX"
|
|
||||||
self.dff_insts[row,col]=self.add_inst(name=name,
|
self.dff_insts[row,col]=self.add_inst(name=name,
|
||||||
mod=self.dff,
|
mod=self.dff)
|
||||||
offset=base,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst([self.get_din_name(row,col),
|
self.connect_inst([self.get_din_name(row,col),
|
||||||
self.get_dout_name(row,col),
|
self.get_dout_name(row,col),
|
||||||
self.get_dout_bar_name(row,col),
|
self.get_dout_bar_name(row,col),
|
||||||
|
|
@ -69,44 +69,57 @@ class dff_inv_array(design.design):
|
||||||
"vdd",
|
"vdd",
|
||||||
"gnd"])
|
"gnd"])
|
||||||
|
|
||||||
|
def place_dff_array(self):
|
||||||
|
for row in range(self.rows):
|
||||||
|
for col in range(self.columns):
|
||||||
|
name = "Xdff_r{0}_c{1}".format(row,col)
|
||||||
|
if (row % 2 == 0):
|
||||||
|
base = vector(col*self.dff.width,row*self.dff.height)
|
||||||
|
mirror = "R0"
|
||||||
|
else:
|
||||||
|
base = vector(col*self.dff.width,(row+1)*self.dff.height)
|
||||||
|
mirror = "MX"
|
||||||
|
self.dff_insts[row,col].place(offset=base,
|
||||||
|
mirror=mirror)
|
||||||
|
|
||||||
def get_din_name(self, row, col):
|
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
|
||||||
|
|
||||||
def add_layout_pins(self):
|
def add_layout_pins(self):
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
for col in range(self.columns):
|
for col in range(self.columns):
|
||||||
# Continous vdd rail along with label.
|
# Adds power pin on left of row
|
||||||
vdd_pin=self.dff_insts[row,col].get_pin("vdd")
|
vdd_pin=self.dff_insts[row,col].get_pin("vdd")
|
||||||
self.add_power_pin("vdd", vdd_pin.lc())
|
self.add_power_pin("vdd", vdd_pin.lc())
|
||||||
|
|
||||||
# Continous gnd rail along with label.
|
# Adds gnd pin on left of row
|
||||||
gnd_pin=self.dff_insts[row,col].get_pin("gnd")
|
gnd_pin=self.dff_insts[row,col].get_pin("gnd")
|
||||||
self.add_power_pin("gnd", gnd_pin.lc())
|
self.add_power_pin("gnd", gnd_pin.lc())
|
||||||
|
|
||||||
|
|
@ -140,6 +153,7 @@ class dff_inv_array(design.design):
|
||||||
|
|
||||||
# Create vertical spines to a single horizontal rail
|
# 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",
|
||||||
|
|
@ -150,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
|
||||||
|
|
@ -161,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))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,12 @@ class hierarchical_decoder(design.design):
|
||||||
from importlib import reload
|
from importlib import reload
|
||||||
c = reload(__import__(OPTS.bitcell))
|
c = reload(__import__(OPTS.bitcell))
|
||||||
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
||||||
self.bitcell_height = self.mod_bitcell.height
|
b = self.mod_bitcell()
|
||||||
|
self.bitcell_height = b.height
|
||||||
|
|
||||||
|
self.NAND_FORMAT = "DEC_NAND_{0}"
|
||||||
|
self.INV_FORMAT = "DEC_INV_{0}"
|
||||||
|
|
||||||
self.pre2x4_inst = []
|
self.pre2x4_inst = []
|
||||||
self.pre3x8_inst = []
|
self.pre3x8_inst = []
|
||||||
|
|
||||||
|
|
@ -33,22 +37,28 @@ class hierarchical_decoder(design.design):
|
||||||
self.num_inputs = int(math.log(self.rows, 2))
|
self.num_inputs = int(math.log(self.rows, 2))
|
||||||
(self.no_of_pre2x4,self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs)
|
(self.no_of_pre2x4,self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs)
|
||||||
|
|
||||||
self.create_layout()
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
self.offset_all_coordinates()
|
|
||||||
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
def create_layout(self):
|
def create_netlist(self):
|
||||||
self.add_modules()
|
self.add_modules()
|
||||||
self.setup_layout_constants()
|
self.setup_netlist_constants()
|
||||||
self.add_pins()
|
self.add_pins()
|
||||||
self.create_pre_decoder()
|
self.create_pre_decoder()
|
||||||
self.create_row_decoder()
|
self.create_row_decoder()
|
||||||
self.create_input_rail()
|
|
||||||
self.create_predecode_rail()
|
|
||||||
self.route_vdd_gnd()
|
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
self.setup_layout_constants()
|
||||||
|
self.place_pre_decoder()
|
||||||
|
self.place_row_decoder()
|
||||||
|
self.route_input_rails()
|
||||||
|
self.route_predecode_rails()
|
||||||
|
self.route_vdd_gnd()
|
||||||
|
self.offset_all_coordinates()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
self.inv = pinv()
|
self.inv = pinv()
|
||||||
self.add_mod(self.inv)
|
self.add_mod(self.inv)
|
||||||
|
|
@ -89,7 +99,7 @@ class hierarchical_decoder(design.design):
|
||||||
else:
|
else:
|
||||||
debug.error("Invalid number of inputs for hierarchical decoder",-1)
|
debug.error("Invalid number of inputs for hierarchical decoder",-1)
|
||||||
|
|
||||||
def setup_layout_constants(self):
|
def setup_netlist_constants(self):
|
||||||
self.predec_groups = [] # This array is a 2D array.
|
self.predec_groups = [] # This array is a 2D array.
|
||||||
|
|
||||||
# Distributing vertical rails to different groups. One group belongs to one pre-decoder.
|
# Distributing vertical rails to different groups. One group belongs to one pre-decoder.
|
||||||
|
|
@ -112,99 +122,16 @@ class hierarchical_decoder(design.design):
|
||||||
index = index + 1
|
index = index + 1
|
||||||
self.predec_groups.append(lines)
|
self.predec_groups.append(lines)
|
||||||
|
|
||||||
self.calculate_dimensions()
|
|
||||||
|
|
||||||
def create_input_rail(self):
|
def setup_layout_constants(self):
|
||||||
""" Create input rails for the predecoders """
|
""" Calculate the overall dimensions of the hierarchical decoder """
|
||||||
# inputs should be as high as the decoders
|
|
||||||
input_height = self.no_of_pre2x4*self.pre2_4.height + self.no_of_pre3x8*self.pre3_8.height
|
|
||||||
|
|
||||||
# Find the left-most predecoder
|
|
||||||
min_x = 0
|
|
||||||
if self.no_of_pre2x4 > 0:
|
|
||||||
min_x = min(min_x, -self.pre2_4.width)
|
|
||||||
if self.no_of_pre3x8 > 0:
|
|
||||||
min_x = min(min_x, -self.pre3_8.width)
|
|
||||||
input_offset=vector(min_x - self.input_routing_width,0)
|
|
||||||
|
|
||||||
input_bus_names = ["addr[{0}]".format(i) for i in range(self.num_inputs)]
|
|
||||||
self.input_rails = self.create_vertical_pin_bus(layer="metal2",
|
|
||||||
pitch=self.m2_pitch,
|
|
||||||
offset=input_offset,
|
|
||||||
names=input_bus_names,
|
|
||||||
length=input_height)
|
|
||||||
|
|
||||||
self.connect_input_to_predecodes()
|
|
||||||
|
|
||||||
|
|
||||||
def connect_input_to_predecodes(self):
|
|
||||||
""" Connect the vertical input rail to the predecoders """
|
|
||||||
for pre_num in range(self.no_of_pre2x4):
|
|
||||||
for i in range(2):
|
|
||||||
index = pre_num * 2 + i
|
|
||||||
|
|
||||||
input_pos = self.input_rails["addr[{}]".format(index)]
|
|
||||||
|
|
||||||
in_name = "in[{}]".format(i)
|
|
||||||
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
|
|
||||||
|
|
||||||
# To prevent conflicts, we will offset each input connect so
|
|
||||||
# that it aligns with the vdd/gnd rails
|
|
||||||
decoder_offset = decoder_pin.bc() + vector(0,(i+1)*self.inv.height)
|
|
||||||
input_offset = input_pos.scale(1,0) + decoder_offset.scale(0,1)
|
|
||||||
|
|
||||||
self.connect_input_rail(decoder_offset, input_offset)
|
|
||||||
|
|
||||||
|
|
||||||
for pre_num in range(self.no_of_pre3x8):
|
|
||||||
for i in range(3):
|
|
||||||
index = pre_num * 3 + i + self.no_of_pre2x4 * 2
|
|
||||||
|
|
||||||
input_pos = self.input_rails["addr[{}]".format(index)]
|
|
||||||
|
|
||||||
in_name = "in[{}]".format(i)
|
|
||||||
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
|
|
||||||
|
|
||||||
# To prevent conflicts, we will offset each input connect so
|
|
||||||
# that it aligns with the vdd/gnd rails
|
|
||||||
decoder_offset = decoder_pin.bc() + vector(0,(i+1)*self.inv.height)
|
|
||||||
input_offset = input_pos.scale(1,0) + decoder_offset.scale(0,1)
|
|
||||||
|
|
||||||
self.connect_input_rail(decoder_offset, input_offset)
|
|
||||||
|
|
||||||
|
|
||||||
def connect_input_rail(self, input_offset, output_offset):
|
|
||||||
""" Connect a vertical M2 coordinate to another vertical M2 coordinate to the predecode inputs """
|
|
||||||
|
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
|
||||||
offset=input_offset,
|
|
||||||
rotate=90)
|
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
|
||||||
offset=output_offset,
|
|
||||||
rotate=90)
|
|
||||||
self.add_path(("metal3"), [input_offset, output_offset])
|
|
||||||
|
|
||||||
|
|
||||||
def add_pins(self):
|
|
||||||
""" Add the module pins """
|
|
||||||
|
|
||||||
for i in range(self.num_inputs):
|
|
||||||
self.add_pin("addr[{0}]".format(i))
|
|
||||||
|
|
||||||
for j in range(self.rows):
|
|
||||||
self.add_pin("decode[{0}]".format(j))
|
|
||||||
self.add_pin("vdd")
|
|
||||||
self.add_pin("gnd")
|
|
||||||
|
|
||||||
def calculate_dimensions(self):
|
|
||||||
""" Calculate the overal dimensions of the hierarchical decoder """
|
|
||||||
|
|
||||||
# If we have 4 or fewer rows, the predecoder is the decoder itself
|
# If we have 4 or fewer rows, the predecoder is the decoder itself
|
||||||
if self.num_inputs>=4:
|
if self.num_inputs>=4:
|
||||||
self.total_number_of_predecoder_outputs = 4*self.no_of_pre2x4 + 8*self.no_of_pre3x8
|
self.total_number_of_predecoder_outputs = 4*self.no_of_pre2x4 + 8*self.no_of_pre3x8
|
||||||
else:
|
else:
|
||||||
self.total_number_of_predecoder_outputs = 0
|
self.total_number_of_predecoder_outputs = 0
|
||||||
debug.error("Not enough rows for a hierarchical decoder. Non-hierarchical not supported yet.",-1)
|
debug.error("Not enough rows ({}) for a hierarchical decoder. Non-hierarchical not supported yet.".format(self.num_inputs),-1)
|
||||||
|
|
||||||
# Calculates height and width of pre-decoder,
|
# Calculates height and width of pre-decoder,
|
||||||
if self.no_of_pre3x8 > 0:
|
if self.no_of_pre3x8 > 0:
|
||||||
|
|
@ -227,49 +154,122 @@ class hierarchical_decoder(design.design):
|
||||||
self.height = self.row_decoder_height
|
self.height = self.row_decoder_height
|
||||||
self.width = self.input_routing_width + self.predecoder_width \
|
self.width = self.input_routing_width + self.predecoder_width \
|
||||||
+ self.internal_routing_width + nand_width + self.inv.width
|
+ self.internal_routing_width + nand_width + self.inv.width
|
||||||
|
|
||||||
|
def route_input_rails(self):
|
||||||
|
""" Create input rails for the predecoders """
|
||||||
|
# inputs should be as high as the decoders
|
||||||
|
input_height = self.no_of_pre2x4*self.pre2_4.height + self.no_of_pre3x8*self.pre3_8.height
|
||||||
|
|
||||||
|
# Find the left-most predecoder
|
||||||
|
min_x = 0
|
||||||
|
if self.no_of_pre2x4 > 0:
|
||||||
|
min_x = min(min_x, -self.pre2_4.width)
|
||||||
|
if self.no_of_pre3x8 > 0:
|
||||||
|
min_x = min(min_x, -self.pre3_8.width)
|
||||||
|
input_offset=vector(min_x - self.input_routing_width,0)
|
||||||
|
|
||||||
|
input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)]
|
||||||
|
self.input_rails = self.create_vertical_pin_bus(layer="metal2",
|
||||||
|
pitch=self.m2_pitch,
|
||||||
|
offset=input_offset,
|
||||||
|
names=input_bus_names,
|
||||||
|
length=input_height)
|
||||||
|
|
||||||
|
self.route_input_to_predecodes()
|
||||||
|
|
||||||
|
|
||||||
|
def route_input_to_predecodes(self):
|
||||||
|
""" Route the vertical input rail to the predecoders """
|
||||||
|
for pre_num in range(self.no_of_pre2x4):
|
||||||
|
for i in range(2):
|
||||||
|
index = pre_num * 2 + i
|
||||||
|
|
||||||
|
input_pos = self.input_rails["addr_{}".format(index)]
|
||||||
|
|
||||||
|
in_name = "in_{}".format(i)
|
||||||
|
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
|
||||||
|
|
||||||
|
# To prevent conflicts, we will offset each input connect so
|
||||||
|
# that it aligns with the vdd/gnd rails
|
||||||
|
decoder_offset = decoder_pin.bc() + vector(0,(i+1)*self.inv.height)
|
||||||
|
input_offset = input_pos.scale(1,0) + decoder_offset.scale(0,1)
|
||||||
|
|
||||||
|
self.route_input_rail(decoder_offset, input_offset)
|
||||||
|
|
||||||
|
|
||||||
|
for pre_num in range(self.no_of_pre3x8):
|
||||||
|
for i in range(3):
|
||||||
|
index = pre_num * 3 + i + self.no_of_pre2x4 * 2
|
||||||
|
|
||||||
|
input_pos = self.input_rails["addr_{}".format(index)]
|
||||||
|
|
||||||
|
in_name = "in_{}".format(i)
|
||||||
|
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
|
||||||
|
|
||||||
|
# To prevent conflicts, we will offset each input connect so
|
||||||
|
# that it aligns with the vdd/gnd rails
|
||||||
|
decoder_offset = decoder_pin.bc() + vector(0,(i+1)*self.inv.height)
|
||||||
|
input_offset = input_pos.scale(1,0) + decoder_offset.scale(0,1)
|
||||||
|
|
||||||
|
self.route_input_rail(decoder_offset, input_offset)
|
||||||
|
|
||||||
|
|
||||||
|
def route_input_rail(self, input_offset, output_offset):
|
||||||
|
""" Route a vertical M2 coordinate to another vertical M2 coordinate to the predecode inputs """
|
||||||
|
|
||||||
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||||
|
offset=input_offset,
|
||||||
|
rotate=90)
|
||||||
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||||
|
offset=output_offset,
|
||||||
|
rotate=90)
|
||||||
|
self.add_path(("metal3"), [input_offset, output_offset])
|
||||||
|
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
""" Add the module pins """
|
||||||
|
|
||||||
|
for i in range(self.num_inputs):
|
||||||
|
self.add_pin("addr_{0}".format(i))
|
||||||
|
|
||||||
|
for j in range(self.rows):
|
||||||
|
self.add_pin("decode_{0}".format(j))
|
||||||
|
self.add_pin("vdd")
|
||||||
|
self.add_pin("gnd")
|
||||||
|
|
||||||
|
|
||||||
def create_pre_decoder(self):
|
def create_pre_decoder(self):
|
||||||
""" Creates pre-decoder and places labels input address [A] """
|
""" Creates pre-decoder and places labels input address [A] """
|
||||||
|
|
||||||
for i in range(self.no_of_pre2x4):
|
for i in range(self.no_of_pre2x4):
|
||||||
self.add_pre2x4(i)
|
self.create_pre2x4(i)
|
||||||
|
|
||||||
for i in range(self.no_of_pre3x8):
|
for i in range(self.no_of_pre3x8):
|
||||||
self.add_pre3x8(i)
|
self.create_pre3x8(i)
|
||||||
|
|
||||||
def add_pre2x4(self,num):
|
def create_pre2x4(self,num):
|
||||||
""" Add a 2x4 predecoder to the left of the origin """
|
""" Add a 2x4 predecoder to the left of the origin """
|
||||||
|
|
||||||
if (self.num_inputs == 2):
|
if (self.num_inputs == 2):
|
||||||
base = vector(-self.pre2_4.width,0)
|
|
||||||
index_off1 = index_off2 = 0
|
index_off1 = index_off2 = 0
|
||||||
else:
|
else:
|
||||||
base= vector(-self.pre2_4.width, num * self.pre2_4.height)
|
|
||||||
index_off1 = num * 2
|
index_off1 = num * 2
|
||||||
index_off2 = num * 4
|
index_off2 = num * 4
|
||||||
|
|
||||||
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))
|
||||||
offset=base))
|
|
||||||
self.connect_inst(pins)
|
self.connect_inst(pins)
|
||||||
|
|
||||||
|
|
||||||
def add_pre3x8(self,num):
|
def create_pre3x8(self,num):
|
||||||
""" Add 3x8 predecoder to the left of the origin and above any 2x4 decoders """
|
""" Add 3x8 predecoder to the left of the origin and above any 2x4 decoders """
|
||||||
if (self.num_inputs == 3):
|
|
||||||
offset = vector(-self.pre_3_8.width,0)
|
|
||||||
mirror ="R0"
|
|
||||||
else:
|
|
||||||
height = self.no_of_pre2x4*self.pre2_4.height + num*self.pre3_8.height
|
|
||||||
offset = vector(-self.pre3_8.width, height)
|
|
||||||
|
|
||||||
# If we had 2x4 predecodes, those are used as the lower
|
# If we had 2x4 predecodes, those are used as the lower
|
||||||
# decode output bits
|
# decode output bits
|
||||||
in_index_offset = num * 3 + self.no_of_pre2x4 * 2
|
in_index_offset = num * 3 + self.no_of_pre2x4 * 2
|
||||||
|
|
@ -277,85 +277,116 @@ 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))
|
||||||
offset=offset))
|
|
||||||
self.connect_inst(pins)
|
self.connect_inst(pins)
|
||||||
|
|
||||||
|
|
||||||
|
def place_pre_decoder(self):
|
||||||
|
""" Creates pre-decoder and places labels input address [A] """
|
||||||
|
|
||||||
|
for i in range(self.no_of_pre2x4):
|
||||||
|
self.place_pre2x4(i)
|
||||||
|
|
||||||
|
for i in range(self.no_of_pre3x8):
|
||||||
|
self.place_pre3x8(i)
|
||||||
|
|
||||||
|
def place_pre2x4(self,num):
|
||||||
|
""" Place 2x4 predecoder to the left of the origin """
|
||||||
|
|
||||||
|
if (self.num_inputs == 2):
|
||||||
|
base = vector(-self.pre2_4.width,0)
|
||||||
|
else:
|
||||||
|
base= vector(-self.pre2_4.width, num * self.pre2_4.height)
|
||||||
|
|
||||||
|
self.pre2x4_inst[num].place(base)
|
||||||
|
|
||||||
|
|
||||||
|
def place_pre3x8(self,num):
|
||||||
|
""" Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """
|
||||||
|
if (self.num_inputs == 3):
|
||||||
|
offset = vector(-self.pre_3_8.width,0)
|
||||||
|
mirror ="R0"
|
||||||
|
else:
|
||||||
|
height = self.no_of_pre2x4*self.pre2_4.height + num*self.pre3_8.height
|
||||||
|
offset = vector(-self.pre3_8.width, height)
|
||||||
|
|
||||||
|
self.pre3x8_inst[num].place(offset)
|
||||||
|
|
||||||
|
|
||||||
def create_row_decoder(self):
|
def create_row_decoder(self):
|
||||||
""" Create the row-decoder by placing NAND2/NAND3 and Inverters
|
""" Create the row-decoder by placing NAND2/NAND3 and Inverters
|
||||||
and add the primary decoder output pins. """
|
and add the primary decoder output pins. """
|
||||||
if (self.num_inputs >= 4):
|
if (self.num_inputs >= 4):
|
||||||
self.add_decoder_nand_array()
|
self.create_decoder_nand_array()
|
||||||
self.add_decoder_inv_array()
|
self.create_decoder_inv_array()
|
||||||
self.route_decoder()
|
|
||||||
|
|
||||||
|
|
||||||
def add_decoder_nand_array(self):
|
def create_decoder_nand_array(self):
|
||||||
""" Add a column of NAND gates for final decode """
|
""" Add a column of NAND gates for final decode """
|
||||||
|
|
||||||
|
self.nand_inst = []
|
||||||
|
|
||||||
# Row Decoder NAND GATE array for address inputs <5.
|
# Row Decoder NAND GATE array for address inputs <5.
|
||||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||||
self.add_nand_array(nand_mod=self.nand2)
|
|
||||||
# FIXME: Can we convert this to the connect_inst with checks?
|
|
||||||
for i in range(len(self.predec_groups[0])):
|
for i in range(len(self.predec_groups[0])):
|
||||||
for j in range(len(self.predec_groups[1])):
|
for j in range(len(self.predec_groups[1])):
|
||||||
pins =["out[{0}]".format(i),
|
row = len(self.predec_groups[1])*i + j
|
||||||
"out[{0}]".format(j + len(self.predec_groups[0])),
|
name = self.NAND_FORMAT.format(row)
|
||||||
"Z[{0}]".format(len(self.predec_groups[1])*i + j),
|
self.nand_inst.append(self.add_inst(name=name,
|
||||||
|
mod=self.nand2))
|
||||||
|
pins =["out_{0}".format(i),
|
||||||
|
"out_{0}".format(j + len(self.predec_groups[0])),
|
||||||
|
"Z_{0}".format(row),
|
||||||
"vdd", "gnd"]
|
"vdd", "gnd"]
|
||||||
self.connect_inst(args=pins, check=False)
|
self.connect_inst(pins)
|
||||||
|
|
||||||
|
|
||||||
# Row Decoder NAND GATE array for address inputs >5.
|
# Row Decoder NAND GATE array for address inputs >5.
|
||||||
elif (self.num_inputs > 5):
|
elif (self.num_inputs > 5):
|
||||||
self.add_nand_array(nand_mod=self.nand3,
|
|
||||||
correct=drc["minwidth_metal1"])
|
|
||||||
# This will not check that the inst connections match.
|
|
||||||
for i in range(len(self.predec_groups[0])):
|
for i in range(len(self.predec_groups[0])):
|
||||||
for j in range(len(self.predec_groups[1])):
|
for j in range(len(self.predec_groups[1])):
|
||||||
for k in range(len(self.predec_groups[2])):
|
for k in range(len(self.predec_groups[2])):
|
||||||
Z_index = len(self.predec_groups[1])*len(self.predec_groups[2]) * i \
|
row = len(self.predec_groups[1])*len(self.predec_groups[2]) * i \
|
||||||
+ len(self.predec_groups[2])*j + k
|
+ len(self.predec_groups[2])*j + k
|
||||||
pins = ["out[{0}]".format(i),
|
|
||||||
"out[{0}]".format(j + len(self.predec_groups[0])),
|
name = self.NAND_FORMAT.format(row)
|
||||||
"out[{0}]".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])),
|
self.nand_inst.append(self.add_inst(name=name,
|
||||||
"Z[{0}]".format(Z_index),
|
mod=self.nand3))
|
||||||
|
|
||||||
|
pins = ["out_{0}".format(i),
|
||||||
|
"out_{0}".format(j + len(self.predec_groups[0])),
|
||||||
|
"out_{0}".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])),
|
||||||
|
"Z_{0}".format(row),
|
||||||
"vdd", "gnd"]
|
"vdd", "gnd"]
|
||||||
self.connect_inst(args=pins, check=False)
|
self.connect_inst(pins)
|
||||||
|
|
||||||
def add_nand_array(self, nand_mod, correct=0):
|
|
||||||
""" Add a column of NAND gates for the decoder above the predecoders."""
|
def create_decoder_inv_array(self):
|
||||||
|
"""
|
||||||
|
Add a column of INV gates for the decoder.
|
||||||
|
"""
|
||||||
|
|
||||||
self.nand_inst = []
|
self.inv_inst = []
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
name = "DEC_NAND[{0}]".format(row)
|
name = self.INV_FORMAT.format(row)
|
||||||
if ((row % 2) == 0):
|
self.inv_inst.append(self.add_inst(name=name,
|
||||||
y_off = nand_mod.height*row
|
mod=self.inv))
|
||||||
y_dir = 1
|
self.connect_inst(args=["Z_{0}".format(row),
|
||||||
mirror = "R0"
|
"decode_{0}".format(row),
|
||||||
else:
|
"vdd", "gnd"])
|
||||||
y_off = nand_mod.height*(row + 1)
|
|
||||||
y_dir = -1
|
|
||||||
mirror = "MX"
|
|
||||||
|
|
||||||
self.nand_inst.append(self.add_inst(name=name,
|
|
||||||
mod=nand_mod,
|
|
||||||
offset=[self.internal_routing_width, y_off],
|
|
||||||
mirror=mirror))
|
|
||||||
|
|
||||||
|
def place_decoder_inv_array(self):
|
||||||
|
"""
|
||||||
def add_decoder_inv_array(self):
|
Place the column of INV gates for the decoder above the predecoders
|
||||||
"""Add a column of INV gates for the decoder above the predecoders
|
and to the right of the NAND decoders.
|
||||||
and to the right of the NAND decoders."""
|
"""
|
||||||
|
|
||||||
z_pin = self.inv.get_pin("Z")
|
z_pin = self.inv.get_pin("Z")
|
||||||
|
|
||||||
|
|
@ -364,9 +395,7 @@ class hierarchical_decoder(design.design):
|
||||||
else:
|
else:
|
||||||
x_off = self.internal_routing_width + self.nand3.width
|
x_off = self.internal_routing_width + self.nand3.width
|
||||||
|
|
||||||
self.inv_inst = []
|
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
name = "DEC_INV_[{0}]".format(row)
|
|
||||||
if (row % 2 == 0):
|
if (row % 2 == 0):
|
||||||
inv_row_height = self.inv.height * row
|
inv_row_height = self.inv.height * row
|
||||||
mirror = "R0"
|
mirror = "R0"
|
||||||
|
|
@ -377,17 +406,50 @@ class hierarchical_decoder(design.design):
|
||||||
y_dir = -1
|
y_dir = -1
|
||||||
y_off = inv_row_height
|
y_off = inv_row_height
|
||||||
offset = vector(x_off,y_off)
|
offset = vector(x_off,y_off)
|
||||||
|
self.inv_inst[row].place(offset=offset,
|
||||||
self.inv_inst.append(self.add_inst(name=name,
|
mirror=mirror)
|
||||||
mod=self.inv,
|
|
||||||
offset=offset,
|
|
||||||
mirror=mirror))
|
|
||||||
|
|
||||||
# This will not check that the inst connections match.
|
def place_row_decoder(self):
|
||||||
self.connect_inst(args=["Z[{0}]".format(row),
|
"""
|
||||||
"decode[{0}]".format(row),
|
Place the row-decoder by placing NAND2/NAND3 and Inverters
|
||||||
"vdd", "gnd"],
|
and add the primary decoder output pins.
|
||||||
check=False)
|
"""
|
||||||
|
if (self.num_inputs >= 4):
|
||||||
|
self.place_decoder_nand_array()
|
||||||
|
self.place_decoder_inv_array()
|
||||||
|
self.route_decoder()
|
||||||
|
|
||||||
|
|
||||||
|
def place_decoder_nand_array(self):
|
||||||
|
""" Add a column of NAND gates for final decode """
|
||||||
|
|
||||||
|
# Row Decoder NAND GATE array for address inputs <5.
|
||||||
|
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||||
|
self.place_nand_array(nand_mod=self.nand2)
|
||||||
|
|
||||||
|
# Row Decoder NAND GATE array for address inputs >5.
|
||||||
|
# FIXME: why this correct offset?)
|
||||||
|
elif (self.num_inputs > 5):
|
||||||
|
self.place_nand_array(nand_mod=self.nand3)
|
||||||
|
|
||||||
|
def place_nand_array(self, nand_mod):
|
||||||
|
""" Add a column of NAND gates for the decoder above the predecoders."""
|
||||||
|
|
||||||
|
for row in range(self.rows):
|
||||||
|
name = self.NAND_FORMAT.format(row)
|
||||||
|
if ((row % 2) == 0):
|
||||||
|
y_off = nand_mod.height*row
|
||||||
|
y_dir = 1
|
||||||
|
mirror = "R0"
|
||||||
|
else:
|
||||||
|
y_off = nand_mod.height*(row + 1)
|
||||||
|
y_dir = -1
|
||||||
|
mirror = "MX"
|
||||||
|
|
||||||
|
self.nand_inst[row].place(offset=[self.internal_routing_width, y_off],
|
||||||
|
mirror=mirror)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def route_decoder(self):
|
def route_decoder(self):
|
||||||
|
|
@ -404,7 +466,7 @@ class hierarchical_decoder(design.design):
|
||||||
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
|
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(),
|
||||||
|
|
@ -412,13 +474,13 @@ class hierarchical_decoder(design.design):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_predecode_rail(self):
|
def route_predecode_rails(self):
|
||||||
""" Creates vertical metal 2 rails to connect predecoder and decoder stages."""
|
""" Creates vertical metal 2 rails to connect predecoder and decoder stages."""
|
||||||
|
|
||||||
# This is not needed for inputs <4 since they have no pre/decode stages.
|
# 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,
|
||||||
|
|
@ -426,32 +488,32 @@ class hierarchical_decoder(design.design):
|
||||||
length=self.height)
|
length=self.height)
|
||||||
|
|
||||||
|
|
||||||
self.connect_rails_to_predecodes()
|
self.route_rails_to_predecodes()
|
||||||
self.connect_rails_to_decoder()
|
self.route_rails_to_decoder()
|
||||||
|
|
||||||
def connect_rails_to_predecodes(self):
|
def route_rails_to_predecodes(self):
|
||||||
""" Iterates through all of the predecodes and connects to the rails including the offsets """
|
""" Iterates through all of the predecodes and connects to the rails including the offsets """
|
||||||
|
|
||||||
# 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.connect_predecode_rail_m3(predecode_name, pin)
|
self.route_predecode_rail_m3(predecode_name, pin)
|
||||||
|
|
||||||
|
|
||||||
# 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.connect_predecode_rail_m3(predecode_name, pin)
|
self.route_predecode_rail_m3(predecode_name, pin)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def connect_rails_to_decoder(self):
|
def route_rails_to_decoder(self):
|
||||||
""" Use the self.predec_groups to determine the connections to the decoder NAND gates.
|
""" Use the self.predec_groups to determine the connections to the decoder NAND gates.
|
||||||
Inputs of NAND2/NAND3 gates come from different groups.
|
Inputs of NAND2/NAND3 gates come from different groups.
|
||||||
For example for these groups [ [0,1,2,3] ,[4,5,6,7],
|
For example for these groups [ [0,1,2,3] ,[4,5,6,7],
|
||||||
|
|
@ -464,10 +526,10 @@ class hierarchical_decoder(design.design):
|
||||||
for index_A in self.predec_groups[0]:
|
for index_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.connect_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.connect_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
|
||||||
|
|
||||||
elif (self.num_inputs > 5):
|
elif (self.num_inputs > 5):
|
||||||
|
|
@ -475,48 +537,45 @@ 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.connect_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.connect_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.connect_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")
|
||||||
self.copy_layout_pin(pre, "gnd")
|
self.copy_layout_pin(pre, "gnd")
|
||||||
|
|
||||||
|
|
||||||
def connect_predecode_rail(self, rail_name, pin):
|
def route_predecode_rail(self, rail_name, pin):
|
||||||
""" Connect the routing rail to the given metal1 pin """
|
""" Connect the routing rail to the given metal1 pin """
|
||||||
rail_pos = vector(self.predecode_rails[rail_name].x,pin.lc().y)
|
rail_pos = vector(self.predecode_rails[rail_name].x,pin.lc().y)
|
||||||
self.add_path("metal1", [rail_pos, pin.lc()])
|
self.add_path("metal1", [rail_pos, pin.lc()])
|
||||||
|
|
@ -525,7 +584,7 @@ class hierarchical_decoder(design.design):
|
||||||
rotate=90)
|
rotate=90)
|
||||||
|
|
||||||
|
|
||||||
def connect_predecode_rail_m3(self, rail_name, pin):
|
def route_predecode_rail_m3(self, rail_name, pin):
|
||||||
""" Connect the routing rail to the given metal1 pin """
|
""" Connect the routing rail to the given metal1 pin """
|
||||||
# This routes the pin up to the rail, basically, to avoid conflicts.
|
# This routes the pin up to the rail, basically, to avoid conflicts.
|
||||||
# It would be fixed with a channel router.
|
# It would be fixed with a channel router.
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
@ -37,27 +37,27 @@ class hierarchical_predecode(design.design):
|
||||||
self.inv = pinv()
|
self.inv = pinv()
|
||||||
self.add_mod(self.inv)
|
self.add_mod(self.inv)
|
||||||
|
|
||||||
self.create_nand(self.number_of_inputs)
|
self.add_nand(self.number_of_inputs)
|
||||||
self.add_mod(self.nand)
|
self.add_mod(self.nand)
|
||||||
|
|
||||||
def create_nand(self,inputs):
|
def add_nand(self,inputs):
|
||||||
""" Create the NAND for the predecode input stage """
|
""" Create the NAND for the predecode input stage """
|
||||||
if inputs==2:
|
if inputs==2:
|
||||||
self.nand = pnand2()
|
self.nand = pnand2()
|
||||||
elif inputs==3:
|
elif inputs==3:
|
||||||
self.nand = pnand3()
|
self.nand = pnand3()
|
||||||
else:
|
else:
|
||||||
debug.error("Invalid number of predecode inputs.",-1)
|
debug.error("Invalid number of predecode inputs: {}".format(inputs),-1)
|
||||||
|
|
||||||
def setup_constraints(self):
|
def setup_layout_constraints(self):
|
||||||
|
|
||||||
self.height = self.number_of_outputs * self.nand.height
|
self.height = self.number_of_outputs * self.nand.height
|
||||||
|
|
||||||
# x offset for input inverters
|
# x offset for input inverters
|
||||||
self.x_off_inv_1 = self.number_of_inputs*self.m2_pitch
|
self.x_off_inv_1 = self.number_of_inputs*self.m2_pitch
|
||||||
|
|
||||||
# x offset to NAND decoder includes the left rails, mid rails and inverters, plus an extra m2 pitch
|
# x offset to NAND decoder includes the left rails, mid rails and inverters, plus two extra m2 pitches
|
||||||
self.x_off_nand = self.x_off_inv_1 + self.inv.width + (2*self.number_of_inputs + 1) * self.m2_pitch
|
self.x_off_nand = self.x_off_inv_1 + self.inv.width + (2*self.number_of_inputs + 2) * self.m2_pitch
|
||||||
|
|
||||||
# x offset to output inverters
|
# x offset to output inverters
|
||||||
self.x_off_inv_2 = self.x_off_nand + self.nand.width
|
self.x_off_inv_2 = self.x_off_nand + self.nand.width
|
||||||
|
|
@ -65,9 +65,9 @@ class hierarchical_predecode(design.design):
|
||||||
# Height width are computed
|
# Height width are computed
|
||||||
self.width = self.x_off_inv_2 + self.inv.width
|
self.width = self.x_off_inv_2 + self.inv.width
|
||||||
|
|
||||||
def create_rails(self):
|
def route_rails(self):
|
||||||
""" Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """
|
""" 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,10 +75,10 @@ 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 + 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",
|
||||||
pitch=self.m2_pitch,
|
pitch=self.m2_pitch,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
|
|
@ -86,12 +86,20 @@ class hierarchical_predecode(design.design):
|
||||||
length=self.height - 2*self.m1_width)
|
length=self.height - 2*self.m1_width)
|
||||||
|
|
||||||
|
|
||||||
def add_input_inverters(self):
|
def create_input_inverters(self):
|
||||||
""" Create the input inverters to invert input signals for the decode stage. """
|
""" 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,
|
||||||
|
mod=self.inv))
|
||||||
|
self.connect_inst(["in_{0}".format(inv_num),
|
||||||
|
"inbar_{0}".format(inv_num),
|
||||||
|
"vdd", "gnd"])
|
||||||
|
|
||||||
|
def place_input_inverters(self):
|
||||||
|
""" Place the input inverters to invert input signals for the decode stage. """
|
||||||
|
for inv_num in range(self.number_of_inputs):
|
||||||
if (inv_num % 2 == 0):
|
if (inv_num % 2 == 0):
|
||||||
y_off = inv_num * (self.inv.height)
|
y_off = inv_num * (self.inv.height)
|
||||||
mirror = "R0"
|
mirror = "R0"
|
||||||
|
|
@ -99,20 +107,24 @@ class hierarchical_predecode(design.design):
|
||||||
y_off = (inv_num + 1) * (self.inv.height)
|
y_off = (inv_num + 1) * (self.inv.height)
|
||||||
mirror="MX"
|
mirror="MX"
|
||||||
offset = vector(self.x_off_inv_1, y_off)
|
offset = vector(self.x_off_inv_1, y_off)
|
||||||
self.in_inst.append(self.add_inst(name=name,
|
self.in_inst[inv_num].place(offset=offset,
|
||||||
mod=self.inv,
|
mirror=mirror)
|
||||||
offset=offset,
|
|
||||||
mirror=mirror))
|
|
||||||
self.connect_inst(["in[{0}]".format(inv_num),
|
|
||||||
"inbar[{0}]".format(inv_num),
|
|
||||||
"vdd", "gnd"])
|
|
||||||
|
|
||||||
def add_output_inverters(self):
|
def create_output_inverters(self):
|
||||||
""" 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,
|
||||||
|
mod=self.inv))
|
||||||
|
self.connect_inst(["Z_{}".format(inv_num),
|
||||||
|
"out_{}".format(inv_num),
|
||||||
|
"vdd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
|
def place_output_inverters(self):
|
||||||
|
""" Place inverters for the inverted output decode signals. """
|
||||||
|
for inv_num in range(self.number_of_outputs):
|
||||||
if (inv_num % 2 == 0):
|
if (inv_num % 2 == 0):
|
||||||
y_off = inv_num * self.inv.height
|
y_off = inv_num * self.inv.height
|
||||||
mirror = "R0"
|
mirror = "R0"
|
||||||
|
|
@ -120,22 +132,24 @@ class hierarchical_predecode(design.design):
|
||||||
y_off =(inv_num + 1)*self.inv.height
|
y_off =(inv_num + 1)*self.inv.height
|
||||||
mirror = "MX"
|
mirror = "MX"
|
||||||
offset = vector(self.x_off_inv_2, y_off)
|
offset = vector(self.x_off_inv_2, y_off)
|
||||||
self.inv_inst.append(self.add_inst(name=name,
|
self.inv_inst[inv_num].place(offset=offset,
|
||||||
mod=self.inv,
|
mirror=mirror)
|
||||||
offset=offset,
|
|
||||||
mirror=mirror))
|
|
||||||
self.connect_inst(["Z[{}]".format(inv_num),
|
|
||||||
"out[{}]".format(inv_num),
|
|
||||||
"vdd", "gnd"])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def add_nand(self,connections):
|
def create_nand_array(self,connections):
|
||||||
""" Create the NAND stage for the decodes """
|
""" Create the NAND stage for the decodes """
|
||||||
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,
|
||||||
|
mod=self.nand))
|
||||||
|
self.connect_inst(connections[nand_input])
|
||||||
|
|
||||||
|
|
||||||
|
def place_nand_array(self):
|
||||||
|
""" Place the NAND stage for the decodes """
|
||||||
|
for nand_input in range(self.number_of_outputs):
|
||||||
|
inout = str(self.number_of_inputs)+"x"+str(self.number_of_outputs)
|
||||||
if (nand_input % 2 == 0):
|
if (nand_input % 2 == 0):
|
||||||
y_off = nand_input * self.inv.height
|
y_off = nand_input * self.inv.height
|
||||||
mirror = "R0"
|
mirror = "R0"
|
||||||
|
|
@ -143,11 +157,8 @@ class hierarchical_predecode(design.design):
|
||||||
y_off = (nand_input + 1) * self.inv.height
|
y_off = (nand_input + 1) * self.inv.height
|
||||||
mirror = "MX"
|
mirror = "MX"
|
||||||
offset = vector(self.x_off_nand, y_off)
|
offset = vector(self.x_off_nand, y_off)
|
||||||
self.nand_inst.append(self.add_inst(name=name,
|
self.nand_inst[nand_input].place(offset=offset,
|
||||||
mod=self.nand,
|
mirror=mirror)
|
||||||
offset=offset,
|
|
||||||
mirror=mirror))
|
|
||||||
self.connect_inst(connections[nand_input])
|
|
||||||
|
|
||||||
|
|
||||||
def route(self):
|
def route(self):
|
||||||
|
|
@ -164,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])
|
||||||
|
|
@ -191,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(),
|
||||||
|
|
@ -203,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
|
||||||
|
|
@ -271,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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import debug
|
||||||
import design
|
import design
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from hierarchical_predecode import hierarchical_predecode
|
from hierarchical_predecode import hierarchical_predecode
|
||||||
|
from globals import OPTS
|
||||||
|
|
||||||
class hierarchical_predecode2x4(hierarchical_predecode):
|
class hierarchical_predecode2x4(hierarchical_predecode):
|
||||||
"""
|
"""
|
||||||
|
|
@ -11,11 +12,20 @@ class hierarchical_predecode2x4(hierarchical_predecode):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
hierarchical_predecode.__init__(self, 2)
|
hierarchical_predecode.__init__(self, 2)
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
self.add_pins()
|
self.add_pins()
|
||||||
self.create_modules()
|
self.create_modules()
|
||||||
self.setup_constraints()
|
self.create_input_inverters()
|
||||||
self.create_layout()
|
self.create_output_inverters()
|
||||||
self.DRC_LVS()
|
connections =[["inbar_0", "inbar_1", "Z_0", "vdd", "gnd"],
|
||||||
|
["in_0", "inbar_1", "Z_1", "vdd", "gnd"],
|
||||||
|
["inbar_0", "in_1", "Z_2", "vdd", "gnd"],
|
||||||
|
["in_0", "in_1", "Z_3", "vdd", "gnd"]]
|
||||||
|
self.create_nand_array(connections)
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
""" The general organization is from left to right:
|
""" The general organization is from left to right:
|
||||||
|
|
@ -24,22 +34,20 @@ class hierarchical_predecode2x4(hierarchical_predecode):
|
||||||
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
|
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
|
||||||
4) a set of NAND gates for inversion
|
4) a set of NAND gates for inversion
|
||||||
"""
|
"""
|
||||||
self.create_rails()
|
self.setup_layout_constraints()
|
||||||
self.add_input_inverters()
|
self.route_rails()
|
||||||
self.add_output_inverters()
|
self.place_input_inverters()
|
||||||
connections =[["inbar[0]", "inbar[1]", "Z[0]", "vdd", "gnd"],
|
self.place_output_inverters()
|
||||||
["in[0]", "inbar[1]", "Z[1]", "vdd", "gnd"],
|
self.place_nand_array()
|
||||||
["inbar[0]", "in[1]", "Z[2]", "vdd", "gnd"],
|
|
||||||
["in[0]", "in[1]", "Z[3]", "vdd", "gnd"]]
|
|
||||||
self.add_nand(connections)
|
|
||||||
self.route()
|
self.route()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import debug
|
||||||
import design
|
import design
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from hierarchical_predecode import hierarchical_predecode
|
from hierarchical_predecode import hierarchical_predecode
|
||||||
|
from globals import OPTS
|
||||||
|
|
||||||
class hierarchical_predecode3x8(hierarchical_predecode):
|
class hierarchical_predecode3x8(hierarchical_predecode):
|
||||||
"""
|
"""
|
||||||
|
|
@ -11,43 +12,51 @@ class hierarchical_predecode3x8(hierarchical_predecode):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
hierarchical_predecode.__init__(self, 3)
|
hierarchical_predecode.__init__(self, 3)
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
self.add_pins()
|
self.add_pins()
|
||||||
self.create_modules()
|
self.create_modules()
|
||||||
self.setup_constraints()
|
self.create_input_inverters()
|
||||||
self.create_layout()
|
self.create_output_inverters()
|
||||||
self.DRC_LVS()
|
connections=[["inbar_0", "inbar_1", "inbar_2", "Z_0", "vdd", "gnd"],
|
||||||
|
["in_0", "inbar_1", "inbar_2", "Z_1", "vdd", "gnd"],
|
||||||
|
["inbar_0", "in_1", "inbar_2", "Z_2", "vdd", "gnd"],
|
||||||
|
["in_0", "in_1", "inbar_2", "Z_3", "vdd", "gnd"],
|
||||||
|
["inbar_0", "inbar_1", "in_2", "Z_4", "vdd", "gnd"],
|
||||||
|
["in_0", "inbar_1", "in_2", "Z_5", "vdd", "gnd"],
|
||||||
|
["inbar_0", "in_1", "in_2", "Z_6", "vdd", "gnd"],
|
||||||
|
["in_0", "in_1", "in_2", "Z_7", "vdd", "gnd"]]
|
||||||
|
self.create_nand_array(connections)
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
""" The general organization is from left to right:
|
"""
|
||||||
|
The general organization is from left to right:
|
||||||
1) a set of M2 rails for input signals
|
1) a set of M2 rails for input signals
|
||||||
2) a set of inverters to invert input signals
|
2) a set of inverters to invert input signals
|
||||||
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
|
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
|
||||||
4) a set of NAND gates for inversion
|
4) a set of NAND gates for inversion
|
||||||
"""
|
"""
|
||||||
self.create_rails()
|
self.setup_layout_constraints()
|
||||||
self.add_input_inverters()
|
self.route_rails()
|
||||||
self.add_output_inverters()
|
self.place_input_inverters()
|
||||||
connections=[["inbar[0]", "inbar[1]", "inbar[2]", "Z[0]", "vdd", "gnd"],
|
self.place_output_inverters()
|
||||||
["in[0]", "inbar[1]", "inbar[2]", "Z[1]", "vdd", "gnd"],
|
self.place_nand_array()
|
||||||
["inbar[0]", "in[1]", "inbar[2]", "Z[2]", "vdd", "gnd"],
|
|
||||||
["in[0]", "in[1]", "inbar[2]", "Z[3]", "vdd", "gnd"],
|
|
||||||
["inbar[0]", "inbar[1]", "in[2]", "Z[4]", "vdd", "gnd"],
|
|
||||||
["in[0]", "inbar[1]", "in[2]", "Z[5]", "vdd", "gnd"],
|
|
||||||
["inbar[0]", "in[1]", "in[2]", "Z[6]", "vdd", "gnd"],
|
|
||||||
["in[0]", "in[1]", "in[2]", "Z[7]", "vdd", "gnd"]]
|
|
||||||
self.add_nand(connections)
|
|
||||||
self.route()
|
self.route()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
import globals
|
|
||||||
import design
|
|
||||||
from math import log
|
|
||||||
import design
|
|
||||||
from tech import GDS,layer
|
|
||||||
import utils
|
|
||||||
|
|
||||||
class ms_flop(design.design):
|
|
||||||
"""
|
|
||||||
Memory address flip-flop
|
|
||||||
"""
|
|
||||||
|
|
||||||
pin_names = ["din", "dout", "dout_bar", "clk", "vdd", "gnd"]
|
|
||||||
(width,height) = utils.get_libcell_size("ms_flop", GDS["unit"], layer["boundary"])
|
|
||||||
pin_map = utils.get_libcell_pins(pin_names, "ms_flop", GDS["unit"], layer["boundary"])
|
|
||||||
|
|
||||||
def __init__(self, name="ms_flop"):
|
|
||||||
design.design.__init__(self, name)
|
|
||||||
|
|
||||||
self.width = ms_flop.width
|
|
||||||
self.height = ms_flop.height
|
|
||||||
self.pin_map = ms_flop.pin_map
|
|
||||||
|
|
||||||
def analytical_delay(self, slew, load = 0.0):
|
|
||||||
# dont know how to calculate this now, use constant in tech file
|
|
||||||
from tech import spice
|
|
||||||
result = self.return_delay(spice["msflop_delay"], spice["msflop_slew"])
|
|
||||||
return result
|
|
||||||
|
|
||||||
def analytical_power(self, proc, vdd, temp, load):
|
|
||||||
"""Returns dynamic and leakage power. Results in nW"""
|
|
||||||
from tech import spice
|
|
||||||
c_eff = self.calculate_effective_capacitance(load)
|
|
||||||
f = spice["default_event_rate"]
|
|
||||||
power_dyn = c_eff*vdd*vdd*f
|
|
||||||
power_leak = spice["msflop_leakage"]
|
|
||||||
|
|
||||||
total_power = self.return_power(power_dyn, power_leak)
|
|
||||||
return total_power
|
|
||||||
|
|
||||||
def calculate_effective_capacitance(self, load):
|
|
||||||
"""Computes effective capacitance. Results in fF"""
|
|
||||||
from tech import spice, parameter
|
|
||||||
c_load = load
|
|
||||||
c_para = spice["flop_para_cap"]#ff
|
|
||||||
transistion_prob = spice["flop_transisition_prob"]
|
|
||||||
return transistion_prob*(c_load + c_para)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
import debug
|
|
||||||
import design
|
|
||||||
from tech import drc
|
|
||||||
from math import log
|
|
||||||
from vector import vector
|
|
||||||
from globals import OPTS
|
|
||||||
|
|
||||||
class ms_flop_array(design.design):
|
|
||||||
"""
|
|
||||||
An Array of D-Flipflops used for to store Data_in & Data_out of
|
|
||||||
Write_driver & Sense_amp, address inputs of column_mux &
|
|
||||||
hierdecoder
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, columns, word_size, name=""):
|
|
||||||
self.columns = columns
|
|
||||||
self.word_size = word_size
|
|
||||||
if name=="":
|
|
||||||
name = "flop_array_c{0}_w{1}".format(columns,word_size)
|
|
||||||
design.design.__init__(self, name)
|
|
||||||
debug.info(1, "Creating {}".format(self.name))
|
|
||||||
|
|
||||||
from importlib import reload
|
|
||||||
c = reload(__import__(OPTS.ms_flop))
|
|
||||||
self.mod_ms_flop = getattr(c, OPTS.ms_flop)
|
|
||||||
self.ms = self.mod_ms_flop("ms_flop")
|
|
||||||
self.add_mod(self.ms)
|
|
||||||
|
|
||||||
self.width = self.columns * self.ms.width
|
|
||||||
self.height = self.ms.height
|
|
||||||
self.words_per_row = int(self.columns / self.word_size)
|
|
||||||
|
|
||||||
self.create_layout()
|
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
self.add_pins()
|
|
||||||
self.create_ms_flop_array()
|
|
||||||
self.add_layout_pins()
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
def add_pins(self):
|
|
||||||
for i in range(self.word_size):
|
|
||||||
self.add_pin("din[{0}]".format(i))
|
|
||||||
for i in range(self.word_size):
|
|
||||||
self.add_pin("dout[{0}]".format(i))
|
|
||||||
self.add_pin("dout_bar[{0}]".format(i))
|
|
||||||
self.add_pin("clk")
|
|
||||||
self.add_pin("vdd")
|
|
||||||
self.add_pin("gnd")
|
|
||||||
|
|
||||||
def create_ms_flop_array(self):
|
|
||||||
self.ms_inst={}
|
|
||||||
for i in range(0,self.columns,self.words_per_row):
|
|
||||||
name = "Xdff{0}".format(i)
|
|
||||||
if (i % 2 == 0 or self.words_per_row>1):
|
|
||||||
base = vector(i*self.ms.width,0)
|
|
||||||
mirror = "R0"
|
|
||||||
else:
|
|
||||||
base = vector((i+1)*self.ms.width,0)
|
|
||||||
mirror = "MY"
|
|
||||||
|
|
||||||
index = int(i/self.words_per_row)
|
|
||||||
|
|
||||||
self.ms_inst[index]=self.add_inst(name=name,
|
|
||||||
mod=self.ms,
|
|
||||||
offset=base,
|
|
||||||
mirror=mirror)
|
|
||||||
self.connect_inst(["din[{0}]".format(index),
|
|
||||||
"dout[{0}]".format(index),
|
|
||||||
"dout_bar[{0}]".format(index),
|
|
||||||
"clk",
|
|
||||||
"vdd", "gnd"])
|
|
||||||
|
|
||||||
def add_layout_pins(self):
|
|
||||||
|
|
||||||
for i in range(self.word_size):
|
|
||||||
|
|
||||||
# Route both supplies
|
|
||||||
for n in ["vdd", "gnd"]:
|
|
||||||
for supply_pin in self.ms_inst[i].get_pins(n):
|
|
||||||
pin_pos = supply_pin.center()
|
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
|
||||||
offset=pin_pos)
|
|
||||||
self.add_layout_pin_rect_center(text=n,
|
|
||||||
layer="metal3",
|
|
||||||
offset=pin_pos)
|
|
||||||
|
|
||||||
|
|
||||||
din_pins = self.ms_inst[i].get_pins("din")
|
|
||||||
for din_pin in din_pins:
|
|
||||||
self.add_layout_pin(text="din[{}]".format(i),
|
|
||||||
layer=din_pin.layer,
|
|
||||||
offset=din_pin.ll(),
|
|
||||||
width=din_pin.width(),
|
|
||||||
height=din_pin.height())
|
|
||||||
|
|
||||||
dout_pin = self.ms_inst[i].get_pin("dout")
|
|
||||||
self.add_layout_pin(text="dout[{}]".format(i),
|
|
||||||
layer="metal2",
|
|
||||||
offset=dout_pin.ll(),
|
|
||||||
width=dout_pin.width(),
|
|
||||||
height=dout_pin.height())
|
|
||||||
|
|
||||||
doutbar_pin = self.ms_inst[i].get_pin("dout_bar")
|
|
||||||
self.add_layout_pin(text="dout_bar[{}]".format(i),
|
|
||||||
layer="metal2",
|
|
||||||
offset=doutbar_pin.ll(),
|
|
||||||
width=doutbar_pin.width(),
|
|
||||||
height=doutbar_pin.height())
|
|
||||||
|
|
||||||
|
|
||||||
# Continous clk rail along with label.
|
|
||||||
self.add_layout_pin(text="clk",
|
|
||||||
layer="metal1",
|
|
||||||
offset=self.ms_inst[0].get_pin("clk").ll().scale(0,1),
|
|
||||||
width=self.width,
|
|
||||||
height=drc["minwidth_metal1"])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def analytical_delay(self, slew, load=0.0):
|
|
||||||
return self.ms.analytical_delay(slew=slew, load=load)
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,852 @@
|
||||||
|
import sys
|
||||||
|
from tech import drc, parameter
|
||||||
|
import debug
|
||||||
|
import design
|
||||||
|
import math
|
||||||
|
from math import log,sqrt,ceil
|
||||||
|
import contact
|
||||||
|
from pinv import pinv
|
||||||
|
from pnand2 import pnand2
|
||||||
|
from pnor2 import pnor2
|
||||||
|
from vector import vector
|
||||||
|
from pinvbuf import pinvbuf
|
||||||
|
|
||||||
|
from globals import OPTS
|
||||||
|
|
||||||
|
class multibank(design.design):
|
||||||
|
"""
|
||||||
|
Dynamically generated a single bank including bitcell array,
|
||||||
|
hierarchical_decoder, precharge, (optional column_mux and column decoder),
|
||||||
|
write driver and sense amplifiers.
|
||||||
|
This module includes the tristate and bank select logic.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, word_size, num_words, words_per_row, num_banks=1, name=""):
|
||||||
|
|
||||||
|
mod_list = ["tri_gate", "bitcell", "decoder", "wordline_driver",
|
||||||
|
"bitcell_array", "sense_amp_array", "precharge_array",
|
||||||
|
"column_mux_array", "write_driver_array", "tri_gate_array",
|
||||||
|
"dff", "bank_select"]
|
||||||
|
from importlib import reload
|
||||||
|
for mod_name in mod_list:
|
||||||
|
config_mod_name = getattr(OPTS, mod_name)
|
||||||
|
class_file = reload(__import__(config_mod_name))
|
||||||
|
mod_class = getattr(class_file , config_mod_name)
|
||||||
|
setattr (self, "mod_"+mod_name, mod_class)
|
||||||
|
|
||||||
|
if name == "":
|
||||||
|
name = "bank_{0}_{1}".format(word_size, num_words)
|
||||||
|
design.design.__init__(self, name)
|
||||||
|
debug.info(2, "create sram of size {0} with {1} words".format(word_size,num_words))
|
||||||
|
|
||||||
|
self.word_size = word_size
|
||||||
|
self.num_words = num_words
|
||||||
|
self.words_per_row = words_per_row
|
||||||
|
self.num_banks = num_banks
|
||||||
|
|
||||||
|
# The local control signals are gated when we have bank select logic,
|
||||||
|
# so this prefix will be added to all of the input signals to create
|
||||||
|
# the internal gated signals.
|
||||||
|
if self.num_banks>1:
|
||||||
|
self.prefix="gated_"
|
||||||
|
else:
|
||||||
|
self.prefix=""
|
||||||
|
|
||||||
|
self.compute_sizes()
|
||||||
|
self.add_pins()
|
||||||
|
self.create_modules()
|
||||||
|
self.add_modules()
|
||||||
|
self.setup_layout_constraints()
|
||||||
|
|
||||||
|
# FIXME: Move this to the add modules function
|
||||||
|
self.add_bank_select()
|
||||||
|
|
||||||
|
self.route_layout()
|
||||||
|
|
||||||
|
|
||||||
|
# Can remove the following, but it helps for debug!
|
||||||
|
self.add_lvs_correspondence_points()
|
||||||
|
|
||||||
|
# Remember the bank center for further placement
|
||||||
|
self.bank_center=self.offset_all_coordinates().scale(-1,-1)
|
||||||
|
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
""" Adding pins for Bank module"""
|
||||||
|
for i in range(self.word_size):
|
||||||
|
self.add_pin("DOUT_{0}".format(i),"OUT")
|
||||||
|
for i in range(self.word_size):
|
||||||
|
self.add_pin("BANK_DIN_{0}".format(i),"IN")
|
||||||
|
for i in range(self.addr_size):
|
||||||
|
self.add_pin("A_{0}".format(i),"INPUT")
|
||||||
|
|
||||||
|
# For more than one bank, we have a bank select and name
|
||||||
|
# the signals gated_*.
|
||||||
|
if self.num_banks > 1:
|
||||||
|
self.add_pin("bank_sel","INPUT")
|
||||||
|
for pin in ["s_en","w_en","tri_en_bar","tri_en",
|
||||||
|
"clk_buf_bar","clk_buf"]:
|
||||||
|
self.add_pin(pin,"INPUT")
|
||||||
|
self.add_pin("vdd","POWER")
|
||||||
|
self.add_pin("gnd","GROUND")
|
||||||
|
|
||||||
|
def route_layout(self):
|
||||||
|
""" Create routing amoung the modules """
|
||||||
|
self.route_central_bus()
|
||||||
|
self.route_precharge_to_bitcell_array()
|
||||||
|
self.route_col_mux_to_bitcell_array()
|
||||||
|
self.route_sense_amp_to_col_mux_or_bitcell_array()
|
||||||
|
#self.route_sense_amp_to_trigate()
|
||||||
|
#self.route_tri_gate_out()
|
||||||
|
self.route_sense_amp_out()
|
||||||
|
self.route_wordline_driver()
|
||||||
|
self.route_write_driver()
|
||||||
|
self.route_row_decoder()
|
||||||
|
self.route_column_address_lines()
|
||||||
|
self.route_control_lines()
|
||||||
|
self.add_control_pins()
|
||||||
|
if self.num_banks > 1:
|
||||||
|
self.route_bank_select()
|
||||||
|
|
||||||
|
self.route_supplies()
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
|
""" Add modules. The order should not matter! """
|
||||||
|
|
||||||
|
# Above the bitcell array
|
||||||
|
self.add_bitcell_array()
|
||||||
|
self.add_precharge_array()
|
||||||
|
|
||||||
|
# Below the bitcell array
|
||||||
|
self.add_column_mux_array()
|
||||||
|
self.add_sense_amp_array()
|
||||||
|
self.add_write_driver_array()
|
||||||
|
# Not needed for single bank
|
||||||
|
#self.add_tri_gate_array()
|
||||||
|
|
||||||
|
# To the left of the bitcell array
|
||||||
|
self.add_row_decoder()
|
||||||
|
self.add_wordline_driver()
|
||||||
|
self.add_column_decoder()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def compute_sizes(self):
|
||||||
|
""" Computes the required sizes to create the bank """
|
||||||
|
|
||||||
|
self.num_cols = int(self.words_per_row*self.word_size)
|
||||||
|
self.num_rows = int(self.num_words / self.words_per_row)
|
||||||
|
|
||||||
|
self.row_addr_size = int(log(self.num_rows, 2))
|
||||||
|
self.col_addr_size = int(log(self.words_per_row, 2))
|
||||||
|
self.addr_size = self.col_addr_size + self.row_addr_size
|
||||||
|
|
||||||
|
debug.check(self.num_rows*self.num_cols==self.word_size*self.num_words,"Invalid bank sizes.")
|
||||||
|
debug.check(self.addr_size==self.col_addr_size + self.row_addr_size,"Invalid address break down.")
|
||||||
|
|
||||||
|
# Width for the vdd/gnd rails
|
||||||
|
self.supply_rail_width = 4*self.m2_width
|
||||||
|
# FIXME: This spacing should be width dependent...
|
||||||
|
self.supply_rail_pitch = self.supply_rail_width + 4*self.m2_space
|
||||||
|
|
||||||
|
# Number of control lines in the bus
|
||||||
|
self.num_control_lines = 6
|
||||||
|
# The order of the control signals on the control bus:
|
||||||
|
self.input_control_signals = ["clk_buf", "tri_en_bar", "tri_en", "clk_buf_bar", "w_en", "s_en"]
|
||||||
|
# These will be outputs of the gaters if this is multibank, if not, normal signals.
|
||||||
|
if self.num_banks > 1:
|
||||||
|
self.control_signals = ["gated_"+str for str in self.input_control_signals]
|
||||||
|
else:
|
||||||
|
self.control_signals = self.input_control_signals
|
||||||
|
# The central bus is the column address (one hot) and row address (binary)
|
||||||
|
if self.col_addr_size>0:
|
||||||
|
self.num_col_addr_lines = 2**self.col_addr_size
|
||||||
|
else:
|
||||||
|
self.num_col_addr_lines = 0
|
||||||
|
|
||||||
|
# The width of this bus is needed to place other modules (e.g. decoder)
|
||||||
|
# A width on each side too
|
||||||
|
self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width
|
||||||
|
|
||||||
|
# A space for wells or jogging m2
|
||||||
|
self.m2_gap = max(2*drc("pwell_to_nwell"] + drc["well_enclosure_active"),
|
||||||
|
2*self.m2_pitch)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def create_modules(self):
|
||||||
|
""" Create all the modules using the class loader """
|
||||||
|
self.tri = self.mod_tri_gate()
|
||||||
|
self.bitcell = self.mod_bitcell()
|
||||||
|
|
||||||
|
self.bitcell_array = self.mod_bitcell_array(cols=self.num_cols,
|
||||||
|
rows=self.num_rows)
|
||||||
|
self.add_mod(self.bitcell_array)
|
||||||
|
|
||||||
|
self.precharge_array = self.mod_precharge_array(columns=self.num_cols)
|
||||||
|
self.add_mod(self.precharge_array)
|
||||||
|
|
||||||
|
if self.col_addr_size > 0:
|
||||||
|
self.column_mux_array = self.mod_column_mux_array(columns=self.num_cols,
|
||||||
|
word_size=self.word_size)
|
||||||
|
self.add_mod(self.column_mux_array)
|
||||||
|
|
||||||
|
|
||||||
|
self.sense_amp_array = self.mod_sense_amp_array(word_size=self.word_size,
|
||||||
|
words_per_row=self.words_per_row)
|
||||||
|
self.add_mod(self.sense_amp_array)
|
||||||
|
|
||||||
|
self.write_driver_array = self.mod_write_driver_array(columns=self.num_cols,
|
||||||
|
word_size=self.word_size)
|
||||||
|
self.add_mod(self.write_driver_array)
|
||||||
|
|
||||||
|
self.row_decoder = self.mod_decoder(rows=self.num_rows)
|
||||||
|
self.add_mod(self.row_decoder)
|
||||||
|
|
||||||
|
self.tri_gate_array = self.mod_tri_gate_array(columns=self.num_cols,
|
||||||
|
word_size=self.word_size)
|
||||||
|
self.add_mod(self.tri_gate_array)
|
||||||
|
|
||||||
|
self.wordline_driver = self.mod_wordline_driver(rows=self.num_rows)
|
||||||
|
self.add_mod(self.wordline_driver)
|
||||||
|
|
||||||
|
self.inv = pinv()
|
||||||
|
self.add_mod(self.inv)
|
||||||
|
|
||||||
|
if(self.num_banks > 1):
|
||||||
|
self.bank_select = self.mod_bank_select()
|
||||||
|
self.add_mod(self.bank_select)
|
||||||
|
|
||||||
|
|
||||||
|
def add_bitcell_array(self):
|
||||||
|
""" Adding Bitcell Array """
|
||||||
|
|
||||||
|
self.bitcell_array_inst=self.add_inst(name="bitcell_array",
|
||||||
|
mod=self.bitcell_array,
|
||||||
|
offset=vector(0,0))
|
||||||
|
temp = []
|
||||||
|
for i in range(self.num_cols):
|
||||||
|
temp.append("bl_{0}".format(i))
|
||||||
|
temp.append("br_{0}".format(i))
|
||||||
|
for j in range(self.num_rows):
|
||||||
|
temp.append("wl_{0}".format(j))
|
||||||
|
temp.extend(["vdd", "gnd"])
|
||||||
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
||||||
|
def add_precharge_array(self):
|
||||||
|
""" Adding Precharge """
|
||||||
|
|
||||||
|
# The wells must be far enough apart
|
||||||
|
# The enclosure is for the well and the spacing is to the bitcell wells
|
||||||
|
y_offset = self.bitcell_array.height + self.m2_gap
|
||||||
|
self.precharge_array_inst=self.add_inst(name="precharge_array",
|
||||||
|
mod=self.precharge_array,
|
||||||
|
offset=vector(0,y_offset))
|
||||||
|
temp = []
|
||||||
|
for i in range(self.num_cols):
|
||||||
|
temp.append("bl_{0}".format(i))
|
||||||
|
temp.append("br_{0}".format(i))
|
||||||
|
temp.extend([self.prefix+"clk_buf_bar", "vdd"])
|
||||||
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
def add_column_mux_array(self):
|
||||||
|
""" Adding Column Mux when words_per_row > 1 . """
|
||||||
|
if self.col_addr_size > 0:
|
||||||
|
self.column_mux_height = self.column_mux_array.height + self.m2_gap
|
||||||
|
else:
|
||||||
|
self.column_mux_height = 0
|
||||||
|
return
|
||||||
|
|
||||||
|
y_offset = self.column_mux_height
|
||||||
|
self.col_mux_array_inst=self.add_inst(name="column_mux_array",
|
||||||
|
mod=self.column_mux_array,
|
||||||
|
offset=vector(0,y_offset).scale(-1,-1))
|
||||||
|
temp = []
|
||||||
|
for i in range(self.num_cols):
|
||||||
|
temp.append("bl_{0}".format(i))
|
||||||
|
temp.append("br_{0}".format(i))
|
||||||
|
for k in range(self.words_per_row):
|
||||||
|
temp.append("sel_{0}".format(k))
|
||||||
|
for j in range(self.word_size):
|
||||||
|
temp.append("bl_out_{0}".format(j))
|
||||||
|
temp.append("br_out_{0}".format(j))
|
||||||
|
temp.append("gnd")
|
||||||
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
def add_sense_amp_array(self):
|
||||||
|
""" Adding Sense amp """
|
||||||
|
|
||||||
|
y_offset = self.column_mux_height + self.sense_amp_array.height + self.m2_gap
|
||||||
|
self.sense_amp_array_inst=self.add_inst(name="sense_amp_array",
|
||||||
|
mod=self.sense_amp_array,
|
||||||
|
offset=vector(0,y_offset).scale(-1,-1))
|
||||||
|
temp = []
|
||||||
|
for i in range(self.word_size):
|
||||||
|
temp.append("sa_out_{0}".format(i))
|
||||||
|
if self.words_per_row == 1:
|
||||||
|
temp.append("bl_{0}".format(i))
|
||||||
|
temp.append("br_{0}".format(i))
|
||||||
|
else:
|
||||||
|
temp.append("bl_out_{0}".format(i))
|
||||||
|
temp.append("br_out_{0}".format(i))
|
||||||
|
|
||||||
|
temp.extend([self.prefix+"s_en", "vdd", "gnd"])
|
||||||
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
def add_write_driver_array(self):
|
||||||
|
""" Adding Write Driver """
|
||||||
|
|
||||||
|
y_offset = self.sense_amp_array.height + self.column_mux_height \
|
||||||
|
+ self.m2_gap + self.write_driver_array.height
|
||||||
|
self.write_driver_array_inst=self.add_inst(name="write_driver_array",
|
||||||
|
mod=self.write_driver_array,
|
||||||
|
offset=vector(0,y_offset).scale(-1,-1))
|
||||||
|
|
||||||
|
temp = []
|
||||||
|
for i in range(self.word_size):
|
||||||
|
temp.append("BANK_DIN_{0}".format(i))
|
||||||
|
for i in range(self.word_size):
|
||||||
|
if (self.words_per_row == 1):
|
||||||
|
temp.append("bl_{0}".format(i))
|
||||||
|
temp.append("br_{0}".format(i))
|
||||||
|
else:
|
||||||
|
temp.append("bl_out_{0}".format(i))
|
||||||
|
temp.append("br_out_{0}".format(i))
|
||||||
|
temp.extend([self.prefix+"w_en", "vdd", "gnd"])
|
||||||
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
def add_tri_gate_array(self):
|
||||||
|
""" data tri gate to drive the data bus """
|
||||||
|
y_offset = self.sense_amp_array.height+self.column_mux_height \
|
||||||
|
+ self.m2_gap + self.tri_gate_array.height
|
||||||
|
self.tri_gate_array_inst=self.add_inst(name="tri_gate_array",
|
||||||
|
mod=self.tri_gate_array,
|
||||||
|
offset=vector(0,y_offset).scale(-1,-1))
|
||||||
|
|
||||||
|
temp = []
|
||||||
|
for i in range(self.word_size):
|
||||||
|
temp.append("sa_out_{0}".format(i))
|
||||||
|
for i in range(self.word_size):
|
||||||
|
temp.append("DOUT_{0}".format(i))
|
||||||
|
temp.extend([self.prefix+"tri_en", self.prefix+"tri_en_bar", "vdd", "gnd"])
|
||||||
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
def add_row_decoder(self):
|
||||||
|
""" Add the hierarchical row decoder """
|
||||||
|
|
||||||
|
|
||||||
|
# The address and control bus will be in between decoder and the main memory array
|
||||||
|
# This bus will route address bits to the decoder input and column mux inputs.
|
||||||
|
# The wires are actually routed after we placed the stuff on both sides.
|
||||||
|
# The predecoder is below the x-axis and the main decoder is above the x-axis
|
||||||
|
# The address flop and decoder are aligned in the x coord.
|
||||||
|
|
||||||
|
x_offset = -(self.row_decoder.width + self.central_bus_width + self.wordline_driver.width)
|
||||||
|
self.row_decoder_inst=self.add_inst(name="row_decoder",
|
||||||
|
mod=self.row_decoder,
|
||||||
|
offset=vector(x_offset,0))
|
||||||
|
|
||||||
|
temp = []
|
||||||
|
for i in range(self.row_addr_size):
|
||||||
|
temp.append("A_{0}".format(i+self.col_addr_size))
|
||||||
|
for j in range(self.num_rows):
|
||||||
|
temp.append("dec_out_{0}".format(j))
|
||||||
|
temp.extend(["vdd", "gnd"])
|
||||||
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
def add_wordline_driver(self):
|
||||||
|
""" Wordline Driver """
|
||||||
|
|
||||||
|
# The wordline driver is placed to the right of the main decoder width.
|
||||||
|
x_offset = -(self.central_bus_width + self.wordline_driver.width) + self.m2_pitch
|
||||||
|
self.wordline_driver_inst=self.add_inst(name="wordline_driver",
|
||||||
|
mod=self.wordline_driver,
|
||||||
|
offset=vector(x_offset,0))
|
||||||
|
|
||||||
|
temp = []
|
||||||
|
for i in range(self.num_rows):
|
||||||
|
temp.append("dec_out_{0}".format(i))
|
||||||
|
for i in range(self.num_rows):
|
||||||
|
temp.append("wl_{0}".format(i))
|
||||||
|
temp.append(self.prefix+"clk_buf")
|
||||||
|
temp.append("vdd")
|
||||||
|
temp.append("gnd")
|
||||||
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
||||||
|
def add_column_decoder_module(self):
|
||||||
|
"""
|
||||||
|
Create a 2:4 or 3:8 column address decoder.
|
||||||
|
"""
|
||||||
|
# Place the col decoder right aligned with row decoder
|
||||||
|
x_off = -(self.central_bus_width + self.wordline_driver.width + self.col_decoder.width)
|
||||||
|
y_off = -(self.col_decoder.height + 2*drc("well_to_well"))
|
||||||
|
self.col_decoder_inst=self.add_inst(name="col_address_decoder",
|
||||||
|
mod=self.col_decoder,
|
||||||
|
offset=vector(x_off,y_off))
|
||||||
|
|
||||||
|
temp = []
|
||||||
|
for i in range(self.col_addr_size):
|
||||||
|
temp.append("A_{0}".format(i))
|
||||||
|
for j in range(self.num_col_addr_lines):
|
||||||
|
temp.append("sel_{0}".format(j))
|
||||||
|
temp.extend(["vdd", "gnd"])
|
||||||
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
def add_column_decoder(self):
|
||||||
|
"""
|
||||||
|
Create a decoder to decode column select lines. This could be an inverter/buffer for 1:2,
|
||||||
|
2:4 decoder, or 3:8 decoder.
|
||||||
|
"""
|
||||||
|
if self.col_addr_size == 0:
|
||||||
|
return
|
||||||
|
elif self.col_addr_size == 1:
|
||||||
|
self.col_decoder = pinvbuf(height=self.mod_dff.height)
|
||||||
|
self.add_mod(self.col_decoder)
|
||||||
|
elif self.col_addr_size == 2:
|
||||||
|
self.col_decoder = self.row_decoder.pre2_4
|
||||||
|
elif self.col_addr_size == 3:
|
||||||
|
self.col_decoder = self.row_decoder.pre3_8
|
||||||
|
else:
|
||||||
|
# No error checking before?
|
||||||
|
debug.error("Invalid column decoder?",-1)
|
||||||
|
|
||||||
|
self.add_column_decoder_module()
|
||||||
|
|
||||||
|
|
||||||
|
def add_bank_select(self):
|
||||||
|
""" Instantiate the bank select logic. """
|
||||||
|
|
||||||
|
if not self.num_banks > 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
x_off = -(self.row_decoder.width + self.central_bus_width + self.wordline_driver.width)
|
||||||
|
if self.col_addr_size > 0:
|
||||||
|
y_off = min(self.col_decoder_inst.by(), self.col_mux_array_inst.by())
|
||||||
|
else:
|
||||||
|
y_off = self.row_decoder_inst.by()
|
||||||
|
y_off -= (self.bank_select.height + drc("well_to_well"))
|
||||||
|
self.bank_select_pos = vector(x_off,y_off)
|
||||||
|
self.bank_select_inst = self.add_inst(name="bank_select",
|
||||||
|
mod=self.bank_select,
|
||||||
|
offset=self.bank_select_pos)
|
||||||
|
|
||||||
|
temp = []
|
||||||
|
temp.extend(self.input_control_signals)
|
||||||
|
temp.append("bank_sel")
|
||||||
|
temp.extend(self.control_signals)
|
||||||
|
temp.extend(["vdd", "gnd"])
|
||||||
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
def route_supplies(self):
|
||||||
|
""" Propagate all vdd/gnd pins up to this level for all modules """
|
||||||
|
for inst in self.insts:
|
||||||
|
self.copy_power_pins(inst,"vdd")
|
||||||
|
self.copy_power_pins(inst,"gnd")
|
||||||
|
|
||||||
|
def route_bank_select(self):
|
||||||
|
""" Route the bank select logic. """
|
||||||
|
for input_name in self.input_control_signals+["bank_sel"]:
|
||||||
|
self.copy_layout_pin(self.bank_select_inst, input_name)
|
||||||
|
|
||||||
|
for gated_name in self.control_signals:
|
||||||
|
# Connect the inverter output to the central bus
|
||||||
|
out_pos = self.bank_select_inst.get_pin(gated_name).rc()
|
||||||
|
bus_pos = vector(self.bus_xoffset[gated_name], out_pos.y)
|
||||||
|
self.add_path("metal3",[out_pos, bus_pos])
|
||||||
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||||
|
offset=bus_pos,
|
||||||
|
rotate=90)
|
||||||
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||||
|
offset=out_pos,
|
||||||
|
rotate=90)
|
||||||
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||||
|
offset=out_pos,
|
||||||
|
rotate=90)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_layout_constraints(self):
|
||||||
|
""" After the modules are instantiated, find the dimensions for the
|
||||||
|
control bus, power ring, etc. """
|
||||||
|
|
||||||
|
#The minimum point is either the bottom of the address flops,
|
||||||
|
#the column decoder (if there is one) or the tristate output
|
||||||
|
#driver.
|
||||||
|
# Leave room for the output below the tri gate.
|
||||||
|
#tri_gate_min_y_offset = self.tri_gate_array_inst.by() - 3*self.m2_pitch
|
||||||
|
write_driver_min_y_offset = self.write_driver_array_inst.by() - 3*self.m2_pitch
|
||||||
|
row_decoder_min_y_offset = self.row_decoder_inst.by()
|
||||||
|
if self.col_addr_size > 0:
|
||||||
|
col_decoder_min_y_offset = self.col_decoder_inst.by()
|
||||||
|
else:
|
||||||
|
col_decoder_min_y_offset = row_decoder_min_y_offset
|
||||||
|
|
||||||
|
if self.num_banks>1:
|
||||||
|
# The control gating logic is below the decoder
|
||||||
|
# Min of the control gating logic and tri gate.
|
||||||
|
self.min_y_offset = min(col_decoder_min_y_offset - self.bank_select.height, write_driver_min_y_offset)
|
||||||
|
else:
|
||||||
|
# Just the min of the decoder logic logic and tri gate.
|
||||||
|
self.min_y_offset = min(col_decoder_min_y_offset, write_driver_min_y_offset)
|
||||||
|
|
||||||
|
# The max point is always the top of the precharge bitlines
|
||||||
|
# Add a vdd and gnd power rail above the array
|
||||||
|
self.max_y_offset = self.precharge_array_inst.uy() + 3*self.m1_width
|
||||||
|
self.max_x_offset = self.bitcell_array_inst.ur().x + 3*self.m1_width
|
||||||
|
self.min_x_offset = self.row_decoder_inst.lx()
|
||||||
|
|
||||||
|
# # Create the core bbox for the power rings
|
||||||
|
ur = vector(self.max_x_offset, self.max_y_offset)
|
||||||
|
ll = vector(self.min_x_offset, self.min_y_offset)
|
||||||
|
self.core_bbox = [ll, ur]
|
||||||
|
|
||||||
|
self.height = ur.y - ll.y
|
||||||
|
self.width = ur.x - ll.x
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def route_central_bus(self):
|
||||||
|
""" Create the address, supply, and control signal central bus lines. """
|
||||||
|
|
||||||
|
# Overall central bus width. It includes all the column mux lines,
|
||||||
|
# and control lines.
|
||||||
|
# The bank is at (0,0), so this is to the left of the y-axis.
|
||||||
|
# 2 pitches on the right for vias/jogs to access the inputs
|
||||||
|
control_bus_offset = vector(-self.m2_pitch * self.num_control_lines - self.m2_width, 0)
|
||||||
|
control_bus_length = self.bitcell_array_inst.uy()
|
||||||
|
self.bus_xoffset = self.create_vertical_bus(layer="metal2",
|
||||||
|
pitch=self.m2_pitch,
|
||||||
|
offset=control_bus_offset,
|
||||||
|
names=self.control_signals,
|
||||||
|
length=control_bus_length)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def route_precharge_to_bitcell_array(self):
|
||||||
|
""" Routing of BL and BR between pre-charge and bitcell array """
|
||||||
|
|
||||||
|
for i in range(self.num_cols):
|
||||||
|
precharge_bl = self.precharge_array_inst.get_pin("bl_{}".format(i)).bc()
|
||||||
|
precharge_br = self.precharge_array_inst.get_pin("br_{}".format(i)).bc()
|
||||||
|
bitcell_bl = self.bitcell_array_inst.get_pin("bl_{}".format(i)).uc()
|
||||||
|
bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).uc()
|
||||||
|
|
||||||
|
yoffset = 0.5*(precharge_bl.y+bitcell_bl.y)
|
||||||
|
self.add_path("metal2",[precharge_bl, vector(precharge_bl.x,yoffset),
|
||||||
|
vector(bitcell_bl.x,yoffset), bitcell_bl])
|
||||||
|
self.add_path("metal2",[precharge_br, vector(precharge_br.x,yoffset),
|
||||||
|
vector(bitcell_br.x,yoffset), bitcell_br])
|
||||||
|
|
||||||
|
|
||||||
|
def route_col_mux_to_bitcell_array(self):
|
||||||
|
""" Routing of BL and BR between col mux and bitcell array """
|
||||||
|
|
||||||
|
# Only do this if we have a column mux!
|
||||||
|
if self.col_addr_size==0:
|
||||||
|
return
|
||||||
|
|
||||||
|
for i in range(self.num_cols):
|
||||||
|
col_mux_bl = self.col_mux_array_inst.get_pin("bl_{}".format(i)).uc()
|
||||||
|
col_mux_br = self.col_mux_array_inst.get_pin("br_{}".format(i)).uc()
|
||||||
|
bitcell_bl = self.bitcell_array_inst.get_pin("bl_{}".format(i)).bc()
|
||||||
|
bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).bc()
|
||||||
|
|
||||||
|
yoffset = 0.5*(col_mux_bl.y+bitcell_bl.y)
|
||||||
|
self.add_path("metal2",[col_mux_bl, vector(col_mux_bl.x,yoffset),
|
||||||
|
vector(bitcell_bl.x,yoffset), bitcell_bl])
|
||||||
|
self.add_path("metal2",[col_mux_br, vector(col_mux_br.x,yoffset),
|
||||||
|
vector(bitcell_br.x,yoffset), bitcell_br])
|
||||||
|
|
||||||
|
def route_sense_amp_to_col_mux_or_bitcell_array(self):
|
||||||
|
""" Routing of BL and BR between sense_amp and column mux or bitcell array """
|
||||||
|
|
||||||
|
for i in range(self.word_size):
|
||||||
|
sense_amp_bl = self.sense_amp_array_inst.get_pin("bl_{}".format(i)).uc()
|
||||||
|
sense_amp_br = self.sense_amp_array_inst.get_pin("br_{}".format(i)).uc()
|
||||||
|
|
||||||
|
if self.col_addr_size>0:
|
||||||
|
# Sense amp is connected to the col mux
|
||||||
|
connect_bl = self.col_mux_array_inst.get_pin("bl_out_{}".format(i)).bc()
|
||||||
|
connect_br = self.col_mux_array_inst.get_pin("br_out_{}".format(i)).bc()
|
||||||
|
else:
|
||||||
|
# Sense amp is directly connected to the bitcell array
|
||||||
|
connect_bl = self.bitcell_array_inst.get_pin("bl_{}".format(i)).bc()
|
||||||
|
connect_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).bc()
|
||||||
|
|
||||||
|
|
||||||
|
yoffset = 0.5*(sense_amp_bl.y+connect_bl.y)
|
||||||
|
self.add_path("metal2",[sense_amp_bl, vector(sense_amp_bl.x,yoffset),
|
||||||
|
vector(connect_bl.x,yoffset), connect_bl])
|
||||||
|
self.add_path("metal2",[sense_amp_br, vector(sense_amp_br.x,yoffset),
|
||||||
|
vector(connect_br.x,yoffset), connect_br])
|
||||||
|
|
||||||
|
def route_sense_amp_to_trigate(self):
|
||||||
|
""" Routing of sense amp output to tri_gate input """
|
||||||
|
|
||||||
|
for i in range(self.word_size):
|
||||||
|
# Connection of data_out of sense amp to data_in
|
||||||
|
tri_gate_in = self.tri_gate_array_inst.get_pin("in_{}".format(i)).lc()
|
||||||
|
sa_data_out = self.sense_amp_array_inst.get_pin("data_{}".format(i)).bc()
|
||||||
|
|
||||||
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||||
|
offset=tri_gate_in)
|
||||||
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||||
|
offset=sa_data_out)
|
||||||
|
self.add_path("metal3",[sa_data_out,tri_gate_in])
|
||||||
|
|
||||||
|
def route_sense_amp_out(self):
|
||||||
|
""" Add pins for the sense amp output """
|
||||||
|
for i in range(self.word_size):
|
||||||
|
data_pin = self.sense_amp_array_inst.get_pin("data_{}".format(i))
|
||||||
|
self.add_layout_pin_rect_center(text="DOUT_{}".format(i),
|
||||||
|
layer=data_pin.layer,
|
||||||
|
offset=data_pin.center(),
|
||||||
|
height=data_pin.height(),
|
||||||
|
width=data_pin.width()),
|
||||||
|
|
||||||
|
def route_tri_gate_out(self):
|
||||||
|
""" Metal 3 routing of tri_gate output data """
|
||||||
|
for i in range(self.word_size):
|
||||||
|
data_pin = self.tri_gate_array_inst.get_pin("out_{}".format(i))
|
||||||
|
self.add_layout_pin_rect_center(text="DOUT_{}".format(i),
|
||||||
|
layer=data_pin.layer,
|
||||||
|
offset=data_pin.center(),
|
||||||
|
height=data_pin.height(),
|
||||||
|
width=data_pin.width()),
|
||||||
|
|
||||||
|
|
||||||
|
def route_row_decoder(self):
|
||||||
|
""" Routes the row decoder inputs and supplies """
|
||||||
|
|
||||||
|
# Create inputs for the row address lines
|
||||||
|
for i in range(self.row_addr_size):
|
||||||
|
addr_idx = i + self.col_addr_size
|
||||||
|
decoder_name = "A_{}".format(i)
|
||||||
|
addr_name = "A_{}".format(addr_idx)
|
||||||
|
self.copy_layout_pin(self.row_decoder_inst, decoder_name, addr_name)
|
||||||
|
|
||||||
|
|
||||||
|
def route_write_driver(self):
|
||||||
|
""" Connecting write driver """
|
||||||
|
|
||||||
|
for i in range(self.word_size):
|
||||||
|
data_name = "data_{}".format(i)
|
||||||
|
din_name = "BANK_DIN_{}".format(i)
|
||||||
|
self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def route_wordline_driver(self):
|
||||||
|
""" Connecting Wordline driver output to Bitcell WL connection """
|
||||||
|
|
||||||
|
# we don't care about bends after connecting to the input pin, so let the path code decide.
|
||||||
|
for i in range(self.num_rows):
|
||||||
|
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
||||||
|
decoder_out_pos = self.row_decoder_inst.get_pin("decode_{}".format(i)).rc()
|
||||||
|
driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(i)).lc()
|
||||||
|
mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0)
|
||||||
|
mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1)
|
||||||
|
self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
||||||
|
|
||||||
|
# The mid guarantees we exit the input cell to the right.
|
||||||
|
driver_wl_pos = self.wordline_driver_inst.get_pin("wl_{}".format(i)).rc()
|
||||||
|
bitcell_wl_pos = self.bitcell_array_inst.get_pin("wl_{}".format(i)).lc()
|
||||||
|
mid1 = driver_wl_pos.scale(0.5,1)+bitcell_wl_pos.scale(0.5,0)
|
||||||
|
mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1)
|
||||||
|
self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def route_column_address_lines(self):
|
||||||
|
""" Connecting the select lines of column mux to the address bus """
|
||||||
|
if not self.col_addr_size>0:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if self.col_addr_size == 1:
|
||||||
|
|
||||||
|
# Connect to sel[0] and sel[1]
|
||||||
|
decode_names = ["Zb", "Z"]
|
||||||
|
|
||||||
|
# The Address LSB
|
||||||
|
self.copy_layout_pin(self.col_decoder_inst, "A", "A[0]")
|
||||||
|
|
||||||
|
elif self.col_addr_size > 1:
|
||||||
|
decode_names = []
|
||||||
|
for i in range(self.num_col_addr_lines):
|
||||||
|
decode_names.append("out_{}".format(i))
|
||||||
|
|
||||||
|
for i in range(self.col_addr_size):
|
||||||
|
decoder_name = "in_{}".format(i)
|
||||||
|
addr_name = "A_{}".format(i)
|
||||||
|
self.copy_layout_pin(self.col_decoder_inst, decoder_name, addr_name)
|
||||||
|
|
||||||
|
|
||||||
|
# This will do a quick "river route" on two layers.
|
||||||
|
# When above the top select line it will offset "inward" again to prevent conflicts.
|
||||||
|
# This could be done on a single layer, but we follow preferred direction rules for later routing.
|
||||||
|
top_y_offset = self.col_mux_array_inst.get_pin("sel_{}".format(self.num_col_addr_lines-1)).cy()
|
||||||
|
for (decode_name,i) in zip(decode_names,range(self.num_col_addr_lines)):
|
||||||
|
mux_name = "sel_{}".format(i)
|
||||||
|
mux_addr_pos = self.col_mux_array_inst.get_pin(mux_name).lc()
|
||||||
|
|
||||||
|
decode_out_pos = self.col_decoder_inst.get_pin(decode_name).center()
|
||||||
|
|
||||||
|
# To get to the edge of the decoder and one track out
|
||||||
|
delta_offset = self.col_decoder_inst.rx() - decode_out_pos.x + self.m2_pitch
|
||||||
|
if decode_out_pos.y > top_y_offset:
|
||||||
|
mid1_pos = vector(decode_out_pos.x + delta_offset + i*self.m2_pitch,decode_out_pos.y)
|
||||||
|
else:
|
||||||
|
mid1_pos = vector(decode_out_pos.x + delta_offset + (self.num_col_addr_lines-i)*self.m2_pitch,decode_out_pos.y)
|
||||||
|
mid2_pos = vector(mid1_pos.x,mux_addr_pos.y)
|
||||||
|
#self.add_wire(("metal1","via1","metal2"),[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos])
|
||||||
|
self.add_path("metal1",[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def add_lvs_correspondence_points(self):
|
||||||
|
""" This adds some points for easier debugging if LVS goes wrong.
|
||||||
|
These should probably be turned off by default though, since extraction
|
||||||
|
will show these as ports in the extracted netlist.
|
||||||
|
"""
|
||||||
|
# Add the wordline names
|
||||||
|
for i in range(self.num_rows):
|
||||||
|
wl_name = "wl_{}".format(i)
|
||||||
|
wl_pin = self.bitcell_array_inst.get_pin(wl_name)
|
||||||
|
self.add_label(text=wl_name,
|
||||||
|
layer="metal1",
|
||||||
|
offset=wl_pin.center())
|
||||||
|
|
||||||
|
# Add the bitline names
|
||||||
|
for i in range(self.num_cols):
|
||||||
|
bl_name = "bl_{}".format(i)
|
||||||
|
br_name = "br_{}".format(i)
|
||||||
|
bl_pin = self.bitcell_array_inst.get_pin(bl_name)
|
||||||
|
br_pin = self.bitcell_array_inst.get_pin(br_name)
|
||||||
|
self.add_label(text=bl_name,
|
||||||
|
layer="metal2",
|
||||||
|
offset=bl_pin.center())
|
||||||
|
self.add_label(text=br_name,
|
||||||
|
layer="metal2",
|
||||||
|
offset=br_pin.center())
|
||||||
|
|
||||||
|
# # Add the data output names to the sense amp output
|
||||||
|
# for i in range(self.word_size):
|
||||||
|
# data_name = "data_{}".format(i)
|
||||||
|
# data_pin = self.sense_amp_array_inst.get_pin(data_name)
|
||||||
|
# self.add_label(text="sa_out_{}".format(i),
|
||||||
|
# layer="metal2",
|
||||||
|
# offset=data_pin.center())
|
||||||
|
|
||||||
|
# Add labels on the decoder
|
||||||
|
for i in range(self.word_size):
|
||||||
|
data_name = "dec_out_{}".format(i)
|
||||||
|
pin_name = "in_{}".format(i)
|
||||||
|
data_pin = self.wordline_driver_inst.get_pin(pin_name)
|
||||||
|
self.add_label(text=data_name,
|
||||||
|
layer="metal1",
|
||||||
|
offset=data_pin.center())
|
||||||
|
|
||||||
|
|
||||||
|
def route_control_lines(self):
|
||||||
|
""" Route the control lines of the entire bank """
|
||||||
|
|
||||||
|
# Make a list of tuples that we will connect.
|
||||||
|
# From control signal to the module pin
|
||||||
|
# Connection from the central bus to the main control block crosses
|
||||||
|
# pre-decoder and this connection is in metal3
|
||||||
|
connection = []
|
||||||
|
#connection.append((self.prefix+"tri_en_bar", self.tri_gate_array_inst.get_pin("en_bar").lc()))
|
||||||
|
#connection.append((self.prefix+"tri_en", self.tri_gate_array_inst.get_pin("en").lc()))
|
||||||
|
connection.append((self.prefix+"clk_buf_bar", self.precharge_array_inst.get_pin("en").lc()))
|
||||||
|
connection.append((self.prefix+"w_en", self.write_driver_array_inst.get_pin("en").lc()))
|
||||||
|
connection.append((self.prefix+"s_en", self.sense_amp_array_inst.get_pin("en").lc()))
|
||||||
|
|
||||||
|
for (control_signal, pin_pos) in connection:
|
||||||
|
control_pos = vector(self.bus_xoffset[control_signal].x ,pin_pos.y)
|
||||||
|
self.add_path("metal1", [control_pos, pin_pos])
|
||||||
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||||
|
offset=control_pos,
|
||||||
|
rotate=90)
|
||||||
|
|
||||||
|
# clk to wordline_driver
|
||||||
|
control_signal = self.prefix+"clk_buf"
|
||||||
|
pin_pos = self.wordline_driver_inst.get_pin("en").uc()
|
||||||
|
mid_pos = pin_pos + vector(0,self.m1_pitch)
|
||||||
|
control_x_offset = self.bus_xoffset[control_signal].x
|
||||||
|
control_pos = vector(control_x_offset + self.m1_width, mid_pos.y)
|
||||||
|
self.add_wire(("metal1","via1","metal2"),[pin_pos, mid_pos, control_pos])
|
||||||
|
control_via_pos = vector(control_x_offset, mid_pos.y)
|
||||||
|
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||||
|
offset=control_via_pos,
|
||||||
|
rotate=90)
|
||||||
|
|
||||||
|
def add_control_pins(self):
|
||||||
|
""" Add the control signal input pins """
|
||||||
|
|
||||||
|
for ctrl in self.control_signals:
|
||||||
|
# xoffsets are the center of the rail
|
||||||
|
x_offset = self.bus_xoffset[ctrl].x - 0.5*self.m2_width
|
||||||
|
if self.num_banks > 1:
|
||||||
|
# it's not an input pin if we have multiple banks
|
||||||
|
self.add_label_pin(text=ctrl,
|
||||||
|
layer="metal2",
|
||||||
|
offset=vector(x_offset, self.min_y_offset),
|
||||||
|
width=self.m2_width,
|
||||||
|
height=self.max_y_offset-self.min_y_offset)
|
||||||
|
else:
|
||||||
|
self.add_layout_pin(text=ctrl,
|
||||||
|
layer="metal2",
|
||||||
|
offset=vector(x_offset, self.min_y_offset),
|
||||||
|
width=self.m2_width,
|
||||||
|
height=self.max_y_offset-self.min_y_offset)
|
||||||
|
|
||||||
|
|
||||||
|
def connect_rail_from_right(self,inst, pin, rail):
|
||||||
|
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
|
||||||
|
in_pin = inst.get_pin(pin).lc()
|
||||||
|
rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
||||||
|
self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
||||||
|
# Bring it up to M2 for M2/M3 routing
|
||||||
|
self.add_via(layers=("metal1","via1","metal2"),
|
||||||
|
offset=in_pin + contact.m1m2.offset,
|
||||||
|
rotate=90)
|
||||||
|
self.add_via(layers=("metal2","via2","metal3"),
|
||||||
|
offset=in_pin + self.m2m3_via_offset,
|
||||||
|
rotate=90)
|
||||||
|
|
||||||
|
|
||||||
|
def connect_rail_from_left(self,inst, pin, rail):
|
||||||
|
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
|
||||||
|
in_pin = inst.get_pin(pin).rc()
|
||||||
|
rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
||||||
|
self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
||||||
|
self.add_via(layers=("metal1","via1","metal2"),
|
||||||
|
offset=in_pin + contact.m1m2.offset,
|
||||||
|
rotate=90)
|
||||||
|
self.add_via(layers=("metal2","via2","metal3"),
|
||||||
|
offset=in_pin + self.m2m3_via_offset,
|
||||||
|
rotate=90)
|
||||||
|
|
||||||
|
def analytical_delay(self, slew, load):
|
||||||
|
""" return analytical delay of the bank"""
|
||||||
|
decoder_delay = self.row_decoder.analytical_delay(slew, self.wordline_driver.input_load())
|
||||||
|
|
||||||
|
word_driver_delay = self.wordline_driver.analytical_delay(decoder_delay.slew, self.bitcell_array.input_load())
|
||||||
|
|
||||||
|
bitcell_array_delay = self.bitcell_array.analytical_delay(word_driver_delay.slew)
|
||||||
|
|
||||||
|
bl_t_data_out_delay = self.sense_amp_array.analytical_delay(bitcell_array_delay.slew,
|
||||||
|
self.bitcell_array.output_load())
|
||||||
|
# output load of bitcell_array is set to be only small part of bl for sense amp.
|
||||||
|
|
||||||
|
data_t_DATA_delay = self.tri_gate_array.analytical_delay(bl_t_data_out_delay.slew, load)
|
||||||
|
|
||||||
|
result = decoder_delay + word_driver_delay + bitcell_array_delay + bl_t_data_out_delay + data_t_DATA_delay
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
@ -3,7 +3,7 @@ import debug
|
||||||
from tech import drc
|
from tech import drc
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from precharge import precharge
|
from precharge import precharge
|
||||||
|
from globals import OPTS
|
||||||
|
|
||||||
class precharge_array(design.design):
|
class precharge_array(design.design):
|
||||||
"""
|
"""
|
||||||
|
|
@ -11,49 +11,80 @@ class precharge_array(design.design):
|
||||||
of bit line columns, height is the height of the bit-cell array.
|
of bit line columns, height is the height of the bit-cell array.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, columns, size=1):
|
unique_id = 1
|
||||||
design.design.__init__(self, "precharge_array")
|
|
||||||
|
def __init__(self, columns, size=1, bitcell_bl="bl", bitcell_br="br"):
|
||||||
|
name = "precharge_array_{}".format(precharge_array.unique_id)
|
||||||
|
precharge_array.unique_id += 1
|
||||||
|
design.design.__init__(self, name)
|
||||||
debug.info(1, "Creating {0}".format(self.name))
|
debug.info(1, "Creating {0}".format(self.name))
|
||||||
|
|
||||||
self.columns = columns
|
self.columns = columns
|
||||||
|
self.size = size
|
||||||
|
self.bitcell_bl = bitcell_bl
|
||||||
|
self.bitcell_br = bitcell_br
|
||||||
|
|
||||||
self.pc_cell = precharge(name="precharge", size=size)
|
self.create_netlist()
|
||||||
self.add_mod(self.pc_cell)
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
self.width = self.columns * self.pc_cell.width
|
|
||||||
self.height = self.pc_cell.height
|
|
||||||
|
|
||||||
self.add_pins()
|
|
||||||
self.create_layout()
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
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")
|
||||||
|
|
||||||
def create_layout(self):
|
def create_netlist(self):
|
||||||
self.add_insts()
|
self.add_modules()
|
||||||
self.add_layout_pins()
|
self.add_pins()
|
||||||
|
self.create_insts()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
self.width = self.columns * self.pc_cell.width
|
||||||
|
self.height = self.pc_cell.height
|
||||||
|
|
||||||
|
self.place_insts()
|
||||||
|
self.add_layout_pins()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
|
self.pc_cell = precharge(name="precharge",
|
||||||
|
size=self.size,
|
||||||
|
bitcell_bl=self.bitcell_bl,
|
||||||
|
bitcell_br=self.bitcell_br)
|
||||||
|
self.add_mod(self.pc_cell)
|
||||||
|
|
||||||
|
|
||||||
def add_layout_pins(self):
|
def add_layout_pins(self):
|
||||||
|
|
||||||
self.add_layout_pin(text="en",
|
self.add_layout_pin(text="en",
|
||||||
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")
|
||||||
|
|
||||||
|
for i in range(len(self.local_insts)):
|
||||||
|
inst = self.local_insts[i]
|
||||||
|
bl_pin = inst.get_pin("bl")
|
||||||
|
self.add_layout_pin(text="bl_{0}".format(i),
|
||||||
|
layer="metal2",
|
||||||
|
offset=bl_pin.ll(),
|
||||||
|
width=drc("minwidth_metal2"),
|
||||||
|
height=bl_pin.height())
|
||||||
|
br_pin = inst.get_pin("br")
|
||||||
|
self.add_layout_pin(text="br_{0}".format(i),
|
||||||
|
layer="metal2",
|
||||||
|
offset=br_pin.ll(),
|
||||||
|
width=drc("minwidth_metal2"),
|
||||||
|
height=bl_pin.height())
|
||||||
|
|
||||||
|
|
||||||
def add_insts(self):
|
def create_insts(self):
|
||||||
"""Creates a precharge array by horizontally tiling the precharge cell"""
|
"""Creates a precharge array by horizontally tiling the precharge cell"""
|
||||||
self.local_insts = []
|
self.local_insts = []
|
||||||
for i in range(self.columns):
|
for i in range(self.columns):
|
||||||
|
|
@ -63,18 +94,11 @@ 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"])
|
|
||||||
bl_pin = inst.get_pin("bl")
|
|
||||||
self.add_layout_pin(text="bl[{0}]".format(i),
|
|
||||||
layer="metal2",
|
|
||||||
offset=bl_pin.ll(),
|
|
||||||
width=drc["minwidth_metal2"],
|
|
||||||
height=bl_pin.height())
|
|
||||||
br_pin = inst.get_pin("br")
|
|
||||||
self.add_layout_pin(text="br[{0}]".format(i),
|
|
||||||
layer="metal2",
|
|
||||||
offset=br_pin.ll(),
|
|
||||||
width=drc["minwidth_metal2"],
|
|
||||||
height=bl_pin.height())
|
|
||||||
|
|
||||||
|
|
||||||
|
def place_insts(self):
|
||||||
|
""" Places precharge array by horizontally tiling the precharge cell"""
|
||||||
|
for i in range(self.columns):
|
||||||
|
offset = vector(self.pc_cell.width * i, 0)
|
||||||
|
self.local_insts[i].place(offset)
|
||||||
|
|
|
||||||
|
|
@ -18,36 +18,39 @@ class replica_bitline(design.design):
|
||||||
def __init__(self, delay_stages, delay_fanout, bitcell_loads, name="replica_bitline"):
|
def __init__(self, delay_stages, delay_fanout, bitcell_loads, name="replica_bitline"):
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
from importlib import reload
|
|
||||||
g = reload(__import__(OPTS.delay_chain))
|
|
||||||
self.mod_delay_chain = getattr(g, OPTS.delay_chain)
|
|
||||||
|
|
||||||
g = reload(__import__(OPTS.replica_bitcell))
|
|
||||||
self.mod_replica_bitcell = getattr(g, OPTS.replica_bitcell)
|
|
||||||
|
|
||||||
for pin in ["en", "out", "vdd", "gnd"]:
|
|
||||||
self.add_pin(pin)
|
|
||||||
self.bitcell_loads = bitcell_loads
|
self.bitcell_loads = bitcell_loads
|
||||||
self.delay_stages = delay_stages
|
self.delay_stages = delay_stages
|
||||||
self.delay_fanout = delay_fanout
|
self.delay_fanout = delay_fanout
|
||||||
|
|
||||||
self.create_modules()
|
self.create_netlist()
|
||||||
self.calculate_module_offsets()
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
self.add_modules()
|
self.add_modules()
|
||||||
|
self.add_pins()
|
||||||
|
self.create_modules()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
self.calculate_module_offsets()
|
||||||
|
self.place_modules()
|
||||||
self.route()
|
self.route()
|
||||||
|
self.add_layout_pins()
|
||||||
|
|
||||||
self.offset_all_coordinates()
|
self.offset_all_coordinates()
|
||||||
|
|
||||||
self.add_layout_pins()
|
|
||||||
|
|
||||||
#self.add_lvs_correspondence_points()
|
#self.add_lvs_correspondence_points()
|
||||||
|
|
||||||
# Plus a pitch for the WL contacts on the RBL
|
# Extra pitch on top and right
|
||||||
self.width = self.rbl_inst.rx() - self.dc_inst.lx() + self.m1_pitch
|
self.width = self.rbl_inst.rx() - self.dc_inst.lx() + self.m2_pitch
|
||||||
self.height = max(self.rbl_inst.uy(), self.dc_inst.uy())
|
self.height = max(self.rbl_inst.uy(), self.dc_inst.uy()) + self.m3_pitch
|
||||||
|
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
for pin in ["en", "out", "vdd", "gnd"]:
|
||||||
|
self.add_pin(pin)
|
||||||
|
|
||||||
def calculate_module_offsets(self):
|
def calculate_module_offsets(self):
|
||||||
""" Calculate all the module offsets """
|
""" Calculate all the module offsets """
|
||||||
|
|
||||||
|
|
@ -73,9 +76,16 @@ class replica_bitline(design.design):
|
||||||
self.access_tx_offset = vector(-gap_width-self.access_tx.width-self.inv.width, 0.5*self.inv.height)
|
self.access_tx_offset = vector(-gap_width-self.access_tx.width-self.inv.width, 0.5*self.inv.height)
|
||||||
|
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
|
""" Add the modules for later usage """
|
||||||
|
|
||||||
def create_modules(self):
|
from importlib import reload
|
||||||
""" Create modules for later instantiation """
|
#g = reload(__import__(OPTS.delay_chain))
|
||||||
|
#self.mod_delay_chain = getattr(g, OPTS.delay_chain)
|
||||||
|
|
||||||
|
g = reload(__import__(OPTS.replica_bitcell))
|
||||||
|
self.mod_replica_bitcell = getattr(g, OPTS.replica_bitcell)
|
||||||
|
|
||||||
self.bitcell = self.replica_bitcell = self.mod_replica_bitcell()
|
self.bitcell = self.replica_bitcell = self.mod_replica_bitcell()
|
||||||
self.add_mod(self.bitcell)
|
self.add_mod(self.bitcell)
|
||||||
|
|
||||||
|
|
@ -84,7 +94,8 @@ class replica_bitline(design.design):
|
||||||
self.add_mod(self.rbl)
|
self.add_mod(self.rbl)
|
||||||
|
|
||||||
# FIXME: The FO and depth of this should be tuned
|
# FIXME: The FO and depth of this should be tuned
|
||||||
self.delay_chain = self.mod_delay_chain([self.delay_fanout]*self.delay_stages)
|
from delay_chain import delay_chain
|
||||||
|
self.delay_chain = delay_chain([self.delay_fanout]*self.delay_stages)
|
||||||
self.add_mod(self.delay_chain)
|
self.add_mod(self.delay_chain)
|
||||||
|
|
||||||
self.inv = pinv()
|
self.inv = pinv()
|
||||||
|
|
@ -93,40 +104,69 @@ class replica_bitline(design.design):
|
||||||
self.access_tx = ptx(tx_type="pmos")
|
self.access_tx = ptx(tx_type="pmos")
|
||||||
self.add_mod(self.access_tx)
|
self.add_mod(self.access_tx)
|
||||||
|
|
||||||
def add_modules(self):
|
def create_modules(self):
|
||||||
""" Add all of the module instances in the logical netlist """
|
""" Create all of the module instances in the logical netlist """
|
||||||
|
|
||||||
# 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)
|
||||||
offset=self.rbl_inv_offset,
|
self.connect_inst(["bl0_0", "out", "vdd", "gnd"])
|
||||||
rotate=180)
|
|
||||||
self.connect_inst(["bl[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)
|
||||||
offset=self.access_tx_offset)
|
|
||||||
# D, G, S, B
|
# D, G, S, B
|
||||||
self.connect_inst(["vdd", "delayed_en", "bl[0]", "vdd"])
|
self.connect_inst(["vdd", "delayed_en", "bl0_0", "vdd"])
|
||||||
# add the well and poly contact
|
# add the well and poly contact
|
||||||
|
|
||||||
self.dc_inst=self.add_inst(name="delay_chain",
|
self.dc_inst=self.add_inst(name="delay_chain",
|
||||||
mod=self.delay_chain,
|
mod=self.delay_chain)
|
||||||
offset=self.delay_chain_offset)
|
|
||||||
self.connect_inst(["en", "delayed_en", "vdd", "gnd"])
|
self.connect_inst(["en", "delayed_en", "vdd", "gnd"])
|
||||||
|
|
||||||
self.rbc_inst=self.add_inst(name="bitcell",
|
self.rbc_inst=self.add_inst(name="bitcell",
|
||||||
mod=self.replica_bitcell,
|
mod=self.replica_bitcell)
|
||||||
offset=self.bitcell_offset,
|
temp = []
|
||||||
mirror="MX")
|
for port in self.all_ports:
|
||||||
self.connect_inst(["bl[0]", "br[0]", "delayed_en", "vdd", "gnd"])
|
temp.append("bl{}_0".format(port))
|
||||||
|
temp.append("br{}_0".format(port))
|
||||||
|
for port in self.all_ports:
|
||||||
|
temp.append("delayed_en")
|
||||||
|
temp.append("vdd")
|
||||||
|
temp.append("gnd")
|
||||||
|
self.connect_inst(temp)
|
||||||
|
#self.connect_inst(["bl_0", "br_0", "delayed_en", "vdd", "gnd"])
|
||||||
|
|
||||||
self.rbl_inst=self.add_inst(name="load",
|
self.rbl_inst=self.add_inst(name="load",
|
||||||
mod=self.rbl,
|
mod=self.rbl)
|
||||||
offset=self.rbl_offset)
|
|
||||||
self.connect_inst(["bl[0]", "br[0]"] + ["gnd"]*self.bitcell_loads + ["vdd", "gnd"])
|
|
||||||
|
|
||||||
|
temp = []
|
||||||
|
for port in self.all_ports:
|
||||||
|
temp.append("bl{}_0".format(port))
|
||||||
|
temp.append("br{}_0".format(port))
|
||||||
|
for wl in range(self.bitcell_loads):
|
||||||
|
for port in self.all_ports:
|
||||||
|
temp.append("gnd")
|
||||||
|
temp.append("vdd")
|
||||||
|
temp.append("gnd")
|
||||||
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
self.wl_list = self.rbl.cell.list_all_wl_names()
|
||||||
|
self.bl_list = self.rbl.cell.list_all_bl_names()
|
||||||
|
|
||||||
|
def place_modules(self):
|
||||||
|
""" Add all of the module instances in the logical netlist """
|
||||||
|
|
||||||
|
# This is the threshold detect inverter on the output of the RBL
|
||||||
|
self.rbl_inv_inst.place(offset=self.rbl_inv_offset,
|
||||||
|
rotate=180)
|
||||||
|
|
||||||
|
self.tx_inst.place(self.access_tx_offset)
|
||||||
|
|
||||||
|
self.dc_inst.place(self.delay_chain_offset)
|
||||||
|
|
||||||
|
self.rbc_inst.place(offset=self.bitcell_offset,
|
||||||
|
mirror="MX")
|
||||||
|
|
||||||
|
self.rbl_inst.place(self.rbl_offset)
|
||||||
|
|
||||||
|
|
||||||
def route(self):
|
def route(self):
|
||||||
|
|
@ -139,19 +179,76 @@ 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 = "wl[{}]".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
|
# Route the connection to the right so that it doesn't interfere with the cells
|
||||||
# with the cells
|
# Wordlines may be close to each other when tiled, so gnd connections are routed in opposite directions
|
||||||
pin_right = pin.rc()
|
pin_right = pin.rc()
|
||||||
pin_extension = pin_right + vector(self.m1_pitch,0)
|
pin_extension = pin_right + vector(self.m3_pitch,0)
|
||||||
|
|
||||||
if pin.layer != "metal1":
|
if pin.layer != "metal1":
|
||||||
continue
|
continue
|
||||||
self.add_path("metal1", [pin_right, pin_extension])
|
pin_width_ydir = pin.uy()-pin.by()
|
||||||
|
#Width is set to pin y width to avoid DRC issues with m1 gaps
|
||||||
|
self.add_path("metal1", [pin_right, pin_extension], pin_width_ydir)
|
||||||
self.add_power_pin("gnd", pin_extension)
|
self.add_power_pin("gnd", pin_extension)
|
||||||
|
|
||||||
|
# for multiport, need to short wordlines to each other so they all connect to gnd.
|
||||||
|
wl_last = self.wl_list[-1]+"_{}".format(row)
|
||||||
|
pin_last = self.rbl_inst.get_pin(wl_last)
|
||||||
|
self.short_wordlines(pin, pin_last, "right", False, row, vector(self.m3_pitch,0))
|
||||||
|
|
||||||
|
def short_wordlines(self, wl_pin_a, wl_pin_b, pin_side, is_replica_cell, cell_row=0, offset_x_vec=None):
|
||||||
|
"""Connects the word lines together for a single bitcell. Also requires which side of the bitcell to short the pins."""
|
||||||
|
#Assumes input pins are wordlines. Also assumes the word lines are horizontal in metal1. Also assumes pins have same x coord.
|
||||||
|
#This is my (Hunter) first time editing layout in openram so this function is likely not optimal.
|
||||||
|
if len(self.all_ports) > 1:
|
||||||
|
#1. Create vertical metal for all the bitlines to connect to
|
||||||
|
#m1 needs to be extended in the y directions, direction needs to be determined as every other cell is flipped
|
||||||
|
correct_y = vector(0, 0.5*drc("minwidth_metal1"))
|
||||||
|
#x spacing depends on the side being drawn. Unknown to me (Hunter) why the size of the space differs by the side.
|
||||||
|
#I assume this is related to how a wire is draw, but I have not investigated the issue.
|
||||||
|
if pin_side == "right":
|
||||||
|
correct_x = vector(0.5*drc("minwidth_metal1"), 0)
|
||||||
|
if offset_x_vec != None:
|
||||||
|
correct_x = offset_x_vec
|
||||||
|
else:
|
||||||
|
correct_x = vector(1.5*drc("minwidth_metal1"), 0)
|
||||||
|
|
||||||
|
if wl_pin_a.uy() > wl_pin_b.uy():
|
||||||
|
self.add_path("metal1", [wl_pin_a.rc()+correct_x+correct_y, wl_pin_b.rc()+correct_x-correct_y])
|
||||||
|
else:
|
||||||
|
self.add_path("metal1", [wl_pin_a.rc()+correct_x-correct_y, wl_pin_b.rc()+correct_x+correct_y])
|
||||||
|
elif pin_side == "left":
|
||||||
|
if offset_x_vec != None:
|
||||||
|
correct_x = offset_x_vec
|
||||||
|
else:
|
||||||
|
correct_x = vector(1.5*drc("minwidth_metal1"), 0)
|
||||||
|
|
||||||
|
if wl_pin_a.uy() > wl_pin_b.uy():
|
||||||
|
self.add_path("metal1", [wl_pin_a.lc()-correct_x+correct_y, wl_pin_b.lc()-correct_x-correct_y])
|
||||||
|
else:
|
||||||
|
self.add_path("metal1", [wl_pin_a.lc()-correct_x-correct_y, wl_pin_b.lc()-correct_x+correct_y])
|
||||||
|
else:
|
||||||
|
debug.error("Could not connect wordlines on specified input side={}".format(pin_side),1)
|
||||||
|
|
||||||
|
#2. Connect word lines horizontally. Only replica cell needs. Bitline loads currently already do this.
|
||||||
|
for port in self.all_ports:
|
||||||
|
if is_replica_cell:
|
||||||
|
wl = self.wl_list[port]
|
||||||
|
pin = self.rbc_inst.get_pin(wl)
|
||||||
|
else:
|
||||||
|
wl = self.wl_list[port]+"_{}".format(cell_row)
|
||||||
|
pin = self.rbl_inst.get_pin(wl)
|
||||||
|
|
||||||
|
if pin_side == "left":
|
||||||
|
self.add_path("metal1", [pin.lc()-correct_x, pin.lc()])
|
||||||
|
elif pin_side == "right":
|
||||||
|
self.add_path("metal1", [pin.rc()+correct_x, pin.rc()])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def route_supplies(self):
|
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 """
|
||||||
|
|
||||||
|
|
@ -170,8 +267,13 @@ class replica_bitline(design.design):
|
||||||
|
|
||||||
# Replica bitcell needs to be routed up to M3
|
# Replica bitcell needs to be routed up to M3
|
||||||
pin=self.rbc_inst.get_pin("vdd")
|
pin=self.rbc_inst.get_pin("vdd")
|
||||||
# Don't rotate this via to vit in FreePDK45
|
# Don't rotate this via to vit in FreePDK45. In the custom cell, the pin cannot be placed
|
||||||
self.add_power_pin("vdd", pin.center(), False)
|
# directly on vdd or there will be a drc error with a wordline. Place the pin slightly farther
|
||||||
|
# away then route to it. A better solution would be to rotate the m1 in the via or move the pin
|
||||||
|
# a m1_pitch below the entire cell.
|
||||||
|
pin_extension = pin.center() - vector(0,self.m1_pitch)
|
||||||
|
self.add_power_pin("vdd", pin_extension, rotate=0)
|
||||||
|
self.add_path("metal1", [pin.center(), pin_extension])
|
||||||
|
|
||||||
for pin in self.rbc_inst.get_pins("gnd"):
|
for pin in self.rbc_inst.get_pins("gnd"):
|
||||||
self.add_power_pin("gnd", pin.center())
|
self.add_power_pin("gnd", pin.center())
|
||||||
|
|
@ -207,16 +309,28 @@ class replica_bitline(design.design):
|
||||||
|
|
||||||
# 3. Route the contact of previous route to the bitcell WL
|
# 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("WL").lc()
|
wl_offset = self.rbc_inst.get_pin(self.wl_list[0]).lc()
|
||||||
xmid_point= 0.5*(wl_offset.x+contact_offset.x)
|
wl_mid1 = wl_offset - vector(1.5*drc("minwidth_metal1"), 0)
|
||||||
wl_mid1 = vector(xmid_point,contact_offset.y)
|
wl_mid2 = vector(wl_mid1.x, contact_offset.y)
|
||||||
wl_mid2 = vector(xmid_point,wl_offset.y)
|
#xmid_point= 0.5*(wl_offset.x+contact_offset.x)
|
||||||
self.add_path("metal1", [contact_offset, wl_mid1, wl_mid2, wl_offset])
|
#wl_mid1 = vector(xmid_point,contact_offset.y)
|
||||||
|
#wl_mid2 = vector(xmid_point,wl_offset.y)
|
||||||
|
self.add_path("metal1", [wl_offset, wl_mid1, wl_mid2, contact_offset])
|
||||||
|
|
||||||
|
# 4. Short wodlines if multiport
|
||||||
|
wl = self.wl_list[0]
|
||||||
|
wl_last = self.wl_list[-1]
|
||||||
|
pin = self.rbc_inst.get_pin(wl)
|
||||||
|
pin_last = self.rbc_inst.get_pin(wl_last)
|
||||||
|
x_offset = self.short_wordlines(pin, pin_last, "left", True)
|
||||||
|
|
||||||
|
#correct = vector(0.5*drc("minwidth_metal1"), 0)
|
||||||
|
#self.add_path("metal1", [pin.lc()+correct, pin_last.lc()+correct])
|
||||||
|
|
||||||
# DRAIN ROUTE
|
# 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
|
||||||
|
|
@ -226,7 +340,7 @@ class replica_bitline(design.design):
|
||||||
|
|
||||||
# Route the connection of the source route to the RBL bitline (left)
|
# Route the connection of the source route to the RBL bitline (left)
|
||||||
# Via will go halfway down from the bitcell
|
# Via will go halfway down from the bitcell
|
||||||
bl_offset = self.rbc_inst.get_pin("BL").bc()
|
bl_offset = self.rbc_inst.get_pin(self.bl_list[0]).bc()
|
||||||
# Route down a pitch so we can use M2 routing
|
# Route down a pitch so we can use M2 routing
|
||||||
bl_down_offset = bl_offset - vector(0, self.m2_pitch)
|
bl_down_offset = bl_offset - vector(0, self.m2_pitch)
|
||||||
self.add_path("metal2",[source_offset, bl_down_offset, bl_offset])
|
self.add_path("metal2",[source_offset, bl_down_offset, bl_offset])
|
||||||
|
|
@ -248,8 +362,6 @@ class replica_bitline(design.design):
|
||||||
self.copy_layout_pin(self.dc_inst,"vdd")
|
self.copy_layout_pin(self.dc_inst,"vdd")
|
||||||
self.copy_layout_pin(self.rbc_inst,"vdd")
|
self.copy_layout_pin(self.rbc_inst,"vdd")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Connect the WL and vdd pins directly to the center and right vdd rails
|
# Connect the WL and vdd pins directly to the center and right vdd rails
|
||||||
# Connect RBL vdd pins to center and right rails
|
# Connect RBL vdd pins to center and right rails
|
||||||
rbl_vdd_pins = self.rbl_inst.get_pins("vdd")
|
rbl_vdd_pins = self.rbl_inst.get_pins("vdd")
|
||||||
|
|
@ -269,9 +381,6 @@ class replica_bitline(design.design):
|
||||||
offset=end,
|
offset=end,
|
||||||
rotate=90)
|
rotate=90)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Add via for the inverter
|
# Add via for the inverter
|
||||||
pin = self.rbl_inv_inst.get_pin("vdd")
|
pin = self.rbl_inv_inst.get_pin("vdd")
|
||||||
start = vector(self.left_vdd_pin.cx(),pin.cy())
|
start = vector(self.left_vdd_pin.cx(),pin.cy())
|
||||||
|
|
@ -355,7 +464,7 @@ class replica_bitline(design.design):
|
||||||
|
|
||||||
# Connect the WL and gnd pins directly to the center and right gnd rails
|
# 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 = "wl[{}]".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
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class sense_amp(design.design):
|
||||||
|
|
||||||
pin_names = ["bl", "br", "dout", "en", "vdd", "gnd"]
|
pin_names = ["bl", "br", "dout", "en", "vdd", "gnd"]
|
||||||
(width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
|
(width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
|
||||||
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"], layer["boundary"])
|
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"])
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
|
|
@ -23,6 +23,14 @@ class sense_amp(design.design):
|
||||||
self.height = sense_amp.height
|
self.height = sense_amp.height
|
||||||
self.pin_map = sense_amp.pin_map
|
self.pin_map = sense_amp.pin_map
|
||||||
|
|
||||||
|
def input_load(self):
|
||||||
|
#Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
|
||||||
|
from tech import spice, parameter
|
||||||
|
# Default is 8x. Per Samira and Hodges-Jackson book:
|
||||||
|
# "Column-mux transistors driven by the decoder must be sized for optimal speed"
|
||||||
|
bitline_pmos_size = 8 #FIXME: This should be set somewhere and referenced. Probably in tech file.
|
||||||
|
return spice["min_tx_drain_c"]*(bitline_pmos_size/parameter["min_tx_size"])#ff
|
||||||
|
|
||||||
def analytical_delay(self, slew, load=0.0):
|
def analytical_delay(self, slew, load=0.0):
|
||||||
from tech import spice
|
from tech import spice
|
||||||
r = spice["min_tx_r"]/(10)
|
r = spice["min_tx_r"]/(10)
|
||||||
|
|
|
||||||
|
|
@ -14,64 +14,81 @@ class sense_amp_array(design.design):
|
||||||
design.design.__init__(self, "sense_amp_array")
|
design.design.__init__(self, "sense_amp_array")
|
||||||
debug.info(1, "Creating {0}".format(self.name))
|
debug.info(1, "Creating {0}".format(self.name))
|
||||||
|
|
||||||
|
self.word_size = word_size
|
||||||
|
self.words_per_row = words_per_row
|
||||||
|
self.row_size = self.word_size * self.words_per_row
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_modules()
|
||||||
|
self.add_pins()
|
||||||
|
self.create_sense_amp_array()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
self.height = self.amp.height
|
||||||
|
|
||||||
|
if self.bitcell.width > self.amp.width:
|
||||||
|
self.width = self.bitcell.width * self.word_size * self.words_per_row
|
||||||
|
else:
|
||||||
|
self.width = self.amp.width * self.word_size * self.words_per_row
|
||||||
|
|
||||||
|
self.place_sense_amp_array()
|
||||||
|
self.add_layout_pins()
|
||||||
|
self.route_rails()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
for i in range(0,self.word_size):
|
||||||
|
self.add_pin("data_{0}".format(i))
|
||||||
|
self.add_pin("bl_{0}".format(i))
|
||||||
|
self.add_pin("br_{0}".format(i))
|
||||||
|
self.add_pin("en")
|
||||||
|
self.add_pin("vdd")
|
||||||
|
self.add_pin("gnd")
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
from importlib import reload
|
from importlib import reload
|
||||||
c = reload(__import__(OPTS.sense_amp))
|
c = reload(__import__(OPTS.sense_amp))
|
||||||
self.mod_sense_amp = getattr(c, OPTS.sense_amp)
|
self.mod_sense_amp = getattr(c, OPTS.sense_amp)
|
||||||
self.amp = self.mod_sense_amp("sense_amp")
|
self.amp = self.mod_sense_amp("sense_amp")
|
||||||
self.add_mod(self.amp)
|
self.add_mod(self.amp)
|
||||||
|
|
||||||
self.word_size = word_size
|
# This is just used for measurements,
|
||||||
self.words_per_row = words_per_row
|
# so don't add the module
|
||||||
self.row_size = self.word_size * self.words_per_row
|
c = reload(__import__(OPTS.bitcell))
|
||||||
|
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
||||||
self.height = self.amp.height
|
self.bitcell = self.mod_bitcell()
|
||||||
self.width = self.amp.width * self.word_size * self.words_per_row
|
|
||||||
|
|
||||||
self.add_pins()
|
|
||||||
self.create_layout()
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
def add_pins(self):
|
|
||||||
|
|
||||||
for i in range(0,self.word_size):
|
|
||||||
self.add_pin("data[{0}]".format(i))
|
|
||||||
self.add_pin("bl[{0}]".format(i))
|
|
||||||
self.add_pin("br[{0}]".format(i))
|
|
||||||
|
|
||||||
self.add_pin("en")
|
|
||||||
self.add_pin("vdd")
|
|
||||||
self.add_pin("gnd")
|
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
|
|
||||||
self.add_sense_amp()
|
|
||||||
self.connect_rails()
|
|
||||||
|
|
||||||
|
def create_sense_amp_array(self):
|
||||||
def add_sense_amp(self):
|
self.local_insts = []
|
||||||
|
|
||||||
bl_pin = self.amp.get_pin("bl")
|
|
||||||
br_pin = self.amp.get_pin("br")
|
|
||||||
dout_pin = self.amp.get_pin("dout")
|
|
||||||
|
|
||||||
amp_spacing = self.amp.width * self.words_per_row
|
|
||||||
for i in range(0,self.word_size):
|
for i in range(0,self.word_size):
|
||||||
|
|
||||||
name = "sa_d{0}".format(i)
|
name = "sa_d{0}".format(i)
|
||||||
amp_position = vector(amp_spacing * i, 0)
|
self.local_insts.append(self.add_inst(name=name,
|
||||||
|
mod=self.amp))
|
||||||
bl_offset = amp_position + bl_pin.ll().scale(1,0)
|
self.connect_inst(["bl_{0}".format(i),
|
||||||
br_offset = amp_position + br_pin.ll().scale(1,0)
|
"br_{0}".format(i),
|
||||||
dout_offset = amp_position + dout_pin.ll()
|
"data_{0}".format(i),
|
||||||
|
|
||||||
inst = self.add_inst(name=name,
|
|
||||||
mod=self.amp,
|
|
||||||
offset=amp_position)
|
|
||||||
self.connect_inst(["bl[{0}]".format(i),
|
|
||||||
"br[{0}]".format(i),
|
|
||||||
"data[{0}]".format(i),
|
|
||||||
"en", "vdd", "gnd"])
|
"en", "vdd", "gnd"])
|
||||||
|
|
||||||
|
def place_sense_amp_array(self):
|
||||||
|
|
||||||
|
if self.bitcell.width > self.amp.width:
|
||||||
|
amp_spacing = self.bitcell.width * self.words_per_row
|
||||||
|
else:
|
||||||
|
amp_spacing = self.amp.width * self.words_per_row
|
||||||
|
for i in range(0,self.word_size):
|
||||||
|
amp_position = vector(amp_spacing * i, 0)
|
||||||
|
self.local_insts[i].place(amp_position)
|
||||||
|
|
||||||
|
|
||||||
|
def add_layout_pins(self):
|
||||||
|
for i in range(len(self.local_insts)):
|
||||||
|
inst = self.local_insts[i]
|
||||||
|
|
||||||
gnd_pos = inst.get_pin("gnd").center()
|
gnd_pos = inst.get_pin("gnd").center()
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||||
|
|
@ -79,41 +96,47 @@ class sense_amp_array(design.design):
|
||||||
self.add_layout_pin_rect_center(text="gnd",
|
self.add_layout_pin_rect_center(text="gnd",
|
||||||
layer="metal3",
|
layer="metal3",
|
||||||
offset=gnd_pos)
|
offset=gnd_pos)
|
||||||
|
|
||||||
vdd_pos = inst.get_pin("vdd").center()
|
vdd_pos = inst.get_pin("vdd").center()
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||||
offset=vdd_pos)
|
offset=vdd_pos)
|
||||||
self.add_layout_pin_rect_center(text="vdd",
|
self.add_layout_pin_rect_center(text="vdd",
|
||||||
layer="metal3",
|
layer="metal3",
|
||||||
offset=vdd_pos)
|
offset=vdd_pos)
|
||||||
|
|
||||||
|
bl_pin = inst.get_pin("bl")
|
||||||
|
br_pin = inst.get_pin("br")
|
||||||
|
dout_pin = inst.get_pin("dout")
|
||||||
|
|
||||||
self.add_layout_pin(text="bl[{0}]".format(i),
|
self.add_layout_pin(text="bl_{0}".format(i),
|
||||||
layer="metal2",
|
layer="metal2",
|
||||||
offset=bl_offset,
|
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_offset,
|
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_offset,
|
offset=dout_pin.ll(),
|
||||||
width=dout_pin.width(),
|
width=dout_pin.width(),
|
||||||
height=dout_pin.height())
|
height=dout_pin.height())
|
||||||
|
|
||||||
|
|
||||||
def connect_rails(self):
|
def route_rails(self):
|
||||||
# add sclk rail across entire array
|
# add sclk rail across entire array
|
||||||
sclk_offset = self.amp.get_pin("en").ll().scale(0,1)
|
sclk_offset = self.amp.get_pin("en").ll().scale(0,1)
|
||||||
self.add_layout_pin(text="en",
|
self.add_layout_pin(text="en",
|
||||||
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 input_load(self):
|
||||||
|
return self.amp.input_load()
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from tech import drc
|
||||||
import debug
|
import debug
|
||||||
import math
|
import math
|
||||||
from vector import vector
|
from vector import vector
|
||||||
|
from globals import OPTS
|
||||||
|
|
||||||
class single_level_column_mux_array(design.design):
|
class single_level_column_mux_array(design.design):
|
||||||
"""
|
"""
|
||||||
|
|
@ -14,31 +14,31 @@ class single_level_column_mux_array(design.design):
|
||||||
Array of column mux to read the bitlines through the 6T.
|
Array of column mux to read the bitlines through the 6T.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, columns, word_size):
|
unique_id = 1
|
||||||
design.design.__init__(self, "columnmux_array")
|
|
||||||
|
def __init__(self, columns, word_size, bitcell_bl="bl", bitcell_br="br"):
|
||||||
|
name="single_level_column_mux_array_{}".format(single_level_column_mux_array.unique_id)
|
||||||
|
single_level_column_mux_array.unique_id += 1
|
||||||
|
design.design.__init__(self, name)
|
||||||
debug.info(1, "Creating {0}".format(self.name))
|
debug.info(1, "Creating {0}".format(self.name))
|
||||||
self.columns = columns
|
self.columns = columns
|
||||||
self.word_size = word_size
|
self.word_size = word_size
|
||||||
self.words_per_row = int(self.columns / self.word_size)
|
self.words_per_row = int(self.columns / self.word_size)
|
||||||
self.add_pins()
|
self.bitcell_bl = bitcell_bl
|
||||||
self.create_layout()
|
self.bitcell_br = bitcell_br
|
||||||
self.DRC_LVS()
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
def add_pins(self):
|
def create_netlist(self):
|
||||||
for i in range(self.columns):
|
|
||||||
self.add_pin("bl[{}]".format(i))
|
|
||||||
self.add_pin("br[{}]".format(i))
|
|
||||||
for i in range(self.words_per_row):
|
|
||||||
self.add_pin("sel[{}]".format(i))
|
|
||||||
for i in range(self.word_size):
|
|
||||||
self.add_pin("bl_out[{}]".format(i))
|
|
||||||
self.add_pin("br_out[{}]".format(i))
|
|
||||||
self.add_pin("gnd")
|
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
self.add_modules()
|
self.add_modules()
|
||||||
self.setup_layout_constants()
|
self.add_pins()
|
||||||
self.create_array()
|
self.create_array()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
self.setup_layout_constants()
|
||||||
|
self.place_array()
|
||||||
self.add_routing()
|
self.add_routing()
|
||||||
# Find the highest shapes to determine height before adding well
|
# Find the highest shapes to determine height before adding well
|
||||||
highest = self.find_highest_coords()
|
highest = self.find_highest_coords()
|
||||||
|
|
@ -46,11 +46,22 @@ class single_level_column_mux_array(design.design):
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
self.add_enclosure(self.mux_inst, "pwell")
|
self.add_enclosure(self.mux_inst, "pwell")
|
||||||
|
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
for i in range(self.columns):
|
||||||
|
self.add_pin("bl_{}".format(i))
|
||||||
|
self.add_pin("br_{}".format(i))
|
||||||
|
for i in range(self.words_per_row):
|
||||||
|
self.add_pin("sel_{}".format(i))
|
||||||
|
for i in range(self.word_size):
|
||||||
|
self.add_pin("bl_out_{}".format(i))
|
||||||
|
self.add_pin("br_out_{}".format(i))
|
||||||
|
self.add_pin("gnd")
|
||||||
|
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
# FIXME: Why is this 8x?
|
self.mux = single_level_column_mux(bitcell_bl=self.bitcell_bl, bitcell_br=self.bitcell_br)
|
||||||
self.mux = single_level_column_mux(tx_size=8)
|
|
||||||
self.add_mod(self.mux)
|
self.add_mod(self.mux)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -65,22 +76,26 @@ class single_level_column_mux_array(design.design):
|
||||||
|
|
||||||
def create_array(self):
|
def create_array(self):
|
||||||
self.mux_inst = []
|
self.mux_inst = []
|
||||||
|
# For every column, add a pass gate
|
||||||
|
for col_num in range(self.columns):
|
||||||
|
name = "XMUX{0}".format(col_num)
|
||||||
|
self.mux_inst.append(self.add_inst(name=name,
|
||||||
|
mod=self.mux))
|
||||||
|
|
||||||
|
self.connect_inst(["bl_{}".format(col_num),
|
||||||
|
"br_{}".format(col_num),
|
||||||
|
"bl_out_{}".format(int(col_num/self.words_per_row)),
|
||||||
|
"br_out_{}".format(int(col_num/self.words_per_row)),
|
||||||
|
"sel_{}".format(col_num % self.words_per_row),
|
||||||
|
"gnd"])
|
||||||
|
|
||||||
|
def place_array(self):
|
||||||
# For every column, add a pass gate
|
# For every column, add a pass gate
|
||||||
for col_num in range(self.columns):
|
for col_num in range(self.columns):
|
||||||
name = "XMUX{0}".format(col_num)
|
name = "XMUX{0}".format(col_num)
|
||||||
x_off = vector(col_num * self.mux.width, self.route_height)
|
x_off = vector(col_num * self.mux.width, self.route_height)
|
||||||
self.mux_inst.append(self.add_inst(name=name,
|
self.mux_inst[col_num].place(x_off)
|
||||||
mod=self.mux,
|
|
||||||
offset=x_off))
|
|
||||||
|
|
||||||
self.connect_inst(["bl[{}]".format(col_num),
|
|
||||||
"br[{}]".format(col_num),
|
|
||||||
"bl_out[{}]".format(int(col_num/self.words_per_row)),
|
|
||||||
"br_out[{}]".format(int(col_num/self.words_per_row)),
|
|
||||||
"sel[{}]".format(col_num % self.words_per_row),
|
|
||||||
"gnd"])
|
|
||||||
|
|
||||||
|
|
||||||
def add_layout_pins(self):
|
def add_layout_pins(self):
|
||||||
""" Add the pins after we determine the height. """
|
""" Add the pins after we determine the height. """
|
||||||
|
|
@ -88,13 +103,13 @@ class single_level_column_mux_array(design.design):
|
||||||
for col_num in range(self.columns):
|
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)
|
||||||
|
|
@ -112,7 +127,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,
|
||||||
|
|
@ -128,9 +143,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,
|
||||||
|
|
@ -154,23 +169,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
|
||||||
|
|
@ -186,7 +201,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"),
|
||||||
|
|
@ -194,12 +209,20 @@ 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"),
|
||||||
offset= br_out_offset,
|
offset= br_out_offset,
|
||||||
rotate=90)
|
rotate=90)
|
||||||
|
|
||||||
|
def analytical_delay(self, vdd, slew, load=0.0):
|
||||||
|
from tech import spice, parameter
|
||||||
|
r = spice["min_tx_r"]/(self.mux.ptx_width/parameter["min_tx_size"])
|
||||||
|
#Drains of mux transistors make up capacitance.
|
||||||
|
c_para = spice["min_tx_drain_c"]*(self.mux.ptx_width/parameter["min_tx_size"])*self.words_per_row#ff
|
||||||
|
volt_swing = spice["v_threshold_typical"]/vdd
|
||||||
|
|
||||||
|
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = volt_swing)
|
||||||
|
return self.return_delay(result.delay, result.slew)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class tri_gate(design.design):
|
||||||
|
|
||||||
pin_names = ["in", "en", "en_bar", "out", "gnd", "vdd"]
|
pin_names = ["in", "en", "en_bar", "out", "gnd", "vdd"]
|
||||||
(width,height) = utils.get_libcell_size("tri_gate", GDS["unit"], layer["boundary"])
|
(width,height) = utils.get_libcell_size("tri_gate", GDS["unit"], layer["boundary"])
|
||||||
pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"], layer["boundary"])
|
pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"])
|
||||||
|
|
||||||
unique_id = 1
|
unique_id = 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,34 +14,40 @@ class tri_gate_array(design.design):
|
||||||
design.design.__init__(self, "tri_gate_array")
|
design.design.__init__(self, "tri_gate_array")
|
||||||
debug.info(1, "Creating {0}".format(self.name))
|
debug.info(1, "Creating {0}".format(self.name))
|
||||||
|
|
||||||
|
self.columns = columns
|
||||||
|
self.word_size = word_size
|
||||||
|
self.words_per_row = int(self.columns / self.word_size)
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_modules()
|
||||||
|
self.add_pins()
|
||||||
|
self.create_array()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
self.width = (self.columns / self.words_per_row) * self.tri.width
|
||||||
|
self.height = self.tri.height
|
||||||
|
|
||||||
|
self.place_array()
|
||||||
|
self.add_layout_pins()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
from importlib import reload
|
from importlib import reload
|
||||||
c = reload(__import__(OPTS.tri_gate))
|
c = reload(__import__(OPTS.tri_gate))
|
||||||
self.mod_tri_gate = getattr(c, OPTS.tri_gate)
|
self.mod_tri_gate = getattr(c, OPTS.tri_gate)
|
||||||
self.tri = self.mod_tri_gate("tri_gate")
|
self.tri = self.mod_tri_gate("tri_gate")
|
||||||
self.add_mod(self.tri)
|
self.add_mod(self.tri)
|
||||||
|
|
||||||
self.columns = columns
|
|
||||||
self.word_size = word_size
|
|
||||||
|
|
||||||
self.words_per_row = int(self.columns / self.word_size)
|
|
||||||
self.width = (self.columns / self.words_per_row) * self.tri.width
|
|
||||||
self.height = self.tri.height
|
|
||||||
|
|
||||||
self.create_layout()
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
"""generate layout """
|
|
||||||
self.add_pins()
|
|
||||||
self.create_array()
|
|
||||||
self.add_layout_pins()
|
|
||||||
|
|
||||||
def add_pins(self):
|
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)
|
||||||
|
|
||||||
|
|
@ -50,15 +56,19 @@ class tri_gate_array(design.design):
|
||||||
self.tri_inst = {}
|
self.tri_inst = {}
|
||||||
for i in range(0,self.columns,self.words_per_row):
|
for i in range(0,self.columns,self.words_per_row):
|
||||||
name = "Xtri_gate{0}".format(i)
|
name = "Xtri_gate{0}".format(i)
|
||||||
base = vector(i*self.tri.width, 0)
|
|
||||||
self.tri_inst[i]=self.add_inst(name=name,
|
self.tri_inst[i]=self.add_inst(name=name,
|
||||||
mod=self.tri,
|
mod=self.tri)
|
||||||
offset=base)
|
|
||||||
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):
|
||||||
|
""" Place the tri gate to the array """
|
||||||
|
for i in range(0,self.columns,self.words_per_row):
|
||||||
|
base = vector(i*self.tri.width, 0)
|
||||||
|
self.tri_inst[i].place(base)
|
||||||
|
|
||||||
|
|
||||||
def add_layout_pins(self):
|
def add_layout_pins(self):
|
||||||
|
|
||||||
|
|
@ -66,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(),
|
||||||
|
|
@ -97,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"))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,30 +20,39 @@ class wordline_driver(design.design):
|
||||||
design.design.__init__(self, "wordline_driver")
|
design.design.__init__(self, "wordline_driver")
|
||||||
|
|
||||||
self.rows = rows
|
self.rows = rows
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_modules()
|
||||||
self.add_pins()
|
self.add_pins()
|
||||||
self.design_layout()
|
self.create_drivers()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
self.place_drivers()
|
||||||
|
self.route_layout()
|
||||||
|
self.route_vdd_gnd()
|
||||||
self.offset_all_coordinates()
|
self.offset_all_coordinates()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
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")
|
||||||
|
|
||||||
def design_layout(self):
|
|
||||||
self.create_modules()
|
|
||||||
self.add_modules()
|
|
||||||
self.route_layout()
|
|
||||||
self.route_vdd_gnd()
|
|
||||||
|
|
||||||
|
|
||||||
def create_modules(self):
|
def add_modules(self):
|
||||||
|
# This is just used for measurements,
|
||||||
|
# so don't add the module
|
||||||
|
|
||||||
self.inv = pinv()
|
self.inv = pinv()
|
||||||
self.add_mod(self.inv)
|
self.add_mod(self.inv)
|
||||||
|
|
||||||
|
|
@ -52,6 +61,7 @@ class wordline_driver(design.design):
|
||||||
|
|
||||||
self.nand2 = pnand2()
|
self.nand2 = pnand2()
|
||||||
self.add_mod(self.nand2)
|
self.add_mod(self.nand2)
|
||||||
|
|
||||||
|
|
||||||
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. """
|
||||||
|
|
@ -84,15 +94,7 @@ class wordline_driver(design.design):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def add_modules(self):
|
def create_drivers(self):
|
||||||
inv1_xoffset = 2*self.m1_width + 5*self.m1_space
|
|
||||||
nand2_xoffset = inv1_xoffset + self.inv.width
|
|
||||||
inv2_xoffset = nand2_xoffset + self.nand2.width
|
|
||||||
|
|
||||||
self.width = inv2_xoffset + self.inv.width
|
|
||||||
self.height = self.inv.height * self.rows
|
|
||||||
|
|
||||||
|
|
||||||
self.inv1_inst = []
|
self.inv1_inst = []
|
||||||
self.nand_inst = []
|
self.nand_inst = []
|
||||||
self.inv2_inst = []
|
self.inv2_inst = []
|
||||||
|
|
@ -101,11 +103,42 @@ class wordline_driver(design.design):
|
||||||
name_nand = "wl_driver_nand{}".format(row)
|
name_nand = "wl_driver_nand{}".format(row)
|
||||||
name_inv2 = "wl_driver_inv{}".format(row)
|
name_inv2 = "wl_driver_inv{}".format(row)
|
||||||
|
|
||||||
|
# add inv1 based on the info above
|
||||||
|
self.inv1_inst.append(self.add_inst(name=name_inv1,
|
||||||
|
mod=self.inv_no_output))
|
||||||
|
self.connect_inst(["en",
|
||||||
|
"en_bar_{0}".format(row),
|
||||||
|
"vdd", "gnd"])
|
||||||
|
# add nand 2
|
||||||
|
self.nand_inst.append(self.add_inst(name=name_nand,
|
||||||
|
mod=self.nand2))
|
||||||
|
self.connect_inst(["en_bar_{0}".format(row),
|
||||||
|
"in_{0}".format(row),
|
||||||
|
"wl_bar_{0}".format(row),
|
||||||
|
"vdd", "gnd"])
|
||||||
|
# add inv2
|
||||||
|
self.inv2_inst.append(self.add_inst(name=name_inv2,
|
||||||
|
mod=self.inv))
|
||||||
|
self.connect_inst(["wl_bar_{0}".format(row),
|
||||||
|
"wl_{0}".format(row),
|
||||||
|
"vdd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
|
def place_drivers(self):
|
||||||
|
inv1_xoffset = 2*self.m1_width + 5*self.m1_space
|
||||||
|
nand2_xoffset = inv1_xoffset + self.inv.width
|
||||||
|
inv2_xoffset = nand2_xoffset + self.nand2.width
|
||||||
|
|
||||||
|
self.width = inv2_xoffset + self.inv.height
|
||||||
|
driver_height = self.inv.height
|
||||||
|
self.height = self.inv.height * self.rows
|
||||||
|
|
||||||
|
for row in range(self.rows):
|
||||||
if (row % 2):
|
if (row % 2):
|
||||||
y_offset = self.inv.height*(row + 1)
|
y_offset = driver_height*(row + 1)
|
||||||
inst_mirror = "MX"
|
inst_mirror = "MX"
|
||||||
else:
|
else:
|
||||||
y_offset = self.inv.height*row
|
y_offset = driver_height*row
|
||||||
inst_mirror = "R0"
|
inst_mirror = "R0"
|
||||||
|
|
||||||
inv1_offset = [inv1_xoffset, y_offset]
|
inv1_offset = [inv1_xoffset, y_offset]
|
||||||
|
|
@ -113,30 +146,14 @@ class wordline_driver(design.design):
|
||||||
inv2_offset=[inv2_xoffset, y_offset]
|
inv2_offset=[inv2_xoffset, y_offset]
|
||||||
|
|
||||||
# add inv1 based on the info above
|
# add inv1 based on the info above
|
||||||
self.inv1_inst.append(self.add_inst(name=name_inv1,
|
self.inv1_inst[row].place(offset=inv1_offset,
|
||||||
mod=self.inv_no_output,
|
mirror=inst_mirror)
|
||||||
offset=inv1_offset,
|
|
||||||
mirror=inst_mirror))
|
|
||||||
self.connect_inst(["en",
|
|
||||||
"en_bar[{0}]".format(row),
|
|
||||||
"vdd", "gnd"])
|
|
||||||
# add nand 2
|
# add nand 2
|
||||||
self.nand_inst.append(self.add_inst(name=name_nand,
|
self.nand_inst[row].place(offset=nand2_offset,
|
||||||
mod=self.nand2,
|
mirror=inst_mirror)
|
||||||
offset=nand2_offset,
|
|
||||||
mirror=inst_mirror))
|
|
||||||
self.connect_inst(["en_bar[{0}]".format(row),
|
|
||||||
"in[{0}]".format(row),
|
|
||||||
"wl_bar[{0}]".format(row),
|
|
||||||
"vdd", "gnd"])
|
|
||||||
# add inv2
|
# add inv2
|
||||||
self.inv2_inst.append(self.add_inst(name=name_inv2,
|
self.inv2_inst[row].place(offset=inv2_offset,
|
||||||
mod=self.inv,
|
mirror=inst_mirror)
|
||||||
offset=inv2_offset,
|
|
||||||
mirror=inst_mirror))
|
|
||||||
self.connect_inst(["wl_bar[{0}]".format(row),
|
|
||||||
"wl[{0}]".format(row),
|
|
||||||
"vdd", "gnd"])
|
|
||||||
|
|
||||||
|
|
||||||
def route_layout(self):
|
def route_layout(self):
|
||||||
|
|
@ -188,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)
|
||||||
|
|
@ -204,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))
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class write_driver(design.design):
|
||||||
|
|
||||||
pin_names = ["din", "bl", "br", "en", "gnd", "vdd"]
|
pin_names = ["din", "bl", "br", "en", "gnd", "vdd"]
|
||||||
(width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"])
|
(width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"])
|
||||||
pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"], layer["boundary"])
|
pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"])
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
|
|
|
||||||
|
|
@ -15,71 +15,99 @@ class write_driver_array(design.design):
|
||||||
design.design.__init__(self, "write_driver_array")
|
design.design.__init__(self, "write_driver_array")
|
||||||
debug.info(1, "Creating {0}".format(self.name))
|
debug.info(1, "Creating {0}".format(self.name))
|
||||||
|
|
||||||
|
self.columns = columns
|
||||||
|
self.word_size = word_size
|
||||||
|
self.words_per_row = int(columns / word_size)
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_modules()
|
||||||
|
self.add_pins()
|
||||||
|
self.create_write_array()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
|
||||||
|
if self.bitcell.width > self.driver.width:
|
||||||
|
self.width = self.columns * self.bitcell.width
|
||||||
|
else:
|
||||||
|
self.width = self.columns * self.driver.width
|
||||||
|
|
||||||
|
self.height = self.driver.height
|
||||||
|
|
||||||
|
self.place_write_array()
|
||||||
|
self.add_layout_pins()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
for i in range(self.word_size):
|
||||||
|
self.add_pin("data_{0}".format(i))
|
||||||
|
for i in range(self.word_size):
|
||||||
|
self.add_pin("bl_{0}".format(i))
|
||||||
|
self.add_pin("br_{0}".format(i))
|
||||||
|
self.add_pin("en")
|
||||||
|
self.add_pin("vdd")
|
||||||
|
self.add_pin("gnd")
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
from importlib import reload
|
from importlib import reload
|
||||||
c = reload(__import__(OPTS.write_driver))
|
c = reload(__import__(OPTS.write_driver))
|
||||||
self.mod_write_driver = getattr(c, OPTS.write_driver)
|
self.mod_write_driver = getattr(c, OPTS.write_driver)
|
||||||
self.driver = self.mod_write_driver("write_driver")
|
self.driver = self.mod_write_driver("write_driver")
|
||||||
self.add_mod(self.driver)
|
self.add_mod(self.driver)
|
||||||
|
|
||||||
self.columns = columns
|
# This is just used for measurements,
|
||||||
self.word_size = word_size
|
# so don't add the module
|
||||||
self.words_per_row = int(columns / word_size)
|
c = reload(__import__(OPTS.bitcell))
|
||||||
|
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
||||||
self.width = self.columns * self.driver.width
|
self.bitcell = self.mod_bitcell()
|
||||||
self.height = self.height = self.driver.height
|
|
||||||
|
|
||||||
self.add_pins()
|
|
||||||
self.create_layout()
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
def add_pins(self):
|
|
||||||
for i in range(self.word_size):
|
|
||||||
self.add_pin("data[{0}]".format(i))
|
|
||||||
for i in range(self.word_size):
|
|
||||||
self.add_pin("bl[{0}]".format(i))
|
|
||||||
self.add_pin("br[{0}]".format(i))
|
|
||||||
self.add_pin("en")
|
|
||||||
self.add_pin("vdd")
|
|
||||||
self.add_pin("gnd")
|
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
self.create_write_array()
|
|
||||||
self.add_layout_pins()
|
|
||||||
|
|
||||||
def create_write_array(self):
|
def create_write_array(self):
|
||||||
self.driver_insts = {}
|
self.driver_insts = {}
|
||||||
for i in range(0,self.columns,self.words_per_row):
|
for i in range(0,self.columns,self.words_per_row):
|
||||||
name = "Xwrite_driver{}".format(i)
|
name = "Xwrite_driver{}".format(i)
|
||||||
base = vector(i * self.driver.width,0)
|
|
||||||
|
|
||||||
index = int(i/self.words_per_row)
|
index = int(i/self.words_per_row)
|
||||||
self.driver_insts[index]=self.add_inst(name=name,
|
self.driver_insts[index]=self.add_inst(name=name,
|
||||||
mod=self.driver,
|
mod=self.driver)
|
||||||
offset=base)
|
|
||||||
|
|
||||||
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"])
|
||||||
|
|
||||||
|
|
||||||
|
def place_write_array(self):
|
||||||
|
if self.bitcell.width > self.driver.width:
|
||||||
|
driver_spacing = self.bitcell.width
|
||||||
|
else:
|
||||||
|
driver_spacing = self.driver.width
|
||||||
|
|
||||||
|
for i in range(0,self.columns,self.words_per_row):
|
||||||
|
index = int(i/self.words_per_row)
|
||||||
|
base = vector(i * driver_spacing,0)
|
||||||
|
self.driver_insts[index].place(base)
|
||||||
|
|
||||||
|
|
||||||
def add_layout_pins(self):
|
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(),
|
||||||
|
|
@ -102,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
|
||||||
|
|
@ -38,9 +36,15 @@ report_status()
|
||||||
|
|
||||||
# Start importing design modules after we have the config file
|
# Start importing design modules after we have the config file
|
||||||
import verify
|
import verify
|
||||||
import sram
|
from sram import sram
|
||||||
|
from sram_config import sram_config
|
||||||
output_files = ["{0}.{1}".format(OPTS.output_name,x) for x in ["sp","gds","v","lib","lef"]]
|
#from parser import *
|
||||||
|
output_extensions = ["sp","v","lib"]
|
||||||
|
if OPTS.datasheet_gen:
|
||||||
|
output_extensions.append("html")
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
output_extensions.extend(["gds","lef"])
|
||||||
|
output_files = ["{0}.{1}".format(OPTS.output_name,x) for x in output_extensions]
|
||||||
print("Output files are: ")
|
print("Output files are: ")
|
||||||
print(*output_files,sep="\n")
|
print(*output_files,sep="\n")
|
||||||
|
|
||||||
|
|
@ -48,11 +52,13 @@ print(*output_files,sep="\n")
|
||||||
start_time = datetime.datetime.now()
|
start_time = datetime.datetime.now()
|
||||||
print_time("Start",start_time)
|
print_time("Start",start_time)
|
||||||
|
|
||||||
|
# Configure the SRAM organization
|
||||||
|
c = sram_config(word_size=OPTS.word_size,
|
||||||
|
num_words=OPTS.num_words)
|
||||||
|
|
||||||
# import SRAM test generation
|
# import SRAM test generation
|
||||||
s = sram.sram(word_size=OPTS.word_size,
|
s = sram(sram_config=c,
|
||||||
num_words=OPTS.num_words,
|
name=OPTS.output_name)
|
||||||
num_banks=OPTS.num_banks,
|
|
||||||
name=OPTS.output_name)
|
|
||||||
|
|
||||||
# Output the files for the resulting SRAM
|
# Output the files for the resulting SRAM
|
||||||
s.save()
|
s.save()
|
||||||
|
|
|
||||||
|
|
@ -18,18 +18,24 @@ class options(optparse.Values):
|
||||||
# This is the verbosity level to control debug information. 0 is none, 1
|
# This is the verbosity level to control debug information. 0 is none, 1
|
||||||
# is minimal, etc.
|
# is minimal, etc.
|
||||||
debug_level = 0
|
debug_level = 0
|
||||||
|
# When enabled, layout is not generated (and no DRC or LVS are performed)
|
||||||
|
netlist_only = False
|
||||||
# This determines whether LVS and DRC is checked for each submodule.
|
# This determines whether LVS and DRC is checked for each submodule.
|
||||||
check_lvsdrc = True
|
check_lvsdrc = True
|
||||||
# Variable to select the variant of spice
|
# Variable to select the variant of spice
|
||||||
spice_name = ""
|
spice_name = ""
|
||||||
# Should we print out the banner at startup
|
# The spice executable being used which is derived from the user PATH.
|
||||||
print_banner = True
|
spice_exe = ""
|
||||||
|
# Variable to select the variant of drc, lvs, pex
|
||||||
|
drc_name = ""
|
||||||
|
lvs_name = ""
|
||||||
|
pex_name = ""
|
||||||
# The DRC/LVS/PEX executable being used which is derived from the user PATH.
|
# The DRC/LVS/PEX executable being used which is derived from the user PATH.
|
||||||
drc_exe = None
|
drc_exe = None
|
||||||
lvs_exe = None
|
lvs_exe = None
|
||||||
pex_exe = None
|
pex_exe = None
|
||||||
# The spice executable being used which is derived from the user PATH.
|
# Should we print out the banner at startup
|
||||||
spice_exe = ""
|
print_banner = True
|
||||||
# Run with extracted parasitics
|
# Run with extracted parasitics
|
||||||
use_pex = False
|
use_pex = False
|
||||||
# Remove noncritical memory cells for characterization speed-up
|
# Remove noncritical memory cells for characterization speed-up
|
||||||
|
|
@ -46,19 +52,26 @@ class options(optparse.Values):
|
||||||
purge_temp = True
|
purge_temp = True
|
||||||
|
|
||||||
# These are the configuration parameters
|
# These are the configuration parameters
|
||||||
rw_ports = 1
|
num_rw_ports = 1
|
||||||
r_ports = 0
|
num_r_ports = 0
|
||||||
w_ports = 0
|
num_w_ports = 0
|
||||||
|
|
||||||
# These will get initialized by the the file
|
# These will get initialized by the the file
|
||||||
supply_voltages = ""
|
supply_voltages = ""
|
||||||
temperatures = ""
|
temperatures = ""
|
||||||
process_corners = ""
|
process_corners = ""
|
||||||
|
|
||||||
|
|
||||||
|
# These are the main configuration parameters that should be over-ridden
|
||||||
|
# in a configuration file.
|
||||||
|
#num_words = 0
|
||||||
|
#word_size = 0
|
||||||
|
|
||||||
|
# You can manually specify banks, but it is better to auto-detect it.
|
||||||
|
num_banks = 1
|
||||||
|
|
||||||
# These are the default modules that can be over-riden
|
# These are the default modules that can be over-riden
|
||||||
decoder = "hierarchical_decoder"
|
decoder = "hierarchical_decoder"
|
||||||
ms_flop = "ms_flop"
|
dff_array = "dff_array"
|
||||||
ms_flop_array = "ms_flop_array"
|
|
||||||
dff = "dff"
|
dff = "dff"
|
||||||
control_logic = "control_logic"
|
control_logic = "control_logic"
|
||||||
bitcell_array = "bitcell_array"
|
bitcell_array = "bitcell_array"
|
||||||
|
|
|
||||||
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
|
||||||
|
|
@ -11,10 +11,19 @@ class pgate(design.design):
|
||||||
This is a module that implements some shared functions for parameterized gates.
|
This is a module that implements some shared functions for parameterized gates.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name, height=None):
|
||||||
""" Creates a generic cell """
|
""" Creates a generic cell """
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
|
if height:
|
||||||
|
self.height = height
|
||||||
|
elif not height:
|
||||||
|
from importlib import reload
|
||||||
|
c = reload(__import__(OPTS.bitcell))
|
||||||
|
bitcell = getattr(c, OPTS.bitcell)
|
||||||
|
b = bitcell()
|
||||||
|
self.height = b.height
|
||||||
|
|
||||||
|
|
||||||
def connect_pin_to_rail(self,inst,pin,supply):
|
def connect_pin_to_rail(self,inst,pin,supply):
|
||||||
""" Conencts a ptx pin to a supply rail. """
|
""" Conencts a ptx pin to a supply rail. """
|
||||||
|
|
@ -101,7 +110,7 @@ class pgate(design.design):
|
||||||
max_y_offset = self.height + 0.5*self.m1_width
|
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,
|
||||||
|
|
@ -113,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,
|
||||||
|
|
@ -129,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
|
||||||
|
|
@ -176,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
|
||||||
|
|
@ -17,74 +17,84 @@ class pinv(pgate.pgate):
|
||||||
from center of rail to rail.. The route_output will route the
|
from center of rail to rail.. The route_output will route the
|
||||||
output to the right side of the cell for easier access.
|
output to the right side of the cell for easier access.
|
||||||
"""
|
"""
|
||||||
from importlib import reload
|
|
||||||
c = reload(__import__(OPTS.bitcell))
|
|
||||||
bitcell = getattr(c, OPTS.bitcell)
|
|
||||||
|
|
||||||
unique_id = 1
|
unique_id = 1
|
||||||
|
|
||||||
def __init__(self, size=1, beta=parameter["beta"], height=bitcell.height, route_output=True):
|
def __init__(self, size=1, beta=parameter["beta"], height=None, route_output=True):
|
||||||
# We need to keep unique names because outputting to GDSII
|
# 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
|
||||||
# have poly connected, for example.
|
# have poly connected, for example.
|
||||||
name = "pinv_{}".format(pinv.unique_id)
|
name = "pinv_{}".format(pinv.unique_id)
|
||||||
pinv.unique_id += 1
|
pinv.unique_id += 1
|
||||||
pgate.pgate.__init__(self, name)
|
pgate.pgate.__init__(self, name, height)
|
||||||
debug.info(2, "create pinv structure {0} with size of {1}".format(name, size))
|
debug.info(2, "create pinv structure {0} with size of {1}".format(name, size))
|
||||||
|
|
||||||
self.nmos_size = size
|
self.nmos_size = size
|
||||||
self.pmos_size = beta*size
|
self.pmos_size = beta*size
|
||||||
self.beta = beta
|
self.beta = beta
|
||||||
self.height = height # Maybe minimize height if not defined in future?
|
|
||||||
self.route_output = False
|
self.route_output = False
|
||||||
|
|
||||||
self.add_pins()
|
|
||||||
self.create_layout()
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
# for run-time, we won't check every transitor DRC/LVS independently
|
# for run-time, we won't check every transitor DRC/LVS independently
|
||||||
# but this may be uncommented for debug purposes
|
# but this may be uncommented for debug purposes
|
||||||
#self.DRC_LVS()
|
#self.DRC_LVS()
|
||||||
|
|
||||||
def add_pins(self):
|
def create_netlist(self):
|
||||||
""" Adds pins for spice netlist """
|
""" Calls all functions related to the generation of the netlist """
|
||||||
self.add_pin_list(["A", "Z", "vdd", "gnd"])
|
self.add_pins()
|
||||||
|
self.determine_tx_mults()
|
||||||
|
self.add_ptx()
|
||||||
|
self.create_ptx()
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
""" Calls all functions related to the generation of the layout """
|
""" Calls all functions related to the generation of the layout """
|
||||||
|
|
||||||
self.determine_tx_mults()
|
|
||||||
self.create_ptx()
|
|
||||||
self.setup_layout_constants()
|
self.setup_layout_constants()
|
||||||
self.add_supply_rails()
|
self.route_supply_rails()
|
||||||
self.add_ptx()
|
self.place_ptx()
|
||||||
self.add_well_contacts()
|
self.add_well_contacts()
|
||||||
self.extend_wells(self.well_pos)
|
self.extend_wells(self.well_pos)
|
||||||
self.connect_rails()
|
self.connect_rails()
|
||||||
self.route_input_gate(self.pmos_inst, self.nmos_inst, self.output_pos.y, "A", rotate=0)
|
self.route_input_gate(self.pmos_inst, self.nmos_inst, self.output_pos.y, "A", rotate=0)
|
||||||
self.route_outputs()
|
self.route_outputs()
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
""" Adds pins for spice netlist """
|
||||||
|
self.add_pin_list(["A", "Z", "vdd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
def determine_tx_mults(self):
|
def determine_tx_mults(self):
|
||||||
"""
|
"""
|
||||||
Determines the number of fingers needed to achieve the size within
|
Determines the number of fingers needed to achieve the size within
|
||||||
the height constraint. This may fail if the user has a tight height.
|
the height constraint. This may fail if the user has a tight height.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# This may make the result differ when the layout is created...
|
||||||
|
if OPTS.netlist_only:
|
||||||
|
self.tx_mults = 1
|
||||||
|
self.nmos_width = self.nmos_size*drc("minwidth_tx")
|
||||||
|
self.pmos_width = self.pmos_size*drc("minwidth_tx")
|
||||||
|
return
|
||||||
|
|
||||||
# Do a quick sanity check and bail if unlikely feasible height
|
# Do a quick sanity check and bail if unlikely feasible height
|
||||||
# Sanity check. can we make an inverter in the height with minimum tx sizes?
|
# Sanity check. can we make an inverter in the height with minimum tx sizes?
|
||||||
# Assume we need 3 metal 1 pitches (2 power rails, one between the tx for the drain)
|
# 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))
|
||||||
|
|
||||||
|
|
@ -93,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
|
||||||
|
|
@ -114,9 +124,9 @@ class pinv(pgate.pgate):
|
||||||
# We also need to round the width to the grid or we will end up with LVS property
|
# 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):
|
||||||
|
|
@ -127,13 +137,13 @@ 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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_ptx(self):
|
def add_ptx(self):
|
||||||
""" Create the PMOS and NMOS transistors. """
|
""" Create the PMOS and NMOS transistors. """
|
||||||
self.nmos = ptx(width=self.nmos_width,
|
self.nmos = ptx(width=self.nmos_width,
|
||||||
mults=self.tx_mults,
|
mults=self.tx_mults,
|
||||||
|
|
@ -149,7 +159,7 @@ class pinv(pgate.pgate):
|
||||||
connect_active=True)
|
connect_active=True)
|
||||||
self.add_mod(self.pmos)
|
self.add_mod(self.pmos)
|
||||||
|
|
||||||
def add_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. """
|
||||||
self.add_layout_pin_rect_center(text="gnd",
|
self.add_layout_pin_rect_center(text="gnd",
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
|
|
@ -161,26 +171,36 @@ class pinv(pgate.pgate):
|
||||||
offset=vector(0.5*self.width,self.height),
|
offset=vector(0.5*self.width,self.height),
|
||||||
width=self.width)
|
width=self.width)
|
||||||
|
|
||||||
def add_ptx(self):
|
|
||||||
|
|
||||||
|
def create_ptx(self):
|
||||||
"""
|
"""
|
||||||
Add PMOS and NMOS to the layout at the upper-most and lowest position
|
Create the PMOS and NMOS netlist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.pmos_inst=self.add_inst(name="pinv_pmos",
|
||||||
|
mod=self.pmos)
|
||||||
|
self.connect_inst(["Z", "A", "vdd", "vdd"])
|
||||||
|
|
||||||
|
self.nmos_inst=self.add_inst(name="pinv_nmos",
|
||||||
|
mod=self.nmos)
|
||||||
|
self.connect_inst(["Z", "A", "gnd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
|
def place_ptx(self):
|
||||||
|
"""
|
||||||
|
Place PMOS and NMOS to the layout at the upper-most and lowest position
|
||||||
to provide maximum routing in channel
|
to provide maximum routing in channel
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# place PMOS so it is half a poly spacing down from the top
|
# place PMOS so it is half a poly spacing down from the top
|
||||||
self.pmos_pos = self.pmos.active_offset.scale(1,0) \
|
self.pmos_pos = self.pmos.active_offset.scale(1,0) \
|
||||||
+ vector(0, self.height-self.pmos.active_height-self.top_bottom_space)
|
+ vector(0, self.height-self.pmos.active_height-self.top_bottom_space)
|
||||||
self.pmos_inst=self.add_inst(name="pinv_pmos",
|
self.pmos_inst.place(self.pmos_pos)
|
||||||
mod=self.pmos,
|
|
||||||
offset=self.pmos_pos)
|
|
||||||
self.connect_inst(["Z", "A", "vdd", "vdd"])
|
|
||||||
|
|
||||||
# place NMOS so that it is half a poly spacing up from the bottom
|
# place NMOS so that it is half a poly spacing up from the bottom
|
||||||
self.nmos_pos = self.nmos.active_offset.scale(1,0) + vector(0,self.top_bottom_space)
|
self.nmos_pos = self.nmos.active_offset.scale(1,0) + vector(0,self.top_bottom_space)
|
||||||
self.nmos_inst=self.add_inst(name="pinv_nmos",
|
self.nmos_inst.place(self.nmos_pos)
|
||||||
mod=self.nmos,
|
|
||||||
offset=self.nmos_pos)
|
|
||||||
self.connect_inst(["Z", "A", "gnd", "gnd"])
|
|
||||||
|
|
||||||
|
|
||||||
# Output position will be in between the PMOS and NMOS drains
|
# Output position will be in between the PMOS and NMOS drains
|
||||||
|
|
@ -259,5 +279,5 @@ class pinv(pgate.pgate):
|
||||||
"""Computes effective capacitance. Results in fF"""
|
"""Computes effective capacitance. Results in fF"""
|
||||||
c_load = load
|
c_load = load
|
||||||
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
|
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
|
||||||
transistion_prob = spice["inv_transisition_prob"]
|
transition_prob = spice["inv_transition_prob"]
|
||||||
return transistion_prob*(c_load + c_para)
|
return transition_prob*(c_load + c_para)
|
||||||
|
|
|
||||||
|
|
@ -11,55 +11,48 @@ class pinvbuf(design.design):
|
||||||
This is a simple inverter/buffer used for driving loads. It is
|
This is a simple inverter/buffer used for driving loads. It is
|
||||||
used in the column decoder for 1:2 decoding and as the clock buffer.
|
used in the column decoder for 1:2 decoding and as the clock buffer.
|
||||||
"""
|
"""
|
||||||
from importlib import reload
|
|
||||||
c = reload(__import__(OPTS.bitcell))
|
|
||||||
bitcell = getattr(c, OPTS.bitcell)
|
|
||||||
|
|
||||||
unique_id = 1
|
unique_id = 1
|
||||||
|
|
||||||
def __init__(self, driver_size=4, height=bitcell.height, name=""):
|
def __init__(self, driver_size=4, height=None, name=""):
|
||||||
|
|
||||||
stage_effort = 4
|
self.stage_effort = 4
|
||||||
|
self.row_height = height
|
||||||
# FIXME: Change the number of stages to support high drives.
|
# FIXME: Change the number of stages to support high drives.
|
||||||
|
|
||||||
# stage effort of 4 or less
|
# stage effort of 4 or less
|
||||||
# The pinvbuf has a FO of 2 for the first stage, so the second stage
|
# The pinvbuf has a FO of 2 for the first stage, so the second stage
|
||||||
# should be sized "half" to prevent loading of the first stage
|
# should be sized "half" to prevent loading of the first stage
|
||||||
predriver_size = max(int(driver_size/(stage_effort/2)),1)
|
self.driver_size = driver_size
|
||||||
|
self.predriver_size = max(int(self.driver_size/(self.stage_effort/2)),1)
|
||||||
if name=="":
|
if name=="":
|
||||||
name = "pinvbuf_{0}_{1}_{2}".format(predriver_size, driver_size, pinvbuf.unique_id)
|
name = "pinvbuf_{0}_{1}_{2}".format(self.predriver_size, self.driver_size, pinvbuf.unique_id)
|
||||||
pinvbuf.unique_id += 1
|
pinvbuf.unique_id += 1
|
||||||
|
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
debug.info(1, "Creating {}".format(self.name))
|
debug.info(1, "Creating {}".format(self.name))
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
# Shield the cap, but have at least a stage effort of 4
|
if not OPTS.netlist_only:
|
||||||
input_size = max(1,int(predriver_size/stage_effort))
|
self.create_layout()
|
||||||
self.inv = pinv(size=input_size, height=height)
|
|
||||||
self.add_mod(self.inv)
|
|
||||||
|
|
||||||
self.inv1 = pinv(size=predriver_size, height=height)
|
|
||||||
self.add_mod(self.inv1)
|
|
||||||
|
|
||||||
self.inv2 = pinv(size=driver_size, height=height)
|
|
||||||
self.add_mod(self.inv2)
|
def create_netlist(self):
|
||||||
|
self.add_pins()
|
||||||
|
self.add_modules()
|
||||||
|
self.create_insts()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
|
||||||
self.width = 2*self.inv1.width + self.inv2.width
|
self.width = 2*self.inv1.width + self.inv2.width
|
||||||
self.height = 2*self.inv1.height
|
self.height = 2*self.inv1.height
|
||||||
|
|
||||||
self.create_layout()
|
self.place_modules()
|
||||||
|
self.route_wires()
|
||||||
|
self.add_layout_pins()
|
||||||
|
|
||||||
self.offset_all_coordinates()
|
self.offset_all_coordinates()
|
||||||
|
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
self.add_pins()
|
|
||||||
self.add_insts()
|
|
||||||
self.add_wires()
|
|
||||||
self.add_layout_pins()
|
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
self.add_pin("A")
|
self.add_pin("A")
|
||||||
|
|
@ -68,35 +61,54 @@ class pinvbuf(design.design):
|
||||||
self.add_pin("vdd")
|
self.add_pin("vdd")
|
||||||
self.add_pin("gnd")
|
self.add_pin("gnd")
|
||||||
|
|
||||||
def add_insts(self):
|
def add_modules(self):
|
||||||
# Add INV1 to the right (capacitance shield)
|
|
||||||
|
# Shield the cap, but have at least a stage effort of 4
|
||||||
|
input_size = max(1,int(self.predriver_size/self.stage_effort))
|
||||||
|
self.inv = pinv(size=input_size, height=self.row_height)
|
||||||
|
self.add_mod(self.inv)
|
||||||
|
|
||||||
|
self.inv1 = pinv(size=self.predriver_size, height=self.row_height)
|
||||||
|
self.add_mod(self.inv1)
|
||||||
|
|
||||||
|
self.inv2 = pinv(size=self.driver_size, height=self.row_height)
|
||||||
|
self.add_mod(self.inv2)
|
||||||
|
|
||||||
|
def create_insts(self):
|
||||||
|
# Create INV1 (capacitance shield)
|
||||||
self.inv1_inst=self.add_inst(name="buf_inv1",
|
self.inv1_inst=self.add_inst(name="buf_inv1",
|
||||||
mod=self.inv,
|
mod=self.inv)
|
||||||
offset=vector(0,0))
|
|
||||||
self.connect_inst(["A", "zb_int", "vdd", "gnd"])
|
self.connect_inst(["A", "zb_int", "vdd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
# Add INV2 to the right
|
|
||||||
self.inv2_inst=self.add_inst(name="buf_inv2",
|
self.inv2_inst=self.add_inst(name="buf_inv2",
|
||||||
mod=self.inv1,
|
mod=self.inv1)
|
||||||
offset=vector(self.inv1_inst.rx(),0))
|
|
||||||
self.connect_inst(["zb_int", "z_int", "vdd", "gnd"])
|
self.connect_inst(["zb_int", "z_int", "vdd", "gnd"])
|
||||||
|
|
||||||
# Add INV3 to the right
|
|
||||||
self.inv3_inst=self.add_inst(name="buf_inv3",
|
self.inv3_inst=self.add_inst(name="buf_inv3",
|
||||||
mod=self.inv2,
|
mod=self.inv2)
|
||||||
offset=vector(self.inv2_inst.rx(),0))
|
|
||||||
self.connect_inst(["z_int", "Zb", "vdd", "gnd"])
|
self.connect_inst(["z_int", "Zb", "vdd", "gnd"])
|
||||||
|
|
||||||
# Add INV4 to the bottom
|
|
||||||
self.inv4_inst=self.add_inst(name="buf_inv4",
|
self.inv4_inst=self.add_inst(name="buf_inv4",
|
||||||
mod=self.inv2,
|
mod=self.inv2)
|
||||||
offset=vector(self.inv2_inst.rx(),2*self.inv2.height),
|
|
||||||
mirror = "MX")
|
|
||||||
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
|
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
|
||||||
|
|
||||||
|
def place_modules(self):
|
||||||
|
# Add INV1 to the left (capacitance shield)
|
||||||
|
self.inv1_inst.place(vector(0,0))
|
||||||
|
|
||||||
|
# Add INV2 to the right of INV1
|
||||||
|
self.inv2_inst.place(vector(self.inv1_inst.rx(),0))
|
||||||
|
|
||||||
|
# Add INV3 to the right of INV2
|
||||||
|
self.inv3_inst.place(vector(self.inv2_inst.rx(),0))
|
||||||
|
|
||||||
|
# Add INV4 flipped to the bottom aligned with INV2
|
||||||
|
self.inv4_inst.place(offset=vector(self.inv2_inst.rx(),2*self.inv2.height),
|
||||||
|
mirror = "MX")
|
||||||
|
|
||||||
|
|
||||||
def add_wires(self):
|
def route_wires(self):
|
||||||
# inv1 Z to inv2 A
|
# inv1 Z to inv2 A
|
||||||
z1_pin = self.inv1_inst.get_pin("Z")
|
z1_pin = self.inv1_inst.get_pin("Z")
|
||||||
a2_pin = self.inv2_inst.get_pin("A")
|
a2_pin = self.inv2_inst.get_pin("A")
|
||||||
|
|
|
||||||
|
|
@ -12,52 +12,53 @@ class pnand2(pgate.pgate):
|
||||||
This model use ptx to generate a 2-input nand within a cetrain height.
|
This model use ptx to generate a 2-input nand within a cetrain height.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from importlib import reload
|
|
||||||
c = reload(__import__(OPTS.bitcell))
|
|
||||||
bitcell = getattr(c, OPTS.bitcell)
|
|
||||||
|
|
||||||
unique_id = 1
|
unique_id = 1
|
||||||
|
|
||||||
def __init__(self, size=1, height=bitcell.height):
|
def __init__(self, size=1, height=None):
|
||||||
""" Creates a cell for a simple 2 input nand """
|
""" Creates a cell for a simple 2 input nand """
|
||||||
name = "pnand2_{0}".format(pnand2.unique_id)
|
name = "pnand2_{0}".format(pnand2.unique_id)
|
||||||
pnand2.unique_id += 1
|
pnand2.unique_id += 1
|
||||||
pgate.pgate.__init__(self, name)
|
pgate.pgate.__init__(self, name, height)
|
||||||
debug.info(2, "create pnand2 structure {0} with size of {1}".format(name, size))
|
debug.info(2, "create pnand2 structure {0} with size of {1}".format(name, size))
|
||||||
|
|
||||||
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")
|
||||||
self.height = height
|
|
||||||
|
|
||||||
# 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.")
|
||||||
self.tx_mults = 1
|
self.tx_mults = 1
|
||||||
|
|
||||||
self.add_pins()
|
self.create_netlist()
|
||||||
self.create_layout()
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
#self.DRC_LVS()
|
#self.DRC_LVS()
|
||||||
|
|
||||||
|
|
||||||
def add_pins(self):
|
def create_netlist(self):
|
||||||
""" Adds pins for spice netlist """
|
self.add_pins()
|
||||||
self.add_pin_list(["A", "B", "Z", "vdd", "gnd"])
|
self.add_ptx()
|
||||||
|
self.create_ptx()
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
""" Calls all functions related to the generation of the layout """
|
""" Calls all functions related to the generation of the layout """
|
||||||
|
|
||||||
self.create_ptx()
|
|
||||||
self.setup_layout_constants()
|
self.setup_layout_constants()
|
||||||
self.add_supply_rails()
|
self.route_supply_rails()
|
||||||
self.add_ptx()
|
self.place_ptx()
|
||||||
self.connect_rails()
|
self.connect_rails()
|
||||||
self.add_well_contacts()
|
self.add_well_contacts()
|
||||||
self.extend_wells(self.well_pos)
|
self.extend_wells(self.well_pos)
|
||||||
self.route_inputs()
|
self.route_inputs()
|
||||||
self.route_output()
|
self.route_output()
|
||||||
|
|
||||||
def create_ptx(self):
|
def add_pins(self):
|
||||||
|
""" Adds pins for spice netlist """
|
||||||
|
self.add_pin_list(["A", "B", "Z", "vdd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
|
def add_ptx(self):
|
||||||
""" Create the PMOS and NMOS transistors. """
|
""" Create the PMOS and NMOS transistors. """
|
||||||
self.nmos = ptx(width=self.nmos_width,
|
self.nmos = ptx(width=self.nmos_width,
|
||||||
mults=self.tx_mults,
|
mults=self.tx_mults,
|
||||||
|
|
@ -90,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.
|
||||||
|
|
@ -99,9 +100,9 @@ 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 add_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. """
|
||||||
self.add_layout_pin_rect_center(text="gnd",
|
self.add_layout_pin_rect_center(text="gnd",
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
|
|
@ -113,37 +114,47 @@ class pnand2(pgate.pgate):
|
||||||
offset=vector(0.5*self.width,self.height),
|
offset=vector(0.5*self.width,self.height),
|
||||||
width=self.width)
|
width=self.width)
|
||||||
|
|
||||||
def add_ptx(self):
|
def create_ptx(self):
|
||||||
"""
|
"""
|
||||||
Add PMOS and NMOS to the layout at the upper-most and lowest position
|
Add PMOS and NMOS to the netlist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.pmos1_inst=self.add_inst(name="pnand2_pmos1",
|
||||||
|
mod=self.pmos)
|
||||||
|
self.connect_inst(["vdd", "A", "Z", "vdd"])
|
||||||
|
|
||||||
|
self.pmos2_inst = self.add_inst(name="pnand2_pmos2",
|
||||||
|
mod=self.pmos)
|
||||||
|
self.connect_inst(["Z", "B", "vdd", "vdd"])
|
||||||
|
|
||||||
|
self.nmos1_inst=self.add_inst(name="pnand2_nmos1",
|
||||||
|
mod=self.nmos)
|
||||||
|
self.connect_inst(["Z", "B", "net1", "gnd"])
|
||||||
|
|
||||||
|
self.nmos2_inst=self.add_inst(name="pnand2_nmos2",
|
||||||
|
mod=self.nmos)
|
||||||
|
self.connect_inst(["net1", "A", "gnd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
|
def place_ptx(self):
|
||||||
|
"""
|
||||||
|
Place PMOS and NMOS to the layout at the upper-most and lowest position
|
||||||
to provide maximum routing in channel
|
to provide maximum routing in channel
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pmos1_pos = vector(self.pmos.active_offset.x,
|
pmos1_pos = vector(self.pmos.active_offset.x,
|
||||||
self.height - self.pmos.active_height - self.top_bottom_space)
|
self.height - self.pmos.active_height - self.top_bottom_space)
|
||||||
self.pmos1_inst=self.add_inst(name="pnand2_pmos1",
|
self.pmos1_inst.place(pmos1_pos)
|
||||||
mod=self.pmos,
|
|
||||||
offset=pmos1_pos)
|
|
||||||
self.connect_inst(["vdd", "A", "Z", "vdd"])
|
|
||||||
|
|
||||||
self.pmos2_pos = pmos1_pos + self.overlap_offset
|
self.pmos2_pos = pmos1_pos + self.overlap_offset
|
||||||
self.pmos2_inst = self.add_inst(name="pnand2_pmos2",
|
self.pmos2_inst.place(self.pmos2_pos)
|
||||||
mod=self.pmos,
|
|
||||||
offset=self.pmos2_pos)
|
|
||||||
self.connect_inst(["Z", "B", "vdd", "vdd"])
|
|
||||||
|
|
||||||
|
|
||||||
nmos1_pos = vector(self.pmos.active_offset.x, self.top_bottom_space)
|
nmos1_pos = vector(self.pmos.active_offset.x, self.top_bottom_space)
|
||||||
self.nmos1_inst=self.add_inst(name="pnand2_nmos1",
|
self.nmos1_inst.place(nmos1_pos)
|
||||||
mod=self.nmos,
|
|
||||||
offset=nmos1_pos)
|
|
||||||
self.connect_inst(["Z", "B", "net1", "gnd"])
|
|
||||||
|
|
||||||
self.nmos2_pos = nmos1_pos + self.overlap_offset
|
self.nmos2_pos = nmos1_pos + self.overlap_offset
|
||||||
self.nmos2_inst=self.add_inst(name="pnand2_nmos2",
|
self.nmos2_inst.place(self.nmos2_pos)
|
||||||
mod=self.nmos,
|
|
||||||
offset=self.nmos2_pos)
|
|
||||||
self.connect_inst(["net1", "A", "gnd", "gnd"])
|
|
||||||
|
|
||||||
# Output position will be in between the PMOS and NMOS
|
# Output position will be in between the PMOS and NMOS
|
||||||
self.output_pos = vector(0,0.5*(pmos1_pos.y+nmos1_pos.y+self.nmos.active_height))
|
self.output_pos = vector(0,0.5*(pmos1_pos.y+nmos1_pos.y+self.nmos.active_height))
|
||||||
|
|
@ -229,5 +240,5 @@ class pnand2(pgate.pgate):
|
||||||
"""Computes effective capacitance. Results in fF"""
|
"""Computes effective capacitance. Results in fF"""
|
||||||
c_load = load
|
c_load = load
|
||||||
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
|
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
|
||||||
transistion_prob = spice["nand2_transisition_prob"]
|
transition_prob = spice["nand2_transition_prob"]
|
||||||
return transistion_prob*(c_load + c_para)
|
return transition_prob*(c_load + c_para)
|
||||||
|
|
|
||||||
|
|
@ -12,54 +12,53 @@ class pnand3(pgate.pgate):
|
||||||
This model use ptx to generate a 2-input nand within a cetrain height.
|
This model use ptx to generate a 2-input nand within a cetrain height.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from importlib import reload
|
|
||||||
c = reload(__import__(OPTS.bitcell))
|
|
||||||
bitcell = getattr(c, OPTS.bitcell)
|
|
||||||
|
|
||||||
unique_id = 1
|
unique_id = 1
|
||||||
|
|
||||||
def __init__(self, size=1, height=bitcell.height):
|
def __init__(self, size=1, height=None):
|
||||||
""" Creates a cell for a simple 3 input nand """
|
""" Creates a cell for a simple 3 input nand """
|
||||||
name = "pnand3_{0}".format(pnand3.unique_id)
|
name = "pnand3_{0}".format(pnand3.unique_id)
|
||||||
pnand3.unique_id += 1
|
pnand3.unique_id += 1
|
||||||
pgate.pgate.__init__(self, name)
|
pgate.pgate.__init__(self, name, height)
|
||||||
debug.info(2, "create pnand3 structure {0} with size of {1}".format(name, size))
|
debug.info(2, "create pnand3 structure {0} with size of {1}".format(name, size))
|
||||||
|
|
||||||
# We have trouble pitch matching a 3x sizes to the bitcell...
|
# We have trouble pitch matching a 3x sizes to the bitcell...
|
||||||
# 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")
|
||||||
self.height = height
|
|
||||||
|
|
||||||
# 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.")
|
||||||
self.tx_mults = 1
|
self.tx_mults = 1
|
||||||
|
|
||||||
self.add_pins()
|
self.create_netlist()
|
||||||
self.create_layout()
|
if not OPTS.netlist_only:
|
||||||
#self.DRC_LVS()
|
self.create_layout()
|
||||||
|
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
""" Adds pins for spice netlist """
|
""" Adds pins for spice netlist """
|
||||||
self.add_pin_list(["A", "B", "C", "Z", "vdd", "gnd"])
|
self.add_pin_list(["A", "B", "C", "Z", "vdd", "gnd"])
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_pins()
|
||||||
|
self.add_ptx()
|
||||||
|
self.create_ptx()
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
""" Calls all functions related to the generation of the layout """
|
""" Calls all functions related to the generation of the layout """
|
||||||
|
|
||||||
self.create_ptx()
|
|
||||||
self.setup_layout_constants()
|
self.setup_layout_constants()
|
||||||
self.add_supply_rails()
|
self.route_supply_rails()
|
||||||
self.add_ptx()
|
self.place_ptx()
|
||||||
self.connect_rails()
|
self.connect_rails()
|
||||||
self.add_well_contacts()
|
self.add_well_contacts()
|
||||||
self.extend_wells(self.well_pos)
|
self.extend_wells(self.well_pos)
|
||||||
self.route_inputs()
|
self.route_inputs()
|
||||||
self.route_output()
|
self.route_output()
|
||||||
|
|
||||||
def create_ptx(self):
|
def add_ptx(self):
|
||||||
""" Create the PMOS and NMOS transistors. """
|
""" Create the PMOS and NMOS transistors. """
|
||||||
self.nmos = ptx(width=self.nmos_width,
|
self.nmos = ptx(width=self.nmos_width,
|
||||||
mults=self.tx_mults,
|
mults=self.tx_mults,
|
||||||
|
|
@ -84,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.
|
||||||
|
|
@ -97,9 +96,9 @@ 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 add_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. """
|
||||||
self.add_layout_pin_rect_center(text="gnd",
|
self.add_layout_pin_rect_center(text="gnd",
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
|
|
@ -111,50 +110,61 @@ class pnand3(pgate.pgate):
|
||||||
offset=vector(0.5*self.width,self.height),
|
offset=vector(0.5*self.width,self.height),
|
||||||
width=self.width)
|
width=self.width)
|
||||||
|
|
||||||
def add_ptx(self):
|
def create_ptx(self):
|
||||||
"""
|
"""
|
||||||
Add PMOS and NMOS to the layout at the upper-most and lowest position
|
Create the PMOS and NMOS in the netlist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.pmos1_inst=self.add_inst(name="pnand3_pmos1",
|
||||||
|
mod=self.pmos)
|
||||||
|
self.connect_inst(["vdd", "A", "Z", "vdd"])
|
||||||
|
|
||||||
|
self.pmos2_inst = self.add_inst(name="pnand3_pmos2",
|
||||||
|
mod=self.pmos)
|
||||||
|
self.connect_inst(["Z", "B", "vdd", "vdd"])
|
||||||
|
|
||||||
|
self.pmos3_inst = self.add_inst(name="pnand3_pmos3",
|
||||||
|
mod=self.pmos)
|
||||||
|
self.connect_inst(["Z", "C", "vdd", "vdd"])
|
||||||
|
|
||||||
|
self.nmos1_inst=self.add_inst(name="pnand3_nmos1",
|
||||||
|
mod=self.nmos)
|
||||||
|
self.connect_inst(["Z", "C", "net1", "gnd"])
|
||||||
|
|
||||||
|
self.nmos2_inst=self.add_inst(name="pnand3_nmos2",
|
||||||
|
mod=self.nmos)
|
||||||
|
self.connect_inst(["net1", "B", "net2", "gnd"])
|
||||||
|
|
||||||
|
self.nmos3_inst=self.add_inst(name="pnand3_nmos3",
|
||||||
|
mod=self.nmos)
|
||||||
|
self.connect_inst(["net2", "A", "gnd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
|
def place_ptx(self):
|
||||||
|
"""
|
||||||
|
Place the PMOS and NMOS in the layout at the upper-most and lowest position
|
||||||
to provide maximum routing in channel
|
to provide maximum routing in channel
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pmos1_pos = vector(self.pmos.active_offset.x,
|
pmos1_pos = vector(self.pmos.active_offset.x,
|
||||||
self.height - self.pmos.active_height - self.top_bottom_space)
|
self.height - self.pmos.active_height - self.top_bottom_space)
|
||||||
self.pmos1_inst=self.add_inst(name="pnand3_pmos1",
|
self.pmos1_inst.place(pmos1_pos)
|
||||||
mod=self.pmos,
|
|
||||||
offset=pmos1_pos)
|
|
||||||
self.connect_inst(["vdd", "A", "Z", "vdd"])
|
|
||||||
|
|
||||||
pmos2_pos = pmos1_pos + self.overlap_offset
|
pmos2_pos = pmos1_pos + self.overlap_offset
|
||||||
self.pmos2_inst = self.add_inst(name="pnand3_pmos2",
|
self.pmos2_inst.place(pmos2_pos)
|
||||||
mod=self.pmos,
|
|
||||||
offset=pmos2_pos)
|
|
||||||
self.connect_inst(["Z", "B", "vdd", "vdd"])
|
|
||||||
|
|
||||||
self.pmos3_pos = pmos2_pos + self.overlap_offset
|
self.pmos3_pos = pmos2_pos + self.overlap_offset
|
||||||
self.pmos3_inst = self.add_inst(name="pnand3_pmos3",
|
self.pmos3_inst.place(self.pmos3_pos)
|
||||||
mod=self.pmos,
|
|
||||||
offset=self.pmos3_pos)
|
|
||||||
self.connect_inst(["Z", "C", "vdd", "vdd"])
|
|
||||||
|
|
||||||
|
|
||||||
nmos1_pos = vector(self.pmos.active_offset.x, self.top_bottom_space)
|
nmos1_pos = vector(self.pmos.active_offset.x, self.top_bottom_space)
|
||||||
self.nmos1_inst=self.add_inst(name="pnand3_nmos1",
|
self.nmos1_inst.place(nmos1_pos)
|
||||||
mod=self.nmos,
|
|
||||||
offset=nmos1_pos)
|
|
||||||
self.connect_inst(["Z", "C", "net1", "gnd"])
|
|
||||||
|
|
||||||
nmos2_pos = nmos1_pos + self.overlap_offset
|
nmos2_pos = nmos1_pos + self.overlap_offset
|
||||||
self.nmos2_inst=self.add_inst(name="pnand3_nmos2",
|
self.nmos2_inst.place(nmos2_pos)
|
||||||
mod=self.nmos,
|
|
||||||
offset=nmos2_pos)
|
|
||||||
self.connect_inst(["net1", "B", "net2", "gnd"])
|
|
||||||
|
|
||||||
|
|
||||||
self.nmos3_pos = nmos2_pos + self.overlap_offset
|
self.nmos3_pos = nmos2_pos + self.overlap_offset
|
||||||
self.nmos3_inst=self.add_inst(name="pnand3_nmos3",
|
self.nmos3_inst.place(self.nmos3_pos)
|
||||||
mod=self.nmos,
|
|
||||||
offset=self.nmos3_pos)
|
|
||||||
self.connect_inst(["net2", "A", "gnd", "gnd"])
|
|
||||||
|
|
||||||
# This should be placed at the top of the NMOS well
|
# This should be placed at the top of the NMOS well
|
||||||
self.well_pos = vector(0,self.nmos1_inst.uy())
|
self.well_pos = vector(0,self.nmos1_inst.uy())
|
||||||
|
|
@ -181,7 +191,7 @@ class pnand3(pgate.pgate):
|
||||||
metal_spacing = max(self.m1_space + self.m1_width, self.m2_space + self.m2_width,
|
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")
|
||||||
|
|
||||||
|
|
@ -249,5 +259,5 @@ class pnand3(pgate.pgate):
|
||||||
"""Computes effective capacitance. Results in fF"""
|
"""Computes effective capacitance. Results in fF"""
|
||||||
c_load = load
|
c_load = load
|
||||||
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
|
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
|
||||||
transistion_prob = spice["nand3_transisition_prob"]
|
transition_prob = spice["nand3_transition_prob"]
|
||||||
return transistion_prob*(c_load + c_para)
|
return transition_prob*(c_load + c_para)
|
||||||
|
|
|
||||||
|
|
@ -12,31 +12,26 @@ class pnor2(pgate.pgate):
|
||||||
This model use ptx to generate a 2-input nor within a cetrain height.
|
This model use ptx to generate a 2-input nor within a cetrain height.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from importlib import reload
|
|
||||||
c = reload(__import__(OPTS.bitcell))
|
|
||||||
bitcell = getattr(c, OPTS.bitcell)
|
|
||||||
|
|
||||||
unique_id = 1
|
unique_id = 1
|
||||||
|
|
||||||
def __init__(self, size=1, height=bitcell.height):
|
def __init__(self, size=1, height=None):
|
||||||
""" Creates a cell for a simple 2 input nor """
|
""" Creates a cell for a simple 2 input nor """
|
||||||
name = "pnor2_{0}".format(pnor2.unique_id)
|
name = "pnor2_{0}".format(pnor2.unique_id)
|
||||||
pnor2.unique_id += 1
|
pnor2.unique_id += 1
|
||||||
pgate.pgate.__init__(self, name)
|
pgate.pgate.__init__(self, name, height)
|
||||||
debug.info(2, "create pnor2 structure {0} with size of {1}".format(name, size))
|
debug.info(2, "create pnor2 structure {0} with size of {1}".format(name, size))
|
||||||
|
|
||||||
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")
|
||||||
self.height = height
|
|
||||||
|
|
||||||
# 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.")
|
||||||
self.tx_mults = 1
|
self.tx_mults = 1
|
||||||
|
|
||||||
self.add_pins()
|
self.create_netlist()
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
#self.DRC_LVS()
|
#self.DRC_LVS()
|
||||||
|
|
||||||
|
|
@ -45,6 +40,9 @@ class pnor2(pgate.pgate):
|
||||||
""" Adds pins for spice netlist """
|
""" Adds pins for spice netlist """
|
||||||
self.add_pin_list(["A", "B", "Z", "vdd", "gnd"])
|
self.add_pin_list(["A", "B", "Z", "vdd", "gnd"])
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_pins()
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
""" Calls all functions related to the generation of the layout """
|
""" Calls all functions related to the generation of the layout """
|
||||||
|
|
||||||
|
|
@ -94,7 +92,7 @@ class pnor2(pgate.pgate):
|
||||||
# Two PMOS devices and a well contact. Separation between each.
|
# 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.
|
||||||
|
|
@ -103,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. """
|
||||||
|
|
@ -239,6 +237,6 @@ class pnor2(pgate.pgate):
|
||||||
"""Computes effective capacitance. Results in fF"""
|
"""Computes effective capacitance. Results in fF"""
|
||||||
c_load = load
|
c_load = load
|
||||||
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
|
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
|
||||||
transistion_prob = spice["nor2_transisition_prob"]
|
transition_prob = spice["nor2_transition_prob"]
|
||||||
return transistion_prob*(c_load + c_para)
|
return transition_prob*(c_load + c_para)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,11 @@ class precharge(pgate.pgate):
|
||||||
This module implements the precharge bitline cell used in the design.
|
This module implements the precharge bitline cell used in the design.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, size=1):
|
unique_id = 1
|
||||||
|
|
||||||
|
def __init__(self, name, size=1, bitcell_bl="bl", bitcell_br="br"):
|
||||||
|
name = name+"_{}".format(precharge.unique_id)
|
||||||
|
precharge.unique_id += 1
|
||||||
pgate.pgate.__init__(self, name)
|
pgate.pgate.__init__(self, name)
|
||||||
debug.info(2, "create single precharge cell: {0}".format(name))
|
debug.info(2, "create single precharge cell: {0}".format(name))
|
||||||
|
|
||||||
|
|
@ -24,87 +28,110 @@ class precharge(pgate.pgate):
|
||||||
self.beta = parameter["beta"]
|
self.beta = parameter["beta"]
|
||||||
self.ptx_width = self.beta*parameter["min_tx_size"]
|
self.ptx_width = self.beta*parameter["min_tx_size"]
|
||||||
self.width = self.bitcell.width
|
self.width = self.bitcell.width
|
||||||
|
self.bitcell_bl = bitcell_bl
|
||||||
|
self.bitcell_br = bitcell_br
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
self.add_pins()
|
self.add_pins()
|
||||||
self.create_layout()
|
self.add_ptx()
|
||||||
|
self.create_ptx()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
self.place_ptx()
|
||||||
|
self.connect_poly()
|
||||||
|
self.route_en()
|
||||||
|
self.place_nwell_and_contact()
|
||||||
|
self.route_vdd_rail()
|
||||||
|
self.route_bitlines()
|
||||||
|
self.connect_to_bitlines()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
self.add_pin_list(["bl", "br", "en", "vdd"])
|
self.add_pin_list(["bl", "br", "en", "vdd"])
|
||||||
|
|
||||||
def create_layout(self):
|
def add_ptx(self):
|
||||||
self.create_ptx()
|
"""
|
||||||
self.add_ptx()
|
Initializes the upper and lower pmos
|
||||||
self.connect_poly()
|
"""
|
||||||
self.add_en()
|
|
||||||
self.add_nwell_and_contact()
|
|
||||||
self.add_vdd_rail()
|
|
||||||
self.add_bitlines()
|
|
||||||
self.connect_to_bitlines()
|
|
||||||
|
|
||||||
def create_ptx(self):
|
|
||||||
"""Initializes the upper and lower pmos"""
|
|
||||||
self.pmos = ptx(width=self.ptx_width,
|
self.pmos = ptx(width=self.ptx_width,
|
||||||
tx_type="pmos")
|
tx_type="pmos")
|
||||||
self.add_mod(self.pmos)
|
self.add_mod(self.pmos)
|
||||||
|
|
||||||
# Compute the other pmos2 location, but determining offset to overlap the
|
|
||||||
# source and drain pins
|
|
||||||
self.overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def add_vdd_rail(self):
|
def route_vdd_rail(self):
|
||||||
"""Adds a vdd rail at the top of the cell"""
|
"""
|
||||||
# adds the rail across the width of the cell
|
Adds a vdd rail at the top of the cell
|
||||||
vdd_position = vector(0, self.height - self.m1_width)
|
"""
|
||||||
self.add_rect(layer="metal1",
|
|
||||||
offset=vdd_position,
|
# Adds the rail across the width of the cell
|
||||||
width=self.width,
|
vdd_position = vector(0.5*self.width, self.height)
|
||||||
height=self.m1_width)
|
self.add_rect_center(layer="metal1",
|
||||||
|
offset=vdd_position,
|
||||||
|
width=self.width,
|
||||||
|
height=self.m1_width)
|
||||||
|
|
||||||
pmos_pin = self.upper_pmos2_inst.get_pin("S")
|
pmos_pin = self.upper_pmos2_inst.get_pin("S")
|
||||||
# center of vdd rail
|
# center of vdd rail
|
||||||
vdd_pos = vector(pmos_pin.cx(), vdd_position.y + 0.5*self.m1_width)
|
pmos_vdd_pos = vector(pmos_pin.cx(), vdd_position.y)
|
||||||
self.add_path("metal1", [pmos_pin.uc(), vdd_pos])
|
self.add_path("metal1", [pmos_pin.uc(), pmos_vdd_pos])
|
||||||
|
|
||||||
# Add the M1->M2->M3 stack at the left edge
|
# Add vdd pin above the transistor
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_power_pin("vdd", pmos_pin.center(), rotate=0)
|
||||||
offset=vdd_pos.scale(0,1))
|
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
|
||||||
offset=vdd_pos.scale(0,1))
|
|
||||||
self.add_layout_pin_rect_center(text="vdd",
|
|
||||||
layer="metal3",
|
|
||||||
offset=vdd_pos.scale(0,1))
|
|
||||||
|
|
||||||
|
|
||||||
def add_ptx(self):
|
def create_ptx(self):
|
||||||
"""Adds both the upper_pmos and lower_pmos to the module"""
|
"""
|
||||||
# adds the lower pmos to layout
|
Create both the upper_pmos and lower_pmos to the module
|
||||||
#base = vector(self.width - 2*self.pmos.width + self.overlap_offset.x, 0)
|
"""
|
||||||
self.lower_pmos_position = vector(self.bitcell.get_pin("bl").lx(),
|
|
||||||
self.pmos.active_offset.y)
|
|
||||||
self.lower_pmos_inst=self.add_inst(name="lower_pmos",
|
self.lower_pmos_inst=self.add_inst(name="lower_pmos",
|
||||||
mod=self.pmos,
|
mod=self.pmos)
|
||||||
offset=self.lower_pmos_position)
|
|
||||||
self.connect_inst(["bl", "en", "br", "vdd"])
|
self.connect_inst(["bl", "en", "br", "vdd"])
|
||||||
|
|
||||||
|
self.upper_pmos1_inst=self.add_inst(name="upper_pmos1",
|
||||||
|
mod=self.pmos)
|
||||||
|
self.connect_inst(["bl", "en", "vdd", "vdd"])
|
||||||
|
|
||||||
|
self.upper_pmos2_inst=self.add_inst(name="upper_pmos2",
|
||||||
|
mod=self.pmos)
|
||||||
|
self.connect_inst(["br", "en", "vdd", "vdd"])
|
||||||
|
|
||||||
|
|
||||||
|
def place_ptx(self):
|
||||||
|
"""
|
||||||
|
Place both the upper_pmos and lower_pmos to the module
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Compute the other pmos2 location, but determining offset to overlap the
|
||||||
|
# source and drain pins
|
||||||
|
overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll()
|
||||||
|
# This is how much the contact is placed inside the ptx active
|
||||||
|
contact_xdiff = self.pmos.get_pin("S").lx()
|
||||||
|
|
||||||
|
# adds the lower pmos to layout
|
||||||
|
bl_xoffset = self.bitcell.get_pin(self.bitcell_bl).lx()
|
||||||
|
self.lower_pmos_position = vector(max(bl_xoffset - contact_xdiff, self.well_enclose_active),
|
||||||
|
self.pmos.active_offset.y)
|
||||||
|
self.lower_pmos_inst.place(self.lower_pmos_position)
|
||||||
|
|
||||||
# adds the upper pmos(s) to layout
|
# adds the upper pmos(s) to layout
|
||||||
ydiff = self.pmos.height + 2*self.m1_space + contact.poly.width
|
ydiff = self.pmos.height + 2*self.m1_space + contact.poly.width
|
||||||
self.upper_pmos1_pos = self.lower_pmos_position + vector(0, ydiff)
|
self.upper_pmos1_pos = self.lower_pmos_position + vector(0, ydiff)
|
||||||
self.upper_pmos1_inst=self.add_inst(name="upper_pmos1",
|
self.upper_pmos1_inst.place(self.upper_pmos1_pos)
|
||||||
mod=self.pmos,
|
|
||||||
offset=self.upper_pmos1_pos)
|
|
||||||
self.connect_inst(["bl", "en", "vdd", "vdd"])
|
|
||||||
|
|
||||||
upper_pmos2_pos = self.upper_pmos1_pos + self.overlap_offset
|
|
||||||
self.upper_pmos2_inst=self.add_inst(name="upper_pmos2",
|
|
||||||
mod=self.pmos,
|
|
||||||
offset=upper_pmos2_pos)
|
|
||||||
self.connect_inst(["br", "en", "vdd", "vdd"])
|
|
||||||
|
|
||||||
|
upper_pmos2_pos = self.upper_pmos1_pos + overlap_offset
|
||||||
|
self.upper_pmos2_inst.place(upper_pmos2_pos)
|
||||||
|
|
||||||
def connect_poly(self):
|
def connect_poly(self):
|
||||||
"""Connects the upper and lower pmos together"""
|
"""
|
||||||
|
Connects the upper and lower pmos together
|
||||||
|
"""
|
||||||
|
|
||||||
offset = self.lower_pmos_inst.get_pin("G").ll()
|
offset = self.lower_pmos_inst.get_pin("G").ll()
|
||||||
# connects the top and bottom pmos' gates together
|
# connects the top and bottom pmos' gates together
|
||||||
|
|
@ -122,8 +149,11 @@ class precharge(pgate.pgate):
|
||||||
width=xlength,
|
width=xlength,
|
||||||
height=self.poly_width)
|
height=self.poly_width)
|
||||||
|
|
||||||
def add_en(self):
|
def route_en(self):
|
||||||
"""Adds the en input rail, en contact/vias, and connects to the pmos"""
|
"""
|
||||||
|
Adds the en input rail, en contact/vias, and connects to the pmos
|
||||||
|
"""
|
||||||
|
|
||||||
# adds the en contact to connect the gates to the en rail on metal1
|
# adds the en contact to connect the gates to the en rail on metal1
|
||||||
offset = self.lower_pmos_inst.get_pin("G").ul() + vector(0,0.5*self.poly_space)
|
offset = self.lower_pmos_inst.get_pin("G").ul() + vector(0,0.5*self.poly_space)
|
||||||
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||||
|
|
@ -137,78 +167,99 @@ class precharge(pgate.pgate):
|
||||||
end=offset.scale(0,1)+vector(self.width,0))
|
end=offset.scale(0,1)+vector(self.width,0))
|
||||||
|
|
||||||
|
|
||||||
def add_nwell_and_contact(self):
|
def place_nwell_and_contact(self):
|
||||||
"""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",
|
||||||
well_type="n")
|
well_type="n")
|
||||||
|
|
||||||
|
# leave an extra pitch for the height
|
||||||
|
self.height = well_contact_pos.y + contact.well.height + self.m1_pitch
|
||||||
|
|
||||||
self.height = well_contact_pos.y + contact.well.height
|
# nwell should span the whole design since it is pmos only
|
||||||
|
|
||||||
self.add_rect(layer="nwell",
|
self.add_rect(layer="nwell",
|
||||||
offset=vector(0,0),
|
offset=vector(0,0),
|
||||||
width=self.width,
|
width=self.width,
|
||||||
height=self.height)
|
height=self.height)
|
||||||
|
|
||||||
|
|
||||||
def add_bitlines(self):
|
def route_bitlines(self):
|
||||||
"""Adds both bit-line and bit-line-bar to the module"""
|
"""
|
||||||
|
Adds both bit-line and bit-line-bar to the module
|
||||||
|
"""
|
||||||
|
|
||||||
# adds the BL on metal 2
|
# adds the BL on metal 2
|
||||||
offset = vector(self.bitcell.get_pin("bl").cx(),0) - vector(0.5 * self.m2_width,0)
|
offset = vector(self.bitcell.get_pin(self.bitcell_bl).cx(),0) - vector(0.5 * self.m2_width,0)
|
||||||
self.add_layout_pin(text="bl",
|
self.bl_pin = 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
|
||||||
offset = vector(self.bitcell.get_pin("br").cx(),0) - vector(0.5 * self.m2_width,0)
|
offset = vector(self.bitcell.get_pin(self.bitcell_br).cx(),0) - vector(0.5 * self.m2_width,0)
|
||||||
self.add_layout_pin(text="br",
|
self.br_pin = 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):
|
||||||
|
"""
|
||||||
|
Connect the bitlines to the devices
|
||||||
|
"""
|
||||||
self.add_bitline_contacts()
|
self.add_bitline_contacts()
|
||||||
self.connect_pmos(self.lower_pmos_inst.get_pin("S"),self.get_pin("bl"))
|
self.connect_pmos_m2(self.lower_pmos_inst.get_pin("S"),self.get_pin("bl"))
|
||||||
self.connect_pmos(self.lower_pmos_inst.get_pin("D"),self.get_pin("br"))
|
self.connect_pmos_m2(self.upper_pmos1_inst.get_pin("S"),self.get_pin("bl"))
|
||||||
self.connect_pmos(self.upper_pmos1_inst.get_pin("S"),self.get_pin("bl"))
|
self.connect_pmos_m1(self.lower_pmos_inst.get_pin("D"),self.get_pin("br"))
|
||||||
self.connect_pmos(self.upper_pmos2_inst.get_pin("D"),self.get_pin("br"))
|
self.connect_pmos_m1(self.upper_pmos2_inst.get_pin("D"),self.get_pin("br"))
|
||||||
|
|
||||||
|
|
||||||
def add_bitline_contacts(self):
|
def add_bitline_contacts(self):
|
||||||
"""Adds contacts/via from metal1 to metal2 for bit-lines"""
|
"""
|
||||||
|
Adds contacts/via from metal1 to metal2 for bit-lines
|
||||||
|
"""
|
||||||
|
|
||||||
stack=("metal1", "via1", "metal2")
|
stack=("metal1", "via1", "metal2")
|
||||||
pos = self.lower_pmos_inst.get_pin("S").center()
|
upper_pin = self.upper_pmos1_inst.get_pin("S")
|
||||||
self.add_contact_center(layers=stack,
|
lower_pin = self.lower_pmos_inst.get_pin("S")
|
||||||
offset=pos)
|
|
||||||
pos = self.lower_pmos_inst.get_pin("D").center()
|
# BL goes up to M2 at the transistor
|
||||||
self.add_contact_center(layers=stack,
|
self.bl_contact=self.add_contact_center(layers=stack,
|
||||||
offset=pos)
|
offset=upper_pin.center())
|
||||||
pos = self.upper_pmos1_inst.get_pin("S").center()
|
self.add_contact_center(layers=stack,
|
||||||
self.add_contact_center(layers=stack,
|
offset=lower_pin.center())
|
||||||
offset=pos)
|
|
||||||
pos = self.upper_pmos2_inst.get_pin("D").center()
|
# BR routes over on M1 first
|
||||||
self.add_contact_center(layers=stack,
|
self.add_contact_center(layers=stack,
|
||||||
offset=pos)
|
offset = vector(self.br_pin.cx(), upper_pin.cy()))
|
||||||
|
self.add_contact_center(layers=stack,
|
||||||
def connect_pmos(self, pmos_pin, bit_pin):
|
offset = vector(self.br_pin.cx(), lower_pin.cy()))
|
||||||
""" Connect pmos pin to bitline pin """
|
|
||||||
|
def connect_pmos_m1(self, pmos_pin, bit_pin):
|
||||||
ll_pos = vector(min(pmos_pin.lx(),bit_pin.lx()), pmos_pin.by())
|
"""
|
||||||
ur_pos = vector(max(pmos_pin.rx(),bit_pin.rx()), pmos_pin.uy())
|
Connect a pmos pin to bitline pin
|
||||||
|
"""
|
||||||
width = ur_pos.x-ll_pos.x
|
|
||||||
height = ur_pos.y-ll_pos.y
|
left_pos = vector(min(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy())
|
||||||
self.add_rect(layer="metal2",
|
right_pos = vector(max(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy())
|
||||||
offset=ll_pos,
|
|
||||||
width=width,
|
self.add_path("metal1", [ left_pos, right_pos] )
|
||||||
height=height)
|
|
||||||
|
def connect_pmos_m2(self, pmos_pin, bit_pin):
|
||||||
|
"""
|
||||||
|
Connect a pmos pin to bitline pin
|
||||||
|
"""
|
||||||
|
|
||||||
|
left_pos = vector(min(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy())
|
||||||
|
right_pos = vector(max(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy())
|
||||||
|
|
||||||
|
self.add_path("metal2", [ left_pos, right_pos], self.bl_contact.height)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
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
|
||||||
import path
|
import path
|
||||||
|
|
||||||
class ptx(design.design):
|
class ptx(design.design):
|
||||||
|
|
@ -14,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
|
||||||
|
|
@ -39,14 +40,11 @@ class ptx(design.design):
|
||||||
self.connect_poly = connect_poly
|
self.connect_poly = connect_poly
|
||||||
self.num_contacts = num_contacts
|
self.num_contacts = num_contacts
|
||||||
|
|
||||||
self.create_spice()
|
self.create_netlist()
|
||||||
|
# We must always create ptx layout for pbitcell
|
||||||
|
# some transistor sizes in other netlist depend on pbitcell
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
|
||||||
self.translate_all(self.active_offset)
|
|
||||||
|
|
||||||
# for run-time, we won't check every transitor DRC independently
|
|
||||||
# but this may be uncommented for debug purposes
|
|
||||||
#self.DRC()
|
|
||||||
|
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
|
|
@ -56,19 +54,24 @@ class ptx(design.design):
|
||||||
self.add_well_implant()
|
self.add_well_implant()
|
||||||
self.add_poly()
|
self.add_poly()
|
||||||
self.add_active_contacts()
|
self.add_active_contacts()
|
||||||
|
self.translate_all(self.active_offset)
|
||||||
|
|
||||||
def create_spice(self):
|
# for run-time, we won't check every transitor DRC independently
|
||||||
|
# but this may be uncommented for debug purposes
|
||||||
|
#self.DRC()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
self.add_pin_list(["D", "G", "S", "B"])
|
self.add_pin_list(["D", "G", "S", "B"])
|
||||||
|
|
||||||
# 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)
|
||||||
|
|
@ -106,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
|
||||||
|
|
@ -126,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,
|
||||||
|
|
@ -148,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):
|
||||||
"""
|
"""
|
||||||
|
|
@ -178,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):
|
||||||
|
|
@ -266,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,
|
||||||
|
|
@ -277,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
|
||||||
|
|
@ -9,26 +9,35 @@ from globals import OPTS
|
||||||
class single_level_column_mux(design.design):
|
class single_level_column_mux(design.design):
|
||||||
"""
|
"""
|
||||||
This module implements the columnmux bitline cell used in the design.
|
This module implements the columnmux bitline cell used in the design.
|
||||||
Creates a single columnmux cell.
|
Creates a single columnmux cell with the given integer size relative
|
||||||
|
to minimum size. Default is 8x. Per Samira and Hodges-Jackson book:
|
||||||
|
Column-mux transistors driven by the decoder must be sized for optimal speed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, tx_size):
|
# This is needed for different bitline spacings
|
||||||
name="single_level_column_mux_{}".format(tx_size)
|
unique_id = 1
|
||||||
|
|
||||||
|
def __init__(self, tx_size=8, bitcell_bl="bl", bitcell_br="br"):
|
||||||
|
self.tx_size = int(tx_size)
|
||||||
|
name="single_level_column_mux_{}_{}".format(self.tx_size,single_level_column_mux.unique_id)
|
||||||
|
single_level_column_mux.unique_id += 1
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
debug.info(2, "create single column mux cell: {0}".format(name))
|
debug.info(2, "create single column mux cell: {0}".format(name))
|
||||||
|
|
||||||
from importlib import reload
|
self.bitcell_bl = bitcell_bl
|
||||||
c = reload(__import__(OPTS.bitcell))
|
self.bitcell_br = bitcell_br
|
||||||
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
|
||||||
self.bitcell = self.mod_bitcell()
|
|
||||||
|
|
||||||
self.ptx_width = tx_size * drc["minwidth_tx"]
|
self.create_netlist()
|
||||||
self.add_pin_list(["bl", "br", "bl_out", "br_out", "sel", "gnd"])
|
if not OPTS.netlist_only:
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_modules()
|
||||||
|
self.add_pins()
|
||||||
self.add_ptx()
|
self.add_ptx()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
|
||||||
self.pin_height = 2*self.m2_width
|
self.pin_height = 2*self.m2_width
|
||||||
self.width = self.bitcell.width
|
self.width = self.bitcell.width
|
||||||
self.height = self.nmos_upper.uy() + self.pin_height
|
self.height = self.nmos_upper.uy() + self.pin_height
|
||||||
|
|
@ -36,12 +45,30 @@ class single_level_column_mux(design.design):
|
||||||
self.add_bitline_pins()
|
self.add_bitline_pins()
|
||||||
self.connect_bitlines()
|
self.connect_bitlines()
|
||||||
self.add_wells()
|
self.add_wells()
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
|
# This is just used for measurements,
|
||||||
|
# so don't add the module
|
||||||
|
from importlib import reload
|
||||||
|
c = reload(__import__(OPTS.bitcell))
|
||||||
|
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
||||||
|
self.bitcell = self.mod_bitcell()
|
||||||
|
|
||||||
|
# Adds nmos_lower,nmos_upper to the module
|
||||||
|
self.ptx_width = self.tx_size*drc("minwidth_tx")
|
||||||
|
self.nmos = ptx(width=self.ptx_width)
|
||||||
|
self.add_mod(self.nmos)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
self.add_pin_list(["bl", "br", "bl_out", "br_out", "sel", "gnd"])
|
||||||
|
|
||||||
def add_bitline_pins(self):
|
def add_bitline_pins(self):
|
||||||
""" Add the top and bottom pins to this cell """
|
""" Add the top and bottom pins to this cell """
|
||||||
|
|
||||||
bl_pos = vector(self.bitcell.get_pin("bl").lx(), 0)
|
bl_pos = vector(self.bitcell.get_pin(self.bitcell_bl).lx(), 0)
|
||||||
br_pos = vector(self.bitcell.get_pin("br").lx(), 0)
|
br_pos = vector(self.bitcell.get_pin(self.bitcell_br).lx(), 0)
|
||||||
|
|
||||||
# bl and br
|
# bl and br
|
||||||
self.add_layout_pin(text="bl",
|
self.add_layout_pin(text="bl",
|
||||||
|
|
@ -67,10 +94,6 @@ class single_level_column_mux(design.design):
|
||||||
def add_ptx(self):
|
def add_ptx(self):
|
||||||
""" Create the two pass gate NMOS transistors to switch the bitlines"""
|
""" Create the two pass gate NMOS transistors to switch the bitlines"""
|
||||||
|
|
||||||
# Adds nmos_lower,nmos_upper to the module
|
|
||||||
self.nmos = ptx(width=self.ptx_width)
|
|
||||||
self.add_mod(self.nmos)
|
|
||||||
|
|
||||||
# Space it in the center
|
# Space it in the center
|
||||||
nmos_lower_position = self.nmos.active_offset.scale(0,1) + vector(0.5*self.bitcell.width-0.5*self.nmos.active_width,0)
|
nmos_lower_position = self.nmos.active_offset.scale(0,1) + vector(0.5*self.bitcell.width-0.5*self.nmos.active_width,0)
|
||||||
self.nmos_lower=self.add_inst(name="mux_tx1",
|
self.nmos_lower=self.add_inst(name="mux_tx1",
|
||||||
|
|
@ -124,8 +147,8 @@ class single_level_column_mux(design.design):
|
||||||
# bl_out -> nmos_upper/S on metal2
|
# bl_out -> nmos_upper/S on metal2
|
||||||
self.add_path("metal1",[bl_pin.ll(), vector(nmos_upper_d_pin.cx(),bl_pin.by()), nmos_upper_d_pin.center()])
|
self.add_path("metal1",[bl_pin.ll(), vector(nmos_upper_d_pin.cx(),bl_pin.by()), nmos_upper_d_pin.center()])
|
||||||
# halfway up, move over
|
# halfway up, move over
|
||||||
mid1 = bl_out_pin.uc().scale(1,0.5)+nmos_upper_s_pin.bc().scale(0,0.5)
|
mid1 = bl_out_pin.uc().scale(1,0.4)+nmos_upper_s_pin.bc().scale(0,0.4)
|
||||||
mid2 = bl_out_pin.uc().scale(0,0.5)+nmos_upper_s_pin.bc().scale(1,0.5)
|
mid2 = bl_out_pin.uc().scale(0,0.4)+nmos_upper_s_pin.bc().scale(1,0.4)
|
||||||
self.add_path("metal2",[bl_out_pin.uc(), mid1, mid2, nmos_upper_s_pin.bc()])
|
self.add_path("metal2",[bl_out_pin.uc(), mid1, mid2, nmos_upper_s_pin.bc()])
|
||||||
|
|
||||||
# br -> nmos_lower/D on metal2
|
# br -> nmos_lower/D on metal2
|
||||||
|
|
@ -144,7 +167,7 @@ class single_level_column_mux(design.design):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Add it to the right, aligned in between the two tx
|
# Add it to the right, aligned in between the two tx
|
||||||
active_pos = vector(self.bitcell.width,self.nmos_upper.by())
|
active_pos = vector(self.bitcell.width,self.nmos_upper.by() - 0.5*self.poly_space)
|
||||||
active_via = self.add_via_center(layers=("active", "contact", "metal1"),
|
active_via = self.add_via_center(layers=("active", "contact", "metal1"),
|
||||||
offset=active_pos,
|
offset=active_pos,
|
||||||
implant_type="p",
|
implant_type="p",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
from enum import Enum
|
||||||
|
from vector3d import vector3d
|
||||||
|
|
||||||
|
class direction(Enum):
|
||||||
|
NORTH = 1
|
||||||
|
SOUTH = 2
|
||||||
|
EAST = 3
|
||||||
|
WEST = 4
|
||||||
|
UP = 5
|
||||||
|
DOWN = 6
|
||||||
|
NORTHEAST = 7
|
||||||
|
NORTHWEST = 8
|
||||||
|
SOUTHEAST = 9
|
||||||
|
SOUTHWEST = 10
|
||||||
|
|
||||||
|
|
||||||
|
def get_offset(direct):
|
||||||
|
"""
|
||||||
|
Returns the vector offset for a given direction.
|
||||||
|
"""
|
||||||
|
if direct==direction.NORTH:
|
||||||
|
offset = vector3d(0,1,0)
|
||||||
|
elif direct==direction.SOUTH:
|
||||||
|
offset = vector3d(0,-1,0)
|
||||||
|
elif direct==direction.EAST:
|
||||||
|
offset = vector3d(1,0,0)
|
||||||
|
elif direct==direction.WEST:
|
||||||
|
offset = vector3d(-1,0,0)
|
||||||
|
elif direct==direction.UP:
|
||||||
|
offset = vector3d(0,0,1)
|
||||||
|
elif direct==direction.DOWN:
|
||||||
|
offset = vector3d(0,0,-1)
|
||||||
|
elif direct==direction.NORTHEAST:
|
||||||
|
offset = vector3d(1,1,0)
|
||||||
|
elif direct==direction.NORTHWEST:
|
||||||
|
offset = vector3d(-1,1,0)
|
||||||
|
elif direct==direction.SOUTHEAST:
|
||||||
|
offset = vector3d(1,-1,0)
|
||||||
|
elif direct==direction.SOUTHWEST:
|
||||||
|
offset = vector3d(-1,-1,0)
|
||||||
|
else:
|
||||||
|
debug.error("Invalid direction {}".format(direct))
|
||||||
|
|
||||||
|
return offset
|
||||||
|
|
||||||
|
def cardinal_directions(up_down_too=False):
|
||||||
|
temp_dirs = [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST]
|
||||||
|
if up_down_too:
|
||||||
|
temp_dirs.extend([direction.UP, direction.DOWN])
|
||||||
|
return temp_dirs
|
||||||
|
|
||||||
|
def cardinal_offsets(up_down_too=False):
|
||||||
|
return [direction.get_offset(d) for d in direction.cardinal_directions(up_down_too)]
|
||||||
|
|
||||||
|
def all_directions():
|
||||||
|
return [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST,
|
||||||
|
direction.NORTHEAST, direction.NORTHWEST, direction.SOUTHEAST, direction.SOUTHWEST]
|
||||||
|
|
||||||
|
def all_offsets():
|
||||||
|
return [direction.get_offset(d) for d in direction.all_directions()]
|
||||||
|
|
||||||
|
def all_neighbors(cell):
|
||||||
|
return [cell+x for x in direction.all_offsets()]
|
||||||
|
|
||||||
|
def cardinal_neighbors(cell):
|
||||||
|
return [cell+x for x in direction.cardinal_offsets()]
|
||||||
|
|
||||||
|
|
@ -1,167 +1,111 @@
|
||||||
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
|
|
||||||
|
|
||||||
try:
|
|
||||||
import Queue as Q # ver. < 3.0
|
|
||||||
except ImportError:
|
|
||||||
import queue as Q
|
|
||||||
|
|
||||||
|
|
||||||
class grid:
|
class grid:
|
||||||
"""A two layer routing map. Each cell can be blocked in the vertical
|
|
||||||
or horizontal layer.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
A two layer routing map. Each cell can be blocked in the vertical
|
||||||
|
or horizontal layer.
|
||||||
|
"""
|
||||||
|
# costs are relative to a unit grid
|
||||||
|
# non-preferred cost allows an off-direction jog of 1 grid
|
||||||
|
# rather than 2 vias + preferred direction (cost 5)
|
||||||
|
VIA_COST = 2
|
||||||
|
NONPREFERRED_COST = 4
|
||||||
|
PREFERRED_COST = 1
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
""" Create a routing map of width x height cells and 2 in the z-axis. """
|
|
||||||
|
|
||||||
# costs are relative to a unit grid
|
def __init__(self, ll, ur, track_width):
|
||||||
# non-preferred cost allows an off-direction jog of 1 grid
|
""" Initialize the map and define the costs. """
|
||||||
# rather than 2 vias + preferred direction (cost 5)
|
|
||||||
self.VIA_COST = 2
|
|
||||||
self.NONPREFERRED_COST = 4
|
|
||||||
self.PREFERRED_COST = 1
|
|
||||||
|
|
||||||
# list of the source/target grid coordinates
|
# list of the source/target grid coordinates
|
||||||
self.source = []
|
self.source = []
|
||||||
self.target = []
|
self.target = []
|
||||||
|
|
||||||
|
self.track_width = track_width
|
||||||
|
self.track_widths = [self.track_width, self.track_width, 1.0]
|
||||||
|
self.track_factor = [1/self.track_width, 1/self.track_width, 1.0]
|
||||||
|
|
||||||
|
# The bounds are in grids for this
|
||||||
|
# This is really lower left bottom layer and upper right top layer in 3D.
|
||||||
|
self.ll = vector3d(ll.x,ll.y,0).scale(self.track_factor).round()
|
||||||
|
self.ur = vector3d(ur.x,ur.y,1).scale(self.track_factor).round()
|
||||||
|
|
||||||
# let's leave the map sparse, cells are created on demand to reduce memory
|
# let's leave the map sparse, cells are created on demand to reduce memory
|
||||||
self.map={}
|
self.map={}
|
||||||
|
|
||||||
# priority queue for the maze routing
|
def add_all_grids(self):
|
||||||
self.q = Q.PriorityQueue()
|
for x in range(self.ll.x, self.ur.x, 1):
|
||||||
|
for y in range(self.ll.y, self.ur.y, 1):
|
||||||
def set_blocked(self,n):
|
self.add_map(vector3d(x,y,0))
|
||||||
self.add_map(n)
|
self.add_map(vector3d(x,y,1))
|
||||||
self.map[n].blocked=True
|
|
||||||
|
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 set_source(self,n):
|
|
||||||
self.add_map(n)
|
|
||||||
self.map[n].source=True
|
|
||||||
self.source.append(n)
|
|
||||||
|
|
||||||
def set_target(self,n):
|
|
||||||
self.add_map(n)
|
|
||||||
self.map[n].target=True
|
|
||||||
self.target.append(n)
|
|
||||||
|
|
||||||
def reinit(self):
|
def set_path(self,n,value=True):
|
||||||
""" Reinitialize everything for a new route. """
|
if isinstance(n, (list,tuple,set,frozenset)):
|
||||||
|
for item in n:
|
||||||
|
self.set_path(item,value)
|
||||||
|
else:
|
||||||
|
self.add_map(n)
|
||||||
|
self.map[n].path=value
|
||||||
|
|
||||||
|
def clear_blockages(self):
|
||||||
|
self.set_blocked(set(self.map.keys()),False)
|
||||||
|
|
||||||
|
def set_source(self,n,value=True):
|
||||||
|
if isinstance(n, (list,tuple,set,frozenset)):
|
||||||
|
for item in n:
|
||||||
|
self.set_source(item,value)
|
||||||
|
else:
|
||||||
|
self.add_map(n)
|
||||||
|
self.map[n].source=value
|
||||||
|
self.source.append(n)
|
||||||
|
|
||||||
self.reset_cells()
|
def set_target(self,n,value=True):
|
||||||
|
if isinstance(n, (list,tuple,set,frozenset)):
|
||||||
# clear source and target pins
|
for item in n:
|
||||||
self.source=[]
|
self.set_target(item,value)
|
||||||
self.target=[]
|
else:
|
||||||
|
self.add_map(n)
|
||||||
# clear the queue
|
self.map[n].target=value
|
||||||
while (not self.q.empty()):
|
self.target.append(n)
|
||||||
self.q.get(False)
|
|
||||||
|
|
||||||
|
|
||||||
def add_blockage_shape(self,ll,ur,z):
|
def add_source(self,track_list,value=True):
|
||||||
debug.info(3,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z))
|
|
||||||
for x in range(int(ll[0]),int(ur[0])+1):
|
|
||||||
for y in range(int(ll[1]),int(ur[1])+1):
|
|
||||||
n = vector3d(x,y,z)
|
|
||||||
self.set_blocked(n)
|
|
||||||
|
|
||||||
def add_blockage(self,block_list):
|
|
||||||
debug.info(3,"Adding blockage list={0}".format(str(block_list)))
|
|
||||||
for n in block_list:
|
|
||||||
self.set_blocked(n)
|
|
||||||
|
|
||||||
def add_source(self,track_list):
|
|
||||||
debug.info(3,"Adding source list={0}".format(str(track_list)))
|
debug.info(3,"Adding source list={0}".format(str(track_list)))
|
||||||
for n in track_list:
|
for n in track_list:
|
||||||
if not self.is_blocked(n):
|
debug.info(4,"Adding source ={0}".format(str(n)))
|
||||||
self.set_source(n)
|
self.set_source(n,value)
|
||||||
|
self.set_blocked(n,False)
|
||||||
|
|
||||||
def add_target(self,track_list):
|
|
||||||
|
def add_target(self,track_list,value=True):
|
||||||
debug.info(3,"Adding target list={0}".format(str(track_list)))
|
debug.info(3,"Adding target list={0}".format(str(track_list)))
|
||||||
for n in track_list:
|
for n in track_list:
|
||||||
if not self.is_blocked(n):
|
debug.info(4,"Adding target ={0}".format(str(n)))
|
||||||
self.set_target(n)
|
self.set_target(n,value)
|
||||||
|
self.set_blocked(n,False)
|
||||||
def reset_cells(self):
|
|
||||||
"""
|
|
||||||
Reset the path and costs for all the grid cells.
|
|
||||||
"""
|
|
||||||
for p in self.map.values():
|
|
||||||
p.reset()
|
|
||||||
|
|
||||||
|
|
||||||
def add_path(self,path):
|
|
||||||
"""
|
|
||||||
Mark the path in the routing grid for visualization
|
|
||||||
"""
|
|
||||||
self.path=path
|
|
||||||
for p in path:
|
|
||||||
self.map[p].path=True
|
|
||||||
|
|
||||||
def route(self,detour_scale):
|
|
||||||
"""
|
|
||||||
This does the A* maze routing with preferred direction routing.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# We set a cost bound of the HPWL for run-time. This can be
|
|
||||||
# over-ridden if the route fails due to pruning a feasible solution.
|
|
||||||
cost_bound = detour_scale*self.cost_to_target(self.source[0])*self.PREFERRED_COST
|
|
||||||
|
|
||||||
# Make sure the queue is empty if we run another route
|
|
||||||
while not self.q.empty():
|
|
||||||
self.q.get()
|
|
||||||
|
|
||||||
# Put the source items into the queue
|
|
||||||
self.init_queue()
|
|
||||||
cheapest_path = None
|
|
||||||
cheapest_cost = None
|
|
||||||
|
|
||||||
# Keep expanding and adding to the priority queue until we are done
|
|
||||||
while not self.q.empty():
|
|
||||||
# should we keep the path in the queue as well or just the final node?
|
|
||||||
(cost,path) = self.q.get()
|
|
||||||
debug.info(2,"Queue size: size=" + str(self.q.qsize()) + " " + str(cost))
|
|
||||||
debug.info(3,"Expanding: cost=" + str(cost) + " " + str(path))
|
|
||||||
|
|
||||||
# expand the last element
|
|
||||||
neighbors = self.expand_dirs(path)
|
|
||||||
debug.info(3,"Neighbors: " + str(neighbors))
|
|
||||||
|
|
||||||
for n in neighbors:
|
|
||||||
# node is added to the map by the expand routine
|
|
||||||
newpath = path + [n]
|
|
||||||
# check if we hit the target and are done
|
|
||||||
if self.is_target(n):
|
|
||||||
return (newpath,self.cost(newpath))
|
|
||||||
elif not self.map[n].visited:
|
|
||||||
# current path cost + predicted cost
|
|
||||||
current_cost = self.cost(newpath)
|
|
||||||
target_cost = self.cost_to_target(n)
|
|
||||||
predicted_cost = current_cost + target_cost
|
|
||||||
# only add the cost if it is less than our bound
|
|
||||||
if (predicted_cost < cost_bound):
|
|
||||||
if (self.map[n].min_cost==-1 or current_cost<self.map[n].min_cost):
|
|
||||||
self.map[n].visited=True
|
|
||||||
self.map[n].min_path = newpath
|
|
||||||
self.map[n].min_cost = predicted_cost
|
|
||||||
debug.info(3,"Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath))
|
|
||||||
# add the cost to get to this point if we haven't reached it yet
|
|
||||||
self.q.put((predicted_cost,newpath))
|
|
||||||
|
|
||||||
debug.warning("Unable to route path. Expand the detour_scale to allow detours.")
|
|
||||||
return (None,None)
|
|
||||||
|
|
||||||
def is_target(self,point):
|
def is_target(self,point):
|
||||||
"""
|
"""
|
||||||
|
|
@ -169,131 +113,27 @@ class grid:
|
||||||
"""
|
"""
|
||||||
return point in self.target
|
return point in self.target
|
||||||
|
|
||||||
def expand_dirs(self,path):
|
def add_map(self,n):
|
||||||
"""
|
|
||||||
Expand each of the four cardinal directions plus up or down
|
|
||||||
but not expanding to blocked cells. Expands in all directions
|
|
||||||
regardless of preferred directions.
|
|
||||||
"""
|
|
||||||
# expand from the last point
|
|
||||||
point = path[-1]
|
|
||||||
|
|
||||||
neighbors = []
|
|
||||||
|
|
||||||
east = point + vector3d(1,0,0)
|
|
||||||
if not self.is_blocked(east) and not east in path:
|
|
||||||
neighbors.append(east)
|
|
||||||
|
|
||||||
west= point + vector3d(-1,0,0)
|
|
||||||
if not self.is_blocked(west) and not west in path:
|
|
||||||
neighbors.append(west)
|
|
||||||
|
|
||||||
up = point + vector3d(0,0,1)
|
|
||||||
if up.z<2 and not self.is_blocked(up) and not up in path:
|
|
||||||
neighbors.append(up)
|
|
||||||
|
|
||||||
north = point + vector3d(0,1,0)
|
|
||||||
if not self.is_blocked(north) and not north in path:
|
|
||||||
neighbors.append(north)
|
|
||||||
|
|
||||||
south = point + vector3d(0,-1,0)
|
|
||||||
if not self.is_blocked(south) and not south in path:
|
|
||||||
neighbors.append(south)
|
|
||||||
|
|
||||||
down = point + vector3d(0,0,-1)
|
|
||||||
if down.z>=0 and not self.is_blocked(down) and not down in path:
|
|
||||||
neighbors.append(down)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return neighbors
|
|
||||||
|
|
||||||
def add_map(self,p):
|
|
||||||
"""
|
"""
|
||||||
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)
|
||||||
def init_queue(self):
|
|
||||||
"""
|
|
||||||
Populate the queue with all the source pins with cost
|
|
||||||
to the target. Each item is a path of the grid cells.
|
|
||||||
We will use an A* search, so this cost must be pessimistic.
|
|
||||||
Cost so far will be the length of the path.
|
|
||||||
"""
|
|
||||||
debug.info(4,"Initializing queue.")
|
|
||||||
|
|
||||||
# uniquify the source (and target while we are at it)
|
|
||||||
self.source = list(set(self.source))
|
|
||||||
self.target = list(set(self.target))
|
|
||||||
|
|
||||||
for s in self.source:
|
|
||||||
cost = self.cost_to_target(s)
|
|
||||||
debug.info(4,"Init: cost=" + str(cost) + " " + str([s]))
|
|
||||||
self.q.put((cost,[s]))
|
|
||||||
|
|
||||||
def hpwl(self, src, dest):
|
|
||||||
"""
|
|
||||||
Return half perimeter wire length from point to another.
|
|
||||||
Either point can have positive or negative coordinates.
|
|
||||||
Include the via penalty if there is one.
|
|
||||||
"""
|
|
||||||
hpwl = max(abs(src.x-dest.x),abs(dest.x-src.x))
|
|
||||||
hpwl += max(abs(src.y-dest.y),abs(dest.y-src.y))
|
|
||||||
hpwl += max(abs(src.z-dest.z),abs(dest.z-src.z))
|
|
||||||
if src.x!=dest.x or src.y!=dest.y:
|
|
||||||
hpwl += self.VIA_COST
|
|
||||||
return hpwl
|
|
||||||
|
|
||||||
def cost_to_target(self,source):
|
|
||||||
"""
|
|
||||||
Find the cheapest HPWL distance to any target point ignoring
|
|
||||||
blockages for A* search.
|
|
||||||
"""
|
|
||||||
cost = self.hpwl(source,self.target[0])
|
|
||||||
for t in self.target:
|
|
||||||
cost = min(self.hpwl(source,t),cost)
|
|
||||||
return cost
|
|
||||||
|
|
||||||
|
|
||||||
def cost(self,path):
|
|
||||||
"""
|
|
||||||
The cost of the path is the length plus a penalty for the number
|
|
||||||
of vias. We assume that non-preferred direction is penalized.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Ignore the source pin layer change, FIXME?
|
|
||||||
def pairwise(iterable):
|
|
||||||
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
|
||||||
a, b = tee(iterable)
|
|
||||||
next(b, None)
|
|
||||||
return zip(a, b)
|
|
||||||
|
|
||||||
|
|
||||||
plist = pairwise(path)
|
|
||||||
cost = 0
|
|
||||||
for p0,p1 in plist:
|
|
||||||
if p0.z != p1.z: # via
|
|
||||||
cost += self.VIA_COST
|
|
||||||
elif p0.x != p1.x: # horizontal
|
|
||||||
cost += self.NONPREFERRED_COST if (p0.z == 1) else self.PREFERRED_COST
|
|
||||||
elif p0.y != p1.y: # vertical
|
|
||||||
cost += self.NONPREFERRED_COST if (p0.z == 0) else self.PREFERRED_COST
|
|
||||||
else:
|
|
||||||
debug.error("Non-changing direction!")
|
|
||||||
|
|
||||||
return cost
|
|
||||||
|
|
||||||
def get_inertia(self,p0,p1):
|
|
||||||
"""
|
|
||||||
Sets the direction based on the previous direction we came from.
|
|
||||||
"""
|
|
||||||
# direction (index) of movement
|
|
||||||
if p0.x==p1.x:
|
|
||||||
return 1
|
|
||||||
elif p0.y==p1.y:
|
|
||||||
return 0
|
|
||||||
else:
|
else:
|
||||||
# z direction
|
if n not in self.map.keys():
|
||||||
return 2
|
self.map[n]=grid_cell()
|
||||||
|
|
||||||
|
|
||||||
|
def block_path(self,path):
|
||||||
|
"""
|
||||||
|
Mark the path in the routing grid as blocked.
|
||||||
|
Also unsets the path flag.
|
||||||
|
"""
|
||||||
|
path.set_path(False)
|
||||||
|
path.set_blocked(True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,13 +16,17 @@ 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
|
||||||
self.source=False
|
self.source=False
|
||||||
self.target=False
|
self.target=False
|
||||||
|
|
||||||
|
def get_cost(self):
|
||||||
|
# We can display the cost of the frontier
|
||||||
|
if self.min_cost > 0:
|
||||||
|
return self.min_cost
|
||||||
|
|
||||||
|
|
||||||
def get_type(self):
|
def get_type(self):
|
||||||
if self.blocked:
|
if self.blocked:
|
||||||
|
|
@ -38,8 +41,4 @@ class cell:
|
||||||
if self.path:
|
if self.path:
|
||||||
return "P"
|
return "P"
|
||||||
|
|
||||||
# We can display the cost of the frontier
|
|
||||||
if self.min_cost > 0:
|
|
||||||
return self.min_cost
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
@ -0,0 +1,212 @@
|
||||||
|
import debug
|
||||||
|
from vector3d import vector3d
|
||||||
|
from itertools import tee
|
||||||
|
from grid import grid
|
||||||
|
from grid_cell import grid_cell
|
||||||
|
from direction import direction
|
||||||
|
|
||||||
|
class grid_path:
|
||||||
|
"""
|
||||||
|
A grid path is a list of lists of grid cells.
|
||||||
|
It can have a width that is more than one cell.
|
||||||
|
All of the sublists will be the same dimension.
|
||||||
|
Cells should be continguous.
|
||||||
|
It can have a name to define pin shapes as well.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, items=[], name=""):
|
||||||
|
self.name = name
|
||||||
|
if items:
|
||||||
|
self.pathlist = [items]
|
||||||
|
else:
|
||||||
|
self.pathlist = []
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
#import pprint
|
||||||
|
p = str(self.pathlist) #pprint.pformat(self.pathlist)
|
||||||
|
if self.name != "":
|
||||||
|
return (str(self.name) + " : " + p)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def __setitem__(self, index, value):
|
||||||
|
"""
|
||||||
|
override setitem function
|
||||||
|
can set value by pathinstance[index]=value
|
||||||
|
"""
|
||||||
|
self.pathlist[index]=value
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
"""
|
||||||
|
override getitem function
|
||||||
|
can get value by value=pathinstance[index]
|
||||||
|
"""
|
||||||
|
return self.pathlist[index]
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
"""
|
||||||
|
Determine if cell exists in this path
|
||||||
|
"""
|
||||||
|
# FIXME: Could maintain a hash to make in O(1)
|
||||||
|
for sublist in self.pathlist:
|
||||||
|
for item in sublist:
|
||||||
|
if item == key:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __add__(self, items):
|
||||||
|
"""
|
||||||
|
Override add to do append
|
||||||
|
"""
|
||||||
|
return self.pathlist.extend(items)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.pathlist)
|
||||||
|
|
||||||
|
def trim_last(self):
|
||||||
|
"""
|
||||||
|
Drop the last item
|
||||||
|
"""
|
||||||
|
if len(self.pathlist)>0:
|
||||||
|
self.pathlist.pop()
|
||||||
|
|
||||||
|
def trim_first(self):
|
||||||
|
"""
|
||||||
|
Drop the first item
|
||||||
|
"""
|
||||||
|
if len(self.pathlist)>0:
|
||||||
|
self.pathlist.pop(0)
|
||||||
|
|
||||||
|
def append(self,item):
|
||||||
|
"""
|
||||||
|
Append the list of items to the cells
|
||||||
|
"""
|
||||||
|
self.pathlist.append(item)
|
||||||
|
|
||||||
|
def extend(self,item):
|
||||||
|
"""
|
||||||
|
Extend the list of items to the cells
|
||||||
|
"""
|
||||||
|
self.pathlist.extend(item)
|
||||||
|
|
||||||
|
def set_path(self,value=True):
|
||||||
|
for sublist in self.pathlist:
|
||||||
|
for p in sublist:
|
||||||
|
p.path=value
|
||||||
|
|
||||||
|
def set_blocked(self,value=True):
|
||||||
|
for sublist in self.pathlist:
|
||||||
|
for p in sublist:
|
||||||
|
p.blocked=value
|
||||||
|
|
||||||
|
def get_grids(self):
|
||||||
|
"""
|
||||||
|
Return a set of all the grids in this path.
|
||||||
|
"""
|
||||||
|
newset = set()
|
||||||
|
for sublist in self.pathlist:
|
||||||
|
newset.update(sublist)
|
||||||
|
return newset
|
||||||
|
|
||||||
|
def get_wire_grids(self, start_index, end_index):
|
||||||
|
"""
|
||||||
|
Return a set of all the wire grids in this path.
|
||||||
|
These are the indices in the wave path in a certain range.
|
||||||
|
"""
|
||||||
|
newset = set()
|
||||||
|
for sublist in self.pathlist:
|
||||||
|
newset.update(sublist[start_index:end_index])
|
||||||
|
return newset
|
||||||
|
|
||||||
|
def cost(self):
|
||||||
|
"""
|
||||||
|
The cost of the path is the length plus a penalty for the number
|
||||||
|
of vias. We assume that non-preferred direction is penalized.
|
||||||
|
This cost only works with 1 wide tracks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Ignore the source pin layer change, FIXME?
|
||||||
|
def pairwise(iterable):
|
||||||
|
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||||
|
a, b = tee(iterable)
|
||||||
|
next(b, None)
|
||||||
|
return zip(a, b)
|
||||||
|
|
||||||
|
plist = list(pairwise(self.pathlist))
|
||||||
|
cost = 0
|
||||||
|
for p0list,p1list in plist:
|
||||||
|
# This is because they are "waves" so pick the first item
|
||||||
|
p0=p0list[0]
|
||||||
|
p1=p1list[0]
|
||||||
|
|
||||||
|
if p0.z != p1.z: # via
|
||||||
|
cost += grid.VIA_COST
|
||||||
|
elif p0.x != p1.x and p0.z==1: # horizontal on vertical layer
|
||||||
|
cost += grid.NONPREFERRED_COST
|
||||||
|
elif p0.y != p1.y and p0.z==0: # vertical on horizontal layer
|
||||||
|
cost += grid.NONPREFERRED_COST
|
||||||
|
else:
|
||||||
|
cost += grid.PREFERRED_COST
|
||||||
|
|
||||||
|
return cost
|
||||||
|
|
||||||
|
def expand_dirs(self):
|
||||||
|
"""
|
||||||
|
Expand from the end in each of the four cardinal directions plus up
|
||||||
|
or down but not expanding to blocked cells. Expands in all
|
||||||
|
directions regardless of preferred directions.
|
||||||
|
|
||||||
|
If the width is more than one, it can only expand in one direction
|
||||||
|
(for now). This is assumed for the supply router for now.
|
||||||
|
|
||||||
|
"""
|
||||||
|
neighbors = []
|
||||||
|
|
||||||
|
for d in direction.cardinal_directions(True):
|
||||||
|
n = self.neighbor(d)
|
||||||
|
if n:
|
||||||
|
neighbors.append(n)
|
||||||
|
|
||||||
|
return neighbors
|
||||||
|
|
||||||
|
def neighbor(self, d):
|
||||||
|
offset = direction.get_offset(d)
|
||||||
|
|
||||||
|
newwave = [point + offset for point in self.pathlist[-1]]
|
||||||
|
|
||||||
|
if newwave in self.pathlist:
|
||||||
|
return None
|
||||||
|
elif newwave[0].z>1 or newwave[0].z<0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return newwave
|
||||||
|
|
||||||
|
|
||||||
|
def set_layer(self, zindex):
|
||||||
|
new_pathlist = [vector3d(item.x, item.y, zindex) for wave in self.pathlist for item in wave]
|
||||||
|
self.pathlist = new_pathlist
|
||||||
|
|
||||||
|
|
||||||
|
def overlap(self, other):
|
||||||
|
"""
|
||||||
|
Return the overlap waves ignoring different layers
|
||||||
|
"""
|
||||||
|
|
||||||
|
my_zindex = self.pathlist[0][0].z
|
||||||
|
other_flat_cells = [vector3d(item.x,item.y,my_zindex) for wave in other.pathlist for item in wave]
|
||||||
|
# This keeps the wave structure of the self layer
|
||||||
|
shared_waves = []
|
||||||
|
for wave in self.pathlist:
|
||||||
|
for item in wave:
|
||||||
|
# If any item in the wave is not contained, skip it
|
||||||
|
if not item in other_flat_cells:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
shared_waves.append(wave)
|
||||||
|
|
||||||
|
if len(shared_waves)>0:
|
||||||
|
ll = shared_waves[0][0]
|
||||||
|
ur = shared_waves[-1][-1]
|
||||||
|
return [ll,ur]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
"""
|
||||||
|
Some utility functions for sets of grid cells.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import debug
|
||||||
|
from direction import direction
|
||||||
|
from vector3d import vector3d
|
||||||
|
|
||||||
|
|
||||||
|
def increment_set(curset, direct):
|
||||||
|
"""
|
||||||
|
Return the cells incremented in given direction
|
||||||
|
"""
|
||||||
|
offset = direction.get_offset(direct)
|
||||||
|
|
||||||
|
newset = set()
|
||||||
|
for c in curset:
|
||||||
|
newc = c+offset
|
||||||
|
newset.add(newc)
|
||||||
|
|
||||||
|
return newset
|
||||||
|
|
||||||
|
|
||||||
|
def remove_border(curset, direct):
|
||||||
|
"""
|
||||||
|
Remove the cells on a given border.
|
||||||
|
"""
|
||||||
|
border = get_border(curset, direct)
|
||||||
|
curset.difference_update(border)
|
||||||
|
|
||||||
|
|
||||||
|
def get_upper_right(curset):
|
||||||
|
ur = None
|
||||||
|
for p in curset:
|
||||||
|
if ur == None or (p.x>=ur.x and p.y>=ur.y):
|
||||||
|
ur = p
|
||||||
|
return ur
|
||||||
|
|
||||||
|
def get_lower_left(curset):
|
||||||
|
ll = None
|
||||||
|
for p in curset:
|
||||||
|
if ll == None or (p.x<=ll.x and p.y<=ll.y):
|
||||||
|
ll = p
|
||||||
|
return ll
|
||||||
|
|
||||||
|
def get_border( curset, direct):
|
||||||
|
"""
|
||||||
|
Return the furthest cell(s) in a given direction.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# find direction-most cell(s)
|
||||||
|
maxc = []
|
||||||
|
if direct==direction.NORTH:
|
||||||
|
for c in curset:
|
||||||
|
if len(maxc)==0 or c.y>maxc[0].y:
|
||||||
|
maxc = [c]
|
||||||
|
elif c.y==maxc[0].y:
|
||||||
|
maxc.append(c)
|
||||||
|
elif direct==direct.SOUTH:
|
||||||
|
for c in curset:
|
||||||
|
if len(maxc)==0 or c.y<maxc[0].y:
|
||||||
|
maxc = [c]
|
||||||
|
elif c.y==maxc[0].y:
|
||||||
|
maxc.append(c)
|
||||||
|
elif direct==direct.EAST:
|
||||||
|
for c in curset:
|
||||||
|
if len(maxc)==0 or c.x>maxc[0].x:
|
||||||
|
maxc = [c]
|
||||||
|
elif c.x==maxc[0].x:
|
||||||
|
maxc.append(c)
|
||||||
|
elif direct==direct.WEST:
|
||||||
|
for c in curset:
|
||||||
|
if len(maxc)==0 or c.x<maxc[0].x:
|
||||||
|
maxc = [c]
|
||||||
|
elif c.x==maxc[0].x:
|
||||||
|
maxc.append(c)
|
||||||
|
|
||||||
|
newset = set(maxc)
|
||||||
|
return newset
|
||||||
|
|
||||||
|
def expand_border(curset, direct):
|
||||||
|
"""
|
||||||
|
Expand the current set of sells in a given direction.
|
||||||
|
Only return the contiguous cells.
|
||||||
|
"""
|
||||||
|
border_set = get_border(curset, direct)
|
||||||
|
next_border_set = increment_set(border_set, direct)
|
||||||
|
return next_border_set
|
||||||
|
|
||||||
|
def expand_borders(curset):
|
||||||
|
"""
|
||||||
|
Return the expansions in planar directions.
|
||||||
|
"""
|
||||||
|
north_set=expand_border(curset,direction.NORTH)
|
||||||
|
south_set=expand_border(curset,direction.SOUTH)
|
||||||
|
east_set=expand_border(curset,direction.EAST)
|
||||||
|
west_set=expand_border(curset,direction.WEST)
|
||||||
|
|
||||||
|
return(north_set, east_set, south_set, west_set)
|
||||||
|
|
||||||
|
def inflate_cell(cell, distance):
|
||||||
|
"""
|
||||||
|
Expand the current cell in all directions and return the set.
|
||||||
|
"""
|
||||||
|
newset = set(cell)
|
||||||
|
|
||||||
|
if distance==0:
|
||||||
|
return(newset)
|
||||||
|
|
||||||
|
# recursively call this based on the distance
|
||||||
|
for offset in direction.all_offsets():
|
||||||
|
# FIXME: If distance is large this will be inefficient, but it is like 1 or 2
|
||||||
|
newset.update(inflate_cell(cell+offset,distance-1))
|
||||||
|
|
||||||
|
return newset
|
||||||
|
|
||||||
|
def inflate_set(curset, distance):
|
||||||
|
"""
|
||||||
|
Expand the set in all directions by the given number of grids.
|
||||||
|
"""
|
||||||
|
if distance<=0:
|
||||||
|
return curset
|
||||||
|
|
||||||
|
newset = curset.copy()
|
||||||
|
# Add all my neighbors
|
||||||
|
for c in curset:
|
||||||
|
newset.update(direction.all_neighbors(c))
|
||||||
|
# Recurse with less depth
|
||||||
|
return inflate_set(newset,distance-1)
|
||||||
|
|
||||||
|
def flatten_set(curset):
|
||||||
|
"""
|
||||||
|
Flatten until we have a set of vector3d objects.
|
||||||
|
"""
|
||||||
|
newset = set()
|
||||||
|
for c in curset:
|
||||||
|
if isinstance(c,vector3d):
|
||||||
|
newset.add(c)
|
||||||
|
else:
|
||||||
|
newset.update(flatten_set(c))
|
||||||
|
return newset
|
||||||
|
|
@ -0,0 +1,665 @@
|
||||||
|
from direction import direction
|
||||||
|
from pin_layout import pin_layout
|
||||||
|
from vector3d import vector3d
|
||||||
|
from vector import vector
|
||||||
|
import grid_utils
|
||||||
|
from tech import drc
|
||||||
|
import debug
|
||||||
|
|
||||||
|
class pin_group:
|
||||||
|
"""
|
||||||
|
A class to represent a group of rectangular design pin.
|
||||||
|
It requires a router to define the track widths and blockages which
|
||||||
|
determine how pin shapes get mapped to tracks.
|
||||||
|
It is initially constructed with a single set of (touching) pins.
|
||||||
|
"""
|
||||||
|
def __init__(self, name, pin_set, router):
|
||||||
|
self.name = name
|
||||||
|
# Flag for when it is routed
|
||||||
|
self.routed = False
|
||||||
|
# Flag for when it is enclosed
|
||||||
|
self.enclosed = False
|
||||||
|
|
||||||
|
# Remove any redundant pins (i.e. contained in other pins)
|
||||||
|
irredundant_pin_set = self.remove_redundant_shapes(list(pin_set))
|
||||||
|
|
||||||
|
# This is a list because we can have a pin group of disconnected sets of pins
|
||||||
|
# and these are represented by separate lists
|
||||||
|
self.pins = [set(irredundant_pin_set)]
|
||||||
|
|
||||||
|
self.router = router
|
||||||
|
# These are the corresponding pin grids for each pin group.
|
||||||
|
self.grids = set()
|
||||||
|
# These are the secondary grids that could or could not be part of the pin
|
||||||
|
self.secondary_grids = set()
|
||||||
|
|
||||||
|
# The corresponding set of partially blocked grids for each pin group.
|
||||||
|
# These are blockages for other nets but unblocked for routing this group.
|
||||||
|
# These are also blockages if we used a simple enclosure to route to a rail.
|
||||||
|
self.blockages = set()
|
||||||
|
|
||||||
|
# This is a set of pin_layout shapes to cover the grids
|
||||||
|
self.enclosures = set()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
""" override print function output """
|
||||||
|
total_string = "(pg {} ".format(self.name)
|
||||||
|
|
||||||
|
pin_string = "\n pins={}".format(self.pins)
|
||||||
|
total_string += pin_string
|
||||||
|
|
||||||
|
grids_string = "\n grids={}".format(self.grids)
|
||||||
|
total_string += grids_string
|
||||||
|
|
||||||
|
grids_string = "\n secondary={}".format(self.secondary_grids)
|
||||||
|
total_string += grids_string
|
||||||
|
|
||||||
|
if self.enclosed:
|
||||||
|
enlosure_string = "\n enclose={}".format(self.enclosures)
|
||||||
|
total_string += enclosure_string
|
||||||
|
|
||||||
|
total_string += ")"
|
||||||
|
return total_string
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
""" override repr function output """
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
def size(self):
|
||||||
|
return len(self.grids)
|
||||||
|
|
||||||
|
def set_routed(self, value=True):
|
||||||
|
self.routed = value
|
||||||
|
|
||||||
|
def is_routed(self):
|
||||||
|
return self.routed
|
||||||
|
|
||||||
|
def pins_enclosed(self):
|
||||||
|
"""
|
||||||
|
Check if all of the pin shapes are enclosed.
|
||||||
|
Does not check if the DRC is correct, but just touching.
|
||||||
|
"""
|
||||||
|
for pin_list in self.pins:
|
||||||
|
pin_is_enclosed=False
|
||||||
|
for pin in pin_list:
|
||||||
|
if pin_is_enclosed:
|
||||||
|
break
|
||||||
|
for encosure in self.enclosures:
|
||||||
|
if pin.overlaps(enclosure):
|
||||||
|
pin_is_enclosed=True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def remove_redundant_shapes(self, pin_list):
|
||||||
|
"""
|
||||||
|
Remove any pin layout that is contained within another.
|
||||||
|
Returns a new list without modifying pin_list.
|
||||||
|
"""
|
||||||
|
local_debug = False
|
||||||
|
if local_debug:
|
||||||
|
debug.info(0,"INITIAL: {}".format(pin_list))
|
||||||
|
|
||||||
|
# Make a copy of the list to start
|
||||||
|
new_pin_list = pin_list.copy()
|
||||||
|
|
||||||
|
remove_indices = set()
|
||||||
|
# This is n^2, but the number is small
|
||||||
|
for index1,pin1 in enumerate(pin_list):
|
||||||
|
# If we remove this pin, it can't contain other pins
|
||||||
|
if index1 in remove_indices:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for index2,pin2 in enumerate(pin_list):
|
||||||
|
# Can't contain yourself, but compare the indices and not the pins
|
||||||
|
# so you can remove duplicate copies.
|
||||||
|
if index1==index2:
|
||||||
|
continue
|
||||||
|
# If we already removed it, can't remove it again...
|
||||||
|
if index2 in remove_indices:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if pin1.contains(pin2):
|
||||||
|
if local_debug:
|
||||||
|
debug.info(0,"{0} contains {1}".format(pin1,pin2))
|
||||||
|
remove_indices.add(index2)
|
||||||
|
|
||||||
|
# Remove them in decreasing order to not invalidate the indices
|
||||||
|
for i in sorted(remove_indices, reverse=True):
|
||||||
|
del new_pin_list[i]
|
||||||
|
|
||||||
|
if local_debug:
|
||||||
|
debug.info(0,"FINAL : {}".format(new_pin_list))
|
||||||
|
|
||||||
|
return new_pin_list
|
||||||
|
|
||||||
|
# FIXME: This relies on some technology parameters from router which is not clean.
|
||||||
|
def compute_enclosures(self):
|
||||||
|
"""
|
||||||
|
Find the minimum rectangle enclosures of the given tracks.
|
||||||
|
"""
|
||||||
|
# Enumerate every possible enclosure
|
||||||
|
pin_list = []
|
||||||
|
for seed in self.grids:
|
||||||
|
(ll, ur) = self.enclose_pin_grids(seed, direction.NORTH, direction.EAST)
|
||||||
|
enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z)
|
||||||
|
pin_list.append(enclosure)
|
||||||
|
|
||||||
|
(ll, ur) = self.enclose_pin_grids(seed, direction.EAST, direction.NORTH)
|
||||||
|
enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z)
|
||||||
|
pin_list.append(enclosure)
|
||||||
|
|
||||||
|
|
||||||
|
# Now simplify the enclosure list
|
||||||
|
new_pin_list = self.remove_redundant_shapes(pin_list)
|
||||||
|
|
||||||
|
return new_pin_list
|
||||||
|
|
||||||
|
def compute_connector(self, pin, enclosure):
|
||||||
|
"""
|
||||||
|
Compute a shape to connect the pin to the enclosure shape.
|
||||||
|
This assumes the shape will be the dimension of the pin.
|
||||||
|
"""
|
||||||
|
if pin.xoverlaps(enclosure):
|
||||||
|
# Is it vertical overlap, extend pin shape to enclosure
|
||||||
|
plc = pin.lc()
|
||||||
|
prc = pin.rc()
|
||||||
|
elc = enclosure.lc()
|
||||||
|
erc = enclosure.rc()
|
||||||
|
ymin = min(plc.y,elc.y)
|
||||||
|
ymax = max(plc.y,elc.y)
|
||||||
|
ll = vector(plc.x, ymin)
|
||||||
|
ur = vector(prc.x, ymax)
|
||||||
|
p = pin_layout(pin.name, [ll, ur], pin.layer)
|
||||||
|
elif pin.yoverlaps(enclosure):
|
||||||
|
# Is it horizontal overlap, extend pin shape to enclosure
|
||||||
|
pbc = pin.bc()
|
||||||
|
puc = pin.uc()
|
||||||
|
ebc = enclosure.bc()
|
||||||
|
euc = enclosure.uc()
|
||||||
|
xmin = min(pbc.x,ebc.x)
|
||||||
|
xmax = max(pbc.x,ebc.x)
|
||||||
|
ll = vector(xmin, pbc.y)
|
||||||
|
ur = vector(xmax, puc.y)
|
||||||
|
p = pin_layout(pin.name, [ll, ur], pin.layer)
|
||||||
|
else:
|
||||||
|
# Neither, so we must do a corner-to corner
|
||||||
|
pc = pin.center()
|
||||||
|
ec = enclosure.center()
|
||||||
|
xmin = min(pc.x, ec.x)
|
||||||
|
xmax = max(pc.x, ec.x)
|
||||||
|
ymin = min(pc.y, ec.y)
|
||||||
|
ymax = max(pc.y, ec.y)
|
||||||
|
ll = vector(xmin, ymin)
|
||||||
|
ur = vector(xmax, ymax)
|
||||||
|
p = pin_layout(pin.name, [ll, ur], pin.layer)
|
||||||
|
|
||||||
|
return p
|
||||||
|
|
||||||
|
def find_above_connector(self, pin, enclosures):
|
||||||
|
"""
|
||||||
|
Find the enclosure that is to above the pin
|
||||||
|
and make a connector to it's upper edge.
|
||||||
|
"""
|
||||||
|
# Create the list of shapes that contain the pin edge
|
||||||
|
edge_list = []
|
||||||
|
for shape in enclosures:
|
||||||
|
if shape.xcontains(pin):
|
||||||
|
edge_list.append(shape)
|
||||||
|
|
||||||
|
# Sort them by their bottom edge
|
||||||
|
edge_list.sort(key=lambda x: x.by(), reverse=True)
|
||||||
|
|
||||||
|
# Find the bottom edge that is next to the pin's top edge
|
||||||
|
above_item = None
|
||||||
|
for item in edge_list:
|
||||||
|
if item.by()>=pin.uy():
|
||||||
|
above_item = item
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
# There was nothing
|
||||||
|
if above_item==None:
|
||||||
|
return None
|
||||||
|
# If it already overlaps, no connector needed
|
||||||
|
if above_item.overlaps(pin):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Otherwise, make a connector to the item
|
||||||
|
p = self.compute_connector(pin, above_item)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def find_below_connector(self, pin, enclosures):
|
||||||
|
"""
|
||||||
|
Find the enclosure that is below the pin
|
||||||
|
and make a connector to it's upper edge.
|
||||||
|
"""
|
||||||
|
# Create the list of shapes that contain the pin edge
|
||||||
|
edge_list = []
|
||||||
|
for shape in enclosures:
|
||||||
|
if shape.xcontains(pin):
|
||||||
|
edge_list.append(shape)
|
||||||
|
|
||||||
|
# Sort them by their upper edge
|
||||||
|
edge_list.sort(key=lambda x: x.uy())
|
||||||
|
|
||||||
|
# Find the upper edge that is next to the pin's bottom edge
|
||||||
|
bottom_item = None
|
||||||
|
for item in edge_list:
|
||||||
|
if item.uy()<=pin.by():
|
||||||
|
bottom_item = item
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
# There was nothing to the left
|
||||||
|
if bottom_item==None:
|
||||||
|
return None
|
||||||
|
# If it already overlaps, no connector needed
|
||||||
|
if bottom_item.overlaps(pin):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Otherwise, make a connector to the item
|
||||||
|
p = self.compute_connector(pin, bottom_item)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def find_left_connector(self, pin, enclosures):
|
||||||
|
"""
|
||||||
|
Find the enclosure that is to the left of the pin
|
||||||
|
and make a connector to it's right edge.
|
||||||
|
"""
|
||||||
|
# Create the list of shapes that contain the pin edge
|
||||||
|
edge_list = []
|
||||||
|
for shape in enclosures:
|
||||||
|
if shape.ycontains(pin):
|
||||||
|
edge_list.append(shape)
|
||||||
|
|
||||||
|
# Sort them by their right edge
|
||||||
|
edge_list.sort(key=lambda x: x.rx())
|
||||||
|
|
||||||
|
# Find the right edge that is to the pin's left edge
|
||||||
|
left_item = None
|
||||||
|
for item in edge_list:
|
||||||
|
if item.rx()<=pin.lx():
|
||||||
|
left_item = item
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
# There was nothing to the left
|
||||||
|
if left_item==None:
|
||||||
|
return None
|
||||||
|
# If it already overlaps, no connector needed
|
||||||
|
if left_item.overlaps(pin):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Otherwise, make a connector to the item
|
||||||
|
p = self.compute_connector(pin, left_item)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def find_right_connector(self, pin, enclosures):
|
||||||
|
"""
|
||||||
|
Find the enclosure that is to the right of the pin
|
||||||
|
and make a connector to it's left edge.
|
||||||
|
"""
|
||||||
|
# Create the list of shapes that contain the pin edge
|
||||||
|
edge_list = []
|
||||||
|
for shape in enclosures:
|
||||||
|
if shape.ycontains(pin):
|
||||||
|
edge_list.append(shape)
|
||||||
|
|
||||||
|
# Sort them by their right edge
|
||||||
|
edge_list.sort(key=lambda x: x.lx(), reverse=True)
|
||||||
|
|
||||||
|
# Find the left edge that is next to the pin's right edge
|
||||||
|
right_item = None
|
||||||
|
for item in edge_list:
|
||||||
|
if item.lx()>=pin.rx():
|
||||||
|
right_item = item
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
# There was nothing to the right
|
||||||
|
if right_item==None:
|
||||||
|
return None
|
||||||
|
# If it already overlaps, no connector needed
|
||||||
|
if right_item.overlaps(pin):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Otherwise, make a connector to the item
|
||||||
|
p = self.compute_connector(pin, right_item)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def find_smallest_connector(self, pin_list, shape_list):
|
||||||
|
"""
|
||||||
|
Compute all of the connectors between the overlapping pins and enclosure shape list..
|
||||||
|
Return the smallest.
|
||||||
|
"""
|
||||||
|
smallest = None
|
||||||
|
for pin in pin_list:
|
||||||
|
for enclosure in shape_list:
|
||||||
|
new_enclosure = self.compute_connector(pin, enclosure)
|
||||||
|
if smallest == None or new_enclosure.area()<smallest.area():
|
||||||
|
smallest = new_enclosure
|
||||||
|
|
||||||
|
return smallest
|
||||||
|
|
||||||
|
def find_smallest_overlapping(self, pin_list, shape_list):
|
||||||
|
"""
|
||||||
|
Find the smallest area shape in shape_list that overlaps with any
|
||||||
|
pin in pin_list by a min width.
|
||||||
|
"""
|
||||||
|
|
||||||
|
smallest_shape = None
|
||||||
|
for pin in pin_list:
|
||||||
|
overlap_shape = self.find_smallest_overlapping_pin(pin,shape_list)
|
||||||
|
if overlap_shape:
|
||||||
|
overlap_length = pin.overlap_length(overlap_shape)
|
||||||
|
if smallest_shape == None or overlap_shape.area()<smallest_shape.area():
|
||||||
|
smallest_shape = overlap_shape
|
||||||
|
|
||||||
|
return smallest_shape
|
||||||
|
|
||||||
|
|
||||||
|
def find_smallest_overlapping_pin(self, pin, shape_list):
|
||||||
|
"""
|
||||||
|
Find the smallest area shape in shape_list that overlaps with any
|
||||||
|
pin in pin_list by a min width.
|
||||||
|
"""
|
||||||
|
|
||||||
|
smallest_shape = None
|
||||||
|
zindex=self.router.get_zindex(pin.layer_num)
|
||||||
|
(min_width,min_space) = self.router.get_layer_width_space(zindex)
|
||||||
|
|
||||||
|
# Now compare it with every other shape to check how much they overlap
|
||||||
|
for other in shape_list:
|
||||||
|
overlap_length = pin.overlap_length(other)
|
||||||
|
if overlap_length > min_width:
|
||||||
|
if smallest_shape == None or other.area()<smallest_shape.area():
|
||||||
|
smallest_shape = other
|
||||||
|
|
||||||
|
return smallest_shape
|
||||||
|
|
||||||
|
def overlap_any_shape(self, pin_list, shape_list):
|
||||||
|
"""
|
||||||
|
Does the given pin overlap any of the shapes in the pin list.
|
||||||
|
"""
|
||||||
|
for pin in pin_list:
|
||||||
|
for other in shape_list:
|
||||||
|
if pin.overlaps(other):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def max_pin_layout(self, pin_list):
|
||||||
|
"""
|
||||||
|
Return the max area pin_layout
|
||||||
|
"""
|
||||||
|
biggest = pin_list[0]
|
||||||
|
for pin in pin_list:
|
||||||
|
if pin.area() > biggest.area():
|
||||||
|
biggest = pin
|
||||||
|
|
||||||
|
return pin
|
||||||
|
|
||||||
|
def enclose_pin_grids(self, ll, dir1=direction.NORTH, dir2=direction.EAST):
|
||||||
|
"""
|
||||||
|
This encloses a single pin component with a rectangle
|
||||||
|
starting with the seed and expanding right until blocked
|
||||||
|
and then up until blocked.
|
||||||
|
dir1 and dir2 should be two orthogonal directions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
offset1= direction.get_offset(dir1)
|
||||||
|
offset2= direction.get_offset(dir2)
|
||||||
|
|
||||||
|
# We may have started with an empty set
|
||||||
|
if not self.grids:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Start with the ll and make the widest row
|
||||||
|
row = [ll]
|
||||||
|
# Move in dir1 while we can
|
||||||
|
while True:
|
||||||
|
next_cell = row[-1] + offset1
|
||||||
|
# Can't move if not in the pin shape
|
||||||
|
if next_cell in self.grids and next_cell not in self.router.blocked_grids:
|
||||||
|
row.append(next_cell)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
# Move in dir2 while we can
|
||||||
|
while True:
|
||||||
|
next_row = [x+offset2 for x in row]
|
||||||
|
for cell in next_row:
|
||||||
|
# Can't move if any cell is not in the pin shape
|
||||||
|
if cell not in self.grids or cell in self.router.blocked_grids:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
row = next_row
|
||||||
|
# Skips the second break
|
||||||
|
continue
|
||||||
|
# Breaks from the nested break
|
||||||
|
break
|
||||||
|
|
||||||
|
# Add a shape from ll to ur
|
||||||
|
ur = row[-1]
|
||||||
|
return (ll,ur)
|
||||||
|
|
||||||
|
|
||||||
|
def enclose_pin(self):
|
||||||
|
"""
|
||||||
|
If there is one set of connected pin shapes,
|
||||||
|
this will find the smallest rectangle enclosure that overlaps with any pin.
|
||||||
|
If there is not, it simply returns all the enclosures.
|
||||||
|
"""
|
||||||
|
self.enclosed = True
|
||||||
|
|
||||||
|
# Compute the enclosure pin_layout list of the set of tracks
|
||||||
|
self.enclosures = self.compute_enclosures()
|
||||||
|
|
||||||
|
for pin_list in self.pins:
|
||||||
|
for pin in pin_list:
|
||||||
|
|
||||||
|
# If it is contained, it won't need a connector
|
||||||
|
if pin.contained_by_any(self.enclosures):
|
||||||
|
continue
|
||||||
|
|
||||||
|
left_connector = self.find_left_connector(pin, self.enclosures)
|
||||||
|
right_connector = self.find_right_connector(pin, self.enclosures)
|
||||||
|
above_connector = self.find_above_connector(pin, self.enclosures)
|
||||||
|
below_connector = self.find_below_connector(pin, self.enclosures)
|
||||||
|
for connector in [left_connector, right_connector, above_connector, below_connector]:
|
||||||
|
if connector:
|
||||||
|
self.enclosures.append(connector)
|
||||||
|
|
||||||
|
# Now, make sure each pin touches an enclosure. If not, add a connector.
|
||||||
|
# This could only happen when there was no enclosure in any cardinal direction from a pin
|
||||||
|
for pin_list in self.pins:
|
||||||
|
if not self.overlap_any_shape(pin_list, self.enclosures):
|
||||||
|
connector = self.find_smallest_connector(pin_list, self.enclosures)
|
||||||
|
debug.check(connector!=None, "Could not find a connector for {} with {}".format(pin_list, self.enclosures))
|
||||||
|
self.enclosures.append(connector)
|
||||||
|
|
||||||
|
|
||||||
|
debug.info(3,"Computed enclosure(s) {0}\n {1}\n {2}\n {3}".format(self.name,
|
||||||
|
self.pins,
|
||||||
|
self.grids,
|
||||||
|
self.enclosures))
|
||||||
|
|
||||||
|
def combine_groups(self, pg1, pg2):
|
||||||
|
"""
|
||||||
|
Combine two pin groups into one.
|
||||||
|
"""
|
||||||
|
self.pins = [*pg1.pins, *pg2.pins] # Join the two lists of pins
|
||||||
|
self.grids = pg1.grids | pg2.grids # OR the set of grid locations
|
||||||
|
self.secondary_grids = pg1.secondary_grids | pg2.secondary_grids
|
||||||
|
|
||||||
|
def add_enclosure(self, cell):
|
||||||
|
"""
|
||||||
|
Add the enclosure shape to the given cell.
|
||||||
|
"""
|
||||||
|
for enclosure in self.enclosures:
|
||||||
|
debug.info(2,"Adding enclosure {0} {1}".format(self.name, enclosure))
|
||||||
|
cell.add_rect(layer=enclosure.layer,
|
||||||
|
offset=enclosure.ll(),
|
||||||
|
width=enclosure.width(),
|
||||||
|
height=enclosure.height())
|
||||||
|
|
||||||
|
|
||||||
|
def perimeter_grids(self):
|
||||||
|
"""
|
||||||
|
Return a list of the grids on the perimeter.
|
||||||
|
This assumes that we have a single contiguous shape.
|
||||||
|
"""
|
||||||
|
perimeter_set = set()
|
||||||
|
cardinal_offsets = direction.cardinal_offsets()
|
||||||
|
for g1 in self.grids:
|
||||||
|
neighbor_grids = [g1 + offset for offset in cardinal_offsets]
|
||||||
|
neighbor_count = sum([x in self.grids for x in neighbor_grids])
|
||||||
|
# If we aren't completely enclosed, we are on the perimeter
|
||||||
|
if neighbor_count < 4:
|
||||||
|
perimeter_set.add(g1)
|
||||||
|
|
||||||
|
return perimeter_set
|
||||||
|
|
||||||
|
def adjacent(self, other):
|
||||||
|
"""
|
||||||
|
Chck if the two pin groups have at least one adjacent pin grid.
|
||||||
|
"""
|
||||||
|
# We could optimize this to just check the boundaries
|
||||||
|
for g1 in self.perimeter_grids():
|
||||||
|
for g2 in other.perimeter_grids():
|
||||||
|
if g1.adjacent(g2):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def adjacent_grids(self, other, separation):
|
||||||
|
"""
|
||||||
|
Determine the sets of grids that are within a separation distance
|
||||||
|
of any grid in the other set.
|
||||||
|
"""
|
||||||
|
# We could optimize this to just check the boundaries
|
||||||
|
g1_grids = set()
|
||||||
|
g2_grids = set()
|
||||||
|
for g1 in self.grids:
|
||||||
|
for g2 in other.grids:
|
||||||
|
if g1.distance(g2) <= separation:
|
||||||
|
g1_grids.add(g1)
|
||||||
|
g2_grids.add(g2)
|
||||||
|
|
||||||
|
return g1_grids,g2_grids
|
||||||
|
|
||||||
|
def convert_pin(self):
|
||||||
|
"""
|
||||||
|
Convert the list of pin shapes into sets of routing grids.
|
||||||
|
The secondary set of grids are "optional" pin shapes that could be
|
||||||
|
should be either blocked or part of the pin.
|
||||||
|
"""
|
||||||
|
pin_set = set()
|
||||||
|
blockage_set = set()
|
||||||
|
|
||||||
|
for pin_list in self.pins:
|
||||||
|
for pin in pin_list:
|
||||||
|
debug.info(2," Converting {0}".format(pin))
|
||||||
|
# Determine which tracks the pin overlaps
|
||||||
|
pin_in_tracks=self.router.convert_pin_to_tracks(self.name, pin)
|
||||||
|
pin_set.update(pin_in_tracks)
|
||||||
|
|
||||||
|
# Blockages will be a super-set of pins since it uses the inflated pin shape.
|
||||||
|
blockage_in_tracks = self.router.convert_blockage(pin)
|
||||||
|
blockage_set.update(blockage_in_tracks)
|
||||||
|
|
||||||
|
# If we have a blockage, we must remove the grids
|
||||||
|
# Remember, this excludes the pin blockages already
|
||||||
|
shared_set = pin_set & self.router.blocked_grids
|
||||||
|
if len(shared_set)>0:
|
||||||
|
debug.info(2,"Removing pins {}".format(shared_set))
|
||||||
|
pin_set.difference_update(shared_set)
|
||||||
|
shared_set = blockage_set & self.router.blocked_grids
|
||||||
|
if len(shared_set)>0:
|
||||||
|
debug.info(2,"Removing blocks {}".format(shared_set))
|
||||||
|
blockage_set.difference_update(shared_set)
|
||||||
|
|
||||||
|
# At least one of the groups must have some valid tracks
|
||||||
|
if (len(pin_set)==0 and len(blockage_set)==0):
|
||||||
|
debug.warning("Pin is very close to metal blockage.\nAttempting to expand blocked pin {}".format(self.pins))
|
||||||
|
|
||||||
|
for pin_list in self.pins:
|
||||||
|
for pin in pin_list:
|
||||||
|
debug.info(2," Converting {0}".format(pin))
|
||||||
|
# Determine which tracks the pin overlaps
|
||||||
|
pin_in_tracks=self.router.convert_pin_to_tracks(self.name, pin, expansion=1)
|
||||||
|
pin_set.update(pin_in_tracks)
|
||||||
|
|
||||||
|
if len(pin_set)==0:
|
||||||
|
debug.error("Unable to find unblocked pin {} {}".format(self.name, self.pins))
|
||||||
|
self.router.write_debug_gds("blocked_pin.gds")
|
||||||
|
|
||||||
|
# We need to route each of the components, so don't combine the groups
|
||||||
|
self.grids = pin_set | blockage_set
|
||||||
|
# Remember the secondary grids for removing adjacent pins in wide metal spacing
|
||||||
|
self.secondary_grids = blockage_set - pin_set
|
||||||
|
|
||||||
|
debug.info(2," pins {}".format(self.grids))
|
||||||
|
debug.info(2," secondary {}".format(self.secondary_grids))
|
||||||
|
|
||||||
|
def recurse_simple_overlap_enclosure(self, start_set, direct):
|
||||||
|
"""
|
||||||
|
Recursive function to return set of tracks that connects to
|
||||||
|
the actual supply rail wire in a given direction (or terminating
|
||||||
|
when any track is no longer in the supply rail.
|
||||||
|
"""
|
||||||
|
next_set = grid_utils.expand_border(start_set, direct)
|
||||||
|
|
||||||
|
supply_tracks = self.router.supply_rail_tracks[self.name]
|
||||||
|
supply_wire_tracks = self.router.supply_rail_wire_tracks[self.name]
|
||||||
|
|
||||||
|
supply_overlap = next_set & supply_tracks
|
||||||
|
wire_overlap = next_set & supply_wire_tracks
|
||||||
|
|
||||||
|
# If the rail overlap is the same, we are done, since we connected to the actual wire
|
||||||
|
if len(wire_overlap)==len(start_set):
|
||||||
|
new_set = start_set | wire_overlap
|
||||||
|
# If the supply overlap is the same, keep expanding unti we hit the wire or move out of the rail region
|
||||||
|
elif len(supply_overlap)==len(start_set):
|
||||||
|
recurse_set = self.recurse_simple_overlap_enclosure(supply_overlap, direct)
|
||||||
|
new_set = start_set | supply_overlap | recurse_set
|
||||||
|
else:
|
||||||
|
# If we got no next set, we are done, can't expand!
|
||||||
|
new_set = set()
|
||||||
|
|
||||||
|
return new_set
|
||||||
|
|
||||||
|
def create_simple_overlap_enclosure(self, start_set):
|
||||||
|
"""
|
||||||
|
This takes a set of tracks that overlap a supply rail and creates an enclosure
|
||||||
|
that is ensured to overlap the supply rail wire.
|
||||||
|
It then adds rectangle(s) for the enclosure.
|
||||||
|
"""
|
||||||
|
|
||||||
|
additional_set = set()
|
||||||
|
# Check the layer of any element in the pin to determine which direction to route it
|
||||||
|
e = next(iter(start_set))
|
||||||
|
new_set = start_set.copy()
|
||||||
|
if e.z==0:
|
||||||
|
new_set = self.recurse_simple_overlap_enclosure(start_set, direction.NORTH)
|
||||||
|
if not new_set:
|
||||||
|
new_set = self.recurse_simple_overlap_enclosure(start_set, direction.SOUTH)
|
||||||
|
else:
|
||||||
|
new_set = self.recurse_simple_overlap_enclosure(start_set, direction.EAST)
|
||||||
|
if not new_set:
|
||||||
|
new_set = self.recurse_simple_overlap_enclosure(start_set, direction.WEST)
|
||||||
|
|
||||||
|
# Expand the pin grid set to include some extra grids that connect the supply rail
|
||||||
|
self.grids.update(new_set)
|
||||||
|
|
||||||
|
# Add the inflated set so we don't get wide metal spacing issues (if it exists)
|
||||||
|
self.blockages.update(grid_utils.inflate_set(new_set,self.router.supply_rail_space_width))
|
||||||
|
|
||||||
|
# Add the polygon enclosures and set this pin group as routed
|
||||||
|
self.set_routed()
|
||||||
|
self.enclosures = self.compute_enclosures()
|
||||||
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
python tests/regress.py -t freepdk45
|
|
||||||
python tests/regress.py -t scn3me_subm
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,79 @@
|
||||||
|
from tech import drc,layer
|
||||||
|
from contact import contact
|
||||||
|
from pin_group import pin_group
|
||||||
|
from vector import vector
|
||||||
|
import debug
|
||||||
|
|
||||||
|
class router_tech:
|
||||||
|
"""
|
||||||
|
This is a class to hold the router tech constants.
|
||||||
|
"""
|
||||||
|
def __init__(self, layers):
|
||||||
|
"""
|
||||||
|
Allows us to change the layers that we are routing on. First layer
|
||||||
|
is always horizontal, middle is via, and last is always
|
||||||
|
vertical.
|
||||||
|
"""
|
||||||
|
self.layers = layers
|
||||||
|
(self.horiz_layer_name, self.via_layer_name, self.vert_layer_name) = self.layers
|
||||||
|
|
||||||
|
# This is the minimum routed track spacing
|
||||||
|
via_connect = contact(self.layers, (1, 1))
|
||||||
|
self.max_via_size = max(via_connect.width,via_connect.height)
|
||||||
|
|
||||||
|
self.vert_layer_minwidth = drc("minwidth_{0}".format(self.vert_layer_name))
|
||||||
|
self.vert_layer_spacing = drc(str(self.vert_layer_name)+"_to_"+str(self.vert_layer_name))
|
||||||
|
self.vert_layer_number = layer[self.vert_layer_name]
|
||||||
|
|
||||||
|
self.horiz_layer_minwidth = drc("minwidth_{0}".format(self.horiz_layer_name))
|
||||||
|
self.horiz_layer_spacing = drc(str(self.horiz_layer_name)+"_to_"+str(self.horiz_layer_name))
|
||||||
|
self.horiz_layer_number = layer[self.horiz_layer_name]
|
||||||
|
|
||||||
|
self.horiz_track_width = self.max_via_size + self.horiz_layer_spacing
|
||||||
|
self.vert_track_width = self.max_via_size + self.vert_layer_spacing
|
||||||
|
|
||||||
|
# We'll keep horizontal and vertical tracks the same for simplicity.
|
||||||
|
self.track_width = max(self.horiz_track_width,self.vert_track_width)
|
||||||
|
debug.info(1,"Track width: "+str(self.track_width))
|
||||||
|
|
||||||
|
self.track_widths = vector([self.track_width] * 2)
|
||||||
|
self.track_factor = vector([1/self.track_width] * 2)
|
||||||
|
debug.info(2,"Track factor: {0}".format(self.track_factor))
|
||||||
|
|
||||||
|
# When we actually create the routes, make them the width of the track (minus 1/2 spacing on each side)
|
||||||
|
self.layer_widths = [self.track_width - self.horiz_layer_spacing, 1, self.track_width - self.vert_layer_spacing]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_zindex(self,layer_num):
|
||||||
|
if layer_num==self.horiz_layer_number:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def get_layer(self, zindex):
|
||||||
|
if zindex==1:
|
||||||
|
return self.vert_layer_name
|
||||||
|
elif zindex==0:
|
||||||
|
return self.horiz_layer_name
|
||||||
|
else:
|
||||||
|
debug.error("Invalid zindex {}".format(zindex),-1)
|
||||||
|
|
||||||
|
def get_layer_width_space(self, zindex, width=0, length=0):
|
||||||
|
"""
|
||||||
|
Return the width and spacing of a given layer
|
||||||
|
and wire of a given width and length.
|
||||||
|
"""
|
||||||
|
if zindex==1:
|
||||||
|
layer_name = self.vert_layer_name
|
||||||
|
elif zindex==0:
|
||||||
|
layer_name = self.horiz_layer_name
|
||||||
|
else:
|
||||||
|
debug.error("Invalid zindex for track", -1)
|
||||||
|
|
||||||
|
min_width = drc("minwidth_{0}".format(layer_name), width, length)
|
||||||
|
min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), width, length)
|
||||||
|
|
||||||
|
return (min_width,min_spacing)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
from itertools import tee
|
||||||
|
import debug
|
||||||
|
from heapq import heappush,heappop
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
from grid import grid
|
||||||
|
from grid_path import grid_path
|
||||||
|
from vector3d import vector3d
|
||||||
|
|
||||||
|
class signal_grid(grid):
|
||||||
|
"""
|
||||||
|
Expand the two layer grid to include A* search functions for a source and target.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, ll, ur, track_factor):
|
||||||
|
""" Create a routing map of width x height cells and 2 in the z-axis. """
|
||||||
|
grid.__init__(self, ll, ur, track_factor)
|
||||||
|
|
||||||
|
# priority queue for the maze routing
|
||||||
|
self.q = []
|
||||||
|
|
||||||
|
def reinit(self):
|
||||||
|
""" Reinitialize everything for a new route. """
|
||||||
|
|
||||||
|
# Reset all the cells in the map
|
||||||
|
for p in self.map.values():
|
||||||
|
p.reset()
|
||||||
|
|
||||||
|
# clear source and target pins
|
||||||
|
self.source=[]
|
||||||
|
self.target=[]
|
||||||
|
|
||||||
|
# Clear the queue
|
||||||
|
while len(self.q)>0:
|
||||||
|
heappop(self.q)
|
||||||
|
self.counter = 0
|
||||||
|
|
||||||
|
def init_queue(self):
|
||||||
|
"""
|
||||||
|
Populate the queue with all the source pins with cost
|
||||||
|
to the target. Each item is a path of the grid cells.
|
||||||
|
We will use an A* search, so this cost must be pessimistic.
|
||||||
|
Cost so far will be the length of the path.
|
||||||
|
"""
|
||||||
|
#debug.info(3,"Initializing queue.")
|
||||||
|
|
||||||
|
# Counter is used to not require data comparison in Python 3.x
|
||||||
|
# Items will be returned in order they are added during cost ties
|
||||||
|
self.counter = 0
|
||||||
|
for s in self.source:
|
||||||
|
cost = self.cost_to_target(s)
|
||||||
|
debug.info(3,"Init: cost=" + str(cost) + " " + str([s]))
|
||||||
|
heappush(self.q,(cost,self.counter,grid_path([vector3d(s)])))
|
||||||
|
self.counter+=1
|
||||||
|
|
||||||
|
|
||||||
|
def route(self,detour_scale):
|
||||||
|
"""
|
||||||
|
This does the A* maze routing with preferred direction routing.
|
||||||
|
This only works for 1 track wide routes!
|
||||||
|
"""
|
||||||
|
|
||||||
|
# We set a cost bound of the HPWL for run-time. This can be
|
||||||
|
# over-ridden if the route fails due to pruning a feasible solution.
|
||||||
|
cost_bound = detour_scale*self.cost_to_target(self.source[0])*grid.PREFERRED_COST
|
||||||
|
|
||||||
|
# Check if something in the queue is already a source and a target!
|
||||||
|
for s in self.source:
|
||||||
|
if self.is_target(s):
|
||||||
|
return((grid_path([vector3d(s)]),0))
|
||||||
|
|
||||||
|
# Make sure the queue is empty if we run another route
|
||||||
|
while len(self.q)>0:
|
||||||
|
heappop(self.q)
|
||||||
|
|
||||||
|
# Put the source items into the queue
|
||||||
|
self.init_queue()
|
||||||
|
cheapest_path = None
|
||||||
|
cheapest_cost = None
|
||||||
|
|
||||||
|
|
||||||
|
# Keep expanding and adding to the priority queue until we are done
|
||||||
|
while len(self.q)>0:
|
||||||
|
# should we keep the path in the queue as well or just the final node?
|
||||||
|
(cost,count,curpath) = heappop(self.q)
|
||||||
|
debug.info(3,"Queue size: size=" + str(len(self.q)) + " " + str(cost))
|
||||||
|
debug.info(4,"Expanding: cost=" + str(cost) + " " + str(curpath))
|
||||||
|
|
||||||
|
# expand the last element
|
||||||
|
neighbors = self.expand_dirs(curpath)
|
||||||
|
debug.info(4,"Neighbors: " + str(neighbors))
|
||||||
|
|
||||||
|
for n in neighbors:
|
||||||
|
# make a new copy of the path to not update the old ones
|
||||||
|
newpath = deepcopy(curpath)
|
||||||
|
# node is added to the map by the expand routine
|
||||||
|
newpath.append(n)
|
||||||
|
# check if we hit the target and are done
|
||||||
|
if self.is_target(n[0]): # This uses the [0] item because we are assuming 1-track wide
|
||||||
|
return (newpath,newpath.cost())
|
||||||
|
else:
|
||||||
|
# current path cost + predicted cost
|
||||||
|
current_cost = newpath.cost()
|
||||||
|
target_cost = self.cost_to_target(n[0])
|
||||||
|
predicted_cost = current_cost + target_cost
|
||||||
|
# only add the cost if it is less than our bound
|
||||||
|
if (predicted_cost < cost_bound):
|
||||||
|
if (self.map[n[0]].min_cost==-1 or predicted_cost<self.map[n[0]].min_cost):
|
||||||
|
self.map[n[0]].min_path = newpath
|
||||||
|
self.map[n[0]].min_cost = predicted_cost
|
||||||
|
debug.info(4,"Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath))
|
||||||
|
# add the cost to get to this point if we haven't reached it yet
|
||||||
|
heappush(self.q,(predicted_cost,self.counter,newpath))
|
||||||
|
self.counter += 1
|
||||||
|
#else:
|
||||||
|
# print("Better previous cost.")
|
||||||
|
#else:
|
||||||
|
# print("Cost bounded")
|
||||||
|
|
||||||
|
debug.warning("Unable to route path. Expand the detour_scale to allow detours.")
|
||||||
|
return (None,None)
|
||||||
|
|
||||||
|
def expand_dirs(self,curpath):
|
||||||
|
"""
|
||||||
|
Expand each of the four cardinal directions plus up or down
|
||||||
|
but not expanding to blocked cells. Expands in all directions
|
||||||
|
regardless of preferred directions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Expand all directions.
|
||||||
|
neighbors = curpath.expand_dirs()
|
||||||
|
|
||||||
|
# Filter the blocked ones
|
||||||
|
unblocked_neighbors = [x for x in neighbors if not self.is_blocked(x)]
|
||||||
|
|
||||||
|
return unblocked_neighbors
|
||||||
|
|
||||||
|
|
||||||
|
def hpwl(self, src, dest):
|
||||||
|
"""
|
||||||
|
Return half perimeter wire length from point to another.
|
||||||
|
Either point can have positive or negative coordinates.
|
||||||
|
Include the via penalty if there is one.
|
||||||
|
"""
|
||||||
|
hpwl = max(abs(src.x-dest.x),abs(dest.x-src.x))
|
||||||
|
hpwl += max(abs(src.y-dest.y),abs(dest.y-src.y))
|
||||||
|
hpwl += max(abs(src.z-dest.z),abs(dest.z-src.z))
|
||||||
|
if src.x!=dest.x or src.y!=dest.y:
|
||||||
|
hpwl += grid.VIA_COST
|
||||||
|
return hpwl
|
||||||
|
|
||||||
|
def cost_to_target(self,source):
|
||||||
|
"""
|
||||||
|
Find the cheapest HPWL distance to any target point ignoring
|
||||||
|
blockages for A* search.
|
||||||
|
"""
|
||||||
|
cost = self.hpwl(source,self.target[0])
|
||||||
|
for t in self.target:
|
||||||
|
cost = min(self.hpwl(source,t),cost)
|
||||||
|
|
||||||
|
return cost
|
||||||
|
|
||||||
|
|
||||||
|
def get_inertia(self,p0,p1):
|
||||||
|
"""
|
||||||
|
Sets the direction based on the previous direction we came from.
|
||||||
|
"""
|
||||||
|
# direction (index) of movement
|
||||||
|
if p0.x==p1.x:
|
||||||
|
return 1
|
||||||
|
elif p0.y==p1.y:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
# z direction
|
||||||
|
return 2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
import gdsMill
|
||||||
|
import tech
|
||||||
|
from contact import contact
|
||||||
|
import math
|
||||||
|
import debug
|
||||||
|
from pin_layout import pin_layout
|
||||||
|
from globals import OPTS
|
||||||
|
from router import router
|
||||||
|
|
||||||
|
class signal_router(router):
|
||||||
|
"""
|
||||||
|
A router class to read an obstruction map from a gds and plan a
|
||||||
|
route on a given layer. This is limited to two layer routes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, layers, design, gds_filename=None):
|
||||||
|
"""
|
||||||
|
This will route on layers in design. It will get the blockages from
|
||||||
|
either the gds file name or the design itself (by saving to a gds file).
|
||||||
|
"""
|
||||||
|
router.__init__(self, layers, design, gds_filename)
|
||||||
|
|
||||||
|
|
||||||
|
def create_routing_grid(self):
|
||||||
|
"""
|
||||||
|
Create a sprase routing grid with A* expansion functions.
|
||||||
|
"""
|
||||||
|
# We will add a halo around the boundary
|
||||||
|
# of this many tracks
|
||||||
|
size = self.ur - self.ll
|
||||||
|
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
|
||||||
|
|
||||||
|
import signal_grid
|
||||||
|
self.rg = signal_grid.signal_grid(self.ll, self.ur, self.track_width)
|
||||||
|
|
||||||
|
|
||||||
|
def route(self, src, dest, detour_scale=5):
|
||||||
|
"""
|
||||||
|
Route a single source-destination net and return
|
||||||
|
the simplified rectilinear path. Cost factor is how sub-optimal to explore for a feasible route.
|
||||||
|
This is used to speed up the routing when there is not much detouring needed.
|
||||||
|
"""
|
||||||
|
debug.info(1,"Running signal router from {0} to {1}...".format(src,dest))
|
||||||
|
|
||||||
|
self.pins[src] = []
|
||||||
|
self.pins[dest] = []
|
||||||
|
|
||||||
|
# Clear the pins if we have previously routed
|
||||||
|
if (hasattr(self,'rg')):
|
||||||
|
self.clear_pins()
|
||||||
|
else:
|
||||||
|
# Creat a routing grid over the entire area
|
||||||
|
# FIXME: This could be created only over the routing region,
|
||||||
|
# but this is simplest for now.
|
||||||
|
self.create_routing_grid()
|
||||||
|
|
||||||
|
# Get the pin shapes
|
||||||
|
self.find_pins_and_blockages([src, dest])
|
||||||
|
|
||||||
|
# Block everything
|
||||||
|
self.prepare_blockages()
|
||||||
|
# Clear the pins we are routing
|
||||||
|
self.set_blockages(self.pin_components[src],False)
|
||||||
|
self.set_blockages(self.pin_components[dest],False)
|
||||||
|
|
||||||
|
# Now add the src/tgt if they are not blocked by other shapes
|
||||||
|
self.add_source(src)
|
||||||
|
self.add_target(dest)
|
||||||
|
|
||||||
|
if not self.run_router(detour_scale=detour_scale):
|
||||||
|
self.write_debug_gds(stop_program=False)
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.write_debug_gds(stop_program=False)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue